From 44e00a35cb178944153527453a2349ac2475b070 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:13:56 +0600 Subject: [PATCH 01/11] :label: Created File domain --- internal/domain/file.go | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 internal/domain/file.go diff --git a/internal/domain/file.go b/internal/domain/file.go new file mode 100644 index 0000000..1517f12 --- /dev/null +++ b/internal/domain/file.go @@ -0,0 +1,102 @@ +package domain + +import ( + "crypto/md5" + "path/filepath" + "strings" + + "golang.org/x/text/language" +) + +type File struct { + language language.Tag + baseFileName string + contentBaseName string + dir string + ext string + filename string + logicalName string + path string + translationBaseName string + uniqueId string +} + +func NewFile(path string) File { + out := File{ + language: language.Tag{}, + baseFileName: "", + contentBaseName: "", + dir: filepath.Dir(path) + "/", + ext: strings.TrimPrefix(filepath.Ext(path), "."), + filename: path, + logicalName: filepath.Base(path), + path: path, + translationBaseName: "", + uniqueId: "", + } + out.path, _ = filepath.Abs(path) + out.baseFileName = strings.TrimSuffix(out.logicalName, filepath.Ext(out.logicalName)) + + parts := strings.Split(out.baseFileName, ".") + out.language = language.Make(parts[len(parts)-1]) + out.translationBaseName = strings.Join(parts[:len(parts)-1], ".") + out.contentBaseName = out.translationBaseName + + switch out.translationBaseName { + default: + out.contentBaseName = out.translationBaseName + case "_index", "index": + out.contentBaseName = filepath.Base(out.dir) + } + + hash := md5.New() + _, _ = hash.Write([]byte(out.path)) + out.uniqueId = string(hash.Sum(nil)) + + return out +} + +// BaseFileName returns file name without extention. +func (f File) BaseFileName() string { + return f.baseFileName +} + +func (f File) ContentBaseName() string { + return f.contentBaseName +} + +// Dir returns directory path. +func (f File) Dir() string { + return f.dir +} + +// Ext returns file extention. +func (f File) Ext() string { + return f.ext +} + +func (f File) Filename() string { + return f.filename +} + +// Language returns language.Tag of current file based on his suffix before +// extention. +func (f File) Language() language.Tag { + return f.language +} + +func (f File) LogicalName() string { + return f.logicalName +} + +func (f File) Path() string { + return f.path +} + +func (f File) TranslationBaseName() string { + return f.translationBaseName +} + +func (f File) UniqueID() string { + return f.uniqueId +} From 704e7af69a22795176642ba465db0dc00e06825f Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:14:13 +0600 Subject: [PATCH 02/11] :white_check_mark: Added File methods tests --- internal/domain/file_test.go | 170 +++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 internal/domain/file_test.go diff --git a/internal/domain/file_test.go b/internal/domain/file_test.go new file mode 100644 index 0000000..90f0963 --- /dev/null +++ b/internal/domain/file_test.go @@ -0,0 +1,170 @@ +package domain_test + +import ( + "path/filepath" + "testing" + + "golang.org/x/text/language" + + "source.toby3d.me/toby3d/home/internal/domain" +) + +var ( + testRegularFile string = filepath.Join("news", "a.en.md") + testLeafFile string = filepath.Join("news", "b", "index.en.md") + testBranchFile string = filepath.Join("news", "_index.en.md") +) + +func TestFile_BaseFileName(t *testing.T) { + t.Parallel() + + for name, tc := range map[string]struct { + input, expect string + }{ + "regular": {testRegularFile, "a.en"}, + "leaf": {testLeafFile, "index.en"}, + "branch": {testBranchFile, "_index.en"}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(tc.input).BaseFileName(); actual != tc.expect { + t.Errorf("BaseFileName() = '%s', want '%s'", actual, tc.expect) + } + }) + } +} + +func TestFile_ContentBaseName(t *testing.T) { + t.Parallel() + + for name, tc := range map[string]struct { + input, expect string + }{ + "regular": {testRegularFile, "a"}, + "leaf": {testLeafFile, "b"}, + "branch": {testBranchFile, "news"}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(tc.input).ContentBaseName(); actual != tc.expect { + t.Errorf("ContentBaseName() = '%s', want '%s'", actual, tc.expect) + } + }) + } +} + +func TestFile_Dir(t *testing.T) { + t.Parallel() + + for name, tc := range map[string]struct { + input, expect string + }{ + "regular": {testRegularFile, "news/"}, + "leaf": {testLeafFile, "news/b/"}, + "branch": {testBranchFile, "news/"}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(tc.input).Dir(); actual != tc.expect { + t.Errorf("Dir() = '%s', want '%s'", actual, tc.expect) + } + }) + } +} + +func TestFile_Ext(t *testing.T) { + t.Parallel() + + const expect string = "md" + + for name, input := range map[string]string{ + "regular": testRegularFile, + "leaf": testLeafFile, + "branch": testBranchFile, + } { + name, input := name, input + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(input).Ext(); actual != expect { + t.Errorf("Ext() = '%s', want '%s'", actual, expect) + } + }) + } +} + +func TestFile_Language(t *testing.T) { + t.Parallel() + + var expect language.Tag = language.English + + for name, input := range map[string]string{ + "regular": testRegularFile, + "leaf": testLeafFile, + "branch": testBranchFile, + } { + name, input := name, input + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(input).Language(); actual != expect { + t.Errorf("Language() = '%s', want '%s'", actual, expect) + } + }) + } +} + +func TestFile_LogicalName(t *testing.T) { + t.Parallel() + + for name, tc := range map[string]struct { + input, expect string + }{ + "regular": {testRegularFile, "a.en.md"}, + "leaf": {testLeafFile, "index.en.md"}, + "branch": {testBranchFile, "_index.en.md"}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(tc.input).LogicalName(); actual != tc.expect { + t.Errorf("LogicalName() = '%s', want '%s'", actual, tc.expect) + } + }) + } +} + +func TestFile_TranslationBaseName(t *testing.T) { + t.Parallel() + + for name, tc := range map[string]struct { + input, expect string + }{ + "regular": {testRegularFile, "a"}, + "leaf": {testLeafFile, "index"}, + "branch": {testBranchFile, "_index"}, + } { + name, tc := name, tc + + t.Run(name, func(t *testing.T) { + t.Parallel() + + if actual := domain.NewFile(tc.input).TranslationBaseName(); actual != tc.expect { + t.Errorf("TranslationBaseName() = '%s', want '%s'", actual, tc.expect) + } + }) + } +} From 9eaf9cd39101cac340e7c3bd363afb73bcb4a6dd Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:14:45 +0600 Subject: [PATCH 03/11] :label: Added File property for Page domain --- internal/domain/page.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/domain/page.go b/internal/domain/page.go index f35dbc3..190a92a 100644 --- a/internal/domain/page.go +++ b/internal/domain/page.go @@ -5,6 +5,7 @@ import "golang.org/x/text/language" type Page struct { Language language.Tag Params map[string]any + File File Description string Title string Content []byte From 753e22ad9a4abdd205cae4aa59b3bf4944c93ffa Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:14:50 +0600 Subject: [PATCH 04/11] :label: Added File property for Site domain --- internal/domain/site.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/domain/site.go b/internal/domain/site.go index 9f97401..270f456 100644 --- a/internal/domain/site.go +++ b/internal/domain/site.go @@ -10,8 +10,9 @@ import ( type Site struct { Language language.Tag BaseURL *url.URL - TimeZone *time.Location Params map[string]any + TimeZone *time.Location + File File Title string Resources Resources } From 2215cc751bed5eb483cdc2f2cb1ca53dcb8a5f35 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:24:22 +0600 Subject: [PATCH 05/11] :label: Remake IsHome property of Page as method --- internal/domain/page.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/domain/page.go b/internal/domain/page.go index 190a92a..edd9fb3 100644 --- a/internal/domain/page.go +++ b/internal/domain/page.go @@ -10,5 +10,8 @@ type Page struct { Title string Content []byte Resources Resources - IsHome bool +} + +func (p Page) IsHome() bool { + return p.File.dir == "./" && p.File.translationBaseName == "index" } From 49824ed100f5840210eafb566c3332c3b10891dc Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:25:06 +0600 Subject: [PATCH 06/11] :card_file_box: Refactored Fetch method of static repository --- internal/static/repository/fs/fs_static.go | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/internal/static/repository/fs/fs_static.go b/internal/static/repository/fs/fs_static.go index e210e1d..9dc17f9 100644 --- a/internal/static/repository/fs/fs_static.go +++ b/internal/static/repository/fs/fs_static.go @@ -46,28 +46,40 @@ func (repo *fileServerStaticRepository) Get(ctx context.Context, p string) (*dom return domain.NewResource(info.ModTime(), content, p), nil } -func (repo *fileServerStaticRepository) Fetch(ctx context.Context, dir string) (domain.Resources, int, error) { - targets := make([]string, 0) - if err := fs.WalkDir(repo.root, dir, func(path string, de fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("received error while walking through '%s': %w", dir, err) - } +func (repo *fileServerStaticRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { + var ( + err error + matches []string + ) + + if pattern != "" { + if matches, err = fs.Glob(repo.root, pattern); err != nil { + return nil, 0, fmt.Errorf("cannot match any static by pattern '%s': %w", pattern, err) + } + } else { + if err = fs.WalkDir(repo.root, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("catched error while walk: %w", err) + } + + if d.IsDir() { + return nil + } + + matches = append(matches, path) - if de.IsDir() { return nil + }); err != nil { + return nil, 0, fmt.Errorf("cannot walk through static directories: %w", err) } - - targets = append(targets, path) - - return nil - }); err != nil { - return nil, 0, fmt.Errorf("cannot read directory on path '%s': %w", dir, err) } - out := make(domain.Resources, len(targets)) + out := make(domain.Resources, 0, len(matches)) - for i := range targets { - out[i], _ = repo.Get(ctx, targets[i]) + for i := range matches { + if r, err := repo.Get(ctx, matches[i]); err == nil { + out = append(out, r) + } } return out, len(out), nil From 408ac2e8a3a6d1bef2f8ab3f862c6d630a2f7fc2 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:25:42 +0600 Subject: [PATCH 07/11] :card_file_box: Set File property for Page in page repository --- internal/page/repository/fs/fs_page.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/page/repository/fs/fs_page.go b/internal/page/repository/fs/fs_page.go index fedc8fd..68eed3e 100644 --- a/internal/page/repository/fs/fs_page.go +++ b/internal/page/repository/fs/fs_page.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io/fs" - "strings" "github.com/adrg/frontmatter" "golang.org/x/text/language" @@ -45,11 +44,11 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang language.Tag ext = "." + base.String() + ext } - index := p + ext + target := p + ext - f, err := repo.dir.Open(index) + f, err := repo.dir.Open(target) if err != nil { - return nil, fmt.Errorf("cannot open '%s' page file: %w", index, err) + return nil, fmt.Errorf("cannot open '%s' page file: %w", target, err) } defer f.Close() @@ -61,12 +60,12 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang language.Tag } return &domain.Page{ + File: domain.NewFile(target), Language: lang, Title: data.Title, Content: data.Content, Description: data.Description, Params: data.Params, Resources: make([]*domain.Resource, 0), - IsHome: strings.HasPrefix(index, "index.") && strings.HasSuffix(index, ".md"), }, nil } From 073e70c425c744c7254f52c27c4e187271ca190b Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:25:52 +0600 Subject: [PATCH 08/11] :card_file_box: Set File property for Site in site repository --- internal/site/repository/fs/fs_site.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/site/repository/fs/fs_site.go b/internal/site/repository/fs/fs_site.go index 23fbedf..1cfa99a 100644 --- a/internal/site/repository/fs/fs_site.go +++ b/internal/site/repository/fs/fs_site.go @@ -69,6 +69,7 @@ func (repo *fileSystemSiteRepository) Get(ctx context.Context, lang language.Tag } return &domain.Site{ + File: domain.NewFile(target), Language: lang, Title: data.Title, BaseURL: data.BaseURL.URL, From 77c76fde452c3c8dd2ae2936650d95a2cb7c6816 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:26:21 +0600 Subject: [PATCH 09/11] :art: Changed argument name for Fetch method static repository --- internal/static/repository.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/static/repository.go b/internal/static/repository.go index e1d26da..95bb822 100644 --- a/internal/static/repository.go +++ b/internal/static/repository.go @@ -12,7 +12,7 @@ type ( Get(ctx context.Context, path string) (*domain.Resource, error) // Fetch returns all resources from dir recursevly. - Fetch(ctx context.Context, dir string) (domain.Resources, int, error) + Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) } dummyRepository struct{} @@ -26,6 +26,6 @@ func (dummyRepository) Get(ctx context.Context, path string) (*domain.Resource, return nil, nil } -func (dummyRepository) Fetch(ctx context.Context, dir string) (domain.Resources, int, error) { +func (dummyRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) { return nil, 0, nil } From 1623b470a565221d1f55c2545d4cc8b4950fc823 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:27:03 +0600 Subject: [PATCH 10/11] :necktie: Fetch all Resources for Site --- internal/site/usecase/site_ucase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/site/usecase/site_ucase.go b/internal/site/usecase/site_ucase.go index 53b37a2..7680404 100644 --- a/internal/site/usecase/site_ucase.go +++ b/internal/site/usecase/site_ucase.go @@ -29,7 +29,7 @@ func (ucase *siteUseCase) Do(ctx context.Context, lang language.Tag) (*domain.Si return nil, fmt.Errorf("cannot find base site data: %w", err) } - out.Resources, _, _ = ucase.statics.Fetch(ctx, ".") + out.Resources, _, _ = ucase.statics.Fetch(ctx, "") sub, err := ucase.sites.Get(ctx, lang) if err != nil { From 8649f22879d3c29e6081b40002ed5083439ef39f Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Fri, 10 Nov 2023 17:27:42 +0600 Subject: [PATCH 11/11] :necktie: Fetch Resources for Page in the current Page directory --- internal/page/usecase/page_ucase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/page/usecase/page_ucase.go b/internal/page/usecase/page_ucase.go index bce1b1a..22453c1 100644 --- a/internal/page/usecase/page_ucase.go +++ b/internal/page/usecase/page_ucase.go @@ -49,7 +49,7 @@ func (ucase *pageUseCase) Do(ctx context.Context, lang language.Tag, p string) ( continue } - out.Resources, _, _ = ucase.statics.Fetch(ctx, path.Dir(targets[i])) + out.Resources, _, _ = ucase.statics.Fetch(ctx, out.File.Dir()+"*") return out, nil }