From 4026fb919283a2fceca306cfd6007f4a172d938e Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 3 Feb 2024 20:35:22 +0600 Subject: [PATCH] :truck: Renamed 'page' module back to 'entry' --- internal/entry/repository.go | 18 +++++ .../{page => entry}/repository/fs/fs_page.go | 20 ++++- .../repository/fs/fs_page_test.go | 2 +- .../entry/repository/memory/memory_page.go | 41 ++++++++++ internal/{page => entry}/usecase.go | 5 +- internal/entry/usecase/page_ucase.go | 80 +++++++++++++++++++ internal/entry/usecase/page_ucase_test.go | 70 ++++++++++++++++ internal/page/repository.go | 11 --- internal/page/usecase/page_ucase.go | 71 ---------------- internal/page/usecase/page_ucase_test.go | 62 -------------- 10 files changed, 227 insertions(+), 153 deletions(-) create mode 100644 internal/entry/repository.go rename internal/{page => entry}/repository/fs/fs_page.go (70%) rename internal/{page => entry}/repository/fs/fs_page_test.go (97%) create mode 100644 internal/entry/repository/memory/memory_page.go rename internal/{page => entry}/usecase.go (70%) create mode 100644 internal/entry/usecase/page_ucase.go create mode 100644 internal/entry/usecase/page_ucase_test.go delete mode 100644 internal/page/repository.go delete mode 100644 internal/page/usecase/page_ucase.go delete mode 100644 internal/page/usecase/page_ucase_test.go diff --git a/internal/entry/repository.go b/internal/entry/repository.go new file mode 100644 index 0000000..6a3edd5 --- /dev/null +++ b/internal/entry/repository.go @@ -0,0 +1,18 @@ +package entry + +import ( + "context" + "errors" + + "source.toby3d.me/toby3d/home/internal/domain" +) + +type Repository interface { + Get(ctx context.Context, lang domain.Language, path string) (*domain.Page, error) + + // Stat checks for the existence of a page on the specified path without + // parsing its contents. + Stat(ctx context.Context, lang domain.Language, path string) (bool, error) +} + +var ErrNotExist error = errors.New("entry not exists") diff --git a/internal/page/repository/fs/fs_page.go b/internal/entry/repository/fs/fs_page.go similarity index 70% rename from internal/page/repository/fs/fs_page.go rename to internal/entry/repository/fs/fs_page.go index 90d8aa9..847f19a 100644 --- a/internal/page/repository/fs/fs_page.go +++ b/internal/entry/repository/fs/fs_page.go @@ -2,6 +2,7 @@ package fs import ( "context" + "errors" "fmt" "io/fs" @@ -9,7 +10,7 @@ import ( "gopkg.in/yaml.v3" "source.toby3d.me/toby3d/home/internal/domain" - "source.toby3d.me/toby3d/home/internal/page" + "source.toby3d.me/toby3d/home/internal/entry" ) type ( @@ -29,7 +30,7 @@ var FrontMatterFormats = []*frontmatter.Format{ frontmatter.NewFormat(`---`, `---`, yaml.Unmarshal), } -func NewFileSystemPageRepository(dir fs.FS) page.Repository { +func NewFileSystemPageRepository(dir fs.FS) entry.Repository { return &fileSystemPageRepository{ dir: dir, } @@ -45,7 +46,7 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Langu f, err := repo.dir.Open(target) if err != nil { - return nil, fmt.Errorf("cannot open '%s' page file: %w", target, err) + return nil, fmt.Errorf("cannot open '%s' entry file: %w", target, err) } defer f.Close() @@ -53,7 +54,7 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Langu Params: make(map[string]any), } if data.Content, err = frontmatter.Parse(f, data, FrontMatterFormats...); err != nil { - return nil, fmt.Errorf("cannot parse page content as FrontMatter: %w", err) + return nil, fmt.Errorf("cannot parse entry content as FrontMatter: %w", err) } return &domain.Page{ @@ -67,3 +68,14 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Langu Translations: make([]*domain.Page, 0), }, nil } + +func (repo *fileSystemPageRepository) Stat(_ context.Context, l domain.Language, p string) (bool, error) { + ext := ".md" + if l != domain.LanguageUnd { + ext = "." + l.Lang() + ext + } + + _, err := fs.Stat(repo.dir, p+ext) + + return errors.Is(err, fs.ErrExist), nil +} diff --git a/internal/page/repository/fs/fs_page_test.go b/internal/entry/repository/fs/fs_page_test.go similarity index 97% rename from internal/page/repository/fs/fs_page_test.go rename to internal/entry/repository/fs/fs_page_test.go index cfc1a2e..72f4e7f 100644 --- a/internal/page/repository/fs/fs_page_test.go +++ b/internal/entry/repository/fs/fs_page_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "source.toby3d.me/toby3d/home/internal/domain" - repository "source.toby3d.me/toby3d/home/internal/page/repository/fs" + repository "source.toby3d.me/toby3d/home/internal/entry/repository/fs" ) func TestGet(t *testing.T) { diff --git a/internal/entry/repository/memory/memory_page.go b/internal/entry/repository/memory/memory_page.go new file mode 100644 index 0000000..2cd2851 --- /dev/null +++ b/internal/entry/repository/memory/memory_page.go @@ -0,0 +1,41 @@ +package memory + +import ( + "context" + "errors" + "fmt" + "io/fs" + "sync" + "testing/fstest" + + "source.toby3d.me/toby3d/home/internal/domain" + "source.toby3d.me/toby3d/home/internal/entry" +) + +type memoryEntryRepository struct { + mutex *sync.RWMutex + files fstest.MapFS +} + +func (repo *memoryEntryRepository) Get(_ context.Context, _ domain.Language, p string) (*domain.Page, error) { + f, err := repo.files.Open(p) + if err != nil { + return nil, fmt.Errorf("cannot get entry from memory: %w", err) + } + defer f.Close() + + return nil, nil +} + +func (repo *memoryEntryRepository) Stat(_ context.Context, l domain.Language, p string) (bool, error) { + _, err := fs.Stat(repo.files, p) + + return errors.Is(err, fs.ErrExist), nil +} + +func NewMemoryEntryRepository(files fstest.MapFS) entry.Repository { + return &memoryEntryRepository{ + mutex: new(sync.RWMutex), + files: files, + } +} diff --git a/internal/page/usecase.go b/internal/entry/usecase.go similarity index 70% rename from internal/page/usecase.go rename to internal/entry/usecase.go index 7c46270..b37631f 100644 --- a/internal/page/usecase.go +++ b/internal/entry/usecase.go @@ -1,8 +1,7 @@ -package page +package entry import ( "context" - "errors" "source.toby3d.me/toby3d/home/internal/domain" ) @@ -10,5 +9,3 @@ import ( type UseCase interface { Do(ctx context.Context, lang domain.Language, path string) (*domain.Page, error) } - -var ErrNotExist error = errors.New("page not exists") diff --git a/internal/entry/usecase/page_ucase.go b/internal/entry/usecase/page_ucase.go new file mode 100644 index 0000000..19ce2fc --- /dev/null +++ b/internal/entry/usecase/page_ucase.go @@ -0,0 +1,80 @@ +package usecase + +import ( + "context" + "fmt" + "path" + "strings" + + "source.toby3d.me/toby3d/home/internal/domain" + "source.toby3d.me/toby3d/home/internal/entry" + "source.toby3d.me/toby3d/home/internal/resource" + "source.toby3d.me/toby3d/home/internal/urlutil" +) + +type entryUseCase struct { + entries entry.Repository + resources resource.Repository +} + +func NewEntryUseCase(entries entry.Repository, resources resource.Repository) entry.UseCase { + return &entryUseCase{ + entries: entries, + resources: resources, + } +} + +func (ucase *entryUseCase) Do(ctx context.Context, lang domain.Language, p string) (*domain.Page, error) { + targets := make([]string, 0) + hasExt := path.Ext(p) != "" + head, tail := urlutil.ShiftPath(p) + + if tail == "/" { + if head = strings.TrimSuffix(head, path.Ext(head)); head == "" { + head = "index" + } + + targets = append(targets, head) + } + + if head != "index" { + tail = strings.TrimSuffix(tail, path.Ext(tail)) + if !strings.HasSuffix(tail, "/index") { + if hasExt { + targets = append([]string{path.Join(head, tail, "index")}, targets...) + } else { + targets = append(targets, path.Join(head, tail, "index")) + } + } else { + targets = append(targets, path.Join(head, tail)) + } + } + + for i := len(targets) - 1; 0 <= i; i-- { + result, err := ucase.entries.Get(ctx, lang, targets[i]) + if err != nil { + continue + } + + if result.Resources, _, err = ucase.resources.Fetch(ctx, result.File.Dir()+"*"); err != nil { + return result, nil + } + + for _, res := range result.Resources.GetType(domain.ResourceTypePage) { + if res.File.TranslationBaseName() != result.File.TranslationBaseName() { + continue + } + + translation, err := ucase.entries.Get(ctx, res.File.Language, targets[i]) + if err != nil { + continue + } + + result.Translations = append(result.Translations, translation) + } + + return result, nil + } + + return nil, fmt.Errorf("cannot find page on path '%s': %w", p, entry.ErrNotExist) +} diff --git a/internal/entry/usecase/page_ucase_test.go b/internal/entry/usecase/page_ucase_test.go new file mode 100644 index 0000000..aae1884 --- /dev/null +++ b/internal/entry/usecase/page_ucase_test.go @@ -0,0 +1,70 @@ +package usecase_test + +import ( + "context" + "path/filepath" + "testing" + "testing/fstest" + + "github.com/google/go-cmp/cmp" + + "source.toby3d.me/toby3d/home/internal/domain" + entryfsrepo "source.toby3d.me/toby3d/home/internal/entry/repository/fs" + "source.toby3d.me/toby3d/home/internal/entry/usecase" + resourcedummyrepo "source.toby3d.me/toby3d/home/internal/resource/repository/dummy" +) + +func TestDo(t *testing.T) { + t.Parallel() + + pages := entryfsrepo.NewFileSystemPageRepository(fstest.MapFS{ + filepath.Join("both", "index.md"): &fstest.MapFile{Data: []byte(`both/index.md`)}, + filepath.Join("both.md"): &fstest.MapFile{Data: []byte(`both.md`)}, + filepath.Join("file.md"): &fstest.MapFile{Data: []byte(`file.md`)}, + filepath.Join("folder", "index.md"): &fstest.MapFile{Data: []byte(`folder/index.md`)}, + filepath.Join("foo", "bar", "index.md"): &fstest.MapFile{Data: []byte(`foo/bar/index.md`)}, + filepath.Join("foo", "bar.md"): &fstest.MapFile{Data: []byte(`foo/bar.md`)}, + filepath.Join("index.md"): &fstest.MapFile{Data: []byte(`index.md`)}, + }) + + ucase := usecase.NewEntryUseCase(pages, resourcedummyrepo.NewDummyResourceRepository()) + + for name, tc := range map[string]struct { + input string + expect []byte + }{ + "root": {"/", []byte(`index.md`)}, + "index": {"/index", []byte(`index.md`)}, + "index-ext": {"/index.html", []byte(`index.md`)}, + "file": {"/file", []byte(`file.md`)}, + "file-slash": {"/file/", []byte(`file.md`)}, + "file-ext": {"/file.html", []byte(`file.md`)}, + "both-ext": {"/both.html", []byte(`both.md`)}, + "folder": {"/folder", []byte(`folder/index.md`)}, + "folder-slash": {"/folder/", []byte(`folder/index.md`)}, + "folder-index": {"/folder/index", []byte(`folder/index.md`)}, + "folder-ext": {"/folder/index.html", []byte(`folder/index.md`)}, + "both": {"/both", []byte(`both/index.md`)}, + "both-slash": {"/both/", []byte(`both/index.md`)}, + "both-index": {"/both/index", []byte(`both/index.md`)}, + "both-index-ext": {"/both/index.html", []byte(`both/index.md`)}, + "sub-folder-index": {"/foo/bar/index", []byte(`foo/bar/index.md`)}, + "sub-folder-ext": {"/foo/bar/index.html", []byte(`foo/bar/index.md`)}, + "sub-folder-slash": {"/foo/bar/", []byte(`foo/bar/index.md`)}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual, err := ucase.Do(context.Background(), domain.LanguageUnd, tc.input) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(string(actual.Content), string(tc.expect)); diff != "" { + t.Error(diff) + } + }) + } +} diff --git a/internal/page/repository.go b/internal/page/repository.go deleted file mode 100644 index 9c76990..0000000 --- a/internal/page/repository.go +++ /dev/null @@ -1,11 +0,0 @@ -package page - -import ( - "context" - - "source.toby3d.me/toby3d/home/internal/domain" -) - -type Repository interface { - Get(ctx context.Context, lang domain.Language, path string) (*domain.Page, error) -} diff --git a/internal/page/usecase/page_ucase.go b/internal/page/usecase/page_ucase.go deleted file mode 100644 index c18a76a..0000000 --- a/internal/page/usecase/page_ucase.go +++ /dev/null @@ -1,71 +0,0 @@ -package usecase - -import ( - "context" - "fmt" - "path" - - "source.toby3d.me/toby3d/home/internal/domain" - "source.toby3d.me/toby3d/home/internal/page" - "source.toby3d.me/toby3d/home/internal/resource" - "source.toby3d.me/toby3d/home/internal/urlutil" -) - -type pageUseCase struct { - pages page.Repository - resources resource.Repository -} - -func NewPageUseCase(pages page.Repository, resources resource.Repository) page.UseCase { - return &pageUseCase{ - pages: pages, - resources: resources, - } -} - -func (ucase *pageUseCase) Do(ctx context.Context, lang domain.Language, p string) (*domain.Page, error) { - ext := path.Ext(p) - if ext == ".html" { - p = p[:len(p)-len(ext)] - } - - hasTrailingSlash := p[len(p)-1] == '/' - head, tail := urlutil.ShiftPath(p) - targets := []string{path.Join(head, tail)} - - if tail == "/" { - if hasTrailingSlash || ext == "" { - targets = append([]string{path.Join(head, "index")}, targets...) - } - - targets = append(targets, head) - } - - for i := range targets { - out, err := ucase.pages.Get(ctx, lang, targets[i]) - if err != nil { - continue - } - - if out.Resources, _, err = ucase.resources.Fetch(ctx, out.File.Dir()+"*"); err != nil { - return out, nil - } - - for _, res := range out.Resources.GetType(domain.ResourceTypePage) { - if res.File.TranslationBaseName() != out.File.TranslationBaseName() { - continue - } - - translation, err := ucase.pages.Get(ctx, res.File.Language, targets[i]) - if err != nil { - continue - } - - out.Translations = append(out.Translations, translation) - } - - return out, nil - } - - return nil, fmt.Errorf("cannot find page on path '%s': %w", p, page.ErrNotExist) -} diff --git a/internal/page/usecase/page_ucase_test.go b/internal/page/usecase/page_ucase_test.go deleted file mode 100644 index 51391d8..0000000 --- a/internal/page/usecase/page_ucase_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package usecase_test - -import ( - "context" - "path/filepath" - "testing" - "testing/fstest" - - "github.com/google/go-cmp/cmp" - - "source.toby3d.me/toby3d/home/internal/domain" - pagefsrepo "source.toby3d.me/toby3d/home/internal/page/repository/fs" - "source.toby3d.me/toby3d/home/internal/page/usecase" - "source.toby3d.me/toby3d/home/internal/resource" -) - -func TestDo(t *testing.T) { - t.Parallel() - - pages := pagefsrepo.NewFileSystemPageRepository(fstest.MapFS{ - filepath.Join("both", "index.md"): &fstest.MapFile{Data: []byte(`both/index.md`)}, - filepath.Join("folder", "index.md"): &fstest.MapFile{Data: []byte(`folder/index.md`)}, - filepath.Join("both.md"): &fstest.MapFile{Data: []byte(`both.md`)}, - filepath.Join("file.md"): &fstest.MapFile{Data: []byte(`file.md`)}, - filepath.Join("index.md"): &fstest.MapFile{Data: []byte(`index.md`)}, - }) - - ucase := usecase.NewPageUseCase(pages, resource.NewDummyRepository()) - - for name, tc := range map[string]struct { - input string - expect []byte - }{ - "index": {input: "/", expect: []byte(`index.md`)}, - "index-ext": {input: "/index.html", expect: []byte(`index.md`)}, - "file": {input: "/file", expect: []byte(`file.md`)}, - "file-slash": {input: "/file/", expect: []byte(`file.md`)}, - "file-ext": {input: "/file.html", expect: []byte(`file.md`)}, - "folder": {input: "/folder", expect: []byte(`folder/index.md`)}, - "folder-slash": {input: "/folder/", expect: []byte(`folder/index.md`)}, - "folder-index": {input: "/folder/index.html", expect: []byte(`folder/index.md`)}, - "both": {input: "/both", expect: []byte(`both/index.md`)}, - "both-slash": {input: "/both/", expect: []byte(`both/index.md`)}, - "both-ext": {input: "/both.html", expect: []byte(`both.md`)}, - "both-index": {input: "/both/index.html", expect: []byte(`both/index.md`)}, - } { - name, tc := name, tc - - t.Run(name, func(t *testing.T) { - t.Parallel() - - actual, err := ucase.Do(context.Background(), domain.LanguageUnd, tc.input) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(string(actual.Content), string(tc.expect)); diff != "" { - t.Error(diff) - } - }) - } -}