github.com/gohugoio/hugo@v0.88.1/commands/convert.go (about)

     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/pkg/errors"
    35  
    36  	"github.com/gohugoio/hugo/hugolib"
    37  
    38  	"github.com/spf13/cobra"
    39  )
    40  
    41  var _ cmder = (*convertCmd)(nil)
    42  
    43  type convertCmd struct {
    44  	outputDir string
    45  	unsafe    bool
    46  
    47  	*baseBuilderCmd
    48  }
    49  
    50  func (b *commandsBuilder) newConvertCmd() *convertCmd {
    51  	cc := &convertCmd{}
    52  
    53  	cmd := &cobra.Command{
    54  		Use:   "convert",
    55  		Short: "Convert your content to different formats",
    56  		Long: `Convert your content (e.g. front matter) to different formats.
    57  
    58  See convert's subcommands toJSON, toTOML and toYAML for more information.`,
    59  		RunE: nil,
    60  	}
    61  
    62  	cmd.AddCommand(
    63  		&cobra.Command{
    64  			Use:   "toJSON",
    65  			Short: "Convert front matter to JSON",
    66  			Long: `toJSON converts all front matter in the content directory
    67  to use JSON for the front matter.`,
    68  			RunE: func(cmd *cobra.Command, args []string) error {
    69  				return cc.convertContents(metadecoders.JSON)
    70  			},
    71  		},
    72  		&cobra.Command{
    73  			Use:   "toTOML",
    74  			Short: "Convert front matter to TOML",
    75  			Long: `toTOML converts all front matter in the content directory
    76  to use TOML for the front matter.`,
    77  			RunE: func(cmd *cobra.Command, args []string) error {
    78  				return cc.convertContents(metadecoders.TOML)
    79  			},
    80  		},
    81  		&cobra.Command{
    82  			Use:   "toYAML",
    83  			Short: "Convert front matter to YAML",
    84  			Long: `toYAML converts all front matter in the content directory
    85  to use YAML for the front matter.`,
    86  			RunE: func(cmd *cobra.Command, args []string) error {
    87  				return cc.convertContents(metadecoders.YAML)
    88  			},
    89  		},
    90  	)
    91  
    92  	cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
    93  	cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
    94  
    95  	cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
    96  
    97  	return cc
    98  }
    99  
   100  func (cc *convertCmd) convertContents(format metadecoders.Format) error {
   101  	if cc.outputDir == "" && !cc.unsafe {
   102  		return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path")
   103  	}
   104  
   105  	c, err := initializeConfig(true, false, false, &cc.hugoBuilderCommon, cc, nil)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	c.Cfg.Set("buildDrafts", true)
   111  
   112  	h, err := hugolib.NewHugoSites(*c.DepsCfg)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	if err := h.Build(hugolib.BuildCfg{SkipRender: true}); err != nil {
   118  		return err
   119  	}
   120  
   121  	site := h.Sites[0]
   122  
   123  	site.Log.Println("processing", len(site.AllPages()), "content files")
   124  	for _, p := range site.AllPages() {
   125  		if err := cc.convertAndSavePage(p, site, format); err != nil {
   126  			return err
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  func (cc *convertCmd) convertAndSavePage(p page.Page, site *hugolib.Site, targetFormat metadecoders.Format) error {
   133  	// The resources are not in .Site.AllPages.
   134  	for _, r := range p.Resources().ByType("page") {
   135  		if err := cc.convertAndSavePage(r.(page.Page), site, targetFormat); err != nil {
   136  			return err
   137  		}
   138  	}
   139  
   140  	if p.File().IsZero() {
   141  		// No content file.
   142  		return nil
   143  	}
   144  
   145  	errMsg := fmt.Errorf("Error processing file %q", p.Path())
   146  
   147  	site.Log.Infoln("Attempting to convert", p.File().Filename())
   148  
   149  	f := p.File()
   150  	file, err := f.FileInfo().Meta().Open()
   151  	if err != nil {
   152  		site.Log.Errorln(errMsg)
   153  		file.Close()
   154  		return nil
   155  	}
   156  
   157  	pf, err := pageparser.ParseFrontMatterAndContent(file)
   158  	if err != nil {
   159  		site.Log.Errorln(errMsg)
   160  		file.Close()
   161  		return err
   162  	}
   163  
   164  	file.Close()
   165  
   166  	// better handling of dates in formats that don't have support for them
   167  	if pf.FrontMatterFormat == metadecoders.JSON || pf.FrontMatterFormat == metadecoders.YAML || pf.FrontMatterFormat == metadecoders.TOML {
   168  		for k, v := range pf.FrontMatter {
   169  			switch vv := v.(type) {
   170  			case time.Time:
   171  				pf.FrontMatter[k] = vv.Format(time.RFC3339)
   172  			}
   173  		}
   174  	}
   175  
   176  	var newContent bytes.Buffer
   177  	err = parser.InterfaceToFrontMatter(pf.FrontMatter, targetFormat, &newContent)
   178  	if err != nil {
   179  		site.Log.Errorln(errMsg)
   180  		return err
   181  	}
   182  
   183  	newContent.Write(pf.Content)
   184  
   185  	newFilename := p.File().Filename()
   186  
   187  	if cc.outputDir != "" {
   188  		contentDir := strings.TrimSuffix(newFilename, p.Path())
   189  		contentDir = filepath.Base(contentDir)
   190  
   191  		newFilename = filepath.Join(cc.outputDir, contentDir, p.Path())
   192  	}
   193  
   194  	fs := hugofs.Os
   195  	if err := helpers.WriteToDisk(newFilename, &newContent, fs); err != nil {
   196  		return errors.Wrapf(err, "Failed to save file %q:", newFilename)
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  type parsedFile struct {
   203  	frontMatterFormat metadecoders.Format
   204  	frontMatterSource []byte
   205  	frontMatter       map[string]interface{}
   206  
   207  	// Everything after Front Matter
   208  	content []byte
   209  }