Skip to content

Commit

Permalink
feat(cli): support attributes set/reset in CLI (#495)
Browse files Browse the repository at this point in the history
API break: using a new 'configuration.Configuration' parameter to wrap
all the settings at the `libasciidoc.ConvertToHTML`
level

Fixes #484

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon authored Mar 7, 2020
1 parent 942c50b commit 79c454c
Show file tree
Hide file tree
Showing 44 changed files with 820 additions and 467 deletions.
14 changes: 7 additions & 7 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,21 @@ Libasciidoc provides 2 functions to convert an Asciidoc content into HTML:

1. Converting an `io.Reader` into an HTML document:

func ConvertToHTML(ctx context.Context, source io.Reader, output io.Writer, options renderer.Option...) (map[string]interface{}, error)
ConvertToHTML(r io.Reader, output io.Writer, config configuration.Configuration) (types.Metadata, error)

2. Converting a file (given its name) into an HTML document:

func ConvertFileToHTML(ctx context.Context, filename string, output io.Writer, options renderer.Option...) (map[string]interface{}, error)
ConvertFileToHTML(output io.Writer, config configuration.Configuration) (types.Metadata, error)

where the returned `map[string]interface{}` object contains the document's title which is not part of the generated HTML `<body>` part, as well as the other attributes of the source document.
where the returned `types.Metadata` object contains the document's title which is not part of the generated HTML `<body>` part, as well as the other attributes of the source document.

For now, the sole option to pass as a last argument is `renderer.IncludeHeaderFooter` to include the `<header>` and `<footer>` elements in the generated HTML document or not. Default is `false`, which means that only the `<body>` part of the HTML document is generated.
All options/settings are passed via the `config` parameter.

=== Macro definition

The user can define a macro by calling `renderer.DefineMacro()` and passing return value to conversion functions.
The user can define a macro by calling `renderer.WithMacroTemplate()` and passing return value to conversion functions.

`renderer.DefineMacro()` defines a macro by the given name and associates the given template. The template is an implementation of `renderer.MacroTemplate` interface (ex. `text.Template`)
`renderer.WithMacroTemplate()` defines a macro by the given name and associates the given template. The template is an implementation of `renderer.MacroTemplate` interface (ex. `text.Template`)

Libasciidoc calls `Execute()` method and passes `types.UserMacro` object to template when rendering.

Expand All @@ -85,7 +85,7 @@ var tmpl = template.Must(t.Parse(tmplStr))

output := &strings.Builder{}
content := strings.NewReader(`example::hello world[suffix=!!!!!]`)
libasciidoc.ConvertToHTML(context.Background(), content, output, renderer.DefineMacro(tmpl.Name(), tmpl))
libasciidoc.ConvertToHTML(context.Background(), content, output, renderer.WithMacroTemplate(tmpl.Name(), tmpl))
```

== How to contribute
Expand Down
40 changes: 31 additions & 9 deletions cmd/libasciidoc/root_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"path/filepath"

"github.com/bytesparadise/libasciidoc"
"github.com/bytesparadise/libasciidoc/pkg/configuration"
logsupport "github.com/bytesparadise/libasciidoc/pkg/log"
"github.com/bytesparadise/libasciidoc/pkg/renderer"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -23,6 +23,7 @@ func NewRootCmd() *cobra.Command {
var outputName string
var logLevel string
var css string
var attributes []string

rootCmd := &cobra.Command{
Use: "libasciidoc [flags] FILE",
Expand All @@ -43,13 +44,19 @@ func NewRootCmd() *cobra.Command {
if len(args) == 0 {
return helpCommand.RunE(cmd, args)
}
for _, source := range args {
out, close := getOut(cmd, source, outputName)
attrs := parseAttributes(attributes)
for _, sourcePath := range args {
out, close := getOut(cmd, sourcePath, outputName)
if out != nil {
defer close()
path, _ := filepath.Abs(source)
path, _ := filepath.Abs(sourcePath)
log.Debugf("Starting to process file %v", path)
_, err := libasciidoc.ConvertFileToHTML(source, out, renderer.IncludeHeaderFooter(!noHeaderFooter), renderer.IncludeCSS(css))
config := configuration.NewConfiguration(
configuration.WithFilename(sourcePath),
configuration.WithAttributes(attrs),
configuration.WithCSS(css),
configuration.WithHeaderFooter(!noHeaderFooter))
_, err := libasciidoc.ConvertFileToHTML(out, config)
if err != nil {
return err
}
Expand All @@ -63,6 +70,7 @@ func NewRootCmd() *cobra.Command {
flags.StringVarP(&outputName, "out-file", "o", "", "output file (default: based on path of input file); use - to output to STDOUT")
flags.StringVar(&logLevel, "log", "warning", "log level to set [debug|info|warning|error|fatal|panic]")
flags.StringVar(&css, "css", "", "the path to the CSS file to link to the document")
flags.StringArrayVarP(&attributes, "attribute", "a", []string{}, "a document attribute to set in the form of name, name!, or name=value pair")
return rootCmd
}

Expand All @@ -78,7 +86,7 @@ func newCloseFileFunc(c io.Closer) closeFunc {
}
}

func getOut(cmd *cobra.Command, source, outputName string) (io.Writer, closeFunc) {
func getOut(cmd *cobra.Command, sourcePath, outputName string) (io.Writer, closeFunc) {
if outputName == "-" {
// outfile is STDOUT
return cmd.OutOrStdout(), defaultCloseFunc()
Expand All @@ -89,9 +97,9 @@ func getOut(cmd *cobra.Command, source, outputName string) (io.Writer, closeFunc
log.Warnf("Cannot create output file - %v, skipping", outputName)
}
return outfile, newCloseFileFunc(outfile)
} else if source != "" {
// outfile is based on source
path, _ := filepath.Abs(source)
} else if sourcePath != "" {
// outfile is based on sourcePath
path, _ := filepath.Abs(sourcePath)
outname := strings.TrimSuffix(path, filepath.Ext(path)) + ".html"
outfile, err := os.Create(outname)
if err != nil {
Expand All @@ -102,3 +110,17 @@ func getOut(cmd *cobra.Command, source, outputName string) (io.Writer, closeFunc
}
return cmd.OutOrStdout(), defaultCloseFunc()
}

// converts the `name`, `!name` and `name=value` into a map
func parseAttributes(attributes []string) map[string]string {
result := make(map[string]string, len(attributes))
for _, attr := range attributes {
data := strings.Split(attr, "=")
if len(data) > 1 {
result[data[0]] = data[1]
} else {
result[data[0]] = ""
}
}
return result
}
34 changes: 33 additions & 1 deletion cmd/libasciidoc/root_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
)

var _ = Describe("root cmd", func() {
RegisterFailHandler(Fail)

It("render with STDOUT output", func() {
// given
Expand Down Expand Up @@ -68,6 +67,38 @@ var _ = Describe("root cmd", func() {
Expect(buf.String()).ToNot(ContainSubstring(`<div id="footer">`))
})

It("render with attribute set", func() {
// given
root := main.NewRootCmd()
buf := new(bytes.Buffer)
root.SetOutput(buf)
root.SetArgs([]string{"-s", "-o", "-", "-afoo1=bar1", "-afoo2=bar2", "test/doc_with_attributes.adoc"})
// when
err := root.Execute()
// then
Expect(err).ToNot(HaveOccurred())
Expect(buf.String()).ToNot(BeEmpty())
Expect(buf.String()).To(Equal(`<div class="paragraph">
<p>bar1 and bar2</p>
</div>`))
})

It("render with attribute reset", func() {
// given
root := main.NewRootCmd()
buf := new(bytes.Buffer)
root.SetOutput(buf)
root.SetArgs([]string{"-s", "-o", "-", "-afoo1=bar1", "-a!foo2", "test/doc_with_attributes.adoc"})
// when
err := root.Execute()
// then
Expect(err).ToNot(HaveOccurred())
Expect(buf.String()).ToNot(BeEmpty())
Expect(buf.String()).To(Equal(`<div class="paragraph">
<p>bar1 and {foo2}</p>
</div>`))
})

It("render multiple files", func() {
// given
root := main.NewRootCmd()
Expand Down Expand Up @@ -103,4 +134,5 @@ var _ = Describe("root cmd", func() {
// then
Expect(err).ToNot(HaveOccurred())
})

})
5 changes: 5 additions & 0 deletions cmd/libasciidoc/test/doc_with_attributes.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:foo1: FOO1

:!foo1:

{foo1} and {foo2}
10 changes: 4 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ=
github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
Expand All @@ -24,6 +27,7 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mna/pigeon v1.0.1-0.20190909211542-7ee56e19b15c h1:QRaadf9Fu8xAfNDS8PvaM0VmY2FnYHlddtnIExKj68k=
Expand All @@ -35,8 +39,6 @@ github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -61,8 +63,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83 h1:mgAKeshyNqWKdENOnQsg+8dRTwZFIwFaO3HNl52sweA=
golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
Expand Down Expand Up @@ -91,8 +91,6 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190830223141-573d9926052a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911225940-c7d52e45e2f2 h1:4m1a+ssoIi4N/776T3Dc79eRjDc5sEhMQXFAKoO3TM8=
golang.org/x/tools v0.0.0-20190911225940-c7d52e45e2f2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191220234730-f13409bbebaf h1:K7C8vSrr0PeD/cgNkkjpByDFJqzjr2YDmm3VPRjGfJM=
golang.org/x/tools v0.0.0-20191220234730-f13409bbebaf/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
Expand Down
23 changes: 12 additions & 11 deletions libasciidoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import (
"os"
"time"

"github.com/bytesparadise/libasciidoc/pkg/configuration"
"github.com/bytesparadise/libasciidoc/pkg/parser"
"github.com/bytesparadise/libasciidoc/pkg/renderer"
htmlrenderer "github.com/bytesparadise/libasciidoc/pkg/renderer/html5"
"github.com/bytesparadise/libasciidoc/pkg/types"

"github.com/pkg/errors"

log "github.com/sirupsen/logrus"
)

Expand All @@ -28,35 +29,35 @@ var (
// ConvertFileToHTML converts the content of the given filename into an HTML document.
// The conversion result is written in the given writer `output`, whereas the document metadata (title, etc.) (or an error if a problem occurred) is returned
// as the result of the function call.
func ConvertFileToHTML(filename string, output io.Writer, options ...renderer.Option) (types.Metadata, error) {
file, err := os.Open(filename)
func ConvertFileToHTML(output io.Writer, config configuration.Configuration) (types.Metadata, error) {
file, err := os.Open(config.Filename)
if err != nil {
return types.Metadata{}, errors.Wrapf(err, "error opening %s", filename)
return types.Metadata{}, errors.Wrapf(err, "error opening %s", config.Filename)
}
defer file.Close()
// use the file mtime as the `last updated` value
stat, err := os.Stat(filename)
stat, err := os.Stat(config.Filename)
if err != nil {
return types.Metadata{}, errors.Wrapf(err, "error opening %s", filename)
return types.Metadata{}, errors.Wrapf(err, "error opening %s", config.Filename)
}
options = append(options, renderer.LastUpdated(stat.ModTime()))
return ConvertToHTML(filename, file, output, options...)
config.LastUpdated = stat.ModTime()
return ConvertToHTML(file, output, config)
}

// ConvertToHTML converts the content of the given reader `r` into a full HTML document, written in the given writer `output`.
// Returns an error if a problem occurred
func ConvertToHTML(filename string, r io.Reader, output io.Writer, options ...renderer.Option) (types.Metadata, error) {
func ConvertToHTML(r io.Reader, output io.Writer, config configuration.Configuration) (types.Metadata, error) {
start := time.Now()
defer func() {
duration := time.Since(start)
log.Debugf("rendered the HTML output in %v", duration)
}()
log.Debugf("parsing the asciidoc source...")
doc, err := parser.ParseDocument(filename, r) //, parser.Debug(true))
doc, err := parser.ParseDocument(r, config) //, parser.Debug(true))
if err != nil {
return types.Metadata{}, err
}
rendererCtx := renderer.NewContext(doc, options...)
rendererCtx := renderer.NewContext(doc, config)
// insert tables of contents, preamble and process file inclusions
err = renderer.Prerender(rendererCtx)
if err != nil {
Expand Down
14 changes: 7 additions & 7 deletions libasciidoc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"os"
"time"

"github.com/bytesparadise/libasciidoc/pkg/renderer"
"github.com/bytesparadise/libasciidoc/pkg/configuration"
"github.com/bytesparadise/libasciidoc/pkg/types"
. "github.com/bytesparadise/libasciidoc/testsupport"

Expand Down Expand Up @@ -113,7 +113,7 @@ a paragraph`
Expect(RenderHTML5Title(source)).To(Equal(expectedTitle))
Expect(DocumentMetadata(source, lastUpdated)).To(Equal(types.Metadata{
Title: "a document title",
LastUpdated: lastUpdated.Format(renderer.LastUpdatedFormat),
LastUpdated: lastUpdated.Format(configuration.LastUpdatedFormat),
TableOfContents: types.TableOfContents{
Sections: []types.ToCSection{
{
Expand Down Expand Up @@ -175,7 +175,7 @@ a paragraph with _italic content_`
Expect(RenderHTML5Title(source)).To(Equal(expectedTitle))
Expect(DocumentMetadata(source, lastUpdated)).To(Equal(types.Metadata{
Title: "a document title",
LastUpdated: lastUpdated.Format(renderer.LastUpdatedFormat),
LastUpdated: lastUpdated.Format(configuration.LastUpdatedFormat),
TableOfContents: types.TableOfContents{
Sections: []types.ToCSection{
{
Expand Down Expand Up @@ -215,10 +215,10 @@ a paragraph with _italic content_`
</div>
</div>
</div>`
Expect(RenderHTML5Body(source, WithFilename("test.adoc"), renderer.LastUpdated(lastUpdated))).To(Equal(expected))
Expect(RenderHTML5Body(source, configuration.WithFilename("test.adoc"), configuration.WithLastUpdated(lastUpdated))).To(Equal(expected))
Expect(DocumentMetadata(source, lastUpdated)).To(Equal(types.Metadata{
Title: "",
LastUpdated: lastUpdated.Format(renderer.LastUpdatedFormat),
LastUpdated: lastUpdated.Format(configuration.LastUpdatedFormat),
TableOfContents: types.TableOfContents{
Sections: []types.ToCSection{
{
Expand All @@ -245,7 +245,7 @@ a paragraph with _italic content_`
</div>
</div>
</div>`
Expect(RenderHTML5Body(source, WithFilename("tmp/foo.adoc"))).To(Equal(expectedContent))
Expect(RenderHTML5Body(source, configuration.WithFilename("tmp/foo.adoc"))).To(Equal(expectedContent))
})
})

Expand Down Expand Up @@ -281,7 +281,7 @@ Last updated {{.LastUpdated}}
filename := "test/includes/chapter-a.adoc"
stat, err := os.Stat(filename)
Expect(err).NotTo(HaveOccurred())
Expect(RenderHTML5Document(filename, renderer.IncludeCSS("path/to/style.css"))).To(MatchHTML5Template(expectedContent, stat.ModTime()))
Expect(RenderHTML5Document(filename, configuration.WithCSS("path/to/style.css"), configuration.WithHeaderFooter(true))).To(MatchHTML5Template(expectedContent, stat.ModTime()))
})

})
Expand Down
Loading

0 comments on commit 79c454c

Please sign in to comment.