diff options
author | Joe Mooring <joe.mooring@veriphor.com> | 2023-08-16 15:07:01 -0700 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-08-21 10:38:22 +0200 |
commit | b6538532f45bf833226da7277994c1a96d26a56c (patch) | |
tree | 554e35423b4cee3ca6f1d526097bbc8677acb7b2 | |
parent | 90944aa261acf596af774dcea0db1fc31dccc973 (diff) |
commands/new: Embed site and theme skeletons
The skeletons are used when creating new sites and themes with the CLI.
Closes #11358
41 files changed, 465 insertions, 203 deletions
diff --git a/commands/helpers.go b/commands/helpers.go index d6c5e43ac..3b0c50159 100644 --- a/commands/helpers.go +++ b/commands/helpers.go @@ -14,7 +14,6 @@ package commands import ( - "bytes" "errors" "fmt" "log" @@ -24,8 +23,6 @@ import ( "github.com/bep/simplecobra" "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/afero" "github.com/spf13/pflag" ) @@ -123,11 +120,3 @@ func mkdir(x ...string) { log.Fatal(err) } } - -func touchFile(fs afero.Fs, filename string) { - mkdir(filepath.Dir(filename)) - err := helpers.WriteToDisk(filename, bytes.NewReader([]byte{}), fs) - if err != nil { - log.Fatal(err) - } -} diff --git a/commands/new.go b/commands/new.go index 42644d7f6..3f43a687f 100644 --- a/commands/new.go +++ b/commands/new.go @@ -16,19 +16,13 @@ package commands import ( "bytes" "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/parser" - "github.com/gohugoio/hugo/parser/metadecoders" - "github.com/spf13/afero" + "github.com/gohugoio/hugo/create/skeletons" "github.com/spf13/cobra" ) @@ -99,52 +93,13 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`, } sourceFs := conf.fs.Source - 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 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."+format)) - 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) - } + err = skeletons.CreateSite(createpath, sourceFs, force, format) + if err != nil { + return err } - c.newSiteCreateConfig(sourceFs, createpath, format) - - // 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()) + r.Printf("Congratulations! Your new Hugo site was created in %s.\n\n", createpath) + r.Println(c.newSiteNextStepsText(createpath, format)) return nil }, @@ -173,83 +128,13 @@ according to your needs.`, 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 - } - - 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. + r.Println("Creating new theme in", createpath) -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) + err = skeletons.CreateTheme(createpath, sourceFs) if err != nil { return err } - c.createThemeMD(ps.Fs.Source, createpath) - return nil }, }, @@ -299,77 +184,25 @@ func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error { return nil } -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", - } - - var buf bytes.Buffer - err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf) - if err != nil { - return err - } - - return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+kind), &buf, fs) -} - -func (c *newCommand) newSiteNextStepsText() string { +func (c *newCommand) newSiteNextStepsText(path string, format string) string { + format = strings.ToLower(format) var nextStepsText bytes.Buffer - nextStepsText.WriteString(`Just a few more steps and you're ready to go: + nextStepsText.WriteString(`Just a few more steps... -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 `) +1. Change the current directory to ` + path + `. +2. Create or install a theme: + - Create a new theme with the command "hugo new theme <THEMENAME>" + - Install a theme from https://themes.gohugo.io/ +3. Edit hugo.` + format + `, setting the "theme" property to the theme name. +4. Create new content with the command "hugo new content `) nextStepsText.WriteString(filepath.Join("<SECTIONNAME>", "<FILENAME>.<FORMAT>")) nextStepsText.WriteString(`". -3. Start the built-in live server via "hugo server". +5. Start the embedded web server with the command "hugo server --buildDrafts". -Visit https://gohugo.io/ for quickstart guide and full documentation.`) +See documentation at https://gohugo.io/.`) return nextStepsText.String() } - -func (c *newCommand) createThemeMD(fs afero.Fs, inpath string) (err error) { - - 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.116.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 nil -} diff --git a/create/content.go b/create/content.go index 55159c24c..10442c396 100644 --- a/create/content.go +++ b/create/content.go @@ -42,7 +42,7 @@ const ( // DefaultArchetypeTemplateTemplate is the template used in 'hugo new site' // and the template we use as a fall back. DefaultArchetypeTemplateTemplate = `--- -title: "{{ replace .Name "-" " " | title }}" +title: "{{ replace .File.ContentBaseName "-" " " | title }}" date: {{ .Date }} draft: true --- diff --git a/create/skeletons/site/archetypes/default.md b/create/skeletons/site/archetypes/default.md new file mode 100644 index 000000000..c6f3fcef6 --- /dev/null +++ b/create/skeletons/site/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +title = '{{ replace .File.ContentBaseName "-" " " | title }}' +date = {{ .Date }} +draft = true ++++ diff --git a/create/skeletons/site/assets/.gitkeep b/create/skeletons/site/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/assets/.gitkeep diff --git a/create/skeletons/site/content/.gitkeep b/create/skeletons/site/content/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/content/.gitkeep diff --git a/create/skeletons/site/data/.gitkeep b/create/skeletons/site/data/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/data/.gitkeep diff --git a/create/skeletons/site/i18n/.gitkeep b/create/skeletons/site/i18n/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/i18n/.gitkeep diff --git a/create/skeletons/site/layouts/.gitkeep b/create/skeletons/site/layouts/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/layouts/.gitkeep diff --git a/create/skeletons/site/static/.gitkeep b/create/skeletons/site/static/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/static/.gitkeep diff --git a/create/skeletons/site/themes/.gitkeep b/create/skeletons/site/themes/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/create/skeletons/site/themes/.gitkeep diff --git a/create/skeletons/skeletons.go b/create/skeletons/skeletons.go new file mode 100644 index 000000000..7f7fb1bb7 --- /dev/null +++ b/create/skeletons/skeletons.go @@ -0,0 +1,111 @@ +// 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. +// 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 skeletons + +import ( + "bytes" + "embed" + "errors" + "io/fs" + "path/filepath" + "strings" + + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/parser" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/spf13/afero" +) + +//go:embed all:site/* +var siteFs embed.FS + +//go:embed all:theme/* +var themeFs embed.FS + +// CreateTheme creates a theme skeleton. +func CreateTheme(createpath string, sourceFs afero.Fs) error { + if exists, _ := helpers.Exists(createpath, sourceFs); exists { + return errors.New(createpath + " already exists") + } + return copyFiles(createpath, sourceFs, themeFs) +} + +// CreateSite creates a site skeleton. +func CreateSite(createpath string, sourceFs afero.Fs, force bool, format string) error { + format = strings.ToLower(format) + 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: + var all []string + fs.WalkDir(siteFs, ".", func(path string, d fs.DirEntry, err error) error { + if d.IsDir() && path != "." { + all = append(all, path) + } + return nil + }) + all = append(all, filepath.Join(createpath, "hugo."+format)) + for _, path := range all { + if exists, _ := helpers.Exists(path, sourceFs); exists { + return errors.New(path + " already exists") + } + } + } + } + + err := newSiteCreateConfig(sourceFs, createpath, format) + if err != nil { + return err + } + + return copyFiles(createpath, sourceFs, siteFs) +} + +func copyFiles(createpath string, sourceFs afero.Fs, skeleton embed.FS) error { + return fs.WalkDir(skeleton, ".", func(path string, d fs.DirEntry, err error) error { + _, slug, _ := strings.Cut(path, "/") + if d.IsDir() { + return sourceFs.MkdirAll(filepath.Join(createpath, slug), 0777) + } else { + if filepath.Base(path) != ".gitkeep" { + data, _ := fs.ReadFile(skeleton, path) + return helpers.WriteToDisk(filepath.Join(createpath, slug), bytes.NewReader(data), sourceFs) + } + return nil + } + }) +} + +func newSiteCreateConfig(fs afero.Fs, createpath string, format string) (err error) { + in := map[string]string{ + "baseURL": "https://example.org/", + "title": "My New Hugo Site", + "languageCode": "en-us", + } + + var buf bytes.Buffer + err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(format), &buf) + if err != nil { + return err + } + + return helpers.WriteToDisk(filepath.Join(createpath, "hugo."+format), &buf, fs) +} diff --git a/create/skeletons/theme/LICENSE b/create/skeletons/theme/LICENSE new file mode 100644 index 000000000..8aa26455d --- /dev/null +++ b/create/skeletons/theme/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +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. diff --git a/create/skeletons/theme/README.md b/create/skeletons/theme/README.md new file mode 100644 index 000000000..7cec74e07 --- /dev/null +++ b/create/skeletons/theme/README.md @@ -0,0 +1,7 @@ +# Theme Name + +## Features + +## Installation + +## Configuration diff --git a/create/skeletons/theme/archetypes/default.md b/create/skeletons/theme/archetypes/default.md new file mode 100644 index 000000000..c6f3fcef6 --- /dev/null +++ b/create/skeletons/theme/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +title = '{{ replace .File.ContentBaseName "-" " " | title }}' +date = {{ .Date }} +draft = true ++++ diff --git a/create/skeletons/theme/assets/css/main.css b/create/skeletons/theme/assets/css/main.css new file mode 100644 index 000000000..166ade924 --- /dev/null +++ b/create/skeletons/theme/assets/css/main.css @@ -0,0 +1,22 @@ +body { + color: #222; + font-family: sans-serif; + line-height: 1.5; + margin: 1rem; + max-width: 768px; +} + +header { + border-bottom: 1px solid #222; + margin-bottom: 1rem; +} + +footer { + border-top: 1px solid #222; + margin-top: 1rem; +} + +a { + color: #00e; + text-decoration: none; +} diff --git a/create/skeletons/theme/assets/js/main.js b/create/skeletons/theme/assets/js/main.js new file mode 100644 index 000000000..e2aac5275 --- /dev/null +++ b/create/skeletons/theme/assets/js/main.js @@ -0,0 +1 @@ +console.log('This site was generated by Hugo.'); diff --git a/create/skeletons/theme/config/_default/hugo.toml b/create/skeletons/theme/config/_default/hugo.toml new file mode 100644 index 000000000..123719411 --- /dev/null +++ b/create/skeletons/theme/config/_default/hugo.toml @@ -0,0 +1,4 @@ +[module] + [module.hugoVersion] + extended = false + min = "0.116.0" diff --git a/create/skeletons/theme/config/_default/menus.toml b/create/skeletons/theme/config/_default/menus.toml new file mode 100644 index 000000000..526645f2c --- /dev/null +++ b/create/skeletons/theme/config/_default/menus.toml @@ -0,0 +1,14 @@ +[[main]] +name = 'Home' +pageRef = '/' +weight = 10 + +[[main]] +name = 'Po |