diff --git a/README.md b/README.md index a413bf1..3ac3277 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,6 @@ gojekyll help build - themes, page tags, excerpts, plugins (except for a few listed below), pagination, math, warning mode. - Site variables: `pages`, `static_files`, `html_pages`, `html_files`, `documents`, and `tags` - Jekyll filters: `group_by_exp`, `pop`, `shift`, `cgi_escape`, `uri_escape`, `scssify`, and `smartify`. - - Jekyll's `include_relative` tag - The Go Liquid engine is also missing some tags and a few filters. See its [README](https://github.com/osteele/gojekyll/#status) for status. - Data files must be YAML. CSV and JSON data files are not supported. - `{% highlight %}` uses Pygments. There's no way to tell it to use Rouge. Also, I don't know what will happen if Pygments isn't installed. @@ -104,12 +103,11 @@ The cache is for calls to Pygments (via the `highlight` tag). For another site, - [ ] Jekyll filters - [ ] `group_by_exp` `pop` `shift` `cgi_escape` `uri_escape` `scssify` `smartify` - [x] everything else - - [ ] Jekyll tags + - [x] Jekyll tags - [x] `include` - - [ ] `include_relative` + - [x] `include_relative` - [x] `link` - [x] `post_url` - - [ ] `gist` - [x] `highlight` - [x] Includes - [x] `include` parameters @@ -130,7 +128,7 @@ The cache is for calls to Pygments (via the `highlight` tag). For another site, - [x] `build` - [x] `--source`, `--destination`, `--drafts`, `--future`, `--unpublished` - [ ] `--config`, `--baseurl`, `--lsi`, `--watch`, etc. - - [ ] won't implement: `--force-polling`, `--limit-posts`, `--incremental`, `JEKYLL_ENV=production` + - [ ] not planned: `--force-polling`, `--limit-posts`, `--incremental`, `JEKYLL_ENV=production` - [x] `clean` - [ ] `doctor` - [x] `help` @@ -140,7 +138,7 @@ The cache is for calls to Pygments (via the `highlight` tag). For another site, - [x] `serve` - [x] `--open-uri` - [ ] `--detach`, `--host`, `--port`, `--baseurl` - - [ ] won't implement: `--incremental`, `--ssl-*` + - [ ] not planned: `--incremental`, `--ssl`-* - [ ] Windows ## Contributing diff --git a/filters/filters.go b/filters/filters.go index 11b84ac..59e0293 100644 --- a/filters/filters.go +++ b/filters/filters.go @@ -13,7 +13,7 @@ import ( "github.com/osteele/gojekyll/config" "github.com/osteele/liquid" - "github.com/osteele/liquid/expressions" + "github.com/osteele/liquid/expression" "github.com/osteele/liquid/generics" "github.com/russross/blackfriday" ) @@ -214,7 +214,7 @@ func sortFilter(array []interface{}, key interface{}, nilFirst interface{}) []in return out } -func whereExpFilter(array []interface{}, name string, expr expressions.Closure) ([]interface{}, error) { +func whereExpFilter(array []interface{}, name string, expr expression.Closure) ([]interface{}, error) { rt := reflect.ValueOf(array) if rt.Kind() != reflect.Array && rt.Kind() != reflect.Slice { return nil, nil diff --git a/pages/page.go b/pages/page.go index 4e458f4..25d2d50 100644 --- a/pages/page.go +++ b/pages/page.go @@ -78,8 +78,8 @@ func (p *page) Write(rc RenderingContext, w io.Writer) error { if err != nil { return err } - layout := templates.VariableMap(p.frontMatter).String("layout", "") - if layout != "" { + layout, ok := p.frontMatter["layout"].(string) + if ok && layout != "" { b, err = rp.ApplyLayout(layout, b, p.TemplateContext(rc)) if err != nil { return err diff --git a/pipelines/layouts.go b/pipelines/layouts.go index 0ad48b9..e8fad87 100644 --- a/pipelines/layouts.go +++ b/pipelines/layouts.go @@ -41,7 +41,12 @@ func (p *Pipeline) FindLayout(base string, fm *map[string]interface{}) (t liquid if err != nil { return } - return p.liquidEngine.ParseTemplate(content) + t, err = p.liquidEngine.ParseTemplate(content) + if err != nil { + return nil, err + } + t.SetSourcePath(filename) + return } // LayoutsDir returns the path to the layouts directory. diff --git a/pipelines/pipeline.go b/pipelines/pipeline.go index dd3646c..ee36aff 100644 --- a/pipelines/pipeline.go +++ b/pipelines/pipeline.go @@ -73,8 +73,13 @@ func (p *Pipeline) Render(w io.Writer, b []byte, filename string, e map[string]i return b, nil } -func (p *Pipeline) renderTemplate(tpl []byte, b map[string]interface{}, filename string) ([]byte, error) { - out, err := p.liquidEngine.ParseAndRender(tpl, b) +func (p *Pipeline) renderTemplate(src []byte, b map[string]interface{}, filename string) ([]byte, error) { + tpl, err := p.liquidEngine.ParseTemplate(src) + if err != nil { + return nil, helpers.PathError(err, "Liquid Error", filename) + } + tpl.SetSourcePath(filename) + out, err := tpl.Render(b) if err != nil { return nil, helpers.PathError(err, "Liquid Error", filename) } diff --git a/plugins/avatar.go b/plugins/avatar.go index 5c4355c..ee29c65 100644 --- a/plugins/avatar.go +++ b/plugins/avatar.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/osteele/gojekyll/tags" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) func init() { @@ -17,7 +17,7 @@ func init() { const avatarTemplate = `{user}` -func avatarTag(ctx chunks.RenderContext) (string, error) { +func avatarTag(ctx render.Context) (string, error) { var ( user string size interface{} = 40 diff --git a/plugins/gist.go b/plugins/gist.go index fee9e56..0b6a944 100644 --- a/plugins/gist.go +++ b/plugins/gist.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/osteele/gojekyll/tags" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) func init() { @@ -14,7 +14,7 @@ func init() { }) } -func gistTag(ctx chunks.RenderContext) (string, error) { +func gistTag(ctx render.Context) (string, error) { argsline, err := ctx.ParseTagArgs() if err != nil { return "", err diff --git a/plugins/plugins.go b/plugins/plugins.go index 0f08d1b..6fe9008 100644 --- a/plugins/plugins.go +++ b/plugins/plugins.go @@ -9,7 +9,7 @@ import ( "fmt" "github.com/osteele/liquid" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) // PluginContext is the context for plugin initialization. @@ -22,7 +22,7 @@ type PluginContext interface { func Install(name string, ctx PluginContext) bool { p, found := plugins[name] if p != nil { - if err := p(ctx, pluginHelper{name, ctx}); err != nil { + if err := p(ctx, pluginHelper{ctx, name}); err != nil { panic(err) } } @@ -56,8 +56,8 @@ func init() { } type pluginHelper struct { + PluginContext name string - ctx PluginContext } func (h pluginHelper) stubbed() { @@ -65,12 +65,12 @@ func (h pluginHelper) stubbed() { } func (h pluginHelper) tag(name string, r liquid.Renderer) { - h.ctx.TemplateEngine().RegisterTag(name, r) + h.TemplateEngine().RegisterTag(name, r) } func (h pluginHelper) makeUnimplementedTag() liquid.Renderer { warned := false - return func(ctx chunks.RenderContext) (string, error) { + return func(ctx render.Context) (string, error) { if !warned { fmt.Printf("The %q tag in the %q plugin has not been implemented.\n", ctx.TagName(), h.name) warned = true diff --git a/site/build.go b/site/build.go index 975bd67..917450b 100644 --- a/site/build.go +++ b/site/build.go @@ -46,13 +46,11 @@ func (s *Site) Clean(options BuildOptions) error { // Build cleans the destination and create files in it. // It attends to the global options.dry_run. func (s *Site) Build(options BuildOptions) (int, error) { - count := 0 if err := s.prepareRendering(); err != nil { return 0, err } if err := s.Clean(options); err != nil { return 0, err } - n, err := s.WritePages(options) - return count + n, err + return s.WritePages(options) } diff --git a/site/write.go b/site/write.go index cf19910..b45e8bd 100644 --- a/site/write.go +++ b/site/write.go @@ -33,11 +33,8 @@ func (s *Site) WriteDocument(p pages.Document, w io.Writer) error { // WritePages writes output files. // It attends to options.dry_run. -func (s *Site) WritePages(options BuildOptions) (int, error) { - count := 0 - var err error +func (s *Site) WritePages(options BuildOptions) (count int, err error) { errs := make(chan error) - for _, p := range s.OutputPages() { count++ go func(p pages.Document) { @@ -46,6 +43,7 @@ func (s *Site) WritePages(options BuildOptions) (int, error) { } for i := 0; i < count; i++ { // might as well report the last error as the first + // TODO return an aggregate if e := <-errs; e != nil { err = e } diff --git a/tags/highlight.go b/tags/highlight.go index e57adee..1a89e75 100644 --- a/tags/highlight.go +++ b/tags/highlight.go @@ -7,10 +7,10 @@ import ( "os/exec" "strings" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) -func highlightTag(ctx chunks.RenderContext) (string, error) { +func highlightTag(ctx render.Context) (string, error) { args, err := ctx.ParseTagArgs() if err != nil { return "", err diff --git a/tags/include.go b/tags/include.go index 232654b..93008f9 100644 --- a/tags/include.go +++ b/tags/include.go @@ -2,12 +2,23 @@ package tags import ( "fmt" + "path" "path/filepath" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) -func (tc tagContext) includeTag(ctx chunks.RenderContext) (string, error) { +func (tc tagContext) includeTag(ctx render.Context) (string, error) { + return includeFromDir(ctx, filepath.Join(tc.config.Source, tc.config.IncludesDir)) +} + +func (tc tagContext) includeRelativeTag(ctx render.Context) (string, error) { + fmt.Println("include relative", ctx.SourceFile()) + // TODO Note that you cannot use the ../ syntax + return includeFromDir(ctx, path.Dir(ctx.SourceFile())) +} + +func includeFromDir(ctx render.Context, dirname string) (string, error) { argsline, err := ctx.ParseTagArgs() if err != nil { return "", err @@ -23,8 +34,6 @@ func (tc tagContext) includeTag(ctx chunks.RenderContext) (string, error) { if err != nil { return "", err } - filename := filepath.Join(tc.config.Source, tc.config.IncludesDir, args.Args[0]) - ctx2 := ctx.Clone() - ctx2.UpdateBindings(map[string]interface{}{"include": include}) - return ctx2.RenderFile(filename) + filename := filepath.Join(dirname, args.Args[0]) + return ctx.RenderFile(filename, map[string]interface{}{"include": include}) } diff --git a/tags/include_test.go b/tags/include_test.go new file mode 100644 index 0000000..d36ac16 --- /dev/null +++ b/tags/include_test.go @@ -0,0 +1,46 @@ +package tags + +import ( + "fmt" + "strings" + "testing" + + "github.com/osteele/gojekyll/config" + "github.com/osteele/liquid" + "github.com/stretchr/testify/require" +) + +func TestIncludeTag(t *testing.T) { + engine := liquid.NewEngine() + cfg := config.Default() + cfg.Source = "testdata" + AddJekyllTags(engine, cfg, func(s string) (string, bool) { + fmt.Println("ok") + if s == "_posts/2017-07-04-test.md" { + return "post.html", true + } + return "", false + }) + bindings := map[string]interface{}{} + + s, err := engine.ParseAndRenderString(`{% include include_target.html %}`, bindings) + require.NoError(t, err) + require.Equal(t, "include target", strings.TrimSpace(s)) + + // TODO {% include {{ page.my_variable }} %} + // TODO {% include note.html content="This is my sample note." %} +} + +func TestIncludeRelativeTag(t *testing.T) { + engine := liquid.NewEngine() + cfg := config.Default() + AddJekyllTags(engine, cfg, func(s string) (string, bool) { return "", false }) + bindings := map[string]interface{}{} + + tpl, err := engine.ParseTemplate([]byte(`{% include_relative subdir/include_relative.html %}`)) + require.NoError(t, err) + tpl.SetSourcePath("testdata/dir/include_relative_source.md") + s, err := tpl.Render(bindings) + require.NoError(t, err) + require.Equal(t, "include_relative target", strings.TrimSpace(string(s))) +} diff --git a/tags/parseargs.go b/tags/parseargs.go index 67dafd7..c8fab21 100644 --- a/tags/parseargs.go +++ b/tags/parseargs.go @@ -4,7 +4,7 @@ import ( "fmt" "regexp" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) // TODO string escapes @@ -52,7 +52,7 @@ func ParseArgs(argsline string) (*ParsedArgs, error) { } // EvalOptions evaluates unquoted options. -func (r *ParsedArgs) EvalOptions(ctx chunks.RenderContext) (map[string]interface{}, error) { +func (r *ParsedArgs) EvalOptions(ctx render.Context) (map[string]interface{}, error) { options := map[string]interface{}{} for k, v := range r.Options { if v.quoted { diff --git a/tags/tags.go b/tags/tags.go index 049525a..98d61c3 100644 --- a/tags/tags.go +++ b/tags/tags.go @@ -6,7 +6,7 @@ import ( "github.com/osteele/gojekyll/config" "github.com/osteele/liquid" - "github.com/osteele/liquid/chunks" + "github.com/osteele/liquid/render" ) // A LinkTagHandler given an include tag file name returns a URL. @@ -17,6 +17,7 @@ func AddJekyllTags(e liquid.Engine, c config.Config, lh LinkTagHandler) { tc := tagContext{c, lh} e.RegisterBlock("highlight", highlightTag) e.RegisterTag("include", tc.includeTag) + e.RegisterTag("include_relative", tc.includeRelativeTag) e.RegisterTag("link", tc.linkTag) e.RegisterTag("post_url", tc.postURLTag) } @@ -31,7 +32,7 @@ type tagContext struct { // time it's rendered, and otherwise does nothing. func CreateUnimplementedTag() liquid.Renderer { warned := false - return func(ctx chunks.RenderContext) (string, error) { + return func(ctx render.Context) (string, error) { if !warned { fmt.Printf("The %q tag has not been implemented. It is being ignored.\n", ctx.TagName()) warned = true @@ -40,7 +41,7 @@ func CreateUnimplementedTag() liquid.Renderer { } } -func (tc tagContext) linkTag(ctx chunks.RenderContext) (string, error) { +func (tc tagContext) linkTag(ctx render.Context) (string, error) { filename := ctx.TagArgs() url, found := tc.lh(filename) if !found { @@ -49,7 +50,7 @@ func (tc tagContext) linkTag(ctx chunks.RenderContext) (string, error) { return url, nil } -func (tc tagContext) postURLTag(ctx chunks.RenderContext) (string, error) { +func (tc tagContext) postURLTag(ctx render.Context) (string, error) { var ( filename = ctx.TagArgs() found = false diff --git a/tags/testdata/_includes/include_target.html b/tags/testdata/_includes/include_target.html new file mode 100644 index 0000000..4602ff7 --- /dev/null +++ b/tags/testdata/_includes/include_target.html @@ -0,0 +1 @@ +include target \ No newline at end of file diff --git a/tags/testdata/dir/subdir/include_relative.html b/tags/testdata/dir/subdir/include_relative.html new file mode 100644 index 0000000..aa44b30 --- /dev/null +++ b/tags/testdata/dir/subdir/include_relative.html @@ -0,0 +1 @@ +include_relative target \ No newline at end of file