commit 7d22424cf13320569a4d2773557d198187c09a7a Author: Antonio De Lucreziis Date: Sat Dec 24 03:11:58 2022 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0749f3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +*.local* +bin/ +dist/ diff --git a/cmd/cabret/main.go b/cmd/cabret/main.go new file mode 100644 index 0000000..81461fe --- /dev/null +++ b/cmd/cabret/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + + "github.com/alecthomas/repr" + "github.com/aziis98/cabret/config" +) + +func main() { + log.Printf("Rendering current project") + + site, err := config.ParseCabretfile("./Cabretfile.yaml") + if err != nil { + log.Fatal(err) + } + + repr.Println(site) +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..6756fa1 --- /dev/null +++ b/config/config.go @@ -0,0 +1,43 @@ +package config + +import ( + "os" + + "gopkg.in/yaml.v3" +) + +// Operation is an enum of various operations +type Operation struct { + Layout string `yaml:"layout,omitempty"` + Target string `yaml:"target,omitempty"` + Plugin string `yaml:"plugin,omitempty"` +} + +type EntryPoint struct { + Source string `yaml:"source"` + Pipeline []Operation `yaml:"pipeline"` +} + +type Config struct { + Output string `yaml:"output,omitempty"` +} + +// Site has some configuration for the +type Site struct { + Config Config `yaml:"config,omitempty"` + EntryPoints []EntryPoint `yaml:"entry-points"` +} + +func ParseCabretfile(file string) (*Site, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + + site := new(Site) + if err := yaml.NewDecoder(f).Decode(&site); err != nil { + return nil, err + } + + return site, nil +} diff --git a/examples/basic/Cabretfile.yaml b/examples/basic/Cabretfile.yaml new file mode 100644 index 0000000..ec0ba66 --- /dev/null +++ b/examples/basic/Cabretfile.yaml @@ -0,0 +1,12 @@ +site: + entry-points: + - source: index.html + pipeline: + - layout: layouts/base + - target: dist/index.html + - source: 'posts/{post-name}.md' + pipeline: + - plugin: markdown + - layout: layouts/base + - layout: layouts/post.html + - target: 'dist/posts/{post-name}/index.html' diff --git a/examples/basic/index.html b/examples/basic/index.html new file mode 100644 index 0000000..e69de29 diff --git a/examples/basic/layouts/base.html b/examples/basic/layouts/base.html new file mode 100644 index 0000000..5b17b73 --- /dev/null +++ b/examples/basic/layouts/base.html @@ -0,0 +1,10 @@ + + + + + + + {{ .Metadata.Title }} + + + diff --git a/examples/basic/posts/post-1.md b/examples/basic/posts/post-1.md new file mode 100644 index 0000000..202f981 --- /dev/null +++ b/examples/basic/posts/post-1.md @@ -0,0 +1,11 @@ +--- +title: First Post +description: Lorem ipsum dolor sit amet consectetur adipisicing elit. +publish_date: 2022-12-22 11:00 +--- + +# First Post + +Lorem ipsum dolor sit amet consectetur, adipisicing elit. Beatae ratione eos saepe veritatis, temporibus nemo vero rerum earum voluptatibus minus voluptatem neque consequatur necessitatibus, error magnam deserunt! Ad, accusantium excepturi? + +Lorem ipsum dolor sit amet consectetur, adipisicing elit. Beatae ratione eos saepe veritatis, temporibus nemo vero rerum earum voluptatibus minus voluptatem neque consequatur necessitatibus, error magnam deserunt! Ad, accusantium excepturi? \ No newline at end of file diff --git a/examples/basic/posts/post-2.md b/examples/basic/posts/post-2.md new file mode 100644 index 0000000..935f82a --- /dev/null +++ b/examples/basic/posts/post-2.md @@ -0,0 +1,11 @@ +--- +title: Second Post +description: Lorem ipsum dolor sit amet consectetur adipisicing elit. +publish_date: 2022-12-22 11:00 +--- + +# Second Post + +Lorem ipsum dolor sit amet consectetur, adipisicing elit. Beatae ratione eos saepe veritatis, temporibus nemo vero rerum earum voluptatibus minus voluptatem neque consequatur necessitatibus, error magnam deserunt! Ad, accusantium excepturi? + +Lorem ipsum dolor sit amet consectetur, adipisicing elit. Beatae ratione eos saepe veritatis, temporibus nemo vero rerum earum voluptatibus minus voluptatem neque consequatur necessitatibus, error magnam deserunt! Ad, accusantium excepturi? \ No newline at end of file diff --git a/examples/basic/posts/post-3.md b/examples/basic/posts/post-3.md new file mode 100644 index 0000000..4e29ce8 --- /dev/null +++ b/examples/basic/posts/post-3.md @@ -0,0 +1,11 @@ +--- +title: Third Post +description: Lorem ipsum dolor sit amet consectetur adipisicing elit. +publish_date: 2022-12-22 11:00 +--- + +# Third Post + +Lorem ipsum dolor sit amet consectetur, adipisicing elit. Beatae ratione eos saepe veritatis, temporibus nemo vero rerum earum voluptatibus minus voluptatem neque consequatur necessitatibus, error magnam deserunt! Ad, accusantium excepturi? + +Lorem ipsum dolor sit amet consectetur, adipisicing elit. Beatae ratione eos saepe veritatis, temporibus nemo vero rerum earum voluptatibus minus voluptatem neque consequatur necessitatibus, error magnam deserunt! Ad, accusantium excepturi? \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6977c63 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/aziis98/cabret + +go 1.19 + +require ( + github.com/alecthomas/participle/v2 v2.0.0-beta.5 // indirect + github.com/alecthomas/repr v0.1.1 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/yuin/goldmark v1.5.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools v2.2.0+incompatible // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bec3c0f --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo= +github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM= +github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs= +github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M= +github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/path/path.go b/path/path.go new file mode 100644 index 0000000..37db14d --- /dev/null +++ b/path/path.go @@ -0,0 +1,111 @@ +package path + +import ( + "fmt" + "regexp" + "strings" + + "github.com/alecthomas/participle/v2" + "github.com/alecthomas/participle/v2/lexer" +) + +type PathPart interface { + regex() string +} + +type PathSlash struct { + Value string `"/"` +} + +func (p PathSlash) regex() string { + return "/" +} + +type PathShortPattern struct { + Name string `"{" @Ident "}"` +} + +func (p PathShortPattern) regex() string { + return fmt.Sprintf("(?P<%s>[^/]+?)", p.Name) +} + +type PathLongPattern struct { + Name string `"{{" @Ident "}}"` +} + +func (p PathLongPattern) regex() string { + return fmt.Sprintf("(?P<%s>.+?)", p.Name) +} + +type PathString struct { + Value string `@Ident` +} + +func (p PathString) regex() string { + return regexp.QuoteMeta(p.Value) +} + +type PathPattern struct { + Parts []PathPart `@@+` +} + +func (pp PathPattern) regex() string { + sb := &strings.Builder{} + + for _, p := range pp.Parts { + sb.WriteString(p.regex()) + } + + return sb.String() +} + +func (pp PathPattern) Match(s string) (bool, map[string]string, error) { + r, err := regexp.Compile("^" + pp.regex() + "$") + if err != nil { + return false, nil, err + } + + ms := r.FindStringSubmatch(s) + if ms == nil { + return false, nil, nil + } + + ctx := map[string]string{} + for i, name := range r.SubexpNames() { + ctx[name] = ms[i] + } + + return true, ctx, nil +} + +var parser = participle.MustBuild[PathPattern]( + participle.Union[PathPart]( + &PathSlash{}, + &PathLongPattern{}, + &PathShortPattern{}, + &PathString{}, + ), + participle.Lexer( + lexer.MustSimple([]lexer.SimpleRule{ + {Name: "Slash", Pattern: `/`}, + {Name: "PatternLongOpen", Pattern: `{{`}, + {Name: "PatternLongClose", Pattern: `}}`}, + {Name: "PatternShortOpen", Pattern: `{`}, + {Name: "PatternShortClose", Pattern: `}`}, + {Name: "Ident", Pattern: `[^/{}]+`}, + }), + ), +) + +func ParsePattern(path string) (*PathPattern, error) { + return parser.ParseString("", path) +} + +func MustParsePattern(path string) *PathPattern { + p, err := ParsePattern(path) + if err != nil { + panic(err) + } + + return p +} diff --git a/path/path_test.go b/path/path_test.go new file mode 100644 index 0000000..dbda3e1 --- /dev/null +++ b/path/path_test.go @@ -0,0 +1,73 @@ +package path_test + +import ( + "testing" + + "github.com/aziis98/cabret/path" + "gotest.tools/assert" +) + +func Test1(t *testing.T) { + p, err := path.ParsePattern("/foo/bar") + + assert.NilError(t, err) + assert.DeepEqual(t, p, &path.PathPattern{ + Parts: []path.PathPart{ + &path.PathSlash{}, &path.PathString{"foo"}, &path.PathSlash{}, &path.PathString{"bar"}, + }, + }) +} + +func Test2(t *testing.T) { + p, err := path.ParsePattern("posts/{name}.md") + + assert.NilError(t, err) + assert.DeepEqual(t, p, &path.PathPattern{ + Parts: []path.PathPart{ + &path.PathString{"posts"}, &path.PathSlash{}, &path.PathShortPattern{"name"}, &path.PathString{".md"}, + }, + }) +} + +func Test3(t *testing.T) { + p, err := path.ParsePattern("posts/{{path}}/{name}.md") + + assert.NilError(t, err) + assert.DeepEqual(t, p, &path.PathPattern{ + Parts: []path.PathPart{ + &path.PathString{"posts"}, + &path.PathSlash{}, + &path.PathLongPattern{"path"}, + &path.PathSlash{}, + &path.PathShortPattern{"name"}, + &path.PathString{".md"}, + }, + }) +} + +func Test4(t *testing.T) { + p, err := path.ParsePattern("foo{{path}}/{name}.md") + + assert.NilError(t, err) + assert.DeepEqual(t, p, &path.PathPattern{ + Parts: []path.PathPart{ + &path.PathString{"foo"}, + &path.PathLongPattern{"path"}, + &path.PathSlash{}, + &path.PathShortPattern{"name"}, + &path.PathString{".md"}, + }, + }) + + t.Run("match", func(t *testing.T) { + m, ctx, err := p.Match("foo-1/bar/baz/post-1.md") + + assert.NilError(t, err, nil) + assert.Equal(t, m, true) + assert.DeepEqual(t, ctx, map[string]string{ + "": "foo-1/bar/baz/post-1.md", + "name": "post-1", + "path": "-1/bar/baz", + }) + }) +}