2023-01-04 12:24:36 -05:00
|
|
|
// Copyright 2023 The Hugo Authors. All rights reserved.
|
2015-02-08 10:11:04 -05:00
|
|
|
//
|
2015-11-23 22:16:36 -05:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
2014-05-02 01:06:01 -04:00
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
2015-11-23 22:16:36 -05:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2014-05-02 01:06:01 -04:00
|
|
|
//
|
|
|
|
// 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 (
|
2014-05-08 18:30:11 -04:00
|
|
|
"bytes"
|
2023-01-04 12:24:36 -05:00
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2014-05-08 18:30:11 -04:00
|
|
|
"path/filepath"
|
2014-05-02 01:06:01 -04:00
|
|
|
"strings"
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
"github.com/bep/simplecobra"
|
|
|
|
"github.com/gohugoio/hugo/common/htime"
|
|
|
|
"github.com/gohugoio/hugo/config"
|
2017-06-13 12:42:45 -04:00
|
|
|
"github.com/gohugoio/hugo/create"
|
|
|
|
"github.com/gohugoio/hugo/helpers"
|
2023-01-04 12:24:36 -05:00
|
|
|
"github.com/gohugoio/hugo/parser"
|
|
|
|
"github.com/gohugoio/hugo/parser/metadecoders"
|
2017-06-13 13:07:35 -04:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/spf13/cobra"
|
2014-05-02 01:06:01 -04:00
|
|
|
)
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
func newNewCommand() *newCommand {
|
|
|
|
var (
|
2023-05-22 13:00:07 -04:00
|
|
|
force bool
|
|
|
|
contentType string
|
2023-01-04 12:24:36 -05:00
|
|
|
)
|
2018-04-09 13:36:10 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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
|
|
|
|
}
|
2016-02-05 12:41:40 -05:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
cfg := config.New()
|
|
|
|
cfg.Set("workingDir", createpath)
|
|
|
|
cfg.Set("publishDir", "public")
|
2014-05-02 01:06:01 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sourceFs := conf.fs.Source
|
2015-08-04 05:15:12 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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"),
|
|
|
|
}
|
2015-08-04 05:15:12 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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:
|
2023-05-22 13:00:07 -04:00
|
|
|
all := append(dirs, filepath.Join(createpath, "hugo."+r.format))
|
2023-01-04 12:24:36 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-22 13:00:07 -04:00
|
|
|
c.newSiteCreateConfig(sourceFs, createpath, r.format)
|
2023-01-04 12:24:36 -05:00
|
|
|
|
|
|
|
// 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().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
|
|
|
|
}
|
2018-10-15 11:48:19 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-11-17 22:20:43 -05:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
return c
|
2015-08-04 05:15:12 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
}
|
2014-05-02 01:06:01 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
type newCommand struct {
|
|
|
|
rootCmd *rootCommand
|
2018-04-09 13:36:10 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
commands []simplecobra.Commander
|
|
|
|
}
|
2014-05-08 18:30:11 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
func (c *newCommand) Commands() []simplecobra.Commander {
|
|
|
|
return c.commands
|
2014-05-08 18:30:11 -04:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
func (c *newCommand) Name() string {
|
|
|
|
return "new"
|
|
|
|
}
|
2014-05-02 01:06:01 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
func (c *newCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
|
|
|
return nil
|
|
|
|
}
|
2017-02-04 22:20:06 -05:00
|
|
|
|
2023-05-17 12:45:23 -04:00
|
|
|
func (c *newCommand) Init(cd *simplecobra.Commandeer) error {
|
|
|
|
cmd := cd.CobraCommand
|
2023-01-04 12:24:36 -05:00
|
|
|
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`" + `.
|
2014-05-02 01:06:01 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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
|
2014-05-02 01:06:01 -04:00
|
|
|
}
|
|
|
|
|
2023-05-17 12:45:23 -04:00
|
|
|
func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
2023-01-04 12:24:36 -05:00
|
|
|
c.rootCmd = cd.Root.Command.(*rootCommand)
|
|
|
|
return nil
|
|
|
|
}
|
2014-05-08 18:30:11 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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",
|
2014-05-08 18:30:11 -04:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
var buf bytes.Buffer
|
|
|
|
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
|
2014-05-08 18:30:11 -04:00
|
|
|
if err != nil {
|
2023-01-04 12:24:36 -05:00
|
|
|
return err
|
2014-05-08 18:30:11 -04:00
|
|
|
}
|
2023-01-04 12:24:36 -05:00
|
|
|
|
|
|
|
return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+kind), &buf, fs)
|
2014-05-08 18:30:11 -04:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
func (c *newCommand) newSiteNextStepsText() string {
|
|
|
|
var nextStepsText bytes.Buffer
|
2018-09-19 01:48:17 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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) {
|
2018-09-19 01:48:17 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
by := []byte(`# theme.toml template for a Hugo theme
|
|
|
|
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
|
:sparkles: Implement Page bundling and image handling
This commit is not the smallest in Hugo's history.
Some hightlights include:
* Page bundles (for complete articles, keeping images and content together etc.).
* Bundled images can be processed in as many versions/sizes as you need with the three methods `Resize`, `Fill` and `Fit`.
* Processed images are cached inside `resources/_gen/images` (default) in your project.
* Symbolic links (both files and dirs) are now allowed anywhere inside /content
* A new table based build summary
* The "Total in nn ms" now reports the total including the handling of the files inside /static. So if it now reports more than you're used to, it is just **more real** and probably faster than before (see below).
A site building benchmark run compared to `v0.31.1` shows that this should be slightly faster and use less memory:
```bash
▶ ./benchSite.sh "TOML,num_langs=.*,num_root_sections=5,num_pages=(500|1000),tags_per_page=5,shortcodes,render"
benchmark old ns/op new ns/op delta
BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 101785785 78067944 -23.30%
BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 185481057 149159919 -19.58%
BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 103149918 85679409 -16.94%
BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 203515478 169208775 -16.86%
benchmark old allocs new allocs delta
BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 532464 391539 -26.47%
BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 1056549 772702 -26.87%
BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 555974 406630 -26.86%
BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 1086545 789922 -27.30%
benchmark old bytes new bytes delta
BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 53243246 43598155 -18.12%
BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 105811617 86087116 -18.64%
BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 54558852 44545097 -18.35%
BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 106903858 86978413 -18.64%
```
Fixes #3651
Closes #3158
Fixes #1014
Closes #2021
Fixes #1240
Updates #3757
2017-07-24 03:00:23 -04:00
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
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 = []
|
2023-05-24 10:42:50 -04:00
|
|
|
min_version = "0.112.3"
|
2023-01-04 12:24:36 -05:00
|
|
|
|
|
|
|
[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
|
2015-05-12 12:12:58 -04:00
|
|
|
}
|
|
|
|
|
2023-01-04 12:24:36 -05:00
|
|
|
return nil
|
2015-05-12 12:12:58 -04:00
|
|
|
}
|