diff --git a/go.mod b/go.mod
index eb944753..099c8627 100644
--- a/go.mod
+++ b/go.mod
@@ -98,6 +98,7 @@ require (
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
+ github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
diff --git a/go.sum b/go.sum
index 5d820213..77562eeb 100644
--- a/go.sum
+++ b/go.sum
@@ -355,6 +355,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
+github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=
github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4=
github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo=
diff --git a/internal/domain/contenthub/entity/contenthub.go b/internal/domain/contenthub/entity/contenthub.go
index 26804b1b..ece1ab3f 100644
--- a/internal/domain/contenthub/entity/contenthub.go
+++ b/internal/domain/contenthub/entity/contenthub.go
@@ -17,6 +17,7 @@ type ContentHub struct {
TemplateExecutor contenthub.Template
*Cache
+ *Translator
*PageMap
*PageFinder
diff --git a/internal/domain/contenthub/entity/pagebuilder.go b/internal/domain/contenthub/entity/pagebuilder.go
index 2471bf2e..51847f7b 100644
--- a/internal/domain/contenthub/entity/pagebuilder.go
+++ b/internal/domain/contenthub/entity/pagebuilder.go
@@ -199,6 +199,7 @@ func (b *PageBuilder) applyFrontMatter(p *Page) error {
p.title = b.fm.Title
p.Meta.Weight = b.fm.Weight
p.Meta.Parameters = b.fm.Params
+ p.Meta.Date = b.fm.Date
return nil
}
diff --git a/internal/domain/contenthub/entity/pagecontentprovider.go b/internal/domain/contenthub/entity/pagecontentprovider.go
index 45de92ac..0f6a3642 100644
--- a/internal/domain/contenthub/entity/pagecontentprovider.go
+++ b/internal/domain/contenthub/entity/pagecontentprovider.go
@@ -151,8 +151,11 @@ func (c *ContentProvider) ContentSummary() (valueobject.ContentSummary, error) {
}
}
- v.SummaryTruncated = c.content.summaryTruncated
v.Content = helpers.BytesToHTML(b)
+ if v.IsSummaryEmpty() {
+ v.ExtractSummary(b, c.f.MediaType)
+ c.content.summaryTruncated = v.SummaryTruncated
+ }
return &stale.Value[valueobject.ContentSummary]{
Value: v,
diff --git a/internal/domain/contenthub/entity/pagemeta.go b/internal/domain/contenthub/entity/pagemeta.go
index 877aba49..eb41c08b 100644
--- a/internal/domain/contenthub/entity/pagemeta.go
+++ b/internal/domain/contenthub/entity/pagemeta.go
@@ -1,6 +1,9 @@
package entity
-import "github.com/gohugonet/hugoverse/pkg/maps"
+import (
+ "github.com/gohugonet/hugoverse/pkg/maps"
+ "time"
+)
const (
Never = "never"
@@ -13,6 +16,8 @@ type Meta struct {
List string
Parameters maps.Params
Weight int
+
+ Date time.Time
}
func (m *Meta) Description() string {
@@ -27,6 +32,10 @@ func (m *Meta) PageWeight() int {
return m.Weight
}
+func (m *Meta) PageDate() time.Time {
+ return m.Date
+}
+
func (m *Meta) ShouldList(global bool) bool {
return m.shouldList(global)
}
diff --git a/internal/domain/contenthub/entity/translator.go b/internal/domain/contenthub/entity/translator.go
new file mode 100644
index 00000000..e5a4ae92
--- /dev/null
+++ b/internal/domain/contenthub/entity/translator.go
@@ -0,0 +1,112 @@
+package entity
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/gohugoio/go-i18n/v2/i18n"
+ "github.com/gohugonet/hugoverse/internal/domain/contenthub"
+ "github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject"
+ "github.com/gohugonet/hugoverse/pkg/hreflect"
+ "github.com/gohugonet/hugoverse/pkg/loggers"
+ "github.com/spf13/cast"
+ "reflect"
+ "strings"
+)
+
+const ArtificialLangTagPrefix = "art-x-"
+
+type TranslateFunc func(ctx context.Context, translationID string, templateData any) string
+
+type Translator struct {
+ ContentLanguage string
+ TranslateFuncs map[string]TranslateFunc
+
+ Log loggers.Logger `json:"-"`
+}
+
+func (t *Translator) Translate(ctx context.Context, lang string, translationID string, templateData any) string {
+ if f, ok := t.TranslateFuncs[lang]; ok {
+ return f(ctx, translationID, templateData)
+ }
+
+ t.Log.Infof("Translation func for language %v not found, use default.", lang)
+ if f, ok := t.TranslateFuncs[t.ContentLanguage]; ok {
+ return f(ctx, translationID, templateData)
+ }
+
+ t.Log.Infoln("i18n not initialized; if you need string translations, check that you have a bundle in /i18n that matches the site language or the default language.")
+
+ return ""
+}
+
+func (t *Translator) SetupTranslateFuncs(bndl *i18n.Bundle) {
+ enableMissingTranslationPlaceholders := true
+
+ for _, lang := range bndl.LanguageTags() {
+ currentLang := lang
+ currentLangStr := currentLang.String()
+ // This may be pt-BR; make it case insensitive.
+ currentLangKey := strings.ToLower(strings.TrimPrefix(currentLangStr, ArtificialLangTagPrefix))
+ localizer := i18n.NewLocalizer(bndl, currentLangStr)
+ t.TranslateFuncs[currentLangKey] = func(ctx context.Context, translationID string, templateData any) string {
+ pluralCount := valueobject.GetPluralCount(templateData)
+
+ if templateData != nil {
+ tp := reflect.TypeOf(templateData)
+ if hreflect.IsInt(tp.Kind()) {
+ // This was how go-i18n worked in v1,
+ // and we keep it like this to avoid breaking
+ // lots of sites in the wild.
+ templateData = valueobject.IntCount(cast.ToInt(templateData))
+ } else {
+ //TODO setup with context
+ if _, ok := templateData.(contenthub.Page); ok {
+ // See issue 10782.
+ // The i18n has its own template handling and does not know about
+ // the context.Context.
+ // A common pattern is to pass Page to i18n, and use .ReadingTime etc.
+ // We need to improve this, but that requires some upstream changes.
+ // For now, just create a wrapper.
+ //templateData = page.PageWithContext{Page: p, Ctx: ctx}
+ }
+ }
+ }
+
+ translated, translatedLang, err := localizer.LocalizeWithTag(&i18n.LocalizeConfig{
+ MessageID: translationID,
+ TemplateData: templateData,
+ PluralCount: pluralCount,
+ })
+
+ sameLang := currentLang == translatedLang
+
+ if err == nil && sameLang {
+ return translated
+ }
+
+ if err != nil && sameLang && translated != "" {
+ // See #8492
+ // TODO(bep) this needs to be improved/fixed upstream,
+ // but currently we get an error even if the fallback to
+ // "other" succeeds.
+ if fmt.Sprintf("%T", err) == "i18n.pluralFormNotFoundError" {
+ return translated
+ }
+ }
+
+ var messageNotFoundErr *i18n.MessageNotFoundErr
+ if !errors.As(err, &messageNotFoundErr) {
+ t.Log.Warnf("Failed to get translated string for language %q and ID %q: %s", currentLangStr, translationID, err)
+ }
+
+ t.Log.Warnf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID)
+
+ if enableMissingTranslationPlaceholders {
+ return "[i18n] " + translationID
+ }
+
+ return translated
+ }
+ }
+}
diff --git a/internal/domain/contenthub/factory/hub.go b/internal/domain/contenthub/factory/hub.go
index 4167a2d1..a79d8fb9 100644
--- a/internal/domain/contenthub/factory/hub.go
+++ b/internal/domain/contenthub/factory/hub.go
@@ -1,29 +1,49 @@
package factory
import (
+ "fmt"
"github.com/gohugonet/hugoverse/internal/domain/contenthub"
"github.com/gohugonet/hugoverse/internal/domain/contenthub/entity"
"github.com/gohugonet/hugoverse/internal/domain/contenthub/valueobject"
+ "github.com/gohugonet/hugoverse/internal/domain/fs"
"github.com/gohugonet/hugoverse/pkg/cache/dynacache"
"github.com/gohugonet/hugoverse/pkg/cache/stale"
"github.com/gohugonet/hugoverse/pkg/doctree"
+ "github.com/gohugonet/hugoverse/pkg/helpers"
+ "github.com/gohugonet/hugoverse/pkg/herrors"
"github.com/gohugonet/hugoverse/pkg/loggers"
+ "github.com/gohugonet/hugoverse/pkg/paths"
+ "golang.org/x/text/language"
+ "strings"
+
+ "encoding/json"
+ "github.com/gohugoio/go-i18n/v2/i18n"
+ toml "github.com/pelletier/go-toml/v2"
+ yaml "gopkg.in/yaml.v2"
)
func New(services contenthub.Services) (*entity.ContentHub, error) {
log := loggers.NewDefault()
+ valueobject.SetupDefaultContentTypes()
cs, err := newContentSpec()
if err != nil {
return nil, err
}
+ t, err := newTranslator(services, log)
+ if err != nil {
+ return nil, err
+ }
+
cache := newCache()
ch := &entity.ContentHub{
Cache: cache,
Fs: services,
TemplateExecutor: nil,
+ Translator: t,
+
PageMap: &entity.PageMap{
PageTrees: newPageTree(),
@@ -181,3 +201,93 @@ func newConverterRegistry() (contenthub.ConverterRegistry, error) {
Converters: converters,
}, nil
}
+
+func newTranslator(services contenthub.Services, log loggers.Logger) (*entity.Translator, error) {
+ defaultLangTag, err := language.Parse(services.DefaultLanguage())
+ if err != nil {
+ defaultLangTag = language.English
+ }
+ bundle := i18n.NewBundle(defaultLangTag)
+
+ bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
+ bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
+ bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal)
+ bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
+
+ if err := services.WalkI18n("", fs.WalkCallback{
+ HookPre: nil,
+ WalkFn: func(path string, info fs.FileMetaInfo) error {
+ if info.IsDir() {
+ return nil
+ }
+ file, err := valueobject.NewFileInfo(info)
+ if err != nil {
+ return err
+ }
+ return addTranslationFile(bundle, file)
+ },
+ HookPost: nil,
+ }, fs.WalkwayConfig{}); err != nil {
+ if !herrors.IsNotExist(err) {
+ return nil, err
+ }
+ }
+
+ t := &entity.Translator{
+ ContentLanguage: services.DefaultLanguage(),
+ TranslateFuncs: make(map[string]entity.TranslateFunc),
+
+ Log: log,
+ }
+ t.SetupTranslateFuncs(bundle)
+
+ return t, err
+}
+
+func addTranslationFile(bundle *i18n.Bundle, r *valueobject.File) error {
+ f, err := r.Open()
+ if err != nil {
+ return fmt.Errorf("failed to open translations file %q:: %w", r.LogicalName(), err)
+ }
+
+ b := helpers.ReaderToBytes(f)
+ f.Close()
+
+ name := r.LogicalName()
+ lang := paths.Filename(name)
+ tag := language.Make(lang)
+ if tag == language.Und {
+ try := entity.ArtificialLangTagPrefix + lang
+ _, err = language.Parse(try)
+ if err != nil {
+ return fmt.Errorf("%q: %s", try, err)
+ }
+ name = entity.ArtificialLangTagPrefix + name
+ }
+
+ _, err = bundle.ParseMessageFileBytes(b, name)
+ if err != nil {
+ if strings.Contains(err.Error(), "no plural rule") {
+ // https://github.com/gohugoio/hugo/issues/7798
+ name = entity.ArtificialLangTagPrefix + name
+ _, err = bundle.ParseMessageFileBytes(b, name)
+ if err == nil {
+ return nil
+ }
+ }
+ return errWithFileContext(fmt.Errorf("failed to load translations: %w", err), r)
+ }
+
+ return nil
+}
+
+func errWithFileContext(inerr error, r *valueobject.File) error {
+ realFilename := r.Filename()
+ f, err := r.Open()
+ if err != nil {
+ return inerr
+ }
+ defer f.Close()
+
+ return herrors.NewFileErrorFromName(inerr, realFilename).UpdateContent(f, nil)
+}
diff --git a/internal/domain/contenthub/type.go b/internal/domain/contenthub/type.go
index d612dd31..39442d16 100644
--- a/internal/domain/contenthub/type.go
+++ b/internal/domain/contenthub/type.go
@@ -17,6 +17,7 @@ import (
"github.com/spf13/afero"
goTmpl "html/template"
"io"
+ "time"
)
type ContentHub interface {
@@ -72,6 +73,7 @@ type FsService interface {
ContentFs() afero.Fs
WalkContent(start string, cb fs.WalkCallback, conf fs.WalkwayConfig) error
+ WalkI18n(start string, cb fs.WalkCallback, conf fs.WalkwayConfig) error
ReverseLookupContent(filename string, checkExists bool) ([]fs.ComponentPath, error)
}
@@ -285,6 +287,7 @@ type PageMeta interface {
Description() string
Params() maps.Params
PageWeight() int
+ PageDate() time.Time
ShouldList(global bool) bool
ShouldListAny() bool
diff --git a/internal/domain/contenthub/valueobject/content.go b/internal/domain/contenthub/valueobject/content.go
index be22c363..66308506 100644
--- a/internal/domain/contenthub/valueobject/content.go
+++ b/internal/domain/contenthub/valueobject/content.go
@@ -1,9 +1,38 @@
package valueobject
import (
+ "bytes"
+ "github.com/gohugonet/hugoverse/pkg/helpers"
+ "github.com/gohugonet/hugoverse/pkg/media"
+ "github.com/gohugonet/hugoverse/pkg/types"
"html/template"
+ "regexp"
+ "strings"
+ "unicode"
+ "unicode/utf8"
)
+// DefaultTocConfig is the default ToC configuration.
+var DefaultTocConfig = TocConfig{
+ StartLevel: 2,
+ EndLevel: 3,
+ Ordered: false,
+}
+
+type TocConfig struct {
+ // Heading start level to include in the table of contents, starting
+ // at h1 (inclusive).
+ //
") + closingTag := []byte("
") + + if bytes.Count(input, openingTag) == 1 { + input = bytes.TrimSpace(input) + if bytes.HasPrefix(input, openingTag) && bytes.HasSuffix(input, closingTag) { + input = bytes.TrimPrefix(input, openingTag) + input = bytes.TrimSuffix(input, closingTag) + input = bytes.TrimSpace(input) + } + } + return input } diff --git a/internal/domain/contenthub/valueobject/contenttypes.go b/internal/domain/contenthub/valueobject/contenttypes.go new file mode 100644 index 00000000..b4bba5c7 --- /dev/null +++ b/internal/domain/contenthub/valueobject/contenttypes.go @@ -0,0 +1,107 @@ +package valueobject + +import ( + "github.com/gohugonet/hugoverse/pkg/media" + "path/filepath" + "strings" +) + +var DefaultContentTypes ContentTypes + +func SetupDefaultContentTypes() { + DefaultContentTypes = ContentTypes{ + HTML: media.Builtin.HTMLType, + Markdown: media.Builtin.MarkdownType, + AsciiDoc: media.Builtin.AsciiDocType, + Pandoc: media.Builtin.PandocType, + ReStructuredText: media.Builtin.ReStructuredTextType, + EmacsOrgMode: media.Builtin.EmacsOrgModeType, + } + + DefaultContentTypes.setup() +} + +// ContentTypes holds the media types that are considered content in Hugo. +type ContentTypes struct { + HTML media.Type + Markdown media.Type + AsciiDoc media.Type + Pandoc media.Type + ReStructuredText media.Type + EmacsOrgMode media.Type + + // Created in init(). + types media.Types + extensionSet map[string]bool +} + +func (t *ContentTypes) setup() { + t.types = media.Types{t.HTML, t.Markdown, t.AsciiDoc, t.Pandoc, t.ReStructuredText, t.EmacsOrgMode} + t.extensionSet = make(map[string]bool) + for _, mt := range t.types { + for _, suffix := range mt.Suffixes() { + t.extensionSet[suffix] = true + } + } +} + +func (t ContentTypes) IsContentSuffix(suffix string) bool { + return t.extensionSet[suffix] +} + +// IsContentFile returns whether the given filename is a content file. +func (t ContentTypes) IsContentFile(filename string) bool { + return t.IsContentSuffix(strings.TrimPrefix(filepath.Ext(filename), ".")) +} + +// IsIndexContentFile returns whether the given filename is an index content file. +func (t ContentTypes) IsIndexContentFile(filename string) bool { + if !t.IsContentFile(filename) { + return false + } + + base := filepath.Base(filename) + + return strings.HasPrefix(base, "index.") || strings.HasPrefix(base, "_index.") +} + +// IsHTMLSuffix returns whether the given suffix is a HTML media type. +func (t ContentTypes) IsHTMLSuffix(suffix string) bool { + for _, s := range t.HTML.Suffixes() { + if s == suffix { + return true + } + } + return false +} + +// Types is a slice of media types. +func (t ContentTypes) Types() media.Types { + return t.types +} + +// FromTypes creates a new ContentTypes updated with the values from the given Types. +func (t ContentTypes) FromTypes(types media.Types) ContentTypes { + if tt, ok := types.GetByType(t.HTML.Type); ok { + t.HTML = tt + } + if tt, ok := types.GetByType(t.Markdown.Type); ok { + t.Markdown = tt + } + if tt, ok := types.GetByType(t.AsciiDoc.Type); ok { + t.AsciiDoc = tt + } + if tt, ok := types.GetByType(t.Pandoc.Type); ok { + t.Pandoc = tt + } + if tt, ok := types.GetByType(t.ReStructuredText.Type); ok { + t.ReStructuredText = tt + } + if tt, ok := types.GetByType(t.EmacsOrgMode.Type); ok { + t.EmacsOrgMode = tt + } + + t.setup() + + return t +} diff --git a/internal/domain/contenthub/valueobject/frontmatter.go b/internal/domain/contenthub/valueobject/frontmatter.go index 5fde3dd8..96357727 100644 --- a/internal/domain/contenthub/valueobject/frontmatter.go +++ b/internal/domain/contenthub/valueobject/frontmatter.go @@ -21,6 +21,8 @@ type FrontMatter struct { Title string Weight int + Date time.Time + Terms map[string][]string Params maps.Params @@ -65,11 +67,24 @@ func (b *FrontMatterParser) Parse() (*FrontMatter, error) { return nil, err } + if err := b.parseDate(fm); err != nil { + return nil, err + } + return fm, nil } +func (b *FrontMatterParser) parseDate(fm *FrontMatter) error { + fm.Date = time.Now() + if v, found := b.Params["date"]; found { + fm.Date = cast.ToTime(v) + } + + return nil +} + func (b *FrontMatterParser) parseWeight(fm *FrontMatter) error { - fm.Weight = 0 + fm.Weight = 10000 if v, found := b.Params["weight"]; found { fm.Weight = cast.ToInt(v) } diff --git a/internal/domain/contenthub/valueobject/i18n.go b/internal/domain/contenthub/valueobject/i18n.go new file mode 100644 index 00000000..4f92cd43 --- /dev/null +++ b/internal/domain/contenthub/valueobject/i18n.go @@ -0,0 +1,78 @@ +package valueobject + +import ( + "github.com/gohugonet/hugoverse/pkg/hreflect" + "github.com/spf13/cast" + "reflect" + "strings" +) + +// IntCount wraps the Count method. +type IntCount int + +func (c IntCount) Count() int { + return int(c) +} + +func GetPluralCount(v any) any { + if v == nil { + // i18n called without any argument, make sure it does not + // get any plural count. + return nil + } + + switch v := v.(type) { + case map[string]any: + for k, vv := range v { + if strings.EqualFold(k, countFieldName) { + return toPluralCountValue(vv) + } + } + default: + vv := reflect.Indirect(reflect.ValueOf(v)) + if vv.Kind() == reflect.Interface && !vv.IsNil() { + vv = vv.Elem() + } + tp := vv.Type() + + if tp.Kind() == reflect.Struct { + f := vv.FieldByName(countFieldName) + if f.IsValid() { + return toPluralCountValue(f.Interface()) + } + m := hreflect.GetMethodByName(vv, countFieldName) + if m.IsValid() && m.Type().NumIn() == 0 && m.Type().NumOut() == 1 { + c := m.Call(nil) + return toPluralCountValue(c[0].Interface()) + } + } + } + + return toPluralCountValue(v) +} + +const countFieldName = "Count" + +// go-i18n expects floats to be represented by string. +func toPluralCountValue(in any) any { + k := reflect.TypeOf(in).Kind() + switch { + case hreflect.IsFloat(k): + f := cast.ToString(in) + if !strings.Contains(f, ".") { + f += ".0" + } + return f + case k == reflect.String: + if _, err := cast.ToFloat64E(in); err == nil { + return in + } + // A non-numeric value. + return nil + default: + if i, err := cast.ToIntE(in); err == nil { + return i + } + return nil + } +} diff --git a/internal/domain/contenthub/valueobject/pagenop.go b/internal/domain/contenthub/valueobject/pagenop.go index a175b580..1b9864b1 100644 --- a/internal/domain/contenthub/valueobject/pagenop.go +++ b/internal/domain/contenthub/valueobject/pagenop.go @@ -5,6 +5,7 @@ import ( pio "github.com/gohugonet/hugoverse/pkg/io" "github.com/gohugonet/hugoverse/pkg/maps" "github.com/gohugonet/hugoverse/pkg/paths" + "time" ) var ( @@ -14,6 +15,11 @@ var ( // PageNop implements Page, but does nothing. type nopPage int +func (p *nopPage) PageDate() time.Time { + //TODO implement me + panic("implement me") +} + func (p *nopPage) Truncated() bool { //TODO implement me panic("implement me") diff --git a/internal/domain/contenthub/valueobject/summary.go b/internal/domain/contenthub/valueobject/summary.go new file mode 100644 index 00000000..4024687d --- /dev/null +++ b/internal/domain/contenthub/valueobject/summary.go @@ -0,0 +1,121 @@ +package valueobject + +import ( + "github.com/gohugonet/hugoverse/pkg/media" + "github.com/gohugonet/hugoverse/pkg/types" + "regexp" + "strings" +) + +type HtmlSummary struct { + source string + SummaryLowHigh types.LowHigh[string] + SummaryEndTag types.LowHigh[string] + WrapperStart types.LowHigh[string] + WrapperEnd types.LowHigh[string] + Divider types.LowHigh[string] +} + +func (s *HtmlSummary) wrap(ss string) string { + if s.WrapperStart.IsZero() { + return ss + } + return s.source[s.WrapperStart.Low:s.WrapperStart.High] + ss + s.source[s.WrapperEnd.Low:s.WrapperEnd.High] +} + +func (s *HtmlSummary) wrapLeft(ss string) string { + if s.WrapperStart.IsZero() { + return ss + } + + return s.source[s.WrapperStart.Low:s.WrapperStart.High] + ss +} + +func (s *HtmlSummary) Value(l types.LowHigh[string]) string { + return s.source[l.Low:l.High] +} + +func (s *HtmlSummary) trimSpace(ss string) string { + return strings.TrimSpace(ss) +} + +func (s *HtmlSummary) Content() string { + if s.Divider.IsZero() { + return s.source + } + ss := s.source[:s.Divider.Low] + ss += s.source[s.Divider.High:] + return s.trimSpace(ss) +} + +func (s *HtmlSummary) Summary() string { + if s.Divider.IsZero() { + return s.trimSpace(s.wrap(s.Value(s.SummaryLowHigh))) + } + ss := s.source[s.SummaryLowHigh.Low:s.Divider.Low] + if s.SummaryLowHigh.High > s.Divider.High { + ss += s.source[s.Divider.High:s.SummaryLowHigh.High] + } + if !s.SummaryEndTag.IsZero() { + ss += s.Value(s.SummaryEndTag) + } + return s.trimSpace(s.wrap(ss)) +} + +func (s *HtmlSummary) ContentWithoutSummary() string { + if s.Divider.IsZero() { + if s.SummaryLowHigh.Low == s.WrapperStart.High && s.SummaryLowHigh.High == s.WrapperEnd.Low { + return "" + } + return s.trimSpace(s.wrapLeft(s.source[s.SummaryLowHigh.High:])) + } + if s.SummaryEndTag.IsZero() { + return s.trimSpace(s.wrapLeft(s.source[s.Divider.High:])) + } + return s.trimSpace(s.wrapLeft(s.source[s.SummaryEndTag.High:])) +} + +func (s *HtmlSummary) Truncated() bool { + return s.SummaryLowHigh.High < len(s.source) +} + +func (s *HtmlSummary) resolveParagraphTagAndSetWrapper(mt media.Type) tagReStartEnd { + ptag := startEndP + + switch mt.SubType { + case DefaultContentTypes.AsciiDoc.SubType: + ptag = startEndDiv + case DefaultContentTypes.ReStructuredText.SubType: + const markerStart = "]?>|
]*?>$`), + endEndOfString: regexp.MustCompile(`
$`), + tagName: "p", + } +) + +type tagReStartEnd struct { + startEndOfString *regexp.Regexp + endEndOfString *regexp.Regexp + tagName string +} diff --git a/internal/domain/fs/entity/walk.go b/internal/domain/fs/entity/walk.go index a75bcc1d..e5719543 100644 --- a/internal/domain/fs/entity/walk.go +++ b/internal/domain/fs/entity/walk.go @@ -18,6 +18,10 @@ func (f *Fs) WalkLayouts(start string, cb fs.WalkCallback, conf fs.WalkwayConfig return f.Walk(f.Layouts, start, cb, conf) } +func (f *Fs) WalkI18n(start string, cb fs.WalkCallback, conf fs.WalkwayConfig) error { + return f.Walk(f.I18n, start, cb, conf) +} + func (f *Fs) Walk(fs afero.Fs, start string, cb fs.WalkCallback, conf fs.WalkwayConfig) error { w, err := valueobject.NewWalkway(fs, cb) if err != nil { diff --git a/internal/domain/resources/entity/publisher.go b/internal/domain/resources/entity/publisher.go index 9b1298c1..2b2b633f 100644 --- a/internal/domain/resources/entity/publisher.go +++ b/internal/domain/resources/entity/publisher.go @@ -1,6 +1,7 @@ package entity import ( + "github.com/gohugonet/hugoverse/internal/domain/resources" "github.com/gohugonet/hugoverse/internal/domain/resources/valueobject" "github.com/gohugonet/hugoverse/pkg/helpers" "github.com/spf13/afero" @@ -8,7 +9,8 @@ import ( ) type Publisher struct { - PubFs afero.Fs + PubFs afero.Fs + URLSvc resources.URLConfig } func (p *Publisher) PublishContentToTarget(content, target string) error { @@ -24,6 +26,6 @@ func (p *Publisher) PublishContentToTarget(content, target string) error { } func (p *Publisher) OpenPublishFileForWriting(relTargetPath string) (io.WriteCloser, error) { - filenames := valueobject.NewResourcePaths(relTargetPath).TargetFilenames() + filenames := valueobject.NewResourcePaths(relTargetPath, p.URLSvc).TargetFilenames() return helpers.OpenFilesForWriting(p.PubFs, filenames...) } diff --git a/internal/domain/resources/entity/resbuilder.go b/internal/domain/resources/entity/resbuilder.go index aa9948db..e8417c11 100644 --- a/internal/domain/resources/entity/resbuilder.go +++ b/internal/domain/resources/entity/resbuilder.go @@ -11,7 +11,6 @@ import ( "github.com/gohugonet/hugoverse/pkg/paths" "mime" "os" - "path" ) type resourceBuilder struct { @@ -27,6 +26,7 @@ type resourceBuilder struct { cache *Cache imageSvc resources.ImageConfig imageProc *valueobject.ImageProcessor + urlSvc resources.URLConfig } func newResourceBuilder(relPathname string, openReadSeekCloser io.OpenReadSeekCloser) *resourceBuilder { @@ -46,6 +46,11 @@ func (rs *resourceBuilder) withImageService(imageSvc resources.ImageConfig) *res return rs } +func (rs *resourceBuilder) withURLService(svc resources.URLConfig) *resourceBuilder { + rs.urlSvc = svc + return rs +} + func (rs *resourceBuilder) withImageProcessor(imageProc *valueobject.ImageProcessor) *resourceBuilder { rs.imageProc = imageProc return rs @@ -83,20 +88,7 @@ func (rs *resourceBuilder) build() (resources.Resource, error) { func (rs *resourceBuilder) buildResPaths() error { rs.relPathname = paths.ToSlashPreserveLeading(rs.relPathname) - - dir, name := path.Split(rs.relPathname) - dir = paths.ToSlashPreserveLeading(dir) - if dir == "/" { - dir = "" - } - - rs.resPaths = valueobject.ResourcePaths{ - Dir: dir, - BaseDirLink: "", - BaseDirTarget: "", - - File: name, - } + rs.resPaths = valueobject.NewResourcePaths(rs.relPathname, rs.urlSvc) return nil } @@ -155,6 +147,7 @@ func (rs *resourceBuilder) buildResource() (resources.Resource, error) { publisher: rs.publisher, mediaSvc: rs.mediaSvc, + urlSvc: rs.urlSvc, resourceTransformations: &resourceTransformations{}, diff --git a/internal/domain/resources/entity/resource.go b/internal/domain/resources/entity/resource.go index 48cebaa9..175d6af5 100644 --- a/internal/domain/resources/entity/resource.go +++ b/internal/domain/resources/entity/resource.go @@ -78,6 +78,7 @@ func (l *Resource) publish() { fmt.Println("publish ReadSeekCloser", l.paths.TargetPath(), err) return } + defer r.Close() _, err = io.Copy(publicw, r) if err != nil { diff --git a/internal/domain/resources/entity/resources.go b/internal/domain/resources/entity/resources.go index 9e268028..8efa1d19 100644 --- a/internal/domain/resources/entity/resources.go +++ b/internal/domain/resources/entity/resources.go @@ -28,6 +28,8 @@ type Resources struct { ImageService resources.ImageConfig ImageProc *valueobject.ImageProcessor + URLService resources.URLConfig + *MinifierClient *TemplateClient *IntegrityClient @@ -46,7 +48,7 @@ func (rs *Resources) GetResourceWithOpener(pathname string, opener io.OpenReadSe rsb := newResourceBuilder(pathname, opener) rsb.withCache(rs.Cache).withMediaService(rs.MediaService). withImageService(rs.ImageService).withImageProcessor(rs.ImageProc). - withPublisher(rs.Publisher) + withPublisher(rs.Publisher).withURLService(rs.URLService) return rsb.build() }) @@ -77,7 +79,7 @@ func (rs *Resources) GetResource(pathname string) (resources.Resource, error) { }) rsb.withCache(rs.Cache).withMediaService(rs.MediaService). withImageService(rs.ImageService).withImageProcessor(rs.ImageProc). - withPublisher(rs.Publisher) + withPublisher(rs.Publisher).withURLService(rs.URLService) return rsb.build() }) @@ -107,7 +109,7 @@ func (rs *Resources) match(name, pattern string, matchFunc func(r resources.Reso }) rsb.withCache(rs.Cache).withMediaService(rs.MediaService). withImageService(rs.ImageService).withImageProcessor(rs.ImageProc). - withPublisher(rs.Publisher) + withPublisher(rs.Publisher).withURLService(rs.URLService) r, err := rsb.build() if err != nil { diff --git a/internal/domain/resources/entity/resourcetransformer.go b/internal/domain/resources/entity/resourcetransformer.go index 88eb9785..73fdf30c 100644 --- a/internal/domain/resources/entity/resourcetransformer.go +++ b/internal/domain/resources/entity/resourcetransformer.go @@ -20,6 +20,7 @@ type ResourceTransformer struct { publisher *Publisher mediaSvc resources.MediaTypesConfig + urlSvc resources.URLConfig TransformationCache *Cache @@ -49,6 +50,7 @@ func (r *ResourceTransformer) TransformWithContext(ctx context.Context, t ...Res Resource: *res, publisher: r.publisher, mediaSvc: r.mediaSvc, + urlSvc: r.urlSvc, TransformationCache: r.TransformationCache, resourceTransformations: &resourceTransformations{}, }, nil @@ -97,10 +99,15 @@ func (r *ResourceTransformer) getFromFile(key string) (*Resource, error) { r2 := r.Resource.clone() r2.mediaType = m - r2.paths = valueobject.NewResourcePaths(meta.Target) + r2.paths = valueobject.NewResourcePaths(meta.Target, r.urlSvc) r2.mergeData(meta.MetaData) + + content, err := io.ReadAll(f) + if err != nil { + return nil, err + } r2.openReadSeekCloser = func() (pio.ReadSeekCloser, error) { - return f.(pio.ReadSeekCloser), nil + return pio.NewReadSeekerNoOpCloserFromString(string(content)), nil } return r2, nil @@ -162,7 +169,7 @@ func (r *ResourceTransformer) transform(key string) (*Resource, error) { updates.mediaType = tctx.Source.InMediaType updates.data = tctx.Data - updates.paths = valueobject.NewResourcePaths(tctx.Source.InPath) + updates.paths = valueobject.NewResourcePaths(tctx.Source.InPath, r.urlSvc) var publishwriters []io.WriteCloser //publicw, err := r.publisher.OpenPublishFileForWriting(updates.paths.TargetPath()) diff --git a/internal/domain/resources/factory/resource.go b/internal/domain/resources/factory/resource.go index 3b7dea7e..35220aa8 100644 --- a/internal/domain/resources/factory/resource.go +++ b/internal/domain/resources/factory/resource.go @@ -46,8 +46,11 @@ func NewResources(ws resources.Workspace) (*entity.Resources, error) { } rs := &entity.Resources{ - Cache: c, - Publisher: &entity.Publisher{PubFs: ws.PublishFs()}, + Cache: c, + Publisher: &entity.Publisher{ + PubFs: ws.PublishFs(), + URLSvc: ws, + }, FsService: ws, MediaService: ws, @@ -55,6 +58,8 @@ func NewResources(ws resources.Workspace) (*entity.Resources, error) { ImageService: ws, ImageProc: ip, + URLService: ws, + ExecHelper: execHelper, Common: common, diff --git a/internal/domain/resources/type.go b/internal/domain/resources/type.go index bd1cb075..729591fc 100644 --- a/internal/domain/resources/type.go +++ b/internal/domain/resources/type.go @@ -26,6 +26,11 @@ type Workspace interface { SecurityConfig OutputFormatsConfig MinifyConfig + URLConfig +} + +type URLConfig interface { + BaseUrl() string } type OutputFormatsConfig interface { diff --git a/internal/domain/resources/valueobject/resourcepaths.go b/internal/domain/resources/valueobject/resourcepaths.go index 7bb9dd94..4cf95244 100644 --- a/internal/domain/resources/valueobject/resourcepaths.go +++ b/internal/domain/resources/valueobject/resourcepaths.go @@ -14,6 +14,7 @@ package valueobject import ( + "github.com/gohugonet/hugoverse/internal/domain/resources" "github.com/gohugonet/hugoverse/pkg/paths" "path" "path/filepath" @@ -40,8 +41,9 @@ type ResourcePaths struct { File string } -func NewResourcePaths(targetPath string) ResourcePaths { +func NewResourcePaths(targetPath string, svc resources.URLConfig) ResourcePaths { targetPath = filepath.ToSlash(targetPath) + dir, file := path.Split(targetPath) dir = paths.ToSlashPreserveLeading(dir) if dir == "/" { @@ -51,8 +53,8 @@ func NewResourcePaths(targetPath string) ResourcePaths { return ResourcePaths{ Dir: dir, File: file, - BaseDirLink: "", - BaseDirTarget: "", + BaseDirLink: svc.BaseUrl(), + BaseDirTarget: svc.BaseUrl(), } } diff --git a/internal/domain/site/entity/hugo.go b/internal/domain/site/entity/hugo.go new file mode 100644 index 00000000..a29b25dc --- /dev/null +++ b/internal/domain/site/entity/hugo.go @@ -0,0 +1,29 @@ +package entity + +func (p *Page) LinkTitle() string { + return p.Title() +} + +func (p *Page) Sites() *Sites { + return &Sites{site: p.Site} +} + +type Sites struct { + site *Site +} + +func (s *Sites) First() *Site { + return s.site +} + +func (s *Sites) Default() *Site { + return s.First() +} + +func (s *Site) IsMultilingual() bool { + return s.IsMultiLingual() +} + +func (s *Site) IsMultihost() bool { + return false +} diff --git a/internal/domain/site/entity/pagefields.go b/internal/domain/site/entity/pagefields.go index 63a808bd..7e7ef8e9 100644 --- a/internal/domain/site/entity/pagefields.go +++ b/internal/domain/site/entity/pagefields.go @@ -20,7 +20,7 @@ func (p *Page) Resources() PageResources { } func (p *Page) Date() time.Time { - return time.Now() + return p.Page.PageDate() } func (p *Page) PublishDate() time.Time { @@ -44,18 +44,6 @@ func (p *Page) OutputFormats() valueobject.OutputFormats { return make(valueobject.OutputFormats, 0) } -func (p *Page) Sites() *sites { - return &sites{site: p.Site} -} - -type sites struct { - site *Site -} - -func (s *sites) First() *Site { - return s.site -} - func (p *Page) Data() any { p.dataInit.Do(func() { p.data = make(Data) diff --git a/internal/domain/site/entity/site.go b/internal/domain/site/entity/site.go index fe683ebb..580ab52f 100644 --- a/internal/domain/site/entity/site.go +++ b/internal/domain/site/entity/site.go @@ -12,10 +12,11 @@ import ( ) type Site struct { - ConfigSvc site.ConfigService - ContentSvc site.ContentService - ResourcesSvc site.ResourceService - LanguageSvc site.LanguageService + ConfigSvc site.ConfigService + ContentSvc site.ContentService + TranslationSvc site.TranslationService + ResourcesSvc site.ResourceService + LanguageSvc site.LanguageService GitSvc *valueobject.GitMap diff --git a/internal/domain/site/entity/sitefields.go b/internal/domain/site/entity/sitefields.go index 37a9bf8a..9bcb1d41 100644 --- a/internal/domain/site/entity/sitefields.go +++ b/internal/domain/site/entity/sitefields.go @@ -1,6 +1,7 @@ package entity import ( + "context" "fmt" "github.com/gohugonet/hugoverse/internal/domain/contenthub" "github.com/gohugonet/hugoverse/pkg/maps" @@ -123,3 +124,7 @@ func (s *Site) siteWeightedPage(p contenthub.OrdinalWeightPage) (*WeightedPage, return &WeightedPage{sp, p}, nil } + +func (s *Site) Translate(ctx context.Context, translationID string, templateData any) string { + return s.TranslationSvc.Translate(ctx, s.Language.currentLanguage, translationID, templateData) +} diff --git a/internal/domain/site/entity/url.go b/internal/domain/site/entity/url.go index c380f99a..c6c1e178 100644 --- a/internal/domain/site/entity/url.go +++ b/internal/domain/site/entity/url.go @@ -92,7 +92,7 @@ func (s *Site) RelURL(in string) string { u = s.URL.addContextRoot(u) u = s.URL.handleRootSuffix(in, u) - //u = s.URL.handlePrefix(u) // our use case include preview, just put it relative to the html file + u = s.URL.handlePrefix(u) // our use case include preview, just put it relative to the html file return u } diff --git a/internal/domain/site/factory/site.go b/internal/domain/site/factory/site.go index 7fc32a66..4ed76e27 100644 --- a/internal/domain/site/factory/site.go +++ b/internal/domain/site/factory/site.go @@ -18,10 +18,11 @@ func New(services site.Services) *entity.Site { } s := &entity.Site{ - ConfigSvc: services, - ContentSvc: services, - ResourcesSvc: services, - LanguageSvc: services, + ConfigSvc: services, + ContentSvc: services, + TranslationSvc: services, + ResourcesSvc: services, + LanguageSvc: services, GitSvc: git, diff --git a/internal/domain/site/type.go b/internal/domain/site/type.go index 38e11301..7bce0af4 100644 --- a/internal/domain/site/type.go +++ b/internal/domain/site/type.go @@ -16,6 +16,7 @@ import ( type Services interface { ContentService + TranslationService ResourceService LanguageService FsService @@ -53,6 +54,10 @@ type ContentService interface { GetPageRef(context contenthub.Page, ref string, home contenthub.Page) (contenthub.Page, error) } +type TranslationService interface { + Translate(ctx context.Context, lang string, translationID string, templateData any) string +} + type ResourceService interface { GetResourceWithOpener(pathname string, opener pio.OpenReadSeekCloser) (resources.Resource, error) } diff --git a/internal/domain/template/type.go b/internal/domain/template/type.go index 8769e8b5..89604bc7 100644 --- a/internal/domain/template/type.go +++ b/internal/domain/template/type.go @@ -6,6 +6,7 @@ import ( "github.com/gohugonet/hugoverse/pkg/template/funcs/collections" "github.com/gohugonet/hugoverse/pkg/template/funcs/compare" "github.com/gohugonet/hugoverse/pkg/template/funcs/hugo" + "github.com/gohugonet/hugoverse/pkg/template/funcs/lang" "github.com/gohugonet/hugoverse/pkg/template/funcs/os" "github.com/gohugonet/hugoverse/pkg/template/funcs/resource" "github.com/gohugonet/hugoverse/pkg/template/funcs/site" @@ -75,5 +76,6 @@ type CustomizedFunctions interface { resource.Resource os.Os site.Service - hugo.Version + hugo.Info + lang.Translator } diff --git a/internal/domain/template/valueobject/nscss.go b/internal/domain/template/valueobject/nscss.go new file mode 100644 index 00000000..d5db1e2d --- /dev/null +++ b/internal/domain/template/valueobject/nscss.go @@ -0,0 +1,32 @@ +package valueobject + +import ( + "context" + "github.com/gohugonet/hugoverse/pkg/template/funcs/resource" +) + +const nsCss = "css" + +func registerCss(res resource.Resource) { + f := func() *TemplateFuncsNamespace { + ctx, err := resource.New(res) + if err != nil { + // TODO(bep) no panic. + panic(err) + } + + ns := &TemplateFuncsNamespace{ + Name: nsCss, + Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil }, + } + + ns.AddMethodMapping(ctx.Sass, + []string{"toCSS"}, + [][2]string{}, + ) + + return ns + } + + AddTemplateFuncsNamespace(f) +} diff --git a/internal/domain/template/valueobject/nshugo.go b/internal/domain/template/valueobject/nshugo.go index cf6d357e..b028e765 100644 --- a/internal/domain/template/valueobject/nshugo.go +++ b/internal/domain/template/valueobject/nshugo.go @@ -7,12 +7,15 @@ import ( const nsHugo = "hugo" -func registerHugo(ver hugo.Version) { +func registerHugo(info hugo.Info) { f := func() *TemplateFuncsNamespace { + h := hugo.New(info) ns := &TemplateFuncsNamespace{ - Name: nsHugo, - Context: func(cctx context.Context, args ...any) (any, error) { return ver, nil }, + Name: nsHugo, + Context: func(cctx context.Context, args ...any) (any, error) { + return h, nil + }, } return ns diff --git a/internal/domain/template/valueobject/nslang.go b/internal/domain/template/valueobject/nslang.go index 0d41a3f2..bf584224 100644 --- a/internal/domain/template/valueobject/nslang.go +++ b/internal/domain/template/valueobject/nslang.go @@ -7,9 +7,9 @@ import ( const nsLang = "lang" -func registerLang() { +func registerLang(translator lang.Translator) { f := func() *TemplateFuncsNamespace { - ctx := lang.New() + ctx := lang.New(translator) ns := &TemplateFuncsNamespace{ Name: nsLang, diff --git a/internal/domain/template/valueobject/nsreg.go b/internal/domain/template/valueobject/nsreg.go index a6cf1f8b..98386588 100644 --- a/internal/domain/template/valueobject/nsreg.go +++ b/internal/domain/template/valueobject/nsreg.go @@ -19,7 +19,6 @@ func AddTemplateFuncsNamespace(ns func() *TemplateFuncsNamespace) { func RegisterNamespaces() { registerCast() registerFmt() - registerLang() registerSafe() registerCrypto() registerPath() @@ -36,11 +35,13 @@ func RegisterCallbackNamespaces(cb func(ctx context.Context, name string, data a } func RegisterExtendedNamespaces(functions template.CustomizedFunctions) { + registerLang(functions) registerCompare(functions) registerTransform(functions) registerUrls(functions, functions) registerStrings(functions) registerResources(functions) + registerCss(functions) registerOs(functions) registerSite(functions) registerHugo(functions) diff --git a/internal/domain/template/valueobject/nssite.go b/internal/domain/template/valueobject/nssite.go index 76b529cf..0ed14075 100644 --- a/internal/domain/template/valueobject/nssite.go +++ b/internal/domain/template/valueobject/nssite.go @@ -9,11 +9,7 @@ const nsSite = "site" func registerSite(svc site.Service) { f := func() *TemplateFuncsNamespace { - s, err := site.New(svc) - if err != nil { - // TODO(bep) no panic. - panic(err) - } + s := site.New(svc) ns := &TemplateFuncsNamespace{ Name: nsSite, diff --git a/pkg/media/builtin.go b/pkg/media/builtin.go index 97fb5576..01dd99af 100644 --- a/pkg/media/builtin.go +++ b/pkg/media/builtin.go @@ -34,8 +34,12 @@ type BuiltinTypes struct { OpenTypeFontType Type // Common document types - PDFType Type - MarkdownType Type + PDFType Type + MarkdownType Type + EmacsOrgModeType Type + AsciiDocType Type + PandocType Type + ReStructuredTextType Type // Common video types AVIType Type diff --git a/pkg/paths/pathparser.go b/pkg/paths/pathparser.go index 049093ba..a6f4f6c9 100644 --- a/pkg/paths/pathparser.go +++ b/pkg/paths/pathparser.go @@ -152,7 +152,7 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) { } else { high = len(p.s) } - id := types.LowHigh{Low: i + 1, High: high} + id := types.LowHigh[string]{Low: i + 1, High: high} if len(p.identifiers) == 0 { p.identifiers = append(p.identifiers, id) } else if len(p.identifiers) == 1 { @@ -238,7 +238,7 @@ type Path struct { component string bundleType PathType - identifiers []types.LowHigh + identifiers []types.LowHigh[string] posIdentifierLanguage int disabled bool diff --git a/pkg/template/funcs/hugo/hugo.go b/pkg/template/funcs/hugo/hugo.go new file mode 100644 index 00000000..1224e8e2 --- /dev/null +++ b/pkg/template/funcs/hugo/hugo.go @@ -0,0 +1,13 @@ +package hugo + +// New returns a new instance of the os-namespaced template functions. +func New(info Info) *Namespace { + return &Namespace{ + Info: info, + } +} + +// Namespace provides template functions for the "os" namespace. +type Namespace struct { + Info +} diff --git a/pkg/template/funcs/hugo/type.go b/pkg/template/funcs/hugo/type.go index d5eaeead..3653e9c7 100644 --- a/pkg/template/funcs/hugo/type.go +++ b/pkg/template/funcs/hugo/type.go @@ -1,5 +1,24 @@ package hugo +type Info interface { + Version + Language + Host + Fs +} + type Version interface { Version() string } + +type Language interface { + IsMultilingual() bool +} + +type Host interface { + IsMultihost() bool +} + +type Fs interface { + WorkingDir() string +} diff --git a/pkg/template/funcs/lang/lang.go b/pkg/template/funcs/lang/lang.go index 545e78b9..024a1fba 100644 --- a/pkg/template/funcs/lang/lang.go +++ b/pkg/template/funcs/lang/lang.go @@ -30,28 +30,36 @@ import ( ) // New returns a new instance of the lang-namespaced template functions. -func New() *Namespace { +func New(svc Translator) *Namespace { return &Namespace{ - translator: translators.GetTranslator("en"), // TODO, make it more extensible + translator: translators.GetTranslator("en"), // TODO, make it more extensible + translationSvc: svc, } } // Namespace provides template functions for the "lang" namespace. type Namespace struct { - translator locales.Translator + translator locales.Translator + translationSvc Translator } // Translate returns a translated string for id. func (ns *Namespace) Translate(ctx context.Context, id any, args ...any) (string, error) { + var templateData any + + if len(args) > 0 { + if len(args) > 1 { + return "", fmt.Errorf("wrong number of arguments, expecting at most 2, got %d", len(args)+1) + } + templateData = args[0] + } + sid, err := cast.ToStringE(id) if err != nil { - return "", nil + return "", err } - // TODO, make it more extensible with real translator - // missing i18n, and translation provider - - return sid, nil + return ns.translationSvc.Translate(ctx, sid, templateData), nil } // FormatNumber formats number with the given precision for the current language. diff --git a/pkg/template/funcs/lang/type.go b/pkg/template/funcs/lang/type.go new file mode 100644 index 00000000..0fcc27e1 --- /dev/null +++ b/pkg/template/funcs/lang/type.go @@ -0,0 +1,7 @@ +package lang + +import "context" + +type Translator interface { + Translate(ctx context.Context, translationID string, templateData any) string +} diff --git a/pkg/template/funcs/resource/resources.go b/pkg/template/funcs/resource/resources.go index cf9154dc..da6449cb 100644 --- a/pkg/template/funcs/resource/resources.go +++ b/pkg/template/funcs/resource/resources.go @@ -124,6 +124,10 @@ func (ns *Namespace) Fingerprint(args ...any) (resources.Resource, error) { return ns.resourceService.Fingerprint(r, algo) } +func (ns *Namespace) Sass(args ...any) (resources.Resource, error) { + return ns.ToCSS(args...) +} + // ToCSS converts the given Resource to CSS. You can optional provide an Options object // as second argument. As an option, you can e.g. specify e.g. the target path (string) // for the converted CSS resource. diff --git a/pkg/template/funcs/site/site.go b/pkg/template/funcs/site/site.go index 23ba8558..1976085d 100644 --- a/pkg/template/funcs/site/site.go +++ b/pkg/template/funcs/site/site.go @@ -1,10 +1,10 @@ package site // New returns a new instance of the resources-namespaced template functions. -func New(svc Service) (*Namespace, error) { +func New(svc Service) *Namespace { return &Namespace{ Service: svc, - }, nil + } } // Namespace provides template functions for the "resources" namespace. diff --git a/pkg/types/types.go b/pkg/types/types.go index a0da0259..4787501b 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -51,7 +51,15 @@ func Unwrapv(v any) any { } // LowHigh is typically used to represent a slice boundary. -type LowHigh struct { +type LowHigh[S ~[]byte | string] struct { Low int High int } + +func (l LowHigh[S]) IsZero() bool { + return l.Low < 0 || (l.Low == 0 && l.High == 0) +} + +func (l LowHigh[S]) Value(source S) S { + return source[l.Low:l.High] +}