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 +} 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) + } + }) + } +} diff --git a/internal/domain/page.go b/internal/domain/page.go index f35dbc3..edd9fb3 100644 --- a/internal/domain/page.go +++ b/internal/domain/page.go @@ -5,9 +5,13 @@ import "golang.org/x/text/language" type Page struct { Language language.Tag Params map[string]any + File File Description string Title string Content []byte Resources Resources - IsHome bool +} + +func (p Page) IsHome() bool { + return p.File.dir == "./" && p.File.translationBaseName == "index" } 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 } 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 } 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 } 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, 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 { 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 } 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