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 }