hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

convert.go (5369B)

    1 // Copyright 2019 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package commands
   15 
   16 import (
   17 	"bytes"
   18 	"fmt"
   19 	"path/filepath"
   20 	"strings"
   21 	"time"
   22 
   23 	"github.com/gohugoio/hugo/parser/pageparser"
   24 
   25 	"github.com/gohugoio/hugo/resources/page"
   26 
   27 	"github.com/gohugoio/hugo/hugofs"
   28 
   29 	"github.com/gohugoio/hugo/helpers"
   30 
   31 	"github.com/gohugoio/hugo/parser"
   32 	"github.com/gohugoio/hugo/parser/metadecoders"
   33 
   34 	"github.com/gohugoio/hugo/hugolib"
   35 
   36 	"github.com/spf13/cobra"
   37 )
   38 
   39 var _ cmder = (*convertCmd)(nil)
   40 
   41 type convertCmd struct {
   42 	outputDir string
   43 	unsafe    bool
   44 
   45 	*baseBuilderCmd
   46 }
   47 
   48 func (b *commandsBuilder) newConvertCmd() *convertCmd {
   49 	cc := &convertCmd{}
   50 
   51 	cmd := &cobra.Command{
   52 		Use:   "convert",
   53 		Short: "Convert your content to different formats",
   54 		Long: `Convert your content (e.g. front matter) to different formats.
   55 
   56 See convert's subcommands toJSON, toTOML and toYAML for more information.`,
   57 		RunE: nil,
   58 	}
   59 
   60 	cmd.AddCommand(
   61 		&cobra.Command{
   62 			Use:   "toJSON",
   63 			Short: "Convert front matter to JSON",
   64 			Long: `toJSON converts all front matter in the content directory
   65 to use JSON for the front matter.`,
   66 			RunE: func(cmd *cobra.Command, args []string) error {
   67 				return cc.convertContents(metadecoders.JSON)
   68 			},
   69 		},
   70 		&cobra.Command{
   71 			Use:   "toTOML",
   72 			Short: "Convert front matter to TOML",
   73 			Long: `toTOML converts all front matter in the content directory
   74 to use TOML for the front matter.`,
   75 			RunE: func(cmd *cobra.Command, args []string) error {
   76 				return cc.convertContents(metadecoders.TOML)
   77 			},
   78 		},
   79 		&cobra.Command{
   80 			Use:   "toYAML",
   81 			Short: "Convert front matter to YAML",
   82 			Long: `toYAML converts all front matter in the content directory
   83 to use YAML for the front matter.`,
   84 			RunE: func(cmd *cobra.Command, args []string) error {
   85 				return cc.convertContents(metadecoders.YAML)
   86 			},
   87 		},
   88 	)
   89 
   90 	cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
   91 	cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
   92 
   93 	cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
   94 
   95 	return cc
   96 }
   97 
   98 func (cc *convertCmd) convertContents(format metadecoders.Format) error {
   99 	if cc.outputDir == "" && !cc.unsafe {
  100 		return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
  101 	}
  102 
  103 	c, err := initializeConfig(true, false, false, &cc.hugoBuilderCommon, cc, nil)
  104 	if err != nil {
  105 		return err
  106 	}
  107 
  108 	c.Cfg.Set("buildDrafts", true)
  109 
  110 	h, err := hugolib.NewHugoSites(*c.DepsCfg)
  111 	if err != nil {
  112 		return err
  113 	}
  114 
  115 	if err := h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
  116 		return err
  117 	}
  118 
  119 	site := h.Sites[0]
  120 
  121 	site.Log.Println("processing", len(site.AllPages()), "content files")
  122 	for _, p := range site.AllPages() {
  123 		if err := cc.convertAndSavePage(p, site, format); err != nil {
  124 			return err
  125 		}
  126 	}
  127 	return nil
  128 }
  129 
  130 func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
  131 	// The resources are not in .Site.AllPages.
  132 	for _, r := range p.Resources().ByType("page") {
  133 		if err := cc.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
  134 			return err
  135 		}
  136 	}
  137 
  138 	if p.File().IsZero() {
  139 		// No content file.
  140 		return nil
  141 	}
  142 
  143 	errMsg := fmt.Errorf("Error processing file %q", p.File().Path())
  144 
  145 	site.Log.Infoln("Attempting to convert", p.File().Filename())
  146 
  147 	f := p.File()
  148 	file, err := f.FileInfo().Meta().Open()
  149 	if err != nil {
  150 		site.Log.Errorln(errMsg)
  151 		file.Close()
  152 		return nil
  153 	}
  154 
  155 	pf, err := pageparser.ParseFrontMatterAndContent(file)
  156 	if err != nil {
  157 		site.Log.Errorln(errMsg)
  158 		file.Close()
  159 		return err
  160 	}
  161 
  162 	file.Close()
  163 
  164 	// better handling of dates in formats that don't have support for them
  165 	if pf.FrontMatterFormat == metadecoders.JSON || pf.FrontMatterFormat == metadecoders.YAML || pf.FrontMatterFormat == metadecoders.TOML {
  166 		for k, v := range pf.FrontMatter {
  167 			switch vv := v.(type) {
  168 			case time.Time:
  169 				pf.FrontMatter[k] = vv.Format(time.RFC3339)
  170 			}
  171 		}
  172 	}
  173 
  174 	var newContent bytes.Buffer
  175 	err = parser.InterfaceToFrontMatter(pf.FrontMatter, targetFormat, &newContent)
  176 	if err != nil {
  177 		site.Log.Errorln(errMsg)
  178 		return err
  179 	}
  180 
  181 	newContent.Write(pf.Content)
  182 
  183 	newFilename := p.File().Filename()
  184 
  185 	if cc.outputDir != "" {
  186 		contentDir := strings.TrimSuffix(newFilename, p.File().Path())
  187 		contentDir = filepath.Base(contentDir)
  188 
  189 		newFilename = filepath.Join(cc.outputDir, contentDir, p.File().Path())
  190 	}
  191 
  192 	fs := hugofs.Os
  193 	if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
  194 		return fmt.Errorf("Failed to save file %q:: %w", newFilename, err)
  195 	}
  196 
  197 	return nil
  198 }
  199 
  200 type parsedFile struct {
  201 	frontMatterFormat metadecoders.Format
  202 	frontMatterSource []byte
  203 	frontMatter       map[string]any
  204 
  205 	// Everything after Front Matter
  206 	content []byte
  207 }