Merge branch 'feature/entry' into develop
/ docker (push) Successful in 1m8s
Details
/ docker (push) Successful in 1m8s
Details
This commit is contained in:
commit
6aae1ffa48
21
go.mod
21
go.mod
|
@ -5,24 +5,17 @@ go 1.21.3
|
||||||
require (
|
require (
|
||||||
github.com/adrg/frontmatter v0.2.0
|
github.com/adrg/frontmatter v0.2.0
|
||||||
github.com/caarlos0/env/v10 v10.0.0
|
github.com/caarlos0/env/v10 v10.0.0
|
||||||
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/valyala/quicktemplate v1.7.0
|
github.com/valyala/quicktemplate v1.7.0
|
||||||
)
|
github.com/yuin/goldmark v1.7.0
|
||||||
|
github.com/yuin/goldmark-emoji v1.0.2
|
||||||
require github.com/google/go-cmp v0.6.0
|
golang.org/x/image v0.15.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
require github.com/yuin/goldmark v1.6.0
|
|
||||||
|
|
||||||
require github.com/yuin/goldmark-emoji v1.0.2
|
|
||||||
|
|
||||||
require golang.org/x/image v0.14.0
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
golang.org/x/text v0.14.0
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -19,13 +19,13 @@ github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTc
|
||||||
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
||||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
|
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
|
||||||
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||||
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
|
@ -18,10 +18,10 @@ import (
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/common"
|
"source.toby3d.me/toby3d/home/internal/common"
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/entry"
|
||||||
|
pagefsrepo "source.toby3d.me/toby3d/home/internal/entry/repository/fs"
|
||||||
|
pageucase "source.toby3d.me/toby3d/home/internal/entry/usecase"
|
||||||
"source.toby3d.me/toby3d/home/internal/middleware"
|
"source.toby3d.me/toby3d/home/internal/middleware"
|
||||||
"source.toby3d.me/toby3d/home/internal/page"
|
|
||||||
pagefsrepo "source.toby3d.me/toby3d/home/internal/page/repository/fs"
|
|
||||||
pageucase "source.toby3d.me/toby3d/home/internal/page/usecase"
|
|
||||||
resourcefsrepo "source.toby3d.me/toby3d/home/internal/resource/repository/fs"
|
resourcefsrepo "source.toby3d.me/toby3d/home/internal/resource/repository/fs"
|
||||||
resourceucase "source.toby3d.me/toby3d/home/internal/resource/usecase"
|
resourceucase "source.toby3d.me/toby3d/home/internal/resource/usecase"
|
||||||
servercase "source.toby3d.me/toby3d/home/internal/server/usecase"
|
servercase "source.toby3d.me/toby3d/home/internal/server/usecase"
|
||||||
|
@ -40,11 +40,11 @@ type App struct {
|
||||||
|
|
||||||
func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
themeDir := os.DirFS(config.ThemeDir)
|
themeDir := os.DirFS(config.ThemeDir)
|
||||||
|
|
||||||
partialsDir, err := fs.Sub(themeDir, "partials")
|
partialsDir, err := fs.Sub(themeDir, "partials")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err)
|
return nil, fmt.Errorf("cannot substitute into partials subdirectory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentDir := os.DirFS(config.ContentDir)
|
contentDir := os.DirFS(config.ContentDir)
|
||||||
resources := resourcefsrepo.NewFileServerResourceRepository(contentDir)
|
resources := resourcefsrepo.NewFileServerResourceRepository(contentDir)
|
||||||
sites := sitefsrepo.NewFileSystemSiteRepository(contentDir)
|
sites := sitefsrepo.NewFileSystemSiteRepository(contentDir)
|
||||||
|
@ -55,8 +55,8 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
resourcer := resourceucase.NewResourceUseCase(resources)
|
resourcer := resourceucase.NewResourceUseCase(resources)
|
||||||
themes := themefsrepo.NewFileSystemThemeRepository(themeDir)
|
themes := themefsrepo.NewFileSystemThemeRepository(themeDir)
|
||||||
themer := themeucase.NewThemeUseCase(partialsDir, themes)
|
themer := themeucase.NewThemeUseCase(partialsDir, themes)
|
||||||
pages := pagefsrepo.NewFileSystemPageRepository(contentDir)
|
entries := pagefsrepo.NewFileSystemPageRepository(contentDir)
|
||||||
pager := pageucase.NewPageUseCase(pages, resources)
|
entrier := pageucase.NewEntryUseCase(entries, resources)
|
||||||
serverer := servercase.NewServerUseCase(sites)
|
serverer := servercase.NewServerUseCase(sites)
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// INFO(toby3d): any static file is public and unprotected by design, so it's safe to search it
|
// INFO(toby3d): any static file is public and unprotected by design, so it's safe to search it
|
||||||
|
@ -115,9 +115,9 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := pager.Do(r.Context(), lang, r.URL.Path)
|
e, err := entrier.Do(r.Context(), lang, r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, page.ErrNotExist) {
|
if !errors.Is(err, entry.ErrNotExist) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -158,14 +158,14 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contentLanguage := make([]string, len(p.Translations))
|
contentLanguage := make([]string, len(e.Translations))
|
||||||
for i := range p.Translations {
|
for i := range e.Translations {
|
||||||
contentLanguage[i] = p.Translations[i].Language.Code()
|
contentLanguage[i] = e.Translations[i].Language.Code()
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
|
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
|
||||||
|
|
||||||
template, err := themer.Do(r.Context(), s, p)
|
template, err := themer.Do(r.Context(), s, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MIMETextHTML string = "text/html"
|
MIMETextHTML string = "text/html"
|
||||||
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
|
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
|
||||||
|
MIMETextPlain string = "text/plain"
|
||||||
|
MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8
|
||||||
)
|
)
|
||||||
|
|
||||||
const Und string = "und"
|
const Und string = "und"
|
||||||
|
|
|
@ -41,11 +41,15 @@ func NewPath(path string) Path {
|
||||||
parts := strings.Split(out.baseFileName, ".")
|
parts := strings.Split(out.baseFileName, ".")
|
||||||
out.Language = NewLanguage(parts[len(parts)-1])
|
out.Language = NewLanguage(parts[len(parts)-1])
|
||||||
out.translationBaseName = strings.Join(parts[:len(parts)-1], ".")
|
out.translationBaseName = strings.Join(parts[:len(parts)-1], ".")
|
||||||
out.contentBaseName = out.translationBaseName
|
|
||||||
|
if len(parts) == 1 {
|
||||||
|
out.translationBaseName = parts[0]
|
||||||
|
out.contentBaseName = filepath.Base(out.dir)
|
||||||
|
} else {
|
||||||
|
out.contentBaseName = out.translationBaseName
|
||||||
|
}
|
||||||
|
|
||||||
switch out.translationBaseName {
|
switch out.translationBaseName {
|
||||||
default:
|
|
||||||
out.contentBaseName = out.translationBaseName
|
|
||||||
case "_index", "index":
|
case "_index", "index":
|
||||||
out.contentBaseName = filepath.Base(out.dir)
|
out.contentBaseName = filepath.Base(out.dir)
|
||||||
}
|
}
|
||||||
|
@ -62,6 +66,7 @@ func NewPath(path string) Path {
|
||||||
// /news/a.en.md => a.en
|
// /news/a.en.md => a.en
|
||||||
// /news/b/index.en.md => index.en
|
// /news/b/index.en.md => index.en
|
||||||
// /news/_index.en.md => _index.en
|
// /news/_index.en.md => _index.en
|
||||||
|
// /news/b/photo.jpg => photo
|
||||||
func (p Path) BaseFileName() string {
|
func (p Path) BaseFileName() string {
|
||||||
return p.baseFileName
|
return p.baseFileName
|
||||||
}
|
}
|
||||||
|
@ -71,6 +76,7 @@ func (p Path) BaseFileName() string {
|
||||||
// /news/a.en.md => a
|
// /news/a.en.md => a
|
||||||
// /news/b/index.en.md => b
|
// /news/b/index.en.md => b
|
||||||
// /news/_index.en.md => news
|
// /news/_index.en.md => news
|
||||||
|
// /news/b/photo.jpg => b
|
||||||
func (p Path) ContentBaseName() string {
|
func (p Path) ContentBaseName() string {
|
||||||
return p.contentBaseName
|
return p.contentBaseName
|
||||||
}
|
}
|
||||||
|
@ -80,6 +86,7 @@ func (p Path) ContentBaseName() string {
|
||||||
// /news/a.en.md => news/
|
// /news/a.en.md => news/
|
||||||
// /news/b/index.en.md => news/b/
|
// /news/b/index.en.md => news/b/
|
||||||
// /news/_index.en.md => news/
|
// /news/_index.en.md => news/
|
||||||
|
// /news/b/photo.jpg => news/b/
|
||||||
func (p Path) Dir() string {
|
func (p Path) Dir() string {
|
||||||
return p.dir
|
return p.dir
|
||||||
}
|
}
|
||||||
|
@ -87,6 +94,7 @@ func (p Path) Dir() string {
|
||||||
// Ext returns file extention:
|
// Ext returns file extention:
|
||||||
//
|
//
|
||||||
// /news/b/index.en.md => md
|
// /news/b/index.en.md => md
|
||||||
|
// /news/b/photo.jpg => jpg
|
||||||
func (p Path) Ext() string {
|
func (p Path) Ext() string {
|
||||||
return p.ext
|
return p.ext
|
||||||
}
|
}
|
||||||
|
@ -95,11 +103,12 @@ func (p Path) Filename() string {
|
||||||
return p.filename
|
return p.filename
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogicalName returns fille file name in directory:
|
// LogicalName returns file name in directory:
|
||||||
//
|
//
|
||||||
// /news/a.en.md => a.en.md
|
// /news/a.en.md => a.en.md
|
||||||
// /news/b/index.en.md => index.en.md
|
// /news/b/index.en.md => index.en.md
|
||||||
// /news/_index.en.md => _index.en.md
|
// /news/_index.en.md => _index.en.md
|
||||||
|
// /news/b/photo.jpg => photo.jpg
|
||||||
func (p Path) LogicalName() string {
|
func (p Path) LogicalName() string {
|
||||||
return p.logicalName
|
return p.logicalName
|
||||||
}
|
}
|
||||||
|
@ -113,6 +122,7 @@ func (p Path) Path() string {
|
||||||
// /news/a.en.md => a
|
// /news/a.en.md => a
|
||||||
// /news/b/index.en.md => index
|
// /news/b/index.en.md => index
|
||||||
// /news/_index.en.md => _index
|
// /news/_index.en.md => _index
|
||||||
|
// /news/b/photo.jpg => photo
|
||||||
func (p Path) TranslationBaseName() string {
|
func (p Path) TranslationBaseName() string {
|
||||||
return p.translationBaseName
|
return p.translationBaseName
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ var (
|
||||||
testRegularPath string = filepath.Join("news", "a.en.md")
|
testRegularPath string = filepath.Join("news", "a.en.md")
|
||||||
testLeafPath string = filepath.Join("news", "b", "index.en.md")
|
testLeafPath string = filepath.Join("news", "b", "index.en.md")
|
||||||
testBranchPath string = filepath.Join("news", "_index.en.md")
|
testBranchPath string = filepath.Join("news", "_index.en.md")
|
||||||
|
testResource string = filepath.Join("news", "b", "photo.jpg")
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPath_BaseFileName(t *testing.T) {
|
func TestPath_BaseFileName(t *testing.T) {
|
||||||
|
@ -19,9 +20,10 @@ func TestPath_BaseFileName(t *testing.T) {
|
||||||
for name, tc := range map[string]struct {
|
for name, tc := range map[string]struct {
|
||||||
input, expect string
|
input, expect string
|
||||||
}{
|
}{
|
||||||
"regular": {testRegularPath, "a.en"},
|
"regular": {testRegularPath, "a.en"},
|
||||||
"leaf": {testLeafPath, "index.en"},
|
"leaf": {testLeafPath, "index.en"},
|
||||||
"branch": {testBranchPath, "_index.en"},
|
"branch": {testBranchPath, "_index.en"},
|
||||||
|
"resource": {testResource, "photo"},
|
||||||
} {
|
} {
|
||||||
name, tc := name, tc
|
name, tc := name, tc
|
||||||
|
|
||||||
|
@ -41,9 +43,10 @@ func TestPath_ContentBaseName(t *testing.T) {
|
||||||
for name, tc := range map[string]struct {
|
for name, tc := range map[string]struct {
|
||||||
input, expect string
|
input, expect string
|
||||||
}{
|
}{
|
||||||
"regular": {testRegularPath, "a"},
|
"regular": {testRegularPath, "a"},
|
||||||
"leaf": {testLeafPath, "b"},
|
"leaf": {testLeafPath, "b"},
|
||||||
"branch": {testBranchPath, "news"},
|
"branch": {testBranchPath, "news"},
|
||||||
|
"resource": {testResource, "b"},
|
||||||
} {
|
} {
|
||||||
name, tc := name, tc
|
name, tc := name, tc
|
||||||
|
|
||||||
|
@ -63,9 +66,10 @@ func TestPath_Dir(t *testing.T) {
|
||||||
for name, tc := range map[string]struct {
|
for name, tc := range map[string]struct {
|
||||||
input, expect string
|
input, expect string
|
||||||
}{
|
}{
|
||||||
"regular": {testRegularPath, "news/"},
|
"regular": {testRegularPath, "news/"},
|
||||||
"leaf": {testLeafPath, "news/b/"},
|
"leaf": {testLeafPath, "news/b/"},
|
||||||
"branch": {testBranchPath, "news/"},
|
"branch": {testBranchPath, "news/"},
|
||||||
|
"resource": {testResource, "news/b/"},
|
||||||
} {
|
} {
|
||||||
name, tc := name, tc
|
name, tc := name, tc
|
||||||
|
|
||||||
|
@ -84,18 +88,21 @@ func TestPath_Ext(t *testing.T) {
|
||||||
|
|
||||||
const expect string = "md"
|
const expect string = "md"
|
||||||
|
|
||||||
for name, input := range map[string]string{
|
for name, tc := range map[string]struct {
|
||||||
"regular": testRegularPath,
|
input, expect string
|
||||||
"leaf": testLeafPath,
|
}{
|
||||||
"branch": testBranchPath,
|
"regular": {testRegularPath, "md"},
|
||||||
|
"leaf": {testLeafPath, "md"},
|
||||||
|
"branch": {testBranchPath, "md"},
|
||||||
|
"resource": {testResource, "jpg"},
|
||||||
} {
|
} {
|
||||||
name, input := name, input
|
name, tc := name, tc
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if actual := domain.NewPath(input).Ext(); actual != expect {
|
if actual := domain.NewPath(tc.input).Ext(); actual != tc.expect {
|
||||||
t.Errorf("Ext() = '%s', want '%s'", actual, expect)
|
t.Errorf("Ext() = '%s', want '%s'", actual, tc.expect)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -129,9 +136,10 @@ func TestPath_LogicalName(t *testing.T) {
|
||||||
for name, tc := range map[string]struct {
|
for name, tc := range map[string]struct {
|
||||||
input, expect string
|
input, expect string
|
||||||
}{
|
}{
|
||||||
"regular": {testRegularPath, "a.en.md"},
|
"regular": {testRegularPath, "a.en.md"},
|
||||||
"leaf": {testLeafPath, "index.en.md"},
|
"leaf": {testLeafPath, "index.en.md"},
|
||||||
"branch": {testBranchPath, "_index.en.md"},
|
"branch": {testBranchPath, "_index.en.md"},
|
||||||
|
"resource": {testResource, "photo.jpg"},
|
||||||
} {
|
} {
|
||||||
name, tc := name, tc
|
name, tc := name, tc
|
||||||
|
|
||||||
|
@ -151,9 +159,10 @@ func TestPath_TranslationBaseName(t *testing.T) {
|
||||||
for name, tc := range map[string]struct {
|
for name, tc := range map[string]struct {
|
||||||
input, expect string
|
input, expect string
|
||||||
}{
|
}{
|
||||||
"regular": {testRegularPath, "a"},
|
"regular": {testRegularPath, "a"},
|
||||||
"leaf": {testLeafPath, "index"},
|
"leaf": {testLeafPath, "index"},
|
||||||
"branch": {testBranchPath, "_index"},
|
"branch": {testBranchPath, "_index"},
|
||||||
|
"resource": {testResource, "photo"},
|
||||||
} {
|
} {
|
||||||
name, tc := name, tc
|
name, tc := name, tc
|
||||||
|
|
||||||
|
|
|
@ -15,15 +15,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
File Path
|
|
||||||
|
|
||||||
modTime time.Time
|
modTime time.Time
|
||||||
params map[string]any // TODO(toby3d): set from Page configuration
|
params map[string]any
|
||||||
|
File Path
|
||||||
mediaType MediaType
|
mediaType MediaType
|
||||||
key string
|
key string
|
||||||
name string // TODO(toby3d): set from Page configuration
|
name string
|
||||||
title string // TODO(toby3d): set from Page configuration
|
|
||||||
resourceType ResourceType
|
resourceType ResourceType
|
||||||
|
title string
|
||||||
image image.Config
|
image image.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,13 +68,13 @@ func (r Resource) MediaType() MediaType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width returns width if current r is an image.
|
// Width returns width if current r is an image.
|
||||||
func (r Resource) Width() int {
|
func (r Resource) Width() uint {
|
||||||
return r.image.Width
|
return uint(r.image.Width)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns height if current r is an image.
|
// Height returns height if current r is an image.
|
||||||
func (r Resource) Height() int {
|
func (r Resource) Height() uint {
|
||||||
return r.image.Height
|
return uint(r.image.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Resource) ResourceType() ResourceType {
|
func (r Resource) ResourceType() ResourceType {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package entry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
Get(ctx context.Context, lang domain.Language, path string) (*domain.Page, error)
|
||||||
|
|
||||||
|
// Stat checks for the existence of a page on the specified path without
|
||||||
|
// parsing its contents.
|
||||||
|
Stat(ctx context.Context, lang domain.Language, path string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNotExist error = errors.New("entry not exists")
|
|
@ -2,6 +2,7 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
||||||
|
@ -9,7 +10,7 @@ import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
"source.toby3d.me/toby3d/home/internal/page"
|
"source.toby3d.me/toby3d/home/internal/entry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -29,7 +30,7 @@ var FrontMatterFormats = []*frontmatter.Format{
|
||||||
frontmatter.NewFormat(`---`, `---`, yaml.Unmarshal),
|
frontmatter.NewFormat(`---`, `---`, yaml.Unmarshal),
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileSystemPageRepository(dir fs.FS) page.Repository {
|
func NewFileSystemPageRepository(dir fs.FS) entry.Repository {
|
||||||
return &fileSystemPageRepository{
|
return &fileSystemPageRepository{
|
||||||
dir: dir,
|
dir: dir,
|
||||||
}
|
}
|
||||||
|
@ -45,7 +46,7 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Langu
|
||||||
|
|
||||||
f, err := repo.dir.Open(target)
|
f, err := repo.dir.Open(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot open '%s' page file: %w", target, err)
|
return nil, fmt.Errorf("cannot open '%s' entry file: %w", target, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Langu
|
||||||
Params: make(map[string]any),
|
Params: make(map[string]any),
|
||||||
}
|
}
|
||||||
if data.Content, err = frontmatter.Parse(f, data, FrontMatterFormats...); err != nil {
|
if data.Content, err = frontmatter.Parse(f, data, FrontMatterFormats...); err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse page content as FrontMatter: %w", err)
|
return nil, fmt.Errorf("cannot parse entry content as FrontMatter: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.Page{
|
return &domain.Page{
|
||||||
|
@ -67,3 +68,14 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang domain.Langu
|
||||||
Translations: make([]*domain.Page, 0),
|
Translations: make([]*domain.Page, 0),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *fileSystemPageRepository) Stat(_ context.Context, l domain.Language, p string) (bool, error) {
|
||||||
|
ext := ".md"
|
||||||
|
if l != domain.LanguageUnd {
|
||||||
|
ext = "." + l.Lang() + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := fs.Stat(repo.dir, p+ext)
|
||||||
|
|
||||||
|
return errors.Is(err, fs.ErrExist), nil
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
repository "source.toby3d.me/toby3d/home/internal/page/repository/fs"
|
repository "source.toby3d.me/toby3d/home/internal/entry/repository/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
|
@ -0,0 +1,41 @@
|
||||||
|
package memory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"sync"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/entry"
|
||||||
|
)
|
||||||
|
|
||||||
|
type memoryEntryRepository struct {
|
||||||
|
mutex *sync.RWMutex
|
||||||
|
files fstest.MapFS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *memoryEntryRepository) Get(_ context.Context, _ domain.Language, p string) (*domain.Page, error) {
|
||||||
|
f, err := repo.files.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get entry from memory: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *memoryEntryRepository) Stat(_ context.Context, l domain.Language, p string) (bool, error) {
|
||||||
|
_, err := fs.Stat(repo.files, p)
|
||||||
|
|
||||||
|
return errors.Is(err, fs.ErrExist), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemoryEntryRepository(files fstest.MapFS) entry.Repository {
|
||||||
|
return &memoryEntryRepository{
|
||||||
|
mutex: new(sync.RWMutex),
|
||||||
|
files: files,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
package page
|
package entry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
)
|
)
|
||||||
|
@ -10,5 +9,3 @@ import (
|
||||||
type UseCase interface {
|
type UseCase interface {
|
||||||
Do(ctx context.Context, lang domain.Language, path string) (*domain.Page, error)
|
Do(ctx context.Context, lang domain.Language, path string) (*domain.Page, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNotExist error = errors.New("page not exists")
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/entry"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/resource"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/urlutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type entryUseCase struct {
|
||||||
|
entries entry.Repository
|
||||||
|
resources resource.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntryUseCase(entries entry.Repository, resources resource.Repository) entry.UseCase {
|
||||||
|
return &entryUseCase{
|
||||||
|
entries: entries,
|
||||||
|
resources: resources,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ucase *entryUseCase) Do(ctx context.Context, lang domain.Language, p string) (*domain.Page, error) {
|
||||||
|
targets := make([]string, 0)
|
||||||
|
hasExt := path.Ext(p) != ""
|
||||||
|
head, tail := urlutil.ShiftPath(p)
|
||||||
|
|
||||||
|
if tail == "/" {
|
||||||
|
if head = strings.TrimSuffix(head, path.Ext(head)); head == "" {
|
||||||
|
head = "index"
|
||||||
|
}
|
||||||
|
|
||||||
|
targets = append(targets, head)
|
||||||
|
}
|
||||||
|
|
||||||
|
if head != "index" {
|
||||||
|
tail = strings.TrimSuffix(tail, path.Ext(tail))
|
||||||
|
if !strings.HasSuffix(tail, "/index") {
|
||||||
|
if hasExt {
|
||||||
|
targets = append([]string{path.Join(head, tail, "index")}, targets...)
|
||||||
|
} else {
|
||||||
|
targets = append(targets, path.Join(head, tail, "index"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targets = append(targets, path.Join(head, tail))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(targets) - 1; 0 <= i; i-- {
|
||||||
|
result, err := ucase.entries.Get(ctx, lang, targets[i])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Resources, _, err = ucase.resources.Fetch(ctx, result.File.Dir()+"*"); err != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, res := range result.Resources.GetType(domain.ResourceTypePage) {
|
||||||
|
if res.File.TranslationBaseName() != result.File.TranslationBaseName() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
translation, err := ucase.entries.Get(ctx, res.File.Language, targets[i])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Translations = append(result.Translations, translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("cannot find page on path '%s': %w", p, entry.ErrNotExist)
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package usecase_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
entryfsrepo "source.toby3d.me/toby3d/home/internal/entry/repository/fs"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/entry/usecase"
|
||||||
|
resourcedummyrepo "source.toby3d.me/toby3d/home/internal/resource/repository/dummy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
pages := entryfsrepo.NewFileSystemPageRepository(fstest.MapFS{
|
||||||
|
filepath.Join("both", "index.md"): &fstest.MapFile{Data: []byte(`both/index.md`)},
|
||||||
|
filepath.Join("both.md"): &fstest.MapFile{Data: []byte(`both.md`)},
|
||||||
|
filepath.Join("file.md"): &fstest.MapFile{Data: []byte(`file.md`)},
|
||||||
|
filepath.Join("folder", "index.md"): &fstest.MapFile{Data: []byte(`folder/index.md`)},
|
||||||
|
filepath.Join("foo", "bar", "index.md"): &fstest.MapFile{Data: []byte(`foo/bar/index.md`)},
|
||||||
|
filepath.Join("foo", "bar.md"): &fstest.MapFile{Data: []byte(`foo/bar.md`)},
|
||||||
|
filepath.Join("index.md"): &fstest.MapFile{Data: []byte(`index.md`)},
|
||||||
|
})
|
||||||
|
|
||||||
|
ucase := usecase.NewEntryUseCase(pages, resourcedummyrepo.NewDummyResourceRepository())
|
||||||
|
|
||||||
|
for name, tc := range map[string]struct {
|
||||||
|
input string
|
||||||
|
expect []byte
|
||||||
|
}{
|
||||||
|
"root": {"/", []byte(`index.md`)},
|
||||||
|
"index": {"/index", []byte(`index.md`)},
|
||||||
|
"index-ext": {"/index.html", []byte(`index.md`)},
|
||||||
|
"file": {"/file", []byte(`file.md`)},
|
||||||
|
"file-slash": {"/file/", []byte(`file.md`)},
|
||||||
|
"file-ext": {"/file.html", []byte(`file.md`)},
|
||||||
|
"both-ext": {"/both.html", []byte(`both.md`)},
|
||||||
|
"folder": {"/folder", []byte(`folder/index.md`)},
|
||||||
|
"folder-slash": {"/folder/", []byte(`folder/index.md`)},
|
||||||
|
"folder-index": {"/folder/index", []byte(`folder/index.md`)},
|
||||||
|
"folder-ext": {"/folder/index.html", []byte(`folder/index.md`)},
|
||||||
|
"both": {"/both", []byte(`both/index.md`)},
|
||||||
|
"both-slash": {"/both/", []byte(`both/index.md`)},
|
||||||
|
"both-index": {"/both/index", []byte(`both/index.md`)},
|
||||||
|
"both-index-ext": {"/both/index.html", []byte(`both/index.md`)},
|
||||||
|
"sub-folder-index": {"/foo/bar/index", []byte(`foo/bar/index.md`)},
|
||||||
|
"sub-folder-ext": {"/foo/bar/index.html", []byte(`foo/bar/index.md`)},
|
||||||
|
"sub-folder-slash": {"/foo/bar/", []byte(`foo/bar/index.md`)},
|
||||||
|
} {
|
||||||
|
name, tc := name, tc
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
actual, err := ucase.Do(context.Background(), domain.LanguageUnd, tc.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(string(actual.Content), string(tc.expect)); diff != "" {
|
||||||
|
t.Error(diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package page
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Repository interface {
|
|
||||||
Get(ctx context.Context, lang domain.Language, path string) (*domain.Page, error)
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package usecase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
|
||||||
"source.toby3d.me/toby3d/home/internal/page"
|
|
||||||
"source.toby3d.me/toby3d/home/internal/resource"
|
|
||||||
"source.toby3d.me/toby3d/home/internal/urlutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pageUseCase struct {
|
|
||||||
pages page.Repository
|
|
||||||
resources resource.Repository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPageUseCase(pages page.Repository, resources resource.Repository) page.UseCase {
|
|
||||||
return &pageUseCase{
|
|
||||||
pages: pages,
|
|
||||||
resources: resources,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ucase *pageUseCase) Do(ctx context.Context, lang domain.Language, p string) (*domain.Page, error) {
|
|
||||||
ext := path.Ext(p)
|
|
||||||
if ext == ".html" {
|
|
||||||
p = p[:len(p)-len(ext)]
|
|
||||||
}
|
|
||||||
|
|
||||||
hasTrailingSlash := p[len(p)-1] == '/'
|
|
||||||
head, tail := urlutil.ShiftPath(p)
|
|
||||||
targets := []string{path.Join(head, tail)}
|
|
||||||
|
|
||||||
if tail == "/" {
|
|
||||||
if hasTrailingSlash || ext == "" {
|
|
||||||
targets = append([]string{path.Join(head, "index")}, targets...)
|
|
||||||
}
|
|
||||||
|
|
||||||
targets = append(targets, head)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range targets {
|
|
||||||
out, err := ucase.pages.Get(ctx, lang, targets[i])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.Resources, _, err = ucase.resources.Fetch(ctx, out.File.Dir()+"*"); err != nil {
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, res := range out.Resources.GetType(domain.ResourceTypePage) {
|
|
||||||
if res.File.TranslationBaseName() != out.File.TranslationBaseName() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
translation, err := ucase.pages.Get(ctx, res.File.Language, targets[i])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Translations = append(out.Translations, translation)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot find page on path '%s': %w", p, page.ErrNotExist)
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package usecase_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"testing/fstest"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
|
||||||
pagefsrepo "source.toby3d.me/toby3d/home/internal/page/repository/fs"
|
|
||||||
"source.toby3d.me/toby3d/home/internal/page/usecase"
|
|
||||||
"source.toby3d.me/toby3d/home/internal/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
pages := pagefsrepo.NewFileSystemPageRepository(fstest.MapFS{
|
|
||||||
filepath.Join("both", "index.md"): &fstest.MapFile{Data: []byte(`both/index.md`)},
|
|
||||||
filepath.Join("folder", "index.md"): &fstest.MapFile{Data: []byte(`folder/index.md`)},
|
|
||||||
filepath.Join("both.md"): &fstest.MapFile{Data: []byte(`both.md`)},
|
|
||||||
filepath.Join("file.md"): &fstest.MapFile{Data: []byte(`file.md`)},
|
|
||||||
filepath.Join("index.md"): &fstest.MapFile{Data: []byte(`index.md`)},
|
|
||||||
})
|
|
||||||
|
|
||||||
ucase := usecase.NewPageUseCase(pages, resource.NewDummyRepository())
|
|
||||||
|
|
||||||
for name, tc := range map[string]struct {
|
|
||||||
input string
|
|
||||||
expect []byte
|
|
||||||
}{
|
|
||||||
"index": {input: "/", expect: []byte(`index.md`)},
|
|
||||||
"index-ext": {input: "/index.html", expect: []byte(`index.md`)},
|
|
||||||
"file": {input: "/file", expect: []byte(`file.md`)},
|
|
||||||
"file-slash": {input: "/file/", expect: []byte(`file.md`)},
|
|
||||||
"file-ext": {input: "/file.html", expect: []byte(`file.md`)},
|
|
||||||
"folder": {input: "/folder", expect: []byte(`folder/index.md`)},
|
|
||||||
"folder-slash": {input: "/folder/", expect: []byte(`folder/index.md`)},
|
|
||||||
"folder-index": {input: "/folder/index.html", expect: []byte(`folder/index.md`)},
|
|
||||||
"both": {input: "/both", expect: []byte(`both/index.md`)},
|
|
||||||
"both-slash": {input: "/both/", expect: []byte(`both/index.md`)},
|
|
||||||
"both-ext": {input: "/both.html", expect: []byte(`both.md`)},
|
|
||||||
"both-index": {input: "/both/index.html", expect: []byte(`both/index.md`)},
|
|
||||||
} {
|
|
||||||
name, tc := name, tc
|
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
actual, err := ucase.Do(context.Background(), domain.LanguageUnd, tc.input)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diff := cmp.Diff(string(actual.Content), string(tc.expect)); diff != "" {
|
|
||||||
t.Error(diff)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,26 +6,10 @@ import (
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type Repository interface {
|
||||||
Repository interface {
|
// Get returns Resource on path if exists.
|
||||||
// Get returns Resource on path if exists.
|
Get(ctx context.Context, path string) (*domain.Resource, error)
|
||||||
Get(ctx context.Context, path string) (*domain.Resource, error)
|
|
||||||
|
|
||||||
// Fetch returns all resources from dir recursevly.
|
// Fetch returns all resources from dir recursevly.
|
||||||
Fetch(ctx context.Context, pattern string) (domain.Resources, int, error)
|
Fetch(ctx context.Context, pattern string) (domain.Resources, int, error)
|
||||||
}
|
|
||||||
|
|
||||||
dummyRepository struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewDummyRepository() dummyRepository {
|
|
||||||
return dummyRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dummyRepository) Get(ctx context.Context, path string) (*domain.Resource, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dummyRepository) Fetch(ctx context.Context, pattern string) (domain.Resources, int, error) {
|
|
||||||
return nil, 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dummy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dummyResourceRepository struct{}
|
||||||
|
|
||||||
|
func NewDummyResourceRepository() dummyResourceRepository {
|
||||||
|
return dummyResourceRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dummyResourceRepository) Get(_ context.Context, _ string) (*domain.Resource, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dummyResourceRepository) Fetch(_ context.Context, _ string) (domain.Resources, int, error) {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
|
@ -3,14 +3,8 @@ package fs
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "image/gif"
|
|
||||||
_ "image/jpeg"
|
|
||||||
_ "image/png"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
||||||
_ "golang.org/x/image/bmp"
|
|
||||||
_ "golang.org/x/image/webp"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
"source.toby3d.me/toby3d/home/internal/resource"
|
"source.toby3d.me/toby3d/home/internal/resource"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,17 +5,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
_ "image/gif"
|
|
||||||
_ "image/jpeg"
|
|
||||||
_ "image/png"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
_ "golang.org/x/image/bmp"
|
|
||||||
_ "golang.org/x/image/webp"
|
|
||||||
|
|
||||||
"source.toby3d.me/toby3d/home/internal/domain"
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
"source.toby3d.me/toby3d/home/internal/static"
|
"source.toby3d.me/toby3d/home/internal/static"
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,7 +8,7 @@ goldmark
|
||||||
|
|
||||||
> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
|
> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
|
||||||
|
|
||||||
goldmark is compliant with CommonMark 0.30.
|
goldmark is compliant with CommonMark 0.31.2.
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -260,7 +260,7 @@ You can override autolinking patterns via options.
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithLinkifyAllowedProtocols` | `[][]byte` | List of allowed protocols such as `[][]byte{ []byte("http:") }` |
|
| `extension.WithLinkifyAllowedProtocols` | `[][]byte \| []string` | List of allowed protocols such as `[]string{ "http:" }` |
|
||||||
| `extension.WithLinkifyURLRegexp` | `*regexp.Regexp` | Regexp that defines URLs, including protocols |
|
| `extension.WithLinkifyURLRegexp` | `*regexp.Regexp` | Regexp that defines URLs, including protocols |
|
||||||
| `extension.WithLinkifyWWWRegexp` | `*regexp.Regexp` | Regexp that defines URL starting with `www.`. This pattern corresponds to [the extended www autolink](https://github.github.com/gfm/#extended-www-autolink) |
|
| `extension.WithLinkifyWWWRegexp` | `*regexp.Regexp` | Regexp that defines URL starting with `www.`. This pattern corresponds to [the extended www autolink](https://github.github.com/gfm/#extended-www-autolink) |
|
||||||
| `extension.WithLinkifyEmailRegexp` | `*regexp.Regexp` | Regexp that defines email addresses` |
|
| `extension.WithLinkifyEmailRegexp` | `*regexp.Regexp` | Regexp that defines email addresses` |
|
||||||
|
@ -277,9 +277,9 @@ markdown := goldmark.New(
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.NewLinkify(
|
extension.NewLinkify(
|
||||||
extension.WithLinkifyAllowedProtocols([][]byte{
|
extension.WithLinkifyAllowedProtocols([]string{
|
||||||
[]byte("http:"),
|
"http:",
|
||||||
[]byte("https:"),
|
"https:",
|
||||||
}),
|
}),
|
||||||
extension.WithLinkifyURLRegexp(
|
extension.WithLinkifyURLRegexp(
|
||||||
xurls.Strict,
|
xurls.Strict,
|
||||||
|
@ -297,13 +297,13 @@ This extension has some options:
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.|
|
| `extension.WithFootnoteIDPrefix` | `[]byte \| string` | a prefix for the id attributes.|
|
||||||
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
||||||
| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.|
|
| `extension.WithFootnoteLinkTitle` | `[]byte \| string` | an optional title attribute for footnote links.|
|
||||||
| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. |
|
| `extension.WithFootnoteBacklinkTitle` | `[]byte \| string` | an optional title attribute for footnote backlinks. |
|
||||||
| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. |
|
| `extension.WithFootnoteLinkClass` | `[]byte \| string` | a class for footnote links. This defaults to `footnote-ref`. |
|
||||||
| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
| `extension.WithFootnoteBacklinkClass` | `[]byte \| string` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
||||||
| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `↩︎`. |
|
| `extension.WithFootnoteBacklinkHTML` | `[]byte \| string` | a class for footnote backlinks. This defaults to `↩︎`. |
|
||||||
|
|
||||||
Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ for _, path := range files {
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewFootnote(
|
NewFootnote(
|
||||||
WithFootnoteIDPrefix([]byte(path)),
|
WithFootnoteIDPrefix(path),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -379,7 +379,7 @@ This extension provides additional options for CJK users.
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithEastAsianLineBreaks` | `...extension.EastAsianLineBreaksStyle` | Soft line breaks are rendered as a newline. Some asian users will see it as an unnecessary space. With this option, soft line breaks between east asian wide characters will be ignored. |
|
| `extension.WithEastAsianLineBreaks` | `...extension.EastAsianLineBreaksStyle` | Soft line breaks are rendered as a newline. Some asian users will see it as an unnecessary space. With this option, soft line breaks between east asian wide characters will be ignored. This defaults to `EastAsianLineBreaksStyleSimple`. |
|
||||||
| `extension.WithEscapedSpace` | `-` | Without spaces around an emphasis started with east asian punctuations, it is not interpreted as an emphasis(as defined in CommonMark spec). With this option, you can avoid this inconvenient behavior by putting 'not rendered' spaces around an emphasis like `太郎は\ **「こんにちわ」**\ といった`. |
|
| `extension.WithEscapedSpace` | `-` | Without spaces around an emphasis started with east asian punctuations, it is not interpreted as an emphasis(as defined in CommonMark spec). With this option, you can avoid this inconvenient behavior by putting 'not rendered' spaces around an emphasis like `太郎は\ **「こんにちわ」**\ といった`. |
|
||||||
|
|
||||||
#### Styles of Line Breaking
|
#### Styles of Line Breaking
|
||||||
|
@ -467,6 +467,7 @@ As you can see, goldmark's performance is on par with cmark's.
|
||||||
|
|
||||||
Extensions
|
Extensions
|
||||||
--------------------
|
--------------------
|
||||||
|
### List of extensions
|
||||||
|
|
||||||
- [goldmark-meta](https://github.com/yuin/goldmark-meta): A YAML metadata
|
- [goldmark-meta](https://github.com/yuin/goldmark-meta): A YAML metadata
|
||||||
extension for the goldmark Markdown parser.
|
extension for the goldmark Markdown parser.
|
||||||
|
@ -490,6 +491,13 @@ Extensions
|
||||||
- [goldmark-d2](https://github.com/FurqanSoftware/goldmark-d2): Adds support for [D2](https://d2lang.com/) diagrams.
|
- [goldmark-d2](https://github.com/FurqanSoftware/goldmark-d2): Adds support for [D2](https://d2lang.com/) diagrams.
|
||||||
- [goldmark-katex](https://github.com/FurqanSoftware/goldmark-katex): Adds support for [KaTeX](https://katex.org/) math and equations.
|
- [goldmark-katex](https://github.com/FurqanSoftware/goldmark-katex): Adds support for [KaTeX](https://katex.org/) math and equations.
|
||||||
- [goldmark-img64](https://github.com/tenkoh/goldmark-img64): Adds support for embedding images into the document as DataURL (base64 encoded).
|
- [goldmark-img64](https://github.com/tenkoh/goldmark-img64): Adds support for embedding images into the document as DataURL (base64 encoded).
|
||||||
|
- [goldmark-enclave](https://github.com/quail-ink/goldmark-enclave): Adds support for embedding youtube/bilibili video, X's [oembed tweet](https://publish.twitter.com/), [tradingview](https://www.tradingview.com/widget/)'s chart, [quail](https://quail.ink)'s widget into the document.
|
||||||
|
- [goldmark-wiki-table](https://github.com/movsb/goldmark-wiki-table): Adds support for embedding Wiki Tables.
|
||||||
|
|
||||||
|
### Loading extensions at runtime
|
||||||
|
[goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) allows you to write a goldmark extension in Lua and load it at runtime without re-compilation.
|
||||||
|
|
||||||
|
Please refer to [goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) for details.
|
||||||
|
|
||||||
|
|
||||||
goldmark internal(for extension developers)
|
goldmark internal(for extension developers)
|
||||||
|
|
|
@ -382,8 +382,8 @@ func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||||
func WithFootnoteIDPrefix(a []byte) FootnoteOption {
|
func WithFootnoteIDPrefix[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteIDPrefix{a}
|
return &withFootnoteIDPrefix{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
||||||
|
@ -420,8 +420,8 @@ func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
||||||
func WithFootnoteLinkTitle(a []byte) FootnoteOption {
|
func WithFootnoteLinkTitle[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteLinkTitle{a}
|
return &withFootnoteLinkTitle{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
||||||
|
@ -439,8 +439,8 @@ func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
||||||
func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkTitle[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkTitle{a}
|
return &withFootnoteBacklinkTitle{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
||||||
|
@ -458,8 +458,8 @@ func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
||||||
func WithFootnoteLinkClass(a []byte) FootnoteOption {
|
func WithFootnoteLinkClass[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteLinkClass{a}
|
return &withFootnoteLinkClass{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
||||||
|
@ -477,8 +477,8 @@ func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
||||||
func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkClass[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkClass{a}
|
return &withFootnoteBacklinkClass{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
||||||
|
@ -496,8 +496,8 @@ func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
||||||
func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkHTML[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkHTML{a}
|
return &withFootnoteBacklinkHTML{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||||
|
|
|
@ -66,10 +66,12 @@ func (o *withLinkifyAllowedProtocols) SetLinkifyOption(p *LinkifyConfig) {
|
||||||
// WithLinkifyAllowedProtocols is a functional option that specify allowed
|
// WithLinkifyAllowedProtocols is a functional option that specify allowed
|
||||||
// protocols in autolinks. Each protocol must end with ':' like
|
// protocols in autolinks. Each protocol must end with ':' like
|
||||||
// 'http:' .
|
// 'http:' .
|
||||||
func WithLinkifyAllowedProtocols(value [][]byte) LinkifyOption {
|
func WithLinkifyAllowedProtocols[T []byte | string](value []T) LinkifyOption {
|
||||||
return &withLinkifyAllowedProtocols{
|
opt := &withLinkifyAllowedProtocols{}
|
||||||
value: value,
|
for _, v := range value {
|
||||||
|
opt.value = append(opt.value, []byte(v))
|
||||||
}
|
}
|
||||||
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
type withLinkifyURLRegexp struct {
|
type withLinkifyURLRegexp struct {
|
||||||
|
|
|
@ -115,10 +115,10 @@ func (o *withTypographicSubstitutions) SetTypographerOption(p *TypographerConfig
|
||||||
|
|
||||||
// WithTypographicSubstitutions is a functional otpion that specify replacement text
|
// WithTypographicSubstitutions is a functional otpion that specify replacement text
|
||||||
// for punctuations.
|
// for punctuations.
|
||||||
func WithTypographicSubstitutions(values map[TypographicPunctuation][]byte) TypographerOption {
|
func WithTypographicSubstitutions[T []byte | string](values map[TypographicPunctuation]T) TypographerOption {
|
||||||
replacements := newDefaultSubstitutions()
|
replacements := newDefaultSubstitutions()
|
||||||
for k, v := range values {
|
for k, v := range values {
|
||||||
replacements[k] = v
|
replacements[k] = []byte(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &withTypographicSubstitutions{replacements}
|
return &withTypographicSubstitutions{replacements}
|
||||||
|
|
|
@ -61,8 +61,8 @@ var allowedBlockTags = map[string]bool{
|
||||||
"option": true,
|
"option": true,
|
||||||
"p": true,
|
"p": true,
|
||||||
"param": true,
|
"param": true,
|
||||||
|
"search": true,
|
||||||
"section": true,
|
"section": true,
|
||||||
"source": true,
|
|
||||||
"summary": true,
|
"summary": true,
|
||||||
"table": true,
|
"table": true,
|
||||||
"tbody": true,
|
"tbody": true,
|
||||||
|
|
|
@ -58,47 +58,38 @@ var closeProcessingInstruction = []byte("?>")
|
||||||
var openCDATA = []byte("<![CDATA[")
|
var openCDATA = []byte("<![CDATA[")
|
||||||
var closeCDATA = []byte("]]>")
|
var closeCDATA = []byte("]]>")
|
||||||
var closeDecl = []byte(">")
|
var closeDecl = []byte(">")
|
||||||
var emptyComment = []byte("<!---->")
|
var emptyComment1 = []byte("<!-->")
|
||||||
var invalidComment1 = []byte("<!-->")
|
var emptyComment2 = []byte("<!--->")
|
||||||
var invalidComment2 = []byte("<!--->")
|
|
||||||
var openComment = []byte("<!--")
|
var openComment = []byte("<!--")
|
||||||
var closeComment = []byte("-->")
|
var closeComment = []byte("-->")
|
||||||
var doubleHyphen = []byte("--")
|
|
||||||
|
|
||||||
func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
|
func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
|
||||||
savedLine, savedSegment := block.Position()
|
savedLine, savedSegment := block.Position()
|
||||||
node := ast.NewRawHTML()
|
node := ast.NewRawHTML()
|
||||||
line, segment := block.PeekLine()
|
line, segment := block.PeekLine()
|
||||||
if bytes.HasPrefix(line, emptyComment) {
|
if bytes.HasPrefix(line, emptyComment1) {
|
||||||
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment)))
|
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment1)))
|
||||||
block.Advance(len(emptyComment))
|
block.Advance(len(emptyComment1))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
if bytes.HasPrefix(line, invalidComment1) || bytes.HasPrefix(line, invalidComment2) {
|
if bytes.HasPrefix(line, emptyComment2) {
|
||||||
return nil
|
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment2)))
|
||||||
|
block.Advance(len(emptyComment2))
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
offset := len(openComment)
|
offset := len(openComment)
|
||||||
line = line[offset:]
|
line = line[offset:]
|
||||||
for {
|
for {
|
||||||
hindex := bytes.Index(line, doubleHyphen)
|
index := bytes.Index(line, closeComment)
|
||||||
if hindex > -1 {
|
if index > -1 {
|
||||||
hindex += offset
|
node.Segments.Append(segment.WithStop(segment.Start + offset + index + len(closeComment)))
|
||||||
}
|
block.Advance(offset + index + len(closeComment))
|
||||||
index := bytes.Index(line, closeComment) + offset
|
return node
|
||||||
if index > -1 && hindex == index {
|
|
||||||
if index == 0 || len(line) < 2 || line[index-offset-1] != '-' {
|
|
||||||
node.Segments.Append(segment.WithStop(segment.Start + index + len(closeComment)))
|
|
||||||
block.Advance(index + len(closeComment))
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hindex > 0 {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
offset = 0
|
||||||
node.Segments.Append(segment)
|
node.Segments.Append(segment)
|
||||||
block.AdvanceLine()
|
block.AdvanceLine()
|
||||||
line, segment = block.PeekLine()
|
line, segment = block.PeekLine()
|
||||||
offset = 0
|
|
||||||
if line == nil {
|
if line == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -808,7 +808,7 @@ func IsPunct(c byte) bool {
|
||||||
|
|
||||||
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
||||||
func IsPunctRune(r rune) bool {
|
func IsPunctRune(r rune) bool {
|
||||||
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
|
return unicode.IsSymbol(r) || unicode.IsPunct(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSpace returns true if the given character is a space, otherwise false.
|
// IsSpace returns true if the given character is a space, otherwise false.
|
||||||
|
|
|
@ -39,6 +39,7 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
|
||||||
alpha []byte
|
alpha []byte
|
||||||
alphaStride int
|
alphaStride int
|
||||||
wantAlpha bool
|
wantAlpha bool
|
||||||
|
seenVP8X bool
|
||||||
widthMinusOne uint32
|
widthMinusOne uint32
|
||||||
heightMinusOne uint32
|
heightMinusOne uint32
|
||||||
buf [10]byte
|
buf [10]byte
|
||||||
|
@ -113,6 +114,10 @@ func decode(r io.Reader, configOnly bool) (image.Image, image.Config, error) {
|
||||||
return m, image.Config{}, err
|
return m, image.Config{}, err
|
||||||
|
|
||||||
case fccVP8X:
|
case fccVP8X:
|
||||||
|
if seenVP8X {
|
||||||
|
return nil, image.Config{}, errInvalidFormat
|
||||||
|
}
|
||||||
|
seenVP8X = true
|
||||||
if chunkLen != 10 {
|
if chunkLen != 10 {
|
||||||
return nil, image.Config{}, errInvalidFormat
|
return nil, image.Config{}, errInvalidFormat
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ github.com/valyala/bytebufferpool
|
||||||
# github.com/valyala/quicktemplate v1.7.0
|
# github.com/valyala/quicktemplate v1.7.0
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/valyala/quicktemplate
|
github.com/valyala/quicktemplate
|
||||||
# github.com/yuin/goldmark v1.6.0
|
# github.com/yuin/goldmark v1.7.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.19
|
||||||
github.com/yuin/goldmark
|
github.com/yuin/goldmark
|
||||||
github.com/yuin/goldmark/ast
|
github.com/yuin/goldmark/ast
|
||||||
github.com/yuin/goldmark/extension
|
github.com/yuin/goldmark/extension
|
||||||
|
@ -37,7 +37,7 @@ github.com/yuin/goldmark/util
|
||||||
github.com/yuin/goldmark-emoji
|
github.com/yuin/goldmark-emoji
|
||||||
github.com/yuin/goldmark-emoji/ast
|
github.com/yuin/goldmark-emoji/ast
|
||||||
github.com/yuin/goldmark-emoji/definition
|
github.com/yuin/goldmark-emoji/definition
|
||||||
# golang.org/x/image v0.14.0
|
# golang.org/x/image v0.15.0
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
golang.org/x/image/bmp
|
golang.org/x/image/bmp
|
||||||
golang.org/x/image/riff
|
golang.org/x/image/riff
|
||||||
|
|
Loading…
Reference in New Issue