summaryrefslogtreecommitdiffstats
path: root/commands/new.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /commands/new.go
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff)
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
Diffstat (limited to 'commands/new.go')
-rw-r--r--commands/new.go379
1 files changed, 308 insertions, 71 deletions
diff --git a/commands/new.go b/commands/new.go
index a6c2c8ca1..3a0e3ad71 100644
--- a/commands/new.go
+++ b/commands/new.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2023 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.
@@ -15,114 +15,351 @@ package commands
import (
"bytes"
- "os"
+ "context"
+ "errors"
+ "fmt"
"path/filepath"
"strings"
+ "github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/htime"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/create"
"github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugolib"
+ "github.com/gohugoio/hugo/parser"
+ "github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
"github.com/spf13/cobra"
- jww "github.com/spf13/jwalterweatherman"
)
-var _ cmder = (*newCmd)(nil)
+func newNewCommand() *newCommand {
+ var (
+ configFormat string
+ force bool
+ contentType string
+ )
-type newCmd struct {
- contentEditor string
- contentType string
- force bool
+ var c *newCommand
+ c = &newCommand{
+ commands: []simplecobra.Commander{
+ &simpleCommand{
+ name: "content",
+ use: "content [path]",
+ short: "Create new content for your site",
+ long: `Create a new content file and automatically set the date and title.
+ It will guess which kind of file to create based on the path provided.
+
+ You can also specify the kind with ` + "`-k KIND`" + `.
+
+ If archetypes are provided in your theme or site, they will be used.
+
+ Ensure you run this within the root directory of your site.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ if len(args) < 1 {
+ return errors.New("path needs to be provided")
+ }
+ h, err := r.Hugo(flagsToCfg(cd, nil))
+ if err != nil {
+ return err
+ }
+ return create.NewContent(h, contentType, args[0], force)
+ },
+ withc: func(cmd *cobra.Command) {
+ cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
+ cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
+ cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
+ },
+ },
+ &simpleCommand{
+ name: "site",
+ use: "site [path]",
+ short: "Create a new site (skeleton)",
+ long: `Create a new site in the provided directory.
+The new site will have the correct structure, but no content or theme yet.
+Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ if len(args) < 1 {
+ return errors.New("path needs to be provided")
+ }
+ createpath, err := filepath.Abs(filepath.Clean(args[0]))
+ if err != nil {
+ return err
+ }
- *baseBuilderCmd
-}
+ cfg := config.New()
+ cfg.Set("workingDir", createpath)
+ cfg.Set("publishDir", "public")
-func (b *commandsBuilder) newNewCmd() *newCmd {
- cmd := &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.
-It will guess which kind of file to create based on the path provided.
+ conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
+ if err != nil {
+ return err
+ }
+ sourceFs := conf.fs.Source
-You can also specify the kind with ` + "`-k KIND`" + `.
+ archeTypePath := filepath.Join(createpath, "archetypes")
+ dirs := []string{
+ archeTypePath,
+ filepath.Join(createpath, "assets"),
+ filepath.Join(createpath, "content"),
+ filepath.Join(createpath, "data"),
+ filepath.Join(createpath, "layouts"),
+ filepath.Join(createpath, "static"),
+ filepath.Join(createpath, "themes"),
+ }
-If archetypes are provided in your theme or site, they will be used.
+ if exists, _ := helpers.Exists(createpath, sourceFs); exists {
+ if isDir, _ := helpers.IsDir(createpath, sourceFs); !isDir {
+ return errors.New(createpath + " already exists but not a directory")
+ }
+
+ isEmpty, _ := helpers.IsEmpty(createpath, sourceFs)
+
+ switch {
+ case !isEmpty && !force:
+ return errors.New(createpath + " already exists and is not empty. See --force.")
+
+ case !isEmpty && force:
+ all := append(dirs, filepath.Join(createpath, "hugo."+configFormat))
+ for _, path := range all {
+ if exists, _ := helpers.Exists(path, sourceFs); exists {
+ return errors.New(path + " already exists")
+ }
+ }
+ }
+ }
+
+ for _, dir := range dirs {
+ if err := sourceFs.MkdirAll(dir, 0777); err != nil {
+ return fmt.Errorf("failed to create dir: %w", err)
+ }
+ }
+
+ c.newSiteCreateConfig(sourceFs, createpath, configFormat)
+
+ // Create a default archetype file.
+ helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
+ strings.NewReader(create.DefaultArchetypeTemplateTemplate), sourceFs)
+
+ r.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", createpath)
+ r.Println(c.newSiteNextStepsText())
+
+ return nil
+ },
+ withc: func(cmd *cobra.Command) {
+ cmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "config file format")
+ cmd.Flags().BoolVar(&force, "force", false, "init inside non-empty directory")
+ },
+ },
+ &simpleCommand{
+ name: "theme",
+ use: "theme [path]",
+ short: "Create a new site (skeleton)",
+ long: `Create a new site in the provided directory.
+The new site will have the correct structure, but no content or theme yet.
+Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
+ run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
+ h, err := r.Hugo(flagsToCfg(cd, nil))
+ if err != nil {
+ return err
+ }
+ ps := h.PathSpec
+ sourceFs := ps.Fs.Source
+ themesDir := h.Configs.LoadingInfo.BaseConfig.ThemesDir
+ createpath := ps.AbsPathify(filepath.Join(themesDir, args[0]))
+ r.Println("Creating theme at", createpath)
+
+ if x, _ := helpers.Exists(createpath, sourceFs); x {
+ return errors.New(createpath + " already exists")
+ }
+
+ for _, filename := range []string{
+ "index.html",
+ "404.html",
+ "_default/list.html",
+ "_default/single.html",
+ "partials/head.html",
+ "partials/header.html",
+ "partials/footer.html",
+ } {
+ touchFile(sourceFs, filepath.Join(createpath, "layouts", filename))
+ }
+
+ baseofDefault := []byte(`<!DOCTYPE html>
+<html>
+ {{- partial "head.html" . -}}
+ <body>
+ {{- partial "header.html" . -}}
+ <div id="content">
+ {{- block "main" . }}{{- end }}
+ </div>
+ {{- partial "footer.html" . -}}
+ </body>
+</html>
+`)
+
+ err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), sourceFs)
+ if err != nil {
+ return err
+ }
-Ensure you run this within the root directory of your site.`,
+ mkdir(createpath, "archetypes")
+
+ archDefault := []byte("+++\n+++\n")
+
+ err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), sourceFs)
+ if err != nil {
+ return err
+ }
+
+ mkdir(createpath, "static", "js")
+ mkdir(createpath, "static", "css")
+
+ by := []byte(`The MIT License (MIT)
+
+Copyright (c) ` + htime.Now().Format("2006") + ` YOUR_NAME_HERE
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+`)
+
+ err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), sourceFs)
+ if err != nil {
+ return err
+ }
+
+ c.createThemeMD(ps.Fs.Source, createpath)
+
+ return nil
+ },
+ },
+ },
}
- cc := &newCmd{baseBuilderCmd: b.newBuilderCmd(cmd)}
+ return c
- cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
- cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
- cmd.Flags().BoolVarP(&cc.force, "force", "f", false, "overwrite file if it already exists")
+}
- cmd.AddCommand(b.newNewSiteCmd().getCommand())
- cmd.AddCommand(b.newNewThemeCmd().getCommand())
+type newCommand struct {
+ rootCmd *rootCommand
- cmd.RunE = cc.newContent
+ commands []simplecobra.Commander
+}
- return cc
+func (c *newCommand) Commands() []simplecobra.Commander {
+ return c.commands
}
-func (n *newCmd) newContent(cmd *cobra.Command, args []string) error {
- cfgInit := func(c *commandeer) error {
- if cmd.Flags().Changed("editor") {
- c.Set("newContentEditor", n.contentEditor)
- }
- return nil
- }
+func (c *newCommand) Name() string {
+ return "new"
+}
- c, err := initializeConfig(true, true, false, &n.hugoBuilderCommon, n, cfgInit)
- if err != nil {
- return err
- }
+func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
+ return nil
+}
- if len(args) < 1 {
- return newUserError("path needs to be provided")
- }
+func (c *newCommand) WithCobraCommand(cmd *cobra.Command) error {
+ cmd.Short = "Create new content for your site"
+ cmd.Long = `Create a new content file and automatically set the date and title.
+It will guess which kind of file to create based on the path provided.
+
+You can also specify the kind with ` + "`-k KIND`" + `.
- return create.NewContent(c.hugo(), n.contentType, args[0], n.force)
+If archetypes are provided in your theme or site, they will be used.
+
+Ensure you run this within the root directory of your site.`
+ return nil
}
-func mkdir(x ...string) {
- p := filepath.Join(x...)
+func (c *newCommand) Init(cd, runner *simplecobra.Commandeer) error {
+ c.rootCmd = cd.Root.Command.(*rootCommand)
+ return nil
+}
- err := os.MkdirAll(p, 0777) // before umask
- if err != nil {
- jww.FATAL.Fatalln(err)
+func (c *newCommand) newSiteCreateConfig(fs afero.Fs, inpath string, kind string) (err error) {
+ in := map[string]string{
+ "baseURL": "http://example.org/",
+ "title": "My New Hugo Site",
+ "languageCode": "en-us",
}
-}
-func touchFile(fs afero.Fs, x ...string) {
- inpath := filepath.Join(x...)
- mkdir(filepath.Dir(inpath))
- err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), fs)
+ var buf bytes.Buffer
+ err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
if err != nil {
- jww.FATAL.Fatalln(err)
+ return err
}
+
+ return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+kind), &buf, fs)
}
-func newContentPathSection(h *hugolib.HugoSites, path string) (string, string) {
- // Forward slashes is used in all examples. Convert if needed.
- // Issue #1133
- createpath := filepath.FromSlash(path)
+func (c *newCommand) newSiteNextStepsText() string {
+ var nextStepsText bytes.Buffer
- if h != nil {
- for _, dir := range h.BaseFs.Content.Dirs {
- createpath = strings.TrimPrefix(createpath, dir.Meta().Filename)
- }
- }
+ nextStepsText.WriteString(`Just a few more steps and you're ready to go:
+
+1. Download a theme into the same-named folder.
+ Choose a theme from https://themes.gohugo.io/ or
+ create your own with the "hugo new theme <THEMENAME>" command.
+2. Perhaps you want to add some content. You can add single files
+ with "hugo new `)
+
+ nextStepsText.WriteString(filepath.Join("<SECTIONNAME>", "<FILENAME>.<FORMAT>"))
+
+ nextStepsText.WriteString(`".
+3. Start the built-in live server via "hugo server".
+
+Visit https://gohugo.io/ for quickstart guide and full documentation.`)
+
+ return nextStepsText.String()
+}
+
+func (c *newCommand) createThemeMD(fs afero.Fs, inpath string) (err error) {
- var section string
- // assume the first directory is the section (kind)
- if strings.Contains(createpath[1:], helpers.FilePathSeparator) {
- parts := strings.Split(strings.TrimPrefix(createpath, helpers.FilePathSeparator), helpers.FilePathSeparator)
- if len(parts) > 0 {
- section = parts[0]
- }
+ by := []byte(`# theme.toml template for a Hugo theme
+# See https://github.com/gohugoio/hugoThemes#themetoml for an example
+name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
+license = "MIT"
+licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
+description = ""
+homepage = "http://example.com/"
+tags = []
+features = []
+min_version = "0.41.0"
+
+[author]
+ name = ""
+ homepage = ""
+
+# If porting an existing theme
+[original]
+ name = ""
+ homepage = ""
+ repo = ""
+`)
+
+ err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs)
+ if err != nil {
+ return
+ }
+
+ err = helpers.WriteToDisk(filepath.Join(inpath, "hugo.toml"), strings.NewReader("# Theme config.\n"), fs)
+ if err != nil {
+ return
}
- return createpath, section
+ return nil
}