diff --git a/commands/benchmark.go b/commands/benchmark.go index b1291cc45..409b305a2 100644 --- a/commands/benchmark.go +++ b/commands/benchmark.go @@ -45,8 +45,6 @@ creating a benchmark.`, cmd.Flags().StringVar(&c.memProfileFile, "memprofile", "", "path/filename for the memory profile file") cmd.Flags().IntVarP(&c.benchmarkTimes, "count", "n", 13, "number of times to build the site") - cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)") - cmd.RunE = c.benchmark return c @@ -56,6 +54,7 @@ func (c *benchmarkCmd) benchmark(cmd *cobra.Command, args []string) error { cfgInit := func(c *commandeer) error { return nil } + comm, err := initializeConfig(false, &c.hugoBuilderCommon, c, cfgInit) if err != nil { return err diff --git a/commands/commandeer.go b/commands/commandeer.go index 4c8abd7d8..ba38735c2 100644 --- a/commands/commandeer.go +++ b/commands/commandeer.go @@ -40,7 +40,7 @@ import ( type commandeer struct { *deps.DepsCfg - h *hugoBuilderCommon + h *hugoBuilderCommon ftch flagsToConfigHandler pathSpec *helpers.PathSpec @@ -109,7 +109,7 @@ func newCommandeer(running bool, h *hugoBuilderCommon, f flagsToConfigHandler, d c := &commandeer{ h: h, - ftch: f, + ftch: f, doWithCommandeer: doWithCommandeer, visitedURLs: types.NewEvictingStringQueue(10), debounce: rebuildDebouncer, diff --git a/commands/commands_test.go b/commands/commands_test.go new file mode 100644 index 000000000..f588067e6 --- /dev/null +++ b/commands/commands_test.go @@ -0,0 +1,133 @@ +// Copyright 2018 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCommands(t *testing.T) { + + assert := require.New(t) + + dir, err := createSimpleTestSite(t) + assert.NoError(err) + + dirOut, err := ioutil.TempDir("", "hugo-cli-out") + assert.NoError(err) + + defer func() { + os.RemoveAll(dir) + os.RemoveAll(dirOut) + }() + + sourceFlag := fmt.Sprintf("-s=%s", dir) + + tests := []struct { + commands []string + flags []string + }{ + {[]string{"check", "ulimit"}, nil}, + {[]string{"env"}, nil}, + {[]string{"version"}, nil}, + // no args = hugo build + {nil, []string{sourceFlag}}, + // TODO(bep) cli refactor remove the HugoSites global and enable the below + //{nil, []string{sourceFlag, "--renderToMemory"}}, + {[]string{"benchmark"}, []string{sourceFlag, "-n=1"}}, + {[]string{"convert", "toTOML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "toml")}}, + {[]string{"convert", "toYAML"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "yaml")}}, + {[]string{"convert", "toJSON"}, []string{sourceFlag, "-o=" + filepath.Join(dirOut, "json")}}, + {[]string{"gen", "autocomplete"}, []string{"--completionfile=" + filepath.Join(dirOut, "autocomplete.txt")}}, + {[]string{"gen", "chromastyles"}, []string{"--style=manni"}}, + {[]string{"gen", "doc"}, []string{"--dir=" + filepath.Join(dirOut, "doc")}}, + {[]string{"gen", "man"}, []string{"--dir=" + filepath.Join(dirOut, "man")}}, + {[]string{"list", "drafts"}, []string{sourceFlag}}, + {[]string{"list", "expired"}, []string{sourceFlag}}, + {[]string{"list", "future"}, []string{sourceFlag}}, + {[]string{"new", "new-page.md"}, []string{sourceFlag}}, + {[]string{"new", "site", filepath.Join(dirOut, "new-site")}, nil}, + // TODO(bep) cli refactor fix https://github.com/gohugoio/hugo/issues/4450 + //{[]string{"new", "theme", filepath.Join(dirOut, "new-theme")}, nil}, + } + + for _, test := range tests { + + hugoCmd := newHugoCompleteCmd() + test.flags = append(test.flags, "--quiet") + hugoCmd.SetArgs(append(test.commands, test.flags...)) + + // TODO(bep) capture output and add some simple asserts + + assert.NoError(hugoCmd.Execute(), fmt.Sprintf("%v", test.commands)) + } + +} + +func createSimpleTestSite(t *testing.T) (string, error) { + d, e := ioutil.TempDir("", "hugo-cli") + if e != nil { + return "", e + } + + // Just the basic. These are for CLI tests, not site testing. + writeFile(t, filepath.Join(d, "config.toml"), ` + +baseURL = "https://example.org" +title = "Hugo Commands" + +`) + + writeFile(t, filepath.Join(d, "content", "p1.md"), ` +--- +title: "P1" +weight: 1 +--- + +Content + +`) + + writeFile(t, filepath.Join(d, "layouts", "_default", "single.html"), ` + +Single: {{ .Title }} + +`) + + writeFile(t, filepath.Join(d, "layouts", "_default", "list.html"), ` + +List: {{ .Title }} + +`) + + return d, nil + +} + +func writeFile(t *testing.T, filename, content string) { + must(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755))) + must(t, ioutil.WriteFile(filename, []byte(content), os.FileMode(0755))) +} + +func must(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} diff --git a/commands/convert.go b/commands/convert.go index 9e0a66026..202cc1111 100644 --- a/commands/convert.go +++ b/commands/convert.go @@ -32,17 +32,17 @@ var ( _ cmder = (*convertCmd)(nil) ) -// TODO(bep) cli refactor -var outputDir string -var unsafe bool - type convertCmd struct { + outputDir string + unsafe bool + *baseBuilderCmd } func newConvertCmd() *convertCmd { cc := &convertCmd{} + // TODO(bep) cli refactor this is more than it had cc.baseBuilderCmd = newBuilderCmd(&cobra.Command{ Use: "convert", Short: "Convert your content to different formats", @@ -82,17 +82,16 @@ to use YAML for the front matter.`, }, ) - // TODO(bep) cli refactor - // cmd.PersistentFlags().StringVarP(&outputDir, "output", "o", "", "filesystem path to write files to") - // cmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") - // cmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "enable less safe operations, please backup first") + cc.cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to") + cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from") + cc.cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first") cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) return cc } func (cc *convertCmd) convertContents(mark rune) error { - if outputDir == "" && !unsafe { + if cc.outputDir == "" && !cc.unsafe { return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path") } @@ -114,17 +113,17 @@ func (cc *convertCmd) convertContents(mark rune) error { site.Log.FEEDBACK.Println("processing", len(site.AllPages), "content files") for _, p := range site.AllPages { - if err := convertAndSavePage(p, site, mark); err != nil { + if err := cc.convertAndSavePage(p, site, mark); err != nil { return err } } return nil } -func convertAndSavePage(p *hugolib.Page, site *hugolib.Site, mark rune) error { +func (cc *convertCmd) convertAndSavePage(p *hugolib.Page, site *hugolib.Site, mark rune) error { // The resources are not in .Site.AllPages. for _, r := range p.Resources.ByType("page") { - if err := convertAndSavePage(r.(*hugolib.Page), site, mark); err != nil { + if err := cc.convertAndSavePage(r.(*hugolib.Page), site, mark); err != nil { return err } } @@ -182,8 +181,8 @@ func convertAndSavePage(p *hugolib.Page, site *hugolib.Site, mark rune) error { } newFilename := p.Filename() - if outputDir != "" { - newFilename = filepath.Join(outputDir, p.Dir(), newPage.LogicalName()) + if cc.outputDir != "" { + newFilename = filepath.Join(cc.outputDir, p.Dir(), newPage.LogicalName()) } if err = newPage.SaveSourceAs(newFilename); err != nil { diff --git a/commands/hugo.go b/commands/hugo.go index 1da764d93..b3e5c67c6 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -141,6 +141,9 @@ Complete documentation is available at http://gohugo.io/.`, // Set bash-completion _ = cc.cmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{}) + cc.cmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags) + cc.cmd.SilenceUsage = true + return cc } @@ -191,6 +194,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) { cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files") cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files") cmd.Flags().BoolP("i18n-warnings", "", false, "print missing translations") + cmd.Flags().Bool("renderToMemory", false, "render to memory (only useful for benchmark testing)") cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)") @@ -214,23 +218,11 @@ func Reset() error { return nil } -var ( - hugoCommand = newHugoCmd() - - // HugoCmd is Hugo's root command. - // Every other command attached to HugoCmd is a child command to it. - HugoCmd = hugoCommand.getCommand() -) - // Execute adds all child commands to the root command HugoCmd and sets flags appropriately. func Execute() { - HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags) + hugoCmd := newHugoCompleteCmd() - HugoCmd.SilenceUsage = true - - addAllCommands() - - if c, err := HugoCmd.ExecuteC(); err != nil { + if c, err := hugoCmd.ExecuteC(); err != nil { if isUserError(err) { c.Println("") c.Println(c.UsageString()) @@ -240,9 +232,16 @@ func Execute() { } } +func newHugoCompleteCmd() *cobra.Command { + hugoCmd := newHugoCmd().getCommand() + addAllCommands(hugoCmd) + return hugoCmd +} + // addAllCommands adds child commands to the root command HugoCmd. -func addAllCommands() { +func addAllCommands(root *cobra.Command) { addCommands( + root, newServerCmd(), newVersionCmd(), newEnvCmd(), @@ -257,9 +256,9 @@ func addAllCommands() { ) } -func addCommands(commands ...cmder) { +func addCommands(root *cobra.Command, commands ...cmder) { for _, command := range commands { - HugoCmd.AddCommand(command.getCommand()) + root.AddCommand(command.getCommand()) } } diff --git a/commands/list.go b/commands/list.go index 49024be9f..cf31f2bb4 100644 --- a/commands/list.go +++ b/commands/list.go @@ -151,7 +151,7 @@ expired.`, ) // TODO(bep) cli refactor - // cc.cmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") + cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from") cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) return cc diff --git a/commands/new.go b/commands/new.go index 2fb35a9a3..851951ce9 100644 --- a/commands/new.go +++ b/commands/new.go @@ -30,6 +30,7 @@ import ( var _ cmder = (*newCmd)(nil) type newCmd struct { + hugoBuilderCommon contentEditor string contentType string @@ -37,8 +38,8 @@ type newCmd struct { } func newNewCmd() *newCmd { - ccmd := &newCmd{baseCmd: newBaseCmd(nil)} - cmd := &cobra.Command{ + cc := &newCmd{} + cc.baseCmd = newBaseCmd(&cobra.Command{ Use: "new [path]", Short: "Create new content for your site", Long: `Create a new content file and automatically set the date and title. @@ -48,21 +49,19 @@ You can also specify the kind with ` + "`-k KIND`" + `. If archetypes are provided in your theme or site, they will be used.`, - RunE: ccmd.newContent, - } + RunE: cc.newContent, + }) - cmd.Flags().StringVarP(&ccmd.contentType, "kind", "k", "", "content type to create") + cc.cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create") // TODO(bep) cli refactor - // cmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") - cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) - cmd.Flags().StringVar(&ccmd.contentEditor, "editor", "", "edit new content with this editor, if provided") + cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from") + cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) + cc.cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided") - cmd.AddCommand(newNewSiteCmd().getCommand()) - cmd.AddCommand(newNewThemeCmd().getCommand()) + cc.cmd.AddCommand(newNewSiteCmd().getCommand()) + cc.cmd.AddCommand(newNewThemeCmd().getCommand()) - ccmd.cmd = cmd - - return ccmd + return cc } func (n *newCmd) newContent(cmd *cobra.Command, args []string) error { @@ -73,7 +72,7 @@ func (n *newCmd) newContent(cmd *cobra.Command, args []string) error { return nil } - c, err := initializeConfig(false, nil, n, cfgInit) + c, err := initializeConfig(false, &n.hugoBuilderCommon, n, cfgInit) if err != nil { return err diff --git a/commands/new_theme.go b/commands/new_theme.go index 64220a8fc..3b00cb1df 100644 --- a/commands/new_theme.go +++ b/commands/new_theme.go @@ -32,10 +32,11 @@ var _ cmder = (*newThemeCmd)(nil) type newThemeCmd struct { *baseCmd + hugoBuilderCommon } func newNewThemeCmd() *newThemeCmd { - ccmd := &newThemeCmd{newBaseCmd(nil)} + ccmd := &newThemeCmd{baseCmd: newBaseCmd(nil)} cmd := &cobra.Command{ Use: "theme [name]", @@ -53,7 +54,7 @@ as you see fit.`, } func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error { - c, err := initializeConfig(false, nil, n, nil) + c, err := initializeConfig(false, &n.hugoBuilderCommon, n, nil) if err != nil { return err diff --git a/commands/server.go b/commands/server.go index 7db963e41..775aa402d 100644 --- a/commands/server.go +++ b/commands/server.go @@ -1,4 +1,4 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. +// Copyright 2018 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,24 +56,6 @@ type serverCmd struct { *baseCmd } -func (cc *serverCmd) handleFlags(cmd *cobra.Command) { - // TODO(bep) cli refactor fields vs strings - cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen") - cc.cmd.Flags().IntVar(&cc.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)") - cc.cmd.Flags().StringVarP(&cc.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind") - cc.cmd.Flags().BoolVarP(&cc.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed") - cc.cmd.Flags().BoolVar(&cc.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching") - cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL") - cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild") - cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload") - cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)") - cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes") - - cc.cmd.Flags().String("memstats", "", "log memory usage to this file") - cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".") - -} - func newServerCmd() *serverCmd { cc := &serverCmd{} @@ -96,6 +78,21 @@ of a second, you will be able to save and see your changes nearly instantly.`, RunE: cc.server, }) + // TODO(bep) cli refactor fields vs strings + cc.cmd.Flags().IntVarP(&cc.serverPort, "port", "p", 1313, "port on which the server will listen") + cc.cmd.Flags().IntVar(&cc.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)") + cc.cmd.Flags().StringVarP(&cc.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind") + cc.cmd.Flags().BoolVarP(&cc.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed") + cc.cmd.Flags().BoolVar(&cc.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching") + cc.cmd.Flags().BoolVarP(&cc.serverAppend, "appendPort", "", true, "append port to baseURL") + cc.cmd.Flags().BoolVar(&cc.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild") + cc.cmd.Flags().BoolVar(&cc.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload") + cc.cmd.Flags().BoolVar(&cc.renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)") + cc.cmd.Flags().BoolVar(&cc.disableFastRender, "disableFastRender", false, "enables full re-renders on changes") + + cc.cmd.Flags().String("memstats", "", "log memory usage to this file") + cc.cmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".") + return cc } diff --git a/main.go b/main.go index b408196fc..e1c5b39f8 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ import ( ) func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) commands.Execute()