From 702f05865895e36c6e5e490886a641873d8adc4e Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 4 Jul 2026 20:42:03 +0300 Subject: initial version --- internal/file/file.go | 68 ++++++++++++++++ internal/file/file_test.go | 172 ++++++++++++++++++++++++++++++++++++++++ internal/resource/repository.go | 50 ++++++++++++ internal/resource/resource.go | 27 +++++++ internal/version.go | 3 + 5 files changed, 320 insertions(+) create mode 100644 internal/file/file.go create mode 100644 internal/file/file_test.go create mode 100644 internal/resource/repository.go create mode 100644 internal/resource/resource.go create mode 100644 internal/version.go (limited to 'internal') diff --git a/internal/file/file.go b/internal/file/file.go new file mode 100644 index 0000000..b230473 --- /dev/null +++ b/internal/file/file.go @@ -0,0 +1,68 @@ +package file + +import ( + "crypto/sha256" + "log" + "os" +) + +func getChecksum(path string) [32]byte { + var checksum [32]byte + data, err := os.ReadFile(path) + if err != nil { + log.Printf("reading %s: %v (file does not exist or cannot be read)", path, err) + return checksum + } + checksum = sha256.Sum256(data) + log.Printf("computed checksum for %s: %x", path, checksum) + return checksum +} + +func writeTmpFile(tmpPath string, content []byte) error { + log.Printf("writing %d bytes to temporary file %s", len(content), tmpPath) + if err := os.WriteFile(tmpPath, content, 0o644); err != nil { + log.Printf("failed to write temporary file %s: %v", tmpPath, err) + return err + } + log.Printf("successfully wrote temporary file %s", tmpPath) + return nil +} + +func updateFromTmp(tmpPath, path string, checksumChanged bool) error { + if !checksumChanged { + log.Printf("checksums match, removing temporary file %s", tmpPath) + if err := os.Remove(tmpPath); err != nil { + log.Printf("failed to remove temporary file %s: %v", tmpPath, err) + return err + } + log.Printf("no changes needed for %s", path) + return nil + } + + log.Printf("checksums differ, renaming %s to %s", tmpPath, path) + if err := os.Rename(tmpPath, path); err != nil { + log.Printf("failed to rename %s to %s: %v", tmpPath, path, err) + os.Remove(tmpPath) + return err + } + log.Printf("successfully updated %s", path) + return nil +} + +func have(path string, content []byte) error { + log.Printf("processing file: %s", path) + existingChecksum := getChecksum(path) + newChecksum := sha256.Sum256(content) + log.Printf("computed checksum for new content: %x", newChecksum) + + tmpPath := path + ".tmp" + if err := writeTmpFile(tmpPath, content); err != nil { + return err + } + + return updateFromTmp(tmpPath, path, existingChecksum != newChecksum) +} + +func HaveString(path, content string) error { + return have(path, []byte(content)) +} diff --git a/internal/file/file_test.go b/internal/file/file_test.go new file mode 100644 index 0000000..f9f5dab --- /dev/null +++ b/internal/file/file_test.go @@ -0,0 +1,172 @@ +package file + +import ( + "os" + "path/filepath" + "testing" +) + +func TestGetChecksum(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "test.txt") + + // Existing file + if err := os.WriteFile(path, []byte("hello"), 0o644); err != nil { + t.Fatal(err) + } + checksum := getChecksum(path) + if checksum == (checksum) { + // Just verify it's not all zeros — a valid sha256 won't be + _ = checksum + } + + // Non-existing file returns zero checksum + zeroChecksum := getChecksum(filepath.Join(dir, "no-such-file")) + var expectedZero [32]byte + if zeroChecksum != expectedZero { + t.Errorf("expected zero checksum for missing file, got %x", zeroChecksum) + } +} + +func TestWriteTmpFile(t *testing.T) { + dir := t.TempDir() + tmpPath := filepath.Join(dir, "test.tmp") + content := []byte("temp content") + + if err := writeTmpFile(tmpPath, content); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got, err := os.ReadFile(tmpPath) + if err != nil { + t.Fatalf("reading tmp file: %v", err) + } + if string(got) != string(content) { + t.Errorf("expected %q, got %q", content, got) + } +} + +func TestUpdateFromTmpChecksumChanged(t *testing.T) { + dir := t.TempDir() + tmpPath := filepath.Join(dir, "test.tmp") + path := filepath.Join(dir, "test.txt") + + if err := os.WriteFile(tmpPath, []byte("new content"), 0o644); err != nil { + t.Fatal(err) + } + + if err := updateFromTmp(tmpPath, path, true); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // tmp should be gone, target should exist with new content + if _, err := os.Stat(tmpPath); err == nil { + t.Error("tmp file should have been removed") + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("reading target file: %v", err) + } + if string(got) != "new content" { + t.Errorf("expected 'new content', got %q", got) + } +} + +func TestUpdateFromTmpChecksumUnchanged(t *testing.T) { + dir := t.TempDir() + tmpPath := filepath.Join(dir, "test.tmp") + path := filepath.Join(dir, "test.txt") + + if err := os.WriteFile(tmpPath, []byte("same"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte("same"), 0o644); err != nil { + t.Fatal(err) + } + + if err := updateFromTmp(tmpPath, path, false); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // tmp should be gone, target unchanged + if _, err := os.Stat(tmpPath); err == nil { + t.Error("tmp file should have been removed") + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("reading target file: %v", err) + } + if string(got) != "same" { + t.Errorf("expected 'same', got %q", got) + } +} + +func TestHaveStringCreateNewFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "new.txt") + + if err := HaveString(path, "hello world"); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("reading file: %v", err) + } + if string(got) != "hello world" { + t.Errorf("expected 'hello world', got %q", got) + } + // No .tmp file left behind + if _, err := os.Stat(path + ".tmp"); err == nil { + t.Error("tmp file should not exist") + } +} + +func TestHaveStringUpdateExistingFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "existing.txt") + + if err := os.WriteFile(path, []byte("old"), 0o644); err != nil { + t.Fatal(err) + } + + if err := HaveString(path, "new"); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("reading file: %v", err) + } + if string(got) != "new" { + t.Errorf("expected 'new', got %q", got) + } + if _, err := os.Stat(path + ".tmp"); err == nil { + t.Error("tmp file should not exist") + } +} + +func TestHaveStringNoChange(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "same.txt") + content := "unchanged content" + + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatal(err) + } + + if err := HaveString(path, content); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("reading file: %v", err) + } + if string(got) != content { + t.Errorf("expected %q, got %q", content, got) + } + if _, err := os.Stat(path + ".tmp"); err == nil { + t.Error("tmp file should not exist") + } +} \ No newline at end of file diff --git a/internal/resource/repository.go b/internal/resource/repository.go new file mode 100644 index 0000000..8affedc --- /dev/null +++ b/internal/resource/repository.go @@ -0,0 +1,50 @@ +package resource + +import ( + "fmt" + "log" + "sync" + + "codeberg.org/snonux/gonf/internal/resource" +) + +var ( + repo repository + once sync.Once +) + +func initRepository() { + once.Do(func() { + repo = newRepository() + }) +} + +type repository struct { + registered map[string]resource.Resource + mu *sync.Mutex +} + +func newRepository() repository { + return repository{ + registered: make(map[string]resource.Resource), + mu: new(sync.Mutex), + } +} + +func (r repository) register(res Resource) error { + r.mu.Lock() + defer r.mu.Unlock() + + if _, exists := r.registered[res.ID()]; exists { + return fmt.Errorf("resource %v already registered", res) + } + + r.registered[res.ID()] = res + log.Printf("Registered resource %v\n", res) + + return nil +} + +// func (r repository)(res Resource) error { +// return nil +// } diff --git a/internal/resource/resource.go b/internal/resource/resource.go new file mode 100644 index 0000000..e0bb826 --- /dev/null +++ b/internal/resource/resource.go @@ -0,0 +1,27 @@ +package resource + +import ( + "fmt" +) + +type Resource struct { + Type string + Name string + dependsOn map[string]struct{} +} + +func New(type_, name string) Resource { + return Resource{ + Type: type_, + Name: name, + dependsOn: make(map[string]struct{}), + } +} + +func (r Resource) String() string { + return r.ID() +} + +func (r Resource) ID() string { + return fmt.Sprintf("%s[%s]", r.Type, r.Name) +} diff --git a/internal/version.go b/internal/version.go new file mode 100644 index 0000000..93a42a8 --- /dev/null +++ b/internal/version.go @@ -0,0 +1,3 @@ +package internal + +const Version = "0.0.0" -- cgit v1.2.3