diff --git a/commandsnew/aardvark.go b/commandsnew/aardvark.go index e0fdaad206f..e6d114c0399 100644 --- a/commandsnew/aardvark.go +++ b/commandsnew/aardvark.go @@ -26,11 +26,7 @@ import ( "github.com/spf13/cobra" ) -var ( - // TODO1 - ErrHelp = errors.New("help requested") -) - +// Execute executes a command. func Execute(args []string) error { x, err := newExec() if err != nil { @@ -41,6 +37,33 @@ func Execute(args []string) error { return err } +// newExec wires up all of Hugo's CLI. +func newExec() (*simplecobra.Exec, error) { + rootCmd := &rootCommand{ + commands: []simplecobra.Commander{ + newVersionCmd(), + newEnvCommand(), + newServerCommand(), + newDeployCommand(), + newConfigCommand(), + newNewCommand(), + newConvertCommand(), + newImportCommand(), + newListCommand(), + newModCommands(), + newGenCommand(), + }, + } + + return simplecobra.New(rootCmd) + +} + +var ( + // TODO1 + ErrHelp = errors.New("help requested") +) + func newEnvCommand() simplecobra.Commander { return &simpleCommand{ name: "env", @@ -70,27 +93,6 @@ func newEnvCommand() simplecobra.Commander { } } -// newExec wires up all of Hugo's CLI. -func newExec() (*simplecobra.Exec, error) { - rootCmd := &rootCommand{ - commands: []simplecobra.Commander{ - newVersionCmd(), - newEnvCommand(), - newServerCommand(), - newDeployCommand(), - newConfigCommand(), - newNewCommand(), - newImportCommand(), - newListCommand(), - newModCommands(), - newGenCommand(), - }, - } - - return simplecobra.New(rootCmd) - -} - func newVersionCmd() simplecobra.Commander { return &simpleCommand{ name: "version", diff --git a/commandsnew/convert.go b/commandsnew/convert.go index 20f152964e3..06459c1ee82 100644 --- a/commandsnew/convert.go +++ b/commandsnew/convert.go @@ -1,23 +1,78 @@ package commandsnew import ( + "bytes" "context" + "fmt" + "path/filepath" + "strings" + "time" "github.com/bep/simplecobra" ck "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/parser" + "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/gohugoio/hugo/parser/pageparser" + "github.com/gohugoio/hugo/resources/page" "github.com/spf13/cobra" ) func newConvertCommand() *convertCommand { - return &convertCommand{ - commands: []simplecobra.Commander{}, + var c *convertCommand + c = &convertCommand{ + commands: []simplecobra.Commander{ + &simpleCommand{ + name: "toJSON", + short: "Convert front matter to JSON", + long: `toJSON converts all front matter in the content directory +to use JSON for the front matter.`, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + return c.convertContents(metadecoders.JSON) + }, + withc: func(cmd *cobra.Command) { + }, + }, + &simpleCommand{ + name: "toTOML", + short: "Convert front matter to TOML", + long: `toTOML converts all front matter in the content directory +to use TOML for the front matter.`, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + return c.convertContents(metadecoders.TOML) + }, + withc: func(cmd *cobra.Command) { + }, + }, + &simpleCommand{ + name: "toYAML", + short: "Convert front matter to YAML", + long: `toYAML converts all front matter in the content directory +to use YAML for the front matter.`, + run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error { + return c.convertContents(metadecoders.YAML) + }, + withc: func(cmd *cobra.Command) { + }, + }, + }, } - + return c } type convertCommand struct { + // Flags. + outputDir string + unsafe bool + + // Deps. r *rootCommand + h *hugolib.HugoSites + // Commmands. commands []ck.Commander } @@ -34,12 +89,132 @@ func (c *convertCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, ar } func (c *convertCommand) WithCobraCommand(cmd *cobra.Command) error { - cmd.Short = "Print the site configuration" - cmd.Long = `Print the site configuration, both default and custom settings.` + cmd.Short = "Convert your content to different formats" + cmd.Long = `Convert your content (e.g. front matter) to different formats. + +See convert's subcommands toJSON, toTOML and toYAML for more information.` + + cmd.PersistentFlags().StringVarP(&c.outputDir, "output", "o", "", "filesystem path to write files to") + cmd.PersistentFlags().BoolVar(&c.unsafe, "unsafe", false, "enable less safe operations, please backup first") + return nil } func (c *convertCommand) Init(cd *simplecobra.Commandeer) error { c.r = cd.Root.Command.(*rootCommand) + cfg := config.New() + cfg.Set("buildDrafts", true) + h, err := c.r.Hugo(flagsToCfg(cd, cfg)) + if err != nil { + return err + } + c.h = h return nil } + +func (c *convertCommand) convertContents(format metadecoders.Format) error { + if c.outputDir == "" && !c.unsafe { + return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path") + } + + if err := c.h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil { + return err + } + + site := c.h.Sites[0] + + var pagesBackedByFile page.Pages + for _, p := range site.AllPages() { + if p.File().IsZero() { + continue + } + pagesBackedByFile = append(pagesBackedByFile, p) + } + + site.Log.Println("processing", len(pagesBackedByFile), "content files") + for _, p := range site.AllPages() { + if err := c.convertAndSavePage(p, site, format); err != nil { + return err + } + } + return nil +} + +func (c *convertCommand) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error { + // The resources are not in .Site.AllPages. + for _, r := range p.Resources().ByType("page") { + if err := c.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil { + return err + } + } + + if p.File().IsZero() { + // No content file. + return nil + } + + errMsg := fmt.Errorf("Error processing file %q", p.File().Path()) + + site.Log.Infoln("Attempting to convert", p.File().Filename()) + + f := p.File() + file, err := f.FileInfo().Meta().Open() + if err != nil { + site.Log.Errorln(errMsg) + file.Close() + return nil + } + + pf, err := pageparser.ParseFrontMatterAndContent(file) + if err != nil { + site.Log.Errorln(errMsg) + file.Close() + return err + } + + file.Close() + + // better handling of dates in formats that don't have support for them + if pf.FrontMatterFormat == metadecoders.JSON || pf.FrontMatterFormat == metadecoders.YAML || pf.FrontMatterFormat == metadecoders.TOML { + for k, v := range pf.FrontMatter { + switch vv := v.(type) { + case time.Time: + pf.FrontMatter[k] = vv.Format(time.RFC3339) + } + } + } + + var newContent bytes.Buffer + err = parser.InterfaceToFrontMatter(pf.FrontMatter, targetFormat, &newContent) + if err != nil { + site.Log.Errorln(errMsg) + return err + } + + newContent.Write(pf.Content) + + newFilename := p.File().Filename() + + if c.outputDir != "" { + contentDir := strings.TrimSuffix(newFilename, p.File().Path()) + contentDir = filepath.Base(contentDir) + + newFilename = filepath.Join(c.outputDir, contentDir, p.File().Path()) + } + + fs := hugofs.Os + if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil { + return fmt.Errorf("Failed to save file %q:: %w", newFilename, err) + } + + return nil +} + +type parsedFile struct { + frontMatterFormat metadecoders.Format + frontMatterSource []byte + frontMatter map[string]any + + // Everything after Front Matter + content []byte +} diff --git a/testscripts/commands/convert.txt b/testscripts/commands/convert.txt new file mode 100644 index 00000000000..1cf756215ce --- /dev/null +++ b/testscripts/commands/convert.txt @@ -0,0 +1,42 @@ +# Test the convert commands. + +hugo convert -h +stdout 'Convert your content' +hugo convert toJSON -h +stdout 'to use JSON for the front matter' +hugo convert toTOML -h +stdout 'to use TOML for the front matter' +hugo convert toYAML -h +stdout 'to use YAML for the front matter' + +hugo convert toJSON -o myjsoncontent +stdout 'processing 3 content files' +grep '^{' myjsoncontent/content/mytoml.md +grep '^{' myjsoncontent/content/myjson.md +grep '^{' myjsoncontent/content/myyaml.md +hugo convert toYAML -o myyamlcontent +stdout 'processing 3 content files' +hugo convert toTOML -o mytomlcontent +stdout 'processing 3 content files' + + + + + +-- hugo.toml -- +baseURL = "http://example.org/" +-- content/mytoml.md -- ++++ +title = "TOML" ++++ +TOML content +-- content/myjson.md -- +{ + "title": "JSON" +} +JSON content +-- content/myyaml.md -- +--- +title: YAML +--- +YAML content diff --git a/testscripts/unfinished/convert.txt b/testscripts/unfinished/convert.txt deleted file mode 100644 index 11d064c01d4..00000000000 --- a/testscripts/unfinished/convert.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Test the convert commands. - -hugo convert -h -stdout 'asdfsdf' \ No newline at end of file