Merge branch 'feature/file' into develop
/ docker (push) Successful in 1m11s Details

This commit is contained in:
Maxim Lebedev 2023-11-10 17:27:45 +06:00
commit 6080cce45f
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
10 changed files with 316 additions and 27 deletions

102
internal/domain/file.go Normal file
View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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"
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,

View File

@ -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 {

View File

@ -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
}

View File

@ -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