From b86d307e2a70f94f3765ca669e68fcc58ccaea43 Mon Sep 17 00:00:00 2001 From: Xavier Coulon Date: Sat, 14 Apr 2018 19:16:29 +0200 Subject: [PATCH 1/3] feat(cmd): add command line interface Signed-off-by: Xavier Coulon --- Gopkg.lock | 20 +++++++- Gopkg.toml | 4 ++ cmd/libasciidoc/main.go | 89 +++++++++++++++++++++++++++++++++ cmd/libasciidoc/version.go | 15 ++++++ renderer/html5/renderer_test.go | 3 -- 5 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 cmd/libasciidoc/main.go create mode 100644 cmd/libasciidoc/version.go diff --git a/Gopkg.lock b/Gopkg.lock index 027f5e15..353bab22 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,6 +7,12 @@ revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" +[[projects]] + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + [[projects]] name = "github.com/onsi/ginkgo" packages = [ @@ -76,6 +82,18 @@ source = "https://github.com/sirupsen/logrus.git" version = "v1.0.4" +[[projects]] + name = "github.com/spf13/cobra" + packages = ["."] + revision = "a1f051bc3eba734da4772d60e2d677f47cf93ef4" + version = "v0.0.2" + +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "583c0c0531f06d5278b7d917446061adc344b5cd" + version = "v1.0.1" + [[projects]] name = "github.com/stretchr/testify" packages = [ @@ -143,6 +161,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "98e5d849489dc4bb2d38e6cd8ad370b867d82585a8b4191739c813517388ea56" + inputs-digest = "3f0bf5bee4548209c15bfbb8a882240088cab55922e5e109e172e38542b75db5" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 7fde6fdb..bd14c564 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -49,3 +49,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/spf13/cobra" + version = "0.0.2" diff --git a/cmd/libasciidoc/main.go b/cmd/libasciidoc/main.go new file mode 100644 index 00000000..99c16ecd --- /dev/null +++ b/cmd/libasciidoc/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/bytesparadise/libasciidoc/renderer" + "github.com/bytesparadise/libasciidoc/renderer/html5" + "github.com/bytesparadise/libasciidoc/types" + + "github.com/bytesparadise/libasciidoc/parser" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func main() { + rootCmd := newRootCmd() + rootCmd.AddCommand(versionCmd) + rootCmd.SetHelpCommand(helpCommand) + // rootCmd.SetHelpTemplate(helpTemplate) + // rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage") + // rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help") + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +var helpTemplate = ` +{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` + +func newRootCmd() *cobra.Command { + var source string + rootCmd := &cobra.Command{ + Use: "libasciidoc", + Short: "libasciidoc is a tool to generate an html output from an asciidoc source", + // Long: `A Fast and Flexible Static Site Generator built with + // love by spf13 and friends in Go. + // Complete documentation is available at http://hugo.spf13.com`, + RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Flag("source").Value.String() == "" { + fmt.Println("flag 'source' is required") + os.Exit(1) + } + source := cmd.Flag("source").Value.String() + b, err := ioutil.ReadFile(source) + if err != nil { + fmt.Printf("failed to read the source file: %v\n", err) + os.Exit(1) + } + doc, err := parser.Parse(source, b) + if err != nil { + fmt.Printf("failed to parse the source file: %v\n", err) + os.Exit(1) + } + buff := bytes.NewBuffer(nil) + actualDocument := doc.(types.Document) + rendererCtx := renderer.Wrap(context.Background(), actualDocument) + _, err = html5.Render(rendererCtx, buff) + fmt.Printf("%s\n", buff.String()) + return nil + }, + } + flags := rootCmd.Flags() + flags.StringVarP(&source, "source", "s", "", "the path to the asciidoc source to process") + return rootCmd +} + +var helpCommand = &cobra.Command{ + Use: "help [command]", + Short: "Help about the command", + PersistentPreRun: func(cmd *cobra.Command, args []string) {}, + PersistentPostRun: func(cmd *cobra.Command, args []string) {}, + RunE: func(c *cobra.Command, args []string) error { + cmd, args, e := c.Root().Find(args) + if cmd == nil || e != nil || len(args) > 0 { + return errors.Errorf("unknown help topic: %v", strings.Join(args, " ")) + } + + helpFunc := cmd.HelpFunc() + helpFunc(cmd, args) + return nil + }, +} diff --git a/cmd/libasciidoc/version.go b/cmd/libasciidoc/version.go new file mode 100644 index 00000000..f8cae8dc --- /dev/null +++ b/cmd/libasciidoc/version.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of libasciidoc", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("~HEAD") + }, +} diff --git a/renderer/html5/renderer_test.go b/renderer/html5/renderer_test.go index 7327e9fa..c98b5eec 100644 --- a/renderer/html5/renderer_test.go +++ b/renderer/html5/renderer_test.go @@ -25,9 +25,6 @@ func verify(t GinkgoTInterface, expectedResult, content string, rendererOpts ... buff := bytes.NewBuffer(nil) actualDocument := doc.(types.Document) rendererCtx := renderer.Wrap(context.Background(), actualDocument, rendererOpts...) - // if entrypoint := rendererCtx.Entrypoint(); entrypoint != nil { - - // } _, err = html5.Render(rendererCtx, buff) require.NoError(t, err) if strings.Contains(expectedResult, "{{.LastUpdated}}") { From 99e59ec9fac88ba14691b7f3e8276d021952c547 Mon Sep 17 00:00:00 2001 From: Xavier Coulon Date: Wed, 18 Apr 2018 21:36:19 +0200 Subject: [PATCH 2/3] refactor command and update doc Signed-off-by: Xavier Coulon --- .gitignore | 1 + README.adoc | 21 +++++++++++++++++---- cmd/libasciidoc/main.go | 22 +++------------------- libasciidoc.go | 28 ++++++++++++---------------- libasciidoc_test.go | 2 +- 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index be39ad33..b73fcb21 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ tmp # exclude code coverage merged results coverage.txt *.html +.DS_Store diff --git a/README.adoc b/README.adoc index cc42a29f..7dfbcbdd 100644 --- a/README.adoc +++ b/README.adoc @@ -21,6 +21,7 @@ It is is available under the terms of the https://raw.githubusercontent.com/byte * Inline images in paragraphs (`image://`) * Block images (`image:://`) * Element attributes (`ID`, `link` and `title`, where applicable) on block images, paragraphs, lists and sections +* Labeled, ordered and unordered lists (with nesting and attributes) See the http://LIMITATIONS.adoc[known limitations] page for differences between Asciidoc/Asciidoctor and Libasciidoc. @@ -33,18 +34,30 @@ Further elements will be supported in the future. Feel free to open issues https == Usage +=== Command Line + +The libasciidoc library includes a minimalist command line interface to generate the HTML content from a given file: + +``` +$ libasciidoc -s content.adoc +``` + +=== Code integration + Libasciidoc provides 2 functions to convert an Asciidoc content into HTML: -1. Converting to a complete HTML document: +1. Converting an `io.Reader` into an HTML document: - func ConvertToHTML(context.Context, io.Reader, io.Writer, renderer.Option...) (map[string]interface{}, error) + func ConvertToHTML(ctx context.Context, source io.Reader, output io.Writer, options renderer.Option...) (map[string]interface{}, error) -2. Converting to a `body` element only: +2. Converting a file (giving its name) into an HTML document: - func ConvertToHTMLBody(context.Context, io.Reader, io.Writer) (map[string]interface{}, error) + func ConvertFileToHTML(ctx context.Context, filename string, output io.Writer, options renderer.Option...) (map[string]interface{}, error) where the returned `map[string]interface{}` object contains the document's title (which is not rendered in the HTML's body) and its other attributes. +The currently available option to pass as a last argument is `renderer.IncludeHeaderFooter(false)` to limit the generation to the body of the HTML document. + == How to contribute Please refer to the http://CONTRIBUTE.adoc[Contribute] page. diff --git a/cmd/libasciidoc/main.go b/cmd/libasciidoc/main.go index 99c16ecd..f1052210 100644 --- a/cmd/libasciidoc/main.go +++ b/cmd/libasciidoc/main.go @@ -1,18 +1,13 @@ package main import ( - "bytes" "context" "fmt" - "io/ioutil" "os" "strings" + "github.com/bytesparadise/libasciidoc" "github.com/bytesparadise/libasciidoc/renderer" - "github.com/bytesparadise/libasciidoc/renderer/html5" - "github.com/bytesparadise/libasciidoc/types" - - "github.com/bytesparadise/libasciidoc/parser" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -48,21 +43,10 @@ func newRootCmd() *cobra.Command { os.Exit(1) } source := cmd.Flag("source").Value.String() - b, err := ioutil.ReadFile(source) + _, err := libasciidoc.ConvertFileToHTML(context.Background(), source, os.Stdout, renderer.IncludeHeaderFooter(true)) //renderer.IncludeHeaderFooter(true) if err != nil { - fmt.Printf("failed to read the source file: %v\n", err) - os.Exit(1) - } - doc, err := parser.Parse(source, b) - if err != nil { - fmt.Printf("failed to parse the source file: %v\n", err) - os.Exit(1) + return err } - buff := bytes.NewBuffer(nil) - actualDocument := doc.(types.Document) - rendererCtx := renderer.Wrap(context.Background(), actualDocument) - _, err = html5.Render(rendererCtx, buff) - fmt.Printf("%s\n", buff.String()) return nil }, } diff --git a/libasciidoc.go b/libasciidoc.go index 103ee67c..c9b75ef8 100644 --- a/libasciidoc.go +++ b/libasciidoc.go @@ -12,33 +12,29 @@ import ( log "github.com/sirupsen/logrus" ) -// ConvertToHTMLBody converts the content of the given reader `r` into an set of
elements for an HTML/BODY document. -// The conversion result is written in the given writer `w`, whereas the document metadata (title, etc.) (or an error if a problem occurred) is returned +// 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 ConvertToHTMLBody(ctx context.Context, r io.Reader, w io.Writer) (map[string]interface{}, error) { - doc, err := parser.ParseReader("", r) +func ConvertFileToHTML(ctx context.Context, filename string, output io.Writer, options ...renderer.Option) (map[string]interface{}, error) { + doc, err := parser.ParseFile(filename) if err != nil { return nil, errors.Wrapf(err, "error while parsing the document") } - options := []renderer.Option{renderer.IncludeHeaderFooter(false)} - metadata, err := htmlrenderer.Render(renderer.Wrap(ctx, doc.(types.Document), options...), w) - if err != nil { - return nil, errors.Wrapf(err, "error while rendering the document") - } - log.Debugf("Done processing document") - return metadata, nil + return convertToHTML(ctx, doc, output, options...) } -// ConvertToHTML converts the content of the given reader `r` into a full HTML document, written in the given writer `w`. +// 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(ctx context.Context, r io.Reader, w io.Writer, options ...renderer.Option) (map[string]interface{}, error) { +func ConvertToHTML(ctx context.Context, r io.Reader, output io.Writer, options ...renderer.Option) (map[string]interface{}, error) { doc, err := parser.ParseReader("", r) if err != nil { return nil, errors.Wrapf(err, "error while parsing the document") } - // force/override value - options = append(options, renderer.IncludeHeaderFooter(true)) - metadata, err := htmlrenderer.Render(renderer.Wrap(ctx, doc.(types.Document), options...), w) + return convertToHTML(ctx, doc, output, options...) +} + +func convertToHTML(ctx context.Context, doc interface{}, output io.Writer, options ...renderer.Option) (map[string]interface{}, error) { + metadata, err := htmlrenderer.Render(renderer.Wrap(ctx, doc.(types.Document), options...), output) if err != nil { return nil, errors.Wrapf(err, "error while rendering the document") } diff --git a/libasciidoc_test.go b/libasciidoc_test.go index f73aabd3..36a7768a 100644 --- a/libasciidoc_test.go +++ b/libasciidoc_test.go @@ -179,7 +179,7 @@ func verifyDocumentBody(t GinkgoTInterface, expectedRenderedTitle *string, expec t.Logf("processing '%s'", source) sourceReader := strings.NewReader(source) resultWriter := bytes.NewBuffer(nil) - metadata, err := ConvertToHTMLBody(context.Background(), sourceReader, resultWriter) + metadata, err := ConvertToHTML(context.Background(), sourceReader, resultWriter, renderer.IncludeHeaderFooter(false)) require.Nil(t, err, "Error found while parsing the document") require.NotNil(t, metadata) t.Log("Done processing document") From 6e8412a7fc17005fce7a2cb2aa375e7cb7db7e66 Mon Sep 17 00:00:00 2001 From: Xavier Coulon Date: Wed, 18 Apr 2018 22:08:08 +0200 Subject: [PATCH 3/3] refactor cmd pkg, add tests Signed-off-by: Xavier Coulon --- cmd/libasciidoc/cmd_suite_test.go | 13 +++++++++ cmd/libasciidoc/main.go | 32 +--------------------- cmd/libasciidoc/root_cmd.go | 33 +++++++++++++++++++++++ cmd/libasciidoc/root_cmd_test.go | 44 +++++++++++++++++++++++++++++++ cmd/libasciidoc/test/test.adoc | 1 + libasciidoc_test.go | 2 +- 6 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 cmd/libasciidoc/cmd_suite_test.go create mode 100644 cmd/libasciidoc/root_cmd.go create mode 100644 cmd/libasciidoc/root_cmd_test.go create mode 100644 cmd/libasciidoc/test/test.adoc diff --git a/cmd/libasciidoc/cmd_suite_test.go b/cmd/libasciidoc/cmd_suite_test.go new file mode 100644 index 00000000..46505f6b --- /dev/null +++ b/cmd/libasciidoc/cmd_suite_test.go @@ -0,0 +1,13 @@ +package main_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestCmd(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Cmd Suite") +} diff --git a/cmd/libasciidoc/main.go b/cmd/libasciidoc/main.go index f1052210..73c5078b 100644 --- a/cmd/libasciidoc/main.go +++ b/cmd/libasciidoc/main.go @@ -1,20 +1,16 @@ package main import ( - "context" "fmt" "os" "strings" - "github.com/bytesparadise/libasciidoc" - "github.com/bytesparadise/libasciidoc/renderer" - "github.com/pkg/errors" "github.com/spf13/cobra" ) func main() { - rootCmd := newRootCmd() + rootCmd := NewRootCmd() rootCmd.AddCommand(versionCmd) rootCmd.SetHelpCommand(helpCommand) // rootCmd.SetHelpTemplate(helpTemplate) @@ -29,32 +25,6 @@ func main() { var helpTemplate = ` {{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` -func newRootCmd() *cobra.Command { - var source string - rootCmd := &cobra.Command{ - Use: "libasciidoc", - Short: "libasciidoc is a tool to generate an html output from an asciidoc source", - // Long: `A Fast and Flexible Static Site Generator built with - // love by spf13 and friends in Go. - // Complete documentation is available at http://hugo.spf13.com`, - RunE: func(cmd *cobra.Command, args []string) error { - if cmd.Flag("source").Value.String() == "" { - fmt.Println("flag 'source' is required") - os.Exit(1) - } - source := cmd.Flag("source").Value.String() - _, err := libasciidoc.ConvertFileToHTML(context.Background(), source, os.Stdout, renderer.IncludeHeaderFooter(true)) //renderer.IncludeHeaderFooter(true) - if err != nil { - return err - } - return nil - }, - } - flags := rootCmd.Flags() - flags.StringVarP(&source, "source", "s", "", "the path to the asciidoc source to process") - return rootCmd -} - var helpCommand = &cobra.Command{ Use: "help [command]", Short: "Help about the command", diff --git a/cmd/libasciidoc/root_cmd.go b/cmd/libasciidoc/root_cmd.go new file mode 100644 index 00000000..c3d84973 --- /dev/null +++ b/cmd/libasciidoc/root_cmd.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "fmt" + + "github.com/bytesparadise/libasciidoc" + "github.com/bytesparadise/libasciidoc/renderer" + "github.com/spf13/cobra" +) + +// NewRootCmd returns the root command +func NewRootCmd() *cobra.Command { + var source string + rootCmd := &cobra.Command{ + Use: "libasciidoc", + Short: "libasciidoc is a tool to generate an html output from an asciidoc file", + RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Flag("source").Value.String() == "" { + return fmt.Errorf("flag 'source' is required") + } + source := cmd.Flag("source").Value.String() + _, err := libasciidoc.ConvertFileToHTML(context.Background(), source, cmd.OutOrStdout(), renderer.IncludeHeaderFooter(true)) //renderer.IncludeHeaderFooter(true) + if err != nil { + return err + } + return nil + }, + } + flags := rootCmd.Flags() + flags.StringVarP(&source, "source", "s", "", "the path to the asciidoc source to process") + return rootCmd +} diff --git a/cmd/libasciidoc/root_cmd_test.go b/cmd/libasciidoc/root_cmd_test.go new file mode 100644 index 00000000..9e64f8a5 --- /dev/null +++ b/cmd/libasciidoc/root_cmd_test.go @@ -0,0 +1,44 @@ +package main_test + +import ( + "bytes" + "testing" + + main "github.com/bytesparadise/libasciidoc/cmd/libasciidoc" + "github.com/stretchr/testify/require" +) + +import . "github.com/onsi/ginkgo" + +var _ = Describe("root cmd", func() { + + It("ok", func() { + // given + root := main.NewRootCmd() + buf := new(bytes.Buffer) + root.SetOutput(buf) + root.SetArgs([]string{"-s", "test/test.adoc"}) + // when + err := root.Execute() + // then + require.NoError(GinkgoT(), err) + require.NotEmpty(GinkgoT(), buf) + }) + + It("missing source flag", func() { + // given + root := main.NewRootCmd() + buf := new(bytes.Buffer) + root.SetOutput(buf) + // when + err := root.Execute() + // then + GinkgoT().Logf("command output: %v", buf.String()) + require.Error(GinkgoT(), err) + }) + +}) + +func TestRootCommand(t *testing.T) { + +} diff --git a/cmd/libasciidoc/test/test.adoc b/cmd/libasciidoc/test/test.adoc new file mode 100644 index 00000000..c1539925 --- /dev/null +++ b/cmd/libasciidoc/test/test.adoc @@ -0,0 +1 @@ +some *asciidoc* content \ No newline at end of file diff --git a/libasciidoc_test.go b/libasciidoc_test.go index 36a7768a..9f114e81 100644 --- a/libasciidoc_test.go +++ b/libasciidoc_test.go @@ -202,7 +202,7 @@ func verifyCompleteDocument(t GinkgoTInterface, expectedContent, source string) sourceReader := strings.NewReader(source) resultWriter := bytes.NewBuffer(nil) lastUpdated := time.Now() - _, err := ConvertToHTML(context.Background(), sourceReader, resultWriter, renderer.LastUpdated(lastUpdated)) + _, err := ConvertToHTML(context.Background(), sourceReader, resultWriter, renderer.IncludeHeaderFooter(true), renderer.LastUpdated(lastUpdated)) require.Nil(t, err, "Error found while parsing the document") t.Log("Done processing document") result := resultWriter.String()