From 019249ceac0a818425d17999a86835b16a6e70e6 Mon Sep 17 00:00:00 2001 From: sunwei Date: Thu, 28 Mar 2024 21:13:24 +0800 Subject: [PATCH] add site --- .gitignore | 1 + .hugo_build.lock | 0 internal/application/ssg.go | 8 +- .../domain/contenthub/entity/contenthub.go | 193 ++++++++++++++- .../domain/contenthub/entity/contentmap.go | 19 -- internal/domain/contenthub/entity/fileinfo.go | 180 ++++++++++++++ internal/domain/contenthub/entity/page.go | 75 ++++++ .../contenthub/entity/pagecollections.go | 222 ++++++++++++++++++ .../domain/contenthub/entity/pagecommon.go | 23 +- .../contenthub/entity/pagecontentoutput.go | 21 +- internal/domain/contenthub/entity/pagemeta.go | 46 +++- .../domain/contenthub/entity/pageoutput.go | 41 +--- internal/domain/contenthub/entity/pagepath.go | 17 -- .../domain/contenthub/entity/pagestate.go | 16 +- internal/domain/contenthub/factory/hub.go | 3 + internal/domain/contenthub/type.go | 3 + .../domain/contenthub/valueobject/format.go | 115 +++------ .../valueobject/zero_file.autogen.go | 87 +++++++ internal/domain/site/entity/publisher.go | 57 +++++ internal/domain/site/entity/site.go | 101 +++++++- internal/domain/site/factory/site.go | 22 +- internal/domain/site/type.go | 33 +++ internal/domain/site/valueobject/format.go | 109 +++++++++ .../valueobject/outputformat.go | 0 internal/domain/site/valueobject/pagepath.go | 59 +++++ .../entity => site/valueobject}/targetpath.go | 36 +-- .../{contenthub => site}/valueobject/type.go | 0 pkg/bufferpool/bufpool.go | 38 +++ 28 files changed, 1283 insertions(+), 242 deletions(-) create mode 100644 .hugo_build.lock create mode 100644 internal/domain/contenthub/entity/fileinfo.go create mode 100644 internal/domain/contenthub/entity/page.go delete mode 100644 internal/domain/contenthub/entity/pagepath.go create mode 100644 internal/domain/contenthub/valueobject/zero_file.autogen.go create mode 100644 internal/domain/site/entity/publisher.go create mode 100644 internal/domain/site/valueobject/format.go rename internal/domain/{contenthub => site}/valueobject/outputformat.go (100%) create mode 100644 internal/domain/site/valueobject/pagepath.go rename internal/domain/{contenthub/entity => site/valueobject}/targetpath.go (86%) rename internal/domain/{contenthub => site}/valueobject/type.go (100%) create mode 100644 pkg/bufferpool/bufpool.go diff --git a/.gitignore b/.gitignore index 029ccd7..86378af 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ hugov /tls/ /dddplayer/ +/public/ diff --git a/.hugo_build.lock b/.hugo_build.lock new file mode 100644 index 0000000..e69de29 diff --git a/internal/application/ssg.go b/internal/application/ssg.go index 30c5444..cc20892 100644 --- a/internal/application/ssg.go +++ b/internal/application/ssg.go @@ -5,6 +5,7 @@ import ( chFact "github.com/gohugonet/hugoverse/internal/domain/contenthub/factory" fsFact "github.com/gohugonet/hugoverse/internal/domain/fs/factory" mdFact "github.com/gohugonet/hugoverse/internal/domain/module/factory" + stFact "github.com/gohugonet/hugoverse/internal/domain/site/factory" ) func GenerateStaticSite(projPath string) error { @@ -28,7 +29,12 @@ func GenerateStaticSite(projPath string) error { return err } - if err := ch.Process(); err != nil { + if err := ch.CollectPages(); err != nil { + return err + } + + site := stFact.New(fs, ch) + if err := site.Build(); err != nil { return err } diff --git a/internal/domain/contenthub/entity/contenthub.go b/internal/domain/contenthub/entity/contenthub.go index f5aa84e..9532ce1 100644 --- a/internal/domain/contenthub/entity/contenthub.go +++ b/internal/domain/contenthub/entity/contenthub.go @@ -1,10 +1,19 @@ package entity import ( + "bytes" + "context" "fmt" "github.com/gohugonet/hugoverse/internal/domain/contenthub" + "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" + "github.com/gohugonet/hugoverse/internal/domain/template" + "github.com/gohugonet/hugoverse/pkg/bufferpool" + "io" + "sync" ) +var contentSpec *ContentSpec + type ContentHub struct { Fs contenthub.Fs @@ -14,9 +23,27 @@ type ContentHub struct { *ContentSpec *PageCollections + + cb func(kind string, sec []string, dir, name string, buf *bytes.Buffer) error } -func (ch *ContentHub) Process() error { +func (ch *ContentHub) CS() { + contentSpec = ch.ContentSpec +} + +func (ch *ContentHub) CollectPages() error { + if err := ch.process(); err != nil { + return fmt.Errorf("process: %w", err) + } + + if err := ch.assemble(); err != nil { + return fmt.Errorf("assemble: %w", err) + } + + return nil +} + +func (ch *ContentHub) process() error { if err := ch.readAndProcessContent(); err != nil { return fmt.Errorf("readAndProcessContent: %w", err) } @@ -32,3 +59,167 @@ func (ch *ContentHub) readAndProcessContent() error { return nil } + +func (ch *ContentHub) assemble() error { + if err := ch.PageCollections.PageMap.Assemble(); err != nil { + return err + } + return nil +} + +func (ch *ContentHub) PreparePages() error { + var err error + ch.PageCollections.PageMap.withEveryBundlePage(func(p *pageState) bool { + if err = p.initOutputFormat(); err != nil { + return true + } + return false + }) + return nil +} + +func (ch *ContentHub) RenderPages( + cb func(kind string, sec []string, dir, name string, buf *bytes.Buffer) error) error { + ch.cb = cb + + if err := ch.renderPages(); err != nil { + return fmt.Errorf("renderPages: %w", err) + } + + return nil +} + +// renderPages renders pages each corresponding to a markdown file. +func (ch *ContentHub) renderPages() error { + numWorkers := 3 + + results := make(chan error) + pages := make(chan *pageState, numWorkers) // buffered for performance + errs := make(chan error) + + go ch.errorCollator(results, errs) + + wg := &sync.WaitGroup{} + + for i := 0; i < numWorkers; i++ { + wg.Add(1) + go ch.pageRenderer(pages, results, wg) + } + + var count int + ch.PageCollections.PageMap.PageTrees.Walk(func(ss string, n *contentNode) bool { + select { + default: + count++ + fmt.Println("777 count: ", count, ss, n, n.p) + pages <- n.p + } + + return false + }) + + close(pages) + + wg.Wait() + + close(results) + + err := <-errs + if err != nil { + return fmt.Errorf("failed to render pages: %w", err) + } + return nil +} + +func (ch *ContentHub) pageRenderer(pages <-chan *pageState, results chan<- error, wg *sync.WaitGroup) { + defer wg.Done() + + for p := range pages { + fmt.Printf(">>>> page: %#+v\n", p) + + templ, found, err := ch.resolveTemplate(p) + if err != nil { + fmt.Println("failed to resolve template") + continue + } + + if !found { // layout: "", kind: section, name: HTML + fmt.Printf("layout: %s, kind: %s", p.Layout(), p.Kind()) + continue + } + + if err := ch.renderAndWritePage(p, templ); err != nil { + fmt.Println(" render err") + fmt.Printf("%#v", err) + results <- err + } + } +} + +func (ch *ContentHub) renderAndWritePage(p *pageState, templ template.Template) error { + renderBuffer := bufferpool.GetBuffer() + defer bufferpool.PutBuffer(renderBuffer) + + if err := ch.renderForTemplate(p.Kind(), p, renderBuffer, templ); err != nil { + return err + } + + if renderBuffer.Len() == 0 { + return nil + } + + var ( + dir string + baseName string + ) + + if !p.File().IsZero() { + dir = p.File().Dir() + baseName = p.File().TranslationBaseName() + } + + return ch.cb( + p.Kind(), + p.SectionsEntries(), + dir, + baseName, + renderBuffer) +} + +func (ch *ContentHub) renderForTemplate(name string, d any, w io.Writer, templ template.Template) (err error) { + if templ == nil { + fmt.Println("templ is nil") + return nil + } + + if err = ch.TemplateExecutor.ExecuteWithContext(context.Background(), templ, w, d); err != nil { + return fmt.Errorf("render of %q failed: %w", name, err) + } + return +} + +func (ch *ContentHub) resolveTemplate(p *pageState) (template.Template, bool, error) { + f := valueobject.HTMLFormat // set in shiftToOutputFormat + d := p.getLayoutDescriptor() + + lh := valueobject.NewLayoutHandler() + names, err := lh.For(d, f) + if err != nil { + return nil, false, err + } + + return ch.TemplateExecutor.LookupLayout(names) +} + +func (ch *ContentHub) errorCollator(results <-chan error, errs chan<- error) { + var errors []error + for e := range results { + errors = append(errors, e) + } + + if len(errors) > 0 { + errs <- fmt.Errorf("failed to render pages: %v", errors) + } + + close(errs) +} diff --git a/internal/domain/contenthub/entity/contentmap.go b/internal/domain/contenthub/entity/contentmap.go index 8c54f7b..1fb096b 100644 --- a/internal/domain/contenthub/entity/contentmap.go +++ b/internal/domain/contenthub/entity/contentmap.go @@ -204,22 +204,3 @@ func (m *ContentMap) CreateMissingNodes() error { return nil } - -func (m *PageMap) splitKey(k string) []string { - if k == "" || k == "/" { - return nil - } - - return strings.Split(k, "/")[1:] -} - -// withEveryBundlePage applies fn to every Page, including those bundled inside -// leaf bundles. -func (m *PageMap) withEveryBundlePage(fn func(p *pageState) bool) { - m.BundleTrees.Walk(func(s string, n *contentNode) bool { - if n.p != nil { - return fn(n.p) - } - return false - }) -} diff --git a/internal/domain/contenthub/entity/fileinfo.go b/internal/domain/contenthub/entity/fileinfo.go new file mode 100644 index 0000000..d33489c --- /dev/null +++ b/internal/domain/contenthub/entity/fileinfo.go @@ -0,0 +1,180 @@ +package entity + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "github.com/gohugonet/hugoverse/internal/domain/contenthub" + fsVO "github.com/gohugonet/hugoverse/internal/domain/fs/valueobject" + "github.com/gohugonet/hugoverse/pkg/paths" + "path/filepath" + "strings" + "sync" +) + +type fileInfo struct { + contenthub.File +} + +func newFileInfo(fi fsVO.FileMetaInfo) (*fileInfo, error) { + baseFi, err := NewFileInfo(fi) + if err != nil { + return nil, err + } + + f := &fileInfo{ + File: baseFi, + } + + return f, nil +} + +func NewFileInfo(fi fsVO.FileMetaInfo) (*FileInfo, error) { + m := fi.Meta() + + filename := m.Filename + relPath := m.Path + + if relPath == "" { + return nil, fmt.Errorf("no Path provided by %v (%T)", m, m.Fs) + } + + if filename == "" { + return nil, fmt.Errorf("no Filename provided by %v (%T)", m, m.Fs) + } + + relDir := filepath.Dir(relPath) + if relDir == "." { + relDir = "" + } + if !strings.HasSuffix(relDir, paths.FilePathSeparator) { + relDir = relDir + paths.FilePathSeparator + } + + dir, name := filepath.Split(relPath) + if !strings.HasSuffix(dir, paths.FilePathSeparator) { + dir = dir + paths.FilePathSeparator + } + + ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), ".")) + baseName := paths.Filename(name) + + f := &FileInfo{ + filename: filename, + fi: fi, + ext: ext, + dir: dir, + relDir: relDir, // Dir() + relPath: relPath, // Path() + name: name, + baseName: baseName, // BaseFileName() + classifier: m.Classifier, + } + + return f, nil +} + +// FileInfo describes a source file. +type FileInfo struct { + + // Absolute filename to the file on disk. + filename string + + fi fsVO.FileMetaInfo + + // Derived from filename + ext string // Extension without any "." + + name string + + dir string + relDir string + relPath string + baseName string + translationBaseName string + contentBaseName string + section string + classifier fsVO.ContentClass + + uniqueID string + + lazyInit sync.Once +} + +// Path gets the relative path including file name and extension. The directory +// is relative to the content root. +func (fi *FileInfo) Path() string { return fi.relPath } + +// Section returns a file's section. +func (fi *FileInfo) Section() string { + fi.init() + return fi.section +} + +// We create a lot of these FileInfo objects, but there are parts of it used only +// in some cases that is slightly expensive to construct. +func (fi *FileInfo) init() { + fi.lazyInit.Do(func() { + relDir := strings.Trim(fi.relDir, paths.FilePathSeparator) + parts := strings.Split(relDir, paths.FilePathSeparator) + var section string + if len(parts) > 1 { + section = parts[0] + } + fi.section = section + fi.contentBaseName = fi.translationBaseName + fi.uniqueID = MD5String(filepath.ToSlash(fi.relPath)) + }) +} + +// MD5String takes a string and returns its MD5 hash. +func MD5String(f string) string { + h := md5.New() + h.Write([]byte(f)) + return hex.EncodeToString(h.Sum([]byte{})) +} + +func (fi *FileInfo) IsZero() bool { + return fi == nil +} + +// Dir gets the name of the directory that contains this file. The directory is +// relative to the content root. +func (fi *FileInfo) Dir() string { return fi.relDir } + +// Extension is an alias to Ext(). +func (fi *FileInfo) Extension() string { + return fi.Ext() +} + +// Ext returns a file's extension without the leading period (ie. "md"). +func (fi *FileInfo) Ext() string { return fi.ext } + +// Filename returns a file's absolute path and filename on disk. +func (fi *FileInfo) Filename() string { return fi.filename } + +// LogicalName returns a file's name and extension (ie. "page.sv.md"). +func (fi *FileInfo) LogicalName() string { return fi.name } + +// BaseFileName returns a file's name without extension (ie. "page.sv"). +func (fi *FileInfo) BaseFileName() string { return fi.baseName } + +// TranslationBaseName returns a file's translation base name without the +// language segment (ie. "page"). +func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName } + +// ContentBaseName is a either TranslationBaseName or name of containing folder +// if file is a leaf bundle. +func (fi *FileInfo) ContentBaseName() string { + fi.init() + return fi.contentBaseName +} + +// UniqueID returns a file's unique, MD5 hash identifier. +func (fi *FileInfo) UniqueID() string { + fi.init() + return fi.uniqueID +} + +// FileInfo returns a file's underlying os.FileInfo. +func (fi *FileInfo) FileInfo() fsVO.FileMetaInfo { return fi.fi } diff --git a/internal/domain/contenthub/entity/page.go b/internal/domain/contenthub/entity/page.go new file mode 100644 index 0000000..4d43c18 --- /dev/null +++ b/internal/domain/contenthub/entity/page.go @@ -0,0 +1,75 @@ +package entity + +import ( + "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" + "github.com/gohugonet/hugoverse/pkg/lazy" +) + +var ( + nopPageOutput = &pageOutput{ + // TODO, simplify + } +) + +func newPageBase(metaProvider *pageMeta) (*pageState, error) { + ps := &pageState{ + pageOutput: nopPageOutput, + pageCommon: &pageCommon{ + // Simplify: FileProvider... + FileProvider: metaProvider, + PageMetaProvider: metaProvider, + init: lazy.New(), + m: metaProvider, + }, + } + + return ps, nil +} + +func newPage(n *contentNode, kind string, sections ...string) *pageState { + p, err := newPageFromMeta( + n, + &pageMeta{ + kind: kind, + sections: sections, + }) + if err != nil { + panic(err) + } + + return p +} + +func newPageFromMeta(n *contentNode, metaProvider *pageMeta) (*pageState, error) { + if metaProvider.f == nil { + metaProvider.f = valueobject.NewZeroFile() + } + + ps, err := newPageBase(metaProvider) + if err != nil { + return nil, err + } + + metaProvider.setMetadata() + metaProvider.applyDefaultValues() + + ps.init.Add(func() (any, error) { + makeOut := func() *pageOutput { + return newPageOutput() + } + + ps.pageOutputs = make([]*pageOutput, 1) + po := makeOut() + ps.pageOutputs[0] = po + + contentProvider, err := newPageContentOutput(ps) + if err != nil { + return nil, err + } + po.initContentProvider(contentProvider) + + return nil, nil + }) + + return ps, err +} diff --git a/internal/domain/contenthub/entity/pagecollections.go b/internal/domain/contenthub/entity/pagecollections.go index fee6b3e..c19c40e 100644 --- a/internal/domain/contenthub/entity/pagecollections.go +++ b/internal/domain/contenthub/entity/pagecollections.go @@ -1,5 +1,16 @@ package entity +import ( + "fmt" + "github.com/gohugonet/hugoverse/internal/domain/contenthub" + fsVO "github.com/gohugonet/hugoverse/internal/domain/fs/valueobject" + "github.com/gohugonet/hugoverse/pkg/io" + "github.com/gohugonet/hugoverse/pkg/parser/pageparser" + "github.com/gohugonet/hugoverse/pkg/paths" + "path" + "strings" +) + // PageCollections contains the page collections for a site. type PageCollections struct { PageMap *PageMap @@ -8,3 +19,214 @@ type PageCollections struct { type PageMap struct { *ContentMap } + +func (m *PageMap) Assemble() error { + if err := m.CreateMissingNodes(); err != nil { + return err + } + + if err := m.AssemblePages(); err != nil { + return err + } + + // Handle any new sections created in the step above. + if err := m.AssembleSections(); err != nil { + return err + } + return nil +} + +func (m *PageMap) AssemblePages() error { + var err error + if err = m.AssembleSections(); err != nil { + return err + } + + m.Pages.Walk(func(k string, v any) bool { + n := v.(*contentNode) + if n.p != nil { + return false + } + + _, parent := m.getSection(k) + if parent == nil { + panic(fmt.Sprintf("BUG: parent not set for %q", k)) + } + + n.p, err = m.newPageFromContentNode(n) + fmt.Printf(">>> AssemblePages 000 %+v\n", n.p) + + if err != nil { + return true + } + + return false + }) + + return err +} + +func (m *PageMap) AssembleSections() error { + m.Sections.Walk(func(k string, v any) bool { + n := v.(*contentNode) + sections := m.splitKey(k) + + kind := contenthub.KindSection + if k == "/" { + kind = contenthub.KindHome + } + if n.fi != nil { + panic("assembleSections newPageFromContentNode not ready") + } else { + n.p = newPage(n, kind, sections...) + fmt.Println(">>> assembleSections new page 999", sections, k, n.p) + } + + return false + }) + + return nil +} + +func (m *PageMap) newPageFromContentNode(n *contentNode) (*pageState, error) { + if n.fi == nil { + panic("FileInfo must (currently) be set") + } + + f, err := newFileInfo(n.fi) + if err != nil { + return nil, err + } + + meta := n.fi.Meta() + content := func() (io.ReadSeekCloser, error) { + return meta.Open() + } + + sections := m.sectionsFromFile(f) + kind := m.kindFromFileInfoOrSections(f, sections) + + // TODO, site in meta provider + metaProvider := &pageMeta{kind: kind, sections: sections, bundled: false, f: f} + ps, err := newPageBase(metaProvider) + if err != nil { + return nil, err + } + + n.p = ps + r, err := content() + if err != nil { + return nil, err + } + defer r.Close() + + // .md parseResult + // TODO: parser works way + parseResult, err := pageparser.Parse( + r, + pageparser.Config{EnableEmoji: false}, + ) + if err != nil { + return nil, err + } + + ps.pageContent = pageContent{ + source: rawPageContent{ + parsed: parseResult, + posMainContent: -1, + posSummaryEnd: -1, + posBodyStart: -1, + }, + } + + if err := ps.mapContent(metaProvider); err != nil { + return nil, err + } + metaProvider.applyDefaultValues() + ps.init.Add(func() (any, error) { + fmt.Printf("TODO init page start : %+v\n", ps) + + makeOut := func() *pageOutput { + return newPageOutput() + } + + // Prepare output formats for all sites. + // We do this even if this page does not get rendered on + // its own. It may be referenced via .Site.GetPage and + // it will then need an output format. + ps.pageOutputs = make([]*pageOutput, 1) + po := makeOut() + ps.pageOutputs[0] = po + + contentProvider, err := newPageContentOutput(ps) + if err != nil { + return nil, err + } + po.initContentProvider(contentProvider) + + return nil, nil + }) + + return ps, nil +} + +func (m *PageMap) sectionsFromFile(fi contenthub.File) []string { + dirname := fi.Dir() + + dirname = strings.Trim(dirname, paths.FilePathSeparator) + if dirname == "" { + return nil + } + parts := strings.Split(dirname, paths.FilePathSeparator) + + if fii, ok := fi.(*fileInfo); ok { + if len(parts) > 0 && fii.FileInfo().Meta().Classifier == fsVO.ContentClassLeaf { + // my-section/mybundle/index.md => my-section + return parts[:len(parts)-1] + } + } + + return parts +} + +func (m *PageMap) kindFromFileInfoOrSections(fi *fileInfo, sections []string) string { + if fi.TranslationBaseName() == "_index" { + if fi.Dir() == "" { + return contenthub.KindHome + } + return m.kindFromSections(sections) + } + + return contenthub.KindPage +} + +func (m *PageMap) kindFromSections(sections []string) string { + if len(sections) == 0 { + return contenthub.KindHome + } + + return m.kindFromSectionPath(path.Join(sections...)) +} + +func (m *PageMap) kindFromSectionPath(sectionPath string) string { + return contenthub.KindSection +} + +func (m *PageMap) splitKey(k string) []string { + if k == "" || k == "/" { + return nil + } + + return strings.Split(k, "/")[1:] +} + +// withEveryBundlePage applies fn to every Page, including those bundled inside +// leaf bundles. +func (m *PageMap) withEveryBundlePage(fn func(p *pageState) bool) { + m.BundleTrees.Walk(func(s string, n *contentNode) bool { + if n.p != nil { + return fn(n.p) + } + return false + }) +} diff --git a/internal/domain/contenthub/entity/pagecommon.go b/internal/domain/contenthub/entity/pagecommon.go index 3e49429..3f87646 100644 --- a/internal/domain/contenthub/entity/pagecommon.go +++ b/internal/domain/contenthub/entity/pagecommon.go @@ -3,6 +3,7 @@ package entity import ( "github.com/gohugonet/hugoverse/internal/domain/contenthub" "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" + valueobject2 "github.com/gohugonet/hugoverse/internal/domain/site/valueobject" "github.com/gohugonet/hugoverse/pkg/compare" "github.com/gohugonet/hugoverse/pkg/lazy" "sync" @@ -11,28 +12,18 @@ import ( type pageCommon struct { m *pageMeta - //bucket *pagesMapBucket - //treeRef *contentTreeRef - // Lazily initialized dependencies. init *lazy.Init // All of these represents the common parts of a page.Page - //page.ChildCareProvider contenthub.FileProvider - //contenthub.OutputFormatsProvider contenthub.PageMetaProvider - //hugosites.SitesProvider - //page.TreeProvider - //resource.LanguageProvider - //resource.ResourceMetaProvider - //resource.ResourceParamsProvider - //resource.ResourceTypeProvider + compare.Eqer // Describes how paths and URLs for this page and its descendants // should look like. - targetPathDescriptor TargetPathDescriptor + targetPathDescriptor valueobject2.TargetPathDescriptor layoutDescriptor valueobject.LayoutDescriptor layoutDescriptorInit sync.Once @@ -40,14 +31,6 @@ type pageCommon struct { // The parsed page content. pageContent - // Any bundled resources - //resources resource.Resources - //resourcesInit sync.Once - //resourcesPublishInit sync.Once - - //translations page.Pages - //allTranslations page.Pages - // Calculated an cached translation mapping key translationKey string translationKeyInit sync.Once diff --git a/internal/domain/contenthub/entity/pagecontentoutput.go b/internal/domain/contenthub/entity/pagecontentoutput.go index e3cfcfe..797e81d 100644 --- a/internal/domain/contenthub/entity/pagecontentoutput.go +++ b/internal/domain/contenthub/entity/pagecontentoutput.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "github.com/gohugonet/hugoverse/internal/domain/contenthub" - "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" + "github.com/gohugonet/hugoverse/internal/domain/site/valueobject" "github.com/gohugonet/hugoverse/pkg/lazy" "html/template" "runtime/debug" @@ -13,12 +13,11 @@ import ( "time" ) -func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, error) { +func newPageContentOutput(p *pageState) (*pageContentOutput, error) { parent := p.init cp := &pageContentOutput{ p: p, - f: po.f, } initContent := func() (err error) { @@ -105,6 +104,8 @@ func (cp *pageContentOutput) renderContent( func (cp *pageContentOutput) renderContentWithConverter( c contenthub.Converter, content []byte, renderTOC bool) (contenthub.Result, error) { + fmt.Println("renderContentWithConverter", string(content), renderTOC) + r, err := c.Convert( contenthub.RenderContext{ Src: content, @@ -115,8 +116,16 @@ func (cp *pageContentOutput) renderContentWithConverter( } func (cp *pageContentOutput) Content() (any, error) { - //if cp.p.s.initInit(cp.initMain) { - // return cp.content, nil - //} + if cp.initInit(cp.initMain) { + return cp.content, nil + } return nil, nil } + +func (cp *pageContentOutput) initInit(init *lazy.Init) bool { + _, err := init.Do() + if err != nil { + fmt.Printf("fatal error %v", err) + } + return err == nil +} diff --git a/internal/domain/contenthub/entity/pagemeta.go b/internal/domain/contenthub/entity/pagemeta.go index 0d73821..9173298 100644 --- a/internal/domain/contenthub/entity/pagemeta.go +++ b/internal/domain/contenthub/entity/pagemeta.go @@ -1,8 +1,8 @@ package entity import ( + "fmt" "github.com/gohugonet/hugoverse/internal/domain/contenthub" - "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" "sync" ) @@ -62,6 +62,10 @@ type pageMeta struct { f contenthub.File } +func (p *pageMeta) setMetadata() { + p.markup = "markdown" +} + func (p *pageMeta) applyDefaultValues() { // buildConfig, markup, title if p.markup == "" { p.markup = "markdown" @@ -92,19 +96,41 @@ func (p *pageMeta) Layout() string { return p.layout } -// The output formats this page will be rendered to. -func (p *pageMeta) outputFormats() valueobject.Formats { - // TODO - return nil - //return p.s.OutputFormats[p.Kind()] -} - func (p *pageMeta) noLink() bool { return false } func (p *pageMeta) newContentConverter(ps *pageState, markup string) (contenthub.Converter, error) { - //TODO + if ps == nil { + panic("no Page provided") + } + cp := contentSpec.GetContentProvider(markup) + if cp == nil { + panic(fmt.Errorf("no content renderer found for markup %q", p.markup)) + } + + var id string + var filename string + var path string + if !p.f.IsZero() { + id = p.f.UniqueID() + filename = p.f.Filename() + path = p.f.Path() + } else { + panic("no file provided") + } + + cpp, err := cp.New( + contenthub.DocumentContext{ + Document: nil, //TODO + DocumentID: id, + DocumentName: path, + Filename: filename, + }, + ) + if err != nil { + panic(err) + } - return nil, nil + return cpp, nil } diff --git a/internal/domain/contenthub/entity/pageoutput.go b/internal/domain/contenthub/entity/pageoutput.go index e0a1fa2..314ae17 100644 --- a/internal/domain/contenthub/entity/pageoutput.go +++ b/internal/domain/contenthub/entity/pageoutput.go @@ -1,61 +1,24 @@ package entity import ( - "fmt" "github.com/gohugonet/hugoverse/internal/domain/contenthub" - "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" ) // We create a pageOutput for every output format combination, even if this // particular page isn't configured to be rendered to that format. type pageOutput struct { - // Set if this page isn't configured to be rendered to this format. - render bool - - f valueobject.Format - - // Only set if render is set. - // Note that this will be lazily initialized, so only used if actually - // used in template(s). - //paginator *pagePaginator - // These interface provides the functionality that is specific for this // output format. contenthub.PagePerOutputProviders contenthub.ContentProvider - //page.TableOfContentsProvider // May be nil. cp *pageContentOutput } -func newPageOutput(ps *pageState, pp pagePaths, f valueobject.Format, render bool) *pageOutput { - var targetPathsProvider targetPathsHolder - var linksProvider contenthub.ResourceLinksProvider - - ft, found := pp.targetPaths[f.Name] - if !found { - // Link to the main output format - ft = pp.targetPaths[pp.firstOutputFormat.Format.Name] - } - targetPathsProvider = ft - linksProvider = ft - - providers := struct { - contenthub.ResourceLinksProvider - //contenthub.TargetPather - }{ - linksProvider, - //targetPathsProvider, - } - - fmt.Println(targetPathsProvider) - +func newPageOutput() *pageOutput { po := &pageOutput{ - f: f, - PagePerOutputProviders: providers, - ContentProvider: nil, - render: render, + ContentProvider: nil, } return po diff --git a/internal/domain/contenthub/entity/pagepath.go b/internal/domain/contenthub/entity/pagepath.go deleted file mode 100644 index 44c3f42..0000000 --- a/internal/domain/contenthub/entity/pagepath.go +++ /dev/null @@ -1,17 +0,0 @@ -package entity - -import ( - "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" -) - -type pagePaths struct { - outputFormats valueobject.OutputFormats - firstOutputFormat valueobject.OutputFormat - - targetPaths map[string]targetPathsHolder - targetPathDescriptor TargetPathDescriptor -} - -func (l pagePaths) OutputFormats() valueobject.OutputFormats { - return l.outputFormats -} diff --git a/internal/domain/contenthub/entity/pagestate.go b/internal/domain/contenthub/entity/pagestate.go index c6c9a1b..9e92619 100644 --- a/internal/domain/contenthub/entity/pagestate.go +++ b/internal/domain/contenthub/entity/pagestate.go @@ -87,7 +87,7 @@ func (p *pageState) shiftToOutputFormat() error { cp := p.pageOutput.cp if cp == nil { var err error - cp, err = newPageContentOutput(p, p.pageOutput) + cp, err = newPageContentOutput(p) if err != nil { return err } @@ -105,13 +105,6 @@ func (p *pageState) initPage() error { return nil } -func (p *pageState) initCommonProviders(pp pagePaths) error { - //p.OutputFormatsProvider = pp - p.targetPathDescriptor = pp.targetPathDescriptor - - return nil -} - func (p *pageState) getContentConverter() contenthub.Converter { var err error p.m.contentConverterInit.Do(func() { @@ -129,13 +122,6 @@ func (p *pageState) getContentConverter() contenthub.Converter { return p.m.contentConverter } -func (p *pageState) outputFormat() (f valueobject.Format) { - if p.pageOutput == nil { - panic("no pageOutput") - } - return p.pageOutput.f -} - func (p *pageState) getLayoutDescriptor() valueobject.LayoutDescriptor { p.layoutDescriptorInit.Do(func() { var section string diff --git a/internal/domain/contenthub/factory/hub.go b/internal/domain/contenthub/factory/hub.go index 404135c..97affbc 100644 --- a/internal/domain/contenthub/factory/hub.go +++ b/internal/domain/contenthub/factory/hub.go @@ -28,6 +28,9 @@ func New(fs contenthub.Fs) (*entity.ContentHub, error) { }), } + // TODO remove it + ch.CS() + return ch, nil } diff --git a/internal/domain/contenthub/type.go b/internal/domain/contenthub/type.go index 4ba6c90..031f6a4 100644 --- a/internal/domain/contenthub/type.go +++ b/internal/domain/contenthub/type.go @@ -14,6 +14,8 @@ const ( KindSection = "section" ) +var AllKindsInPages = []string{KindPage, KindHome, KindSection} + type Fs interface { LayoutFs() afero.Fs ContentFs() afero.Fs @@ -21,6 +23,7 @@ type Fs interface { type TemplateExecutor interface { ExecuteWithContext(ctx context.Context, tmpl template.Template, wr io.Writer, data any) error + LookupLayout(layoutNames []string) (template.Template, bool, error) } type ConverterRegistry interface { diff --git a/internal/domain/contenthub/valueobject/format.go b/internal/domain/contenthub/valueobject/format.go index 3959c67..8035632 100644 --- a/internal/domain/contenthub/valueobject/format.go +++ b/internal/domain/contenthub/valueobject/format.go @@ -1,10 +1,11 @@ package valueobject -import ( - "fmt" - "github.com/gohugonet/hugoverse/internal/domain/contenthub" - "strings" -) +// HTMLFormat An ordered list of built-in output formats. +var HTMLFormat = Format{ + Name: "HTML", + MediaType: HTMLType, + BaseName: "index", +} // Format represents an output representation, usually to a file on disk. type Format struct { @@ -18,92 +19,36 @@ type Format struct { BaseName string `json:"baseName"` } -// Formats is a slice of Format. -type Formats []Format +const defaultDelimiter = "." -func (formats Formats) Len() int { return len(formats) } -func (formats Formats) Swap(i, j int) { formats[i], formats[j] = formats[j], formats[i] } -func (formats Formats) Less(i, j int) bool { - fi, fj := formats[i], formats[j] - return fi.Name < fj.Name -} +var HTMLType = newMediaType("text", "html") -// GetByName gets a format by its identifier name. -func (formats Formats) GetByName(name string) (f Format, found bool) { - for _, ff := range formats { - if strings.EqualFold(name, ff.Name) { - f = ff - found = true - return - } - } - return +func newMediaType(main, sub string) Type { + t := Type{ + MainType: main, + SubType: sub, + Delimiter: defaultDelimiter} + return t } -// FromFilename gets a Format given a filename. -func (formats Formats) FromFilename(filename string) (f Format, found bool) { - // mytemplate.amp.html - // mytemplate.html - // mytemplate - var ext, outFormat string - - parts := strings.Split(filename, ".") - if len(parts) > 2 { - outFormat = parts[1] - ext = parts[2] - } else if len(parts) > 1 { - ext = parts[1] - } - - if outFormat != "" { - return formats.GetByName(outFormat) - } - - if ext != "" { - f, found = formats.GetByName(ext) - } - return +type Type struct { + MainType string `json:"mainType"` // i.e. text + SubType string `json:"subType"` // i.e. html + Delimiter string `json:"delimiter"` // e.g. "." } -// HTMLFormat An ordered list of built-in output formats. -var HTMLFormat = Format{ - Name: "HTML", - MediaType: HTMLType, - BaseName: "index", -} - -// DefaultFormats contains the default output formats supported by Hugo. -var DefaultFormats = Formats{ - HTMLFormat, -} - -// DecodeFormats takes a list of output format configurations and merges those, -// in the order given, with the Hugo defaults as the last resort. -func DecodeFormats(mediaTypes Types) Formats { - // Format could be modified by mediaTypes configuration - // just make it simple for example - fmt.Println(mediaTypes) - - f := make(Formats, len(DefaultFormats)) - copy(f, DefaultFormats) - - return f -} - -func CreateSiteOutputFormats(allFormats Formats) map[string]Formats { - defaultOutputFormats := - createDefaultOutputFormats(allFormats) - return defaultOutputFormats +// Type returns a string representing the main- and sub-type of a media type, e.g. "text/css". +// A suffix identifier will be appended after a "+" if set, e.g. "image/svg+xml". +// Hugo will register a set of default media types. +// These can be overridden by the user in the configuration, +// by defining a media type with the same Type. +func (m Type) Type() string { + // Examples are + // image/svg+xml + // text/css + return m.MainType + "/" + m.SubType } -func createDefaultOutputFormats(allFormats Formats) map[string]Formats { - htmlOut, _ := allFormats.GetByName(HTMLFormat.Name) - - m := map[string]Formats{ - contenthub.KindPage: {htmlOut}, - contenthub.KindHome: {htmlOut}, - contenthub.KindSection: {htmlOut}, - } - - return m +func (m Type) FullSuffix() string { + return m.Delimiter + m.SubType } diff --git a/internal/domain/contenthub/valueobject/zero_file.autogen.go b/internal/domain/contenthub/valueobject/zero_file.autogen.go new file mode 100644 index 0000000..5b0a634 --- /dev/null +++ b/internal/domain/contenthub/valueobject/zero_file.autogen.go @@ -0,0 +1,87 @@ +// Copyright 2019 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is autogenerated. + +package valueobject + +import ( + "fmt" + "github.com/gohugonet/hugoverse/internal/domain/contenthub" + "github.com/gohugonet/hugoverse/internal/domain/fs/valueobject" +) + +// ZeroFile represents a zero value of source.File with warnings if invoked. +type zeroFile struct { +} + +func NewZeroFile() contenthub.File { + return zeroFile{} +} + +func (zeroFile) IsZero() bool { + return true +} + +func (z zeroFile) Path() (o0 string) { + fmt.Println(".File.Path on zero object. Wrap it in if or with: {{ with .File }}{{ .Path }}{{ end }}") + return +} +func (z zeroFile) Section() (o0 string) { + fmt.Println(".File.Section on zero object. Wrap it in if or with: {{ with .File }}{{ .Section }}{{ end }}") + return +} +func (z zeroFile) Lang() (o0 string) { + fmt.Println(".File.Lang on zero object. Wrap it in if or with: {{ with .File }}{{ .Lang }}{{ end }}") + return +} +func (z zeroFile) Filename() (o0 string) { + fmt.Println(".File.Filename on zero object. Wrap it in if or with: {{ with .File }}{{ .Filename }}{{ end }}") + return +} +func (z zeroFile) Dir() (o0 string) { + fmt.Println(".File.Dir on zero object. Wrap it in if or with: {{ with .File }}{{ .Dir }}{{ end }}") + return +} +func (z zeroFile) Extension() (o0 string) { + fmt.Println(".File.Extension on zero object. Wrap it in if or with: {{ with .File }}{{ .Extension }}{{ end }}") + return +} +func (z zeroFile) Ext() (o0 string) { + fmt.Println(".File.Ext on zero object. Wrap it in if or with: {{ with .File }}{{ .Ext }}{{ end }}") + return +} +func (z zeroFile) LogicalName() (o0 string) { + fmt.Println(".File.LogicalName on zero object. Wrap it in if or with: {{ with .File }}{{ .LogicalName }}{{ end }}") + return +} +func (z zeroFile) BaseFileName() (o0 string) { + fmt.Println(".File.BaseFileName on zero object. Wrap it in if or with: {{ with .File }}{{ .BaseFileName }}{{ end }}") + return +} +func (z zeroFile) TranslationBaseName() (o0 string) { + fmt.Println(".File.TranslationBaseName on zero object. Wrap it in if or with: {{ with .File }}{{ .TranslationBaseName }}{{ end }}") + return +} +func (z zeroFile) ContentBaseName() (o0 string) { + fmt.Println(".File.ContentBaseName on zero object. Wrap it in if or with: {{ with .File }}{{ .ContentBaseName }}{{ end }}") + return +} +func (z zeroFile) UniqueID() (o0 string) { + fmt.Println(".File.UniqueID on zero object. Wrap it in if or with: {{ with .File }}{{ .UniqueID }}{{ end }}") + return +} +func (z zeroFile) FileInfo() (o0 valueobject.FileMetaInfo) { + fmt.Println(".File.FileInfo on zero object. Wrap it in if or with: {{ with .File }}{{ .FileInfo }}{{ end }}") + return +} diff --git a/internal/domain/site/entity/publisher.go b/internal/domain/site/entity/publisher.go new file mode 100644 index 0000000..048e07c --- /dev/null +++ b/internal/domain/site/entity/publisher.go @@ -0,0 +1,57 @@ +package entity + +import ( + "errors" + "github.com/gohugonet/hugoverse/internal/domain/site" + "github.com/spf13/afero" + "io" + "os" + "path/filepath" +) + +// DestinationPublisher is the default and currently only publisher in Hugo. This +// publisher prepares and publishes an item to the defined destination, e.g. /public. +type DestinationPublisher struct { + Fs afero.Fs +} + +// Publish applies any relevant transformations and writes the file +// to its destination, e.g. /public. +func (p *DestinationPublisher) Publish(d site.Descriptor) error { + if d.TargetPath == "" { + return errors.New("publish: must provide a TargetPath") + } + src := d.Src + + f, err := OpenFileForWriting(p.Fs, d.TargetPath) + if err != nil { + return err + } + defer f.Close() + + var w io.Writer = f + + _, err = io.Copy(w, src) + + return err +} + +// OpenFileForWriting opens or creates the given file. If the target directory +// does not exist, it gets created. +func OpenFileForWriting(fs afero.Fs, filename string) (afero.File, error) { + filename = filepath.Clean(filename) + // Create will truncate if file already exists. + // os.Create will create any new files with mode 0666 (before umask). + f, err := fs.Create(filename) + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + if err = fs.MkdirAll(filepath.Dir(filename), 0777); err != nil { // before umask + return nil, err + } + f, err = fs.Create(filename) + } + + return f, err +} diff --git a/internal/domain/site/entity/site.go b/internal/domain/site/entity/site.go index 6ec59e7..8bc58c7 100644 --- a/internal/domain/site/entity/site.go +++ b/internal/domain/site/entity/site.go @@ -1,4 +1,103 @@ package entity +import ( + "bytes" + "github.com/gohugonet/hugoverse/internal/domain/contenthub" + "github.com/gohugonet/hugoverse/internal/domain/site" + "github.com/gohugonet/hugoverse/internal/domain/site/valueobject" + "sort" +) + type Site struct { -} \ No newline at end of file + // Output formats defined in site config per Page Kind, or some defaults + // if not set. + // Output formats defined in Page front matter will override these. + OutputFormats map[string]valueobject.Formats + + // The output formats that we need to render this site in. This slice + // will be fixed once set. + // This will be the union of Site.Pages' outputFormats. + // This slice will be sorted. + RenderFormats valueobject.Formats + + // All the output formats and media types available for this site. + // These values will be merged from the Hugo defaults, the site config and, + // finally, the language settings. + OutputFormatsConfig valueobject.Formats + MediaTypesConfig valueobject.Types + + Publisher site.Publisher + + ContentSpec site.ContentSpec +} + +func (s *Site) Build() error { + err := s.render() + if err != nil { + return err + } + + return nil +} + +func (s *Site) render() error { + s.initRenderFormats() + + // Get page output ready + if err := s.preparePagesForRender(); err != nil { + return err + } + if err := s.renderPages(); err != nil { + return err + } + + return nil +} + +func (s *Site) renderPages() error { + return s.ContentSpec.RenderPages(func(kind string, sec []string, dir, name string, buf *bytes.Buffer) error { + pp, err := valueobject.NewPagePaths(s.OutputFormatsConfig, kind, sec, dir, name) + if err != nil { + return err + } + + for _, of := range s.RenderFormats { + pd := site.Descriptor{ + Src: buf, + TargetPath: pp.TargetPaths[of.Name].Paths.TargetFilename, + OutputFormat: of, + } + return s.Publisher.Publish(pd) + } + + return nil + }) +} + +func (s *Site) preparePagesForRender() error { + return s.ContentSpec.PreparePages() +} + +func (s *Site) initRenderFormats() { + formatSet := make(map[string]bool) + formats := valueobject.Formats{} + + // media type - format + // site output format - render format + // Add the per kind configured output formats + for _, kind := range contenthub.AllKindsInPages { + if siteFormats, found := s.OutputFormats[kind]; found { + for _, f := range siteFormats { + if !formatSet[f.Name] { + formats = append(formats, f) + formatSet[f.Name] = true + } + } + } + } + + sort.Sort(formats) + + // HTML + s.RenderFormats = formats +} diff --git a/internal/domain/site/factory/site.go b/internal/domain/site/factory/site.go index a488480..726919a 100644 --- a/internal/domain/site/factory/site.go +++ b/internal/domain/site/factory/site.go @@ -1,7 +1,23 @@ package factory -import "github.com/gohugonet/hugoverse/internal/domain/site/entity" +import ( + "github.com/gohugonet/hugoverse/internal/domain/site" + "github.com/gohugonet/hugoverse/internal/domain/site/entity" + "github.com/gohugonet/hugoverse/internal/domain/site/valueobject" +) -func New() *entity.Site { - return &entity.Site{} +func New(fs site.Fs, cs site.ContentSpec) *entity.Site { + mediaTypes := valueobject.DecodeTypes() + formats := valueobject.DecodeFormats(mediaTypes) + outputFormats := valueobject.CreateSiteOutputFormats(formats) + + return &entity.Site{ + OutputFormats: outputFormats, + OutputFormatsConfig: formats, + MediaTypesConfig: mediaTypes, + + Publisher: &entity.DestinationPublisher{Fs: fs.Publish()}, + + ContentSpec: cs, + } } diff --git a/internal/domain/site/type.go b/internal/domain/site/type.go index fa9c383..80a079d 100644 --- a/internal/domain/site/type.go +++ b/internal/domain/site/type.go @@ -1,4 +1,37 @@ package site +import ( + "bytes" + "github.com/gohugonet/hugoverse/internal/domain/site/valueobject" + "github.com/spf13/afero" + "io" +) + +type Fs interface { + Publish() afero.Fs +} + type ContentSpec interface { + PreparePages() error + RenderPages(func(kind string, sec []string, dir, name string, buf *bytes.Buffer) error) error +} + +// Publisher publishes a result file. +type Publisher interface { + Publish(d Descriptor) error +} + +// Descriptor describes the needed publishing chain for an item. +type Descriptor struct { + // The content to publish. + Src io.Reader + + // The OutputFormat of this content. + OutputFormat valueobject.Format + + // Where to publish this content. This is a filesystem-relative path. + TargetPath string + + // If set, will replace all relative URLs with this one. + AbsURLPath string } diff --git a/internal/domain/site/valueobject/format.go b/internal/domain/site/valueobject/format.go new file mode 100644 index 0000000..3959c67 --- /dev/null +++ b/internal/domain/site/valueobject/format.go @@ -0,0 +1,109 @@ +package valueobject + +import ( + "fmt" + "github.com/gohugonet/hugoverse/internal/domain/contenthub" + "strings" +) + +// Format represents an output representation, usually to a file on disk. +type Format struct { + // The Name is used as an identifier. Internal output formats (i.e. HTML and RSS) + // can be overridden by providing a new definition for those types. + Name string `json:"name"` + + MediaType Type `json:"-"` + + // The base output file name used when not using "ugly URLs", defaults to "index". + BaseName string `json:"baseName"` +} + +// Formats is a slice of Format. +type Formats []Format + +func (formats Formats) Len() int { return len(formats) } +func (formats Formats) Swap(i, j int) { formats[i], formats[j] = formats[j], formats[i] } +func (formats Formats) Less(i, j int) bool { + fi, fj := formats[i], formats[j] + return fi.Name < fj.Name +} + +// GetByName gets a format by its identifier name. +func (formats Formats) GetByName(name string) (f Format, found bool) { + for _, ff := range formats { + if strings.EqualFold(name, ff.Name) { + f = ff + found = true + return + } + } + return +} + +// FromFilename gets a Format given a filename. +func (formats Formats) FromFilename(filename string) (f Format, found bool) { + // mytemplate.amp.html + // mytemplate.html + // mytemplate + var ext, outFormat string + + parts := strings.Split(filename, ".") + if len(parts) > 2 { + outFormat = parts[1] + ext = parts[2] + } else if len(parts) > 1 { + ext = parts[1] + } + + if outFormat != "" { + return formats.GetByName(outFormat) + } + + if ext != "" { + f, found = formats.GetByName(ext) + } + return +} + +// HTMLFormat An ordered list of built-in output formats. +var HTMLFormat = Format{ + Name: "HTML", + MediaType: HTMLType, + BaseName: "index", +} + +// DefaultFormats contains the default output formats supported by Hugo. +var DefaultFormats = Formats{ + HTMLFormat, +} + +// DecodeFormats takes a list of output format configurations and merges those, +// in the order given, with the Hugo defaults as the last resort. +func DecodeFormats(mediaTypes Types) Formats { + // Format could be modified by mediaTypes configuration + // just make it simple for example + fmt.Println(mediaTypes) + + f := make(Formats, len(DefaultFormats)) + copy(f, DefaultFormats) + + return f +} + +func CreateSiteOutputFormats(allFormats Formats) map[string]Formats { + defaultOutputFormats := + createDefaultOutputFormats(allFormats) + return defaultOutputFormats +} + +func createDefaultOutputFormats(allFormats Formats) map[string]Formats { + htmlOut, _ := allFormats.GetByName(HTMLFormat.Name) + + m := map[string]Formats{ + contenthub.KindPage: {htmlOut}, + contenthub.KindHome: {htmlOut}, + contenthub.KindSection: {htmlOut}, + } + + return m +} diff --git a/internal/domain/contenthub/valueobject/outputformat.go b/internal/domain/site/valueobject/outputformat.go similarity index 100% rename from internal/domain/contenthub/valueobject/outputformat.go rename to internal/domain/site/valueobject/outputformat.go diff --git a/internal/domain/site/valueobject/pagepath.go b/internal/domain/site/valueobject/pagepath.go new file mode 100644 index 0000000..e1b4ae3 --- /dev/null +++ b/internal/domain/site/valueobject/pagepath.go @@ -0,0 +1,59 @@ +package valueobject + +import ( + "fmt" +) + +func NewPagePaths(ofs Formats, kind string, sec []string, dir, basename string) (PagePaths, error) { + targetPathDescriptor, err := createTargetPathDescriptor(kind, sec, dir, basename) + if err != nil { + return PagePaths{}, err + } + + outputFormats := ofs + if len(outputFormats) == 0 { + fmt.Println("outputFormats is null", outputFormats) + + return PagePaths{}, nil + } + + pageOutputFormats := make(OutputFormats, len(outputFormats)) + targets := make(map[string]TargetPathsHolder) + + for i, f := range outputFormats { + desc := targetPathDescriptor + desc.Type = f + paths := createTargetPaths(desc) + + var relPermalink, permalink string + + pageOutputFormats[i] = NewOutputFormat(relPermalink, permalink, f) + + // Use the main format for permalinks, usually HTML. + permalinksIndex := 0 + targets[f.Name] = TargetPathsHolder{ + Paths: paths, + OutputFormat: pageOutputFormats[permalinksIndex], + } + + } + + return PagePaths{ + outputFormats: pageOutputFormats, + firstOutputFormat: pageOutputFormats[0], + TargetPaths: targets, + targetPathDescriptor: targetPathDescriptor, + }, nil +} + +type PagePaths struct { + outputFormats OutputFormats + firstOutputFormat OutputFormat + + TargetPaths map[string]TargetPathsHolder + targetPathDescriptor TargetPathDescriptor +} + +func (l PagePaths) OutputFormats() OutputFormats { + return l.outputFormats +} diff --git a/internal/domain/contenthub/entity/targetpath.go b/internal/domain/site/valueobject/targetpath.go similarity index 86% rename from internal/domain/contenthub/entity/targetpath.go rename to internal/domain/site/valueobject/targetpath.go index 7981db2..413f4b0 100644 --- a/internal/domain/contenthub/entity/targetpath.go +++ b/internal/domain/site/valueobject/targetpath.go @@ -1,7 +1,6 @@ -package entity +package valueobject import ( - "fmt" "github.com/gohugonet/hugoverse/internal/domain/contenthub" "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject" "path" @@ -9,13 +8,13 @@ import ( "strings" ) -type targetPathsHolder struct { - paths valueobject.TargetPaths - valueobject.OutputFormat +type TargetPathsHolder struct { + Paths valueobject.TargetPaths + OutputFormat } -func (t targetPathsHolder) TargetPaths() valueobject.TargetPaths { - return t.paths +func (t TargetPathsHolder) TargetPaths() valueobject.TargetPaths { + return t.Paths } // TargetPathDescriptor describes how a file path for a given resource @@ -25,7 +24,7 @@ func (t targetPathsHolder) TargetPaths() valueobject.TargetPaths { // The big motivating behind this is to have only one source of truth for URLs, // and by that also get rid of most of the fragile string parsing/encoding etc. type TargetPathDescriptor struct { - Type valueobject.Format + Type Format Kind string Sections []string @@ -55,27 +54,14 @@ type TargetPathDescriptor struct { ExpandedPermalink string } -func createTargetPathDescriptor(p contenthub.Page) (TargetPathDescriptor, error) { - var ( - dir string - baseName string - ) - - fmt.Println("555 createTargetPathDescriptor: ", p) - fmt.Println("555 createTargetPathDescriptor: ", p.File()) - - if !p.File().IsZero() { - dir = p.File().Dir() - baseName = p.File().TranslationBaseName() - } - +func createTargetPathDescriptor(kind string, sec []string, dir, basename string) (TargetPathDescriptor, error) { desc := TargetPathDescriptor{ - Kind: p.Kind(), - Sections: p.SectionsEntries(), + Kind: kind, + Sections: sec, ForcePrefix: false, Dir: dir, URL: "", - BaseName: baseName, + BaseName: basename, } return desc, nil diff --git a/internal/domain/contenthub/valueobject/type.go b/internal/domain/site/valueobject/type.go similarity index 100% rename from internal/domain/contenthub/valueobject/type.go rename to internal/domain/site/valueobject/type.go diff --git a/pkg/bufferpool/bufpool.go b/pkg/bufferpool/bufpool.go new file mode 100644 index 0000000..f05675e --- /dev/null +++ b/pkg/bufferpool/bufpool.go @@ -0,0 +1,38 @@ +// Copyright 2015 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package bufferpool provides a pool of bytes buffers. +package bufferpool + +import ( + "bytes" + "sync" +) + +var bufferPool = &sync.Pool{ + New: func() any { + return &bytes.Buffer{} + }, +} + +// GetBuffer returns a buffer from the pool. +func GetBuffer() (buf *bytes.Buffer) { + return bufferPool.Get().(*bytes.Buffer) +} + +// PutBuffer returns a buffer to the pool. +// The buffer is reset before it is put back into circulation. +func PutBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +}