func (f *Filesystem) captureFiles() { walker := func(filePath string, fi os.FileInfo, err error) error { if err != nil { return nil } b, err := f.shouldRead(filePath, fi) if err != nil { return err } if b { rd, err := NewLazyFileReader(hugofs.Source(), filePath) if err != nil { return err } f.add(filePath, rd) } return err } err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker) if err != nil { jww.ERROR.Println(err) } }
func (f *Filesystem) captureFiles() { walker := func(filePath string, fi os.FileInfo, err error) error { if err != nil { return nil } b, err := f.shouldRead(filePath, fi) if err != nil { return err } if b { rd, err := NewLazyFileReader(hugofs.Source(), filePath) if err != nil { return err } f.add(filePath, rd) } return err } err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker) if err != nil { jww.ERROR.Println(err) if err == helpers.WalkRootTooShortError { panic("The root path is too short. If this is a test, make sure to init the content paths.") } } }
func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) error { t.checkState() // get the suffix and switch on that ext := filepath.Ext(path) switch ext { case ".amber": templateName := strings.TrimSuffix(name, filepath.Ext(name)) + ".html" compiler := amber.New() b, err := afero.ReadFile(hugofs.Source(), path) if err != nil { return err } // Parse the input data if err := compiler.ParseData(b, path); err != nil { return err } if _, err := compiler.CompileWithTemplate(t.New(templateName)); err != nil { return err } case ".ace": var innerContent, baseContent []byte innerContent, err := afero.ReadFile(hugofs.Source(), path) if err != nil { return err } if baseTemplatePath != "" { baseContent, err = afero.ReadFile(hugofs.Source(), baseTemplatePath) if err != nil { return err } } return t.AddAceTemplate(name, baseTemplatePath, path, baseContent, innerContent) default: if baseTemplatePath != "" { return t.AddTemplateFileWithMaster(name, path, baseTemplatePath) } b, err := afero.ReadFile(hugofs.Source(), path) if err != nil { return err } jww.DEBUG.Printf("Add template file from path %s", path) return t.AddTemplate(name, string(b)) } return nil }
func TestDoNewSite_error_force_config_inside_exists(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") configPath := filepath.Join(basepath, "config.toml") hugofs.InitMemFs() hugofs.Source().MkdirAll(basepath, 777) hugofs.Source().Create(configPath) err := doNewSite(basepath, true) assert.NotNil(t, err) }
// resGetResource loads the content of a local or remote file func resGetResource(url string) ([]byte, error) { if url == "" { return nil, nil } if strings.Contains(url, "://") { return resGetRemote(url, hugofs.Source(), http.DefaultClient) } return resGetLocal(url, hugofs.Source()) }
func TestDoNewSite_error_base_exists(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") hugofs.InitMemFs() hugofs.Source().MkdirAll(basepath, 777) hugofs.Source().Create(filepath.Join(basepath, "foo")) // Since the directory already exists and isn't empty, expect an error err := doNewSite(basepath, false) assert.NotNil(t, err) }
// getDirList provides NewWatcher() with a list of directories to watch for changes. func getDirList() []string { var a []string dataDir := helpers.AbsPathify(viper.GetString("DataDir")) layoutDir := helpers.AbsPathify(viper.GetString("LayoutDir")) walker := func(path string, fi os.FileInfo, err error) error { if err != nil { if path == dataDir && os.IsNotExist(err) { jww.WARN.Println("Skip DataDir:", err) return nil } if path == layoutDir && os.IsNotExist(err) { jww.WARN.Println("Skip LayoutDir:", err) return nil } jww.ERROR.Println("Walker: ", err) return nil } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := filepath.EvalSymlinks(path) if err != nil { jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err) return nil } linkfi, err := os.Stat(link) if err != nil { jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) return nil } if !linkfi.Mode().IsRegular() { jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path) } return nil } if fi.IsDir() { if fi.Name() == ".git" || fi.Name() == "node_modules" || fi.Name() == "bower_components" { return filepath.SkipDir } a = append(a, path) } return nil } helpers.SymbolicWalk(hugofs.Source(), dataDir, walker) helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("ContentDir")), walker) helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("LayoutDir")), walker) helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("StaticDir")), walker) if helpers.ThemeSet() { helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("themesDir")+"/"+viper.GetString("theme")), walker) } return a }
func testCommonResetState() { hugofs.InitMemFs() viper.Reset() viper.SetFs(hugofs.Source()) loadDefaultSettings() // Default is false, but true is easier to use as default in tests viper.Set("DefaultContentLanguageInSubdir", true) if err := hugofs.Source().Mkdir("content", 0755); err != nil { panic("Content folder creation failed.") } }
func doNewSite(basepath string, force bool) error { dirs := []string{ filepath.Join(basepath, "layouts"), filepath.Join(basepath, "content"), filepath.Join(basepath, "archetypes"), filepath.Join(basepath, "static"), filepath.Join(basepath, "data"), filepath.Join(basepath, "themes"), } if exists, _ := helpers.Exists(basepath, hugofs.Source()); exists { if isDir, _ := helpers.IsDir(basepath, hugofs.Source()); !isDir { return errors.New(basepath + " already exists but not a directory") } isEmpty, _ := helpers.IsEmpty(basepath, hugofs.Source()) switch { case !isEmpty && !force: return errors.New(basepath + " already exists and is not empty") case !isEmpty && force: all := append(dirs, filepath.Join(basepath, "config."+configFormat)) for _, path := range all { if exists, _ := helpers.Exists(path, hugofs.Source()); exists { return errors.New(path + " already exists") } } } } for _, dir := range dirs { hugofs.Source().MkdirAll(dir, 0777) } createConfig(basepath, configFormat) jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %q.\n\n", basepath) jww.FEEDBACK.Println(`Just a few more steps and you're ready to go: 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 <SECTIONNAME>/<FILENAME>.<FORMAT>" 3. Start the built-in live server via "hugo server" For more information read the documentation at https://gohugo.io.`) return nil }
func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, masterFilename string) error { // There is currently no known way to associate a cloned template with an existing one. // This funky master/overlay design will hopefully improve in a future version of Go. // // Simplicity is hard. // // Until then we'll have to live with this hackery. // // See https://github.com/golang/go/issues/14285 // // So, to do minimum amount of changes to get this to work: // // 1. Lookup or Parse the master // 2. Parse and store the overlay in a separate map masterTpl := t.Lookup(masterFilename) if masterTpl == nil { b, err := afero.ReadFile(hugofs.Source(), masterFilename) if err != nil { return err } masterTpl, err = t.New(masterFilename).Parse(string(b)) if err != nil { // TODO(bep) Add a method that does this t.errors = append(t.errors, &templateErr{name: name, err: err}) return err } } b, err := afero.ReadFile(hugofs.Source(), overlayFilename) if err != nil { return err } overlayTpl, err := template.Must(masterTpl.Clone()).Parse(string(b)) if err != nil { t.errors = append(t.errors, &templateErr{name: name, err: err}) } else { // The extra lookup is a workaround, see // * https://github.com/golang/go/issues/16101 // * https://github.com/spf13/hugo/issues/2549 t.overlays[name] = overlayTpl.Lookup(overlayTpl.Name()) } return err }
// NewContent adds new content to a Hugo site. func NewContent(cmd *cobra.Command, args []string) error { if err := InitializeConfig(); err != nil { return err } if flagChanged(cmd.Flags(), "format") { viper.Set("MetaDataFormat", configFormat) } if flagChanged(cmd.Flags(), "editor") { viper.Set("NewContentEditor", contentEditor) } if len(args) < 1 { return newUserError("path needs to be provided") } createpath := args[0] var kind string createpath, kind = newContentPathSection(createpath) if contentType != "" { kind = contentType } return create.NewContent(hugofs.Source(), kind, createpath) }
func createThemeMD(inpath string) (err error) { by := []byte(`# theme.toml template for a Hugo theme # See https://github.com/spf13/hugoThemes#themetoml for an example name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `" license = "MIT" licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE.md" description = "" homepage = "http://siteforthistheme.com/" tags = ["", ""] features = ["", ""] min_version = 0.15 [author] name = "" homepage = "" # If porting an existing theme [original] name = "" homepage = "" repo = "" `) err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), hugofs.Source()) if err != nil { return } return nil }
// getCSV expects a data separator and one or n-parts of a URL to a resource which // can either be a local or a remote one. // The data separator can be a comma, semi-colon, pipe, etc, but only one character. // If you provide multiple parts for the URL they will be joined together to the final URL. // GetCSV returns nil or a slice slice to use in a short code. func getCSV(sep string, urlParts ...string) [][]string { var d [][]string url := strings.Join(urlParts, "") var clearCacheSleep = func(i int, u string) { jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep) time.Sleep(resSleep) resDeleteCache(url, hugofs.Source()) } for i := 0; i <= resRetries; i++ { c, err := resGetResource(url) if err == nil && false == bytes.Contains(c, []byte(sep)) { err = errors.New("Cannot find separator " + sep + " in CSV.") } if err != nil { jww.ERROR.Printf("Failed to read csv resource %s with error message %s", url, err) clearCacheSleep(i, url) continue } if d, err = parseCSV(c, sep); err != nil { jww.ERROR.Printf("Failed to parse csv file %s with error message %s", url, err) clearCacheSleep(i, url) continue } break } return d }
func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) { if !filepath.IsAbs(inpath) { inpath = helpers.AbsPathify(inpath) } jww.INFO.Println("creating", inpath) if safe { err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), hugofs.Source()) } else { err = helpers.WriteToDisk(inpath, bytes.NewReader(by), hugofs.Source()) } if err != nil { return } return nil }
func loadJekyllConfig(jekyllRoot string) map[string]interface{} { fs := hugofs.Source() path := filepath.Join(jekyllRoot, "_config.yml") exists, err := helpers.Exists(path, fs) if err != nil || !exists { jww.WARN.Println("_config.yaml not found: Is the specified Jekyll root correct?") return nil } f, err := fs.Open(path) if err != nil { return nil } defer f.Close() b, err := ioutil.ReadAll(f) if err != nil { return nil } c, err := parser.HandleYAMLMetaData(b) if err != nil { return nil } return c.(map[string]interface{}) }
func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") hugofs.InitMemFs() hugofs.Source().MkdirAll(basepath, 777) err := doNewSite(basepath, false) assert.Nil(t, err) }
func (f *Filesystem) shouldRead(filePath string, fi os.FileInfo) (bool, error) { if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := filepath.EvalSymlinks(filePath) if err != nil { jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", filePath, err) return false, nil } linkfi, err := hugofs.Source().Stat(link) if err != nil { jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) return false, nil } if !linkfi.Mode().IsRegular() { jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", filePath) } return false, nil } if fi.IsDir() { if f.avoid(filePath) || isNonProcessablePath(filePath) { return false, filepath.SkipDir } return false, nil } if isNonProcessablePath(filePath) { return false, nil } return true, nil }
func initFs() error { hugofs.SetSource(new(afero.MemMapFs)) perm := os.FileMode(0755) var err error // create directories dirs := []string{ "archetypes", "content", filepath.Join("themes", "sample", "archetypes"), } for _, dir := range dirs { dir = filepath.Join(os.TempDir(), dir) err = hugofs.Source().Mkdir(dir, perm) if err != nil { return err } } // create files for _, v := range []struct { path string content string }{ { path: filepath.Join(os.TempDir(), "archetypes", "post.md"), content: "+++\ndate = \"2015-01-12T19:20:04-07:00\"\ntitle = \"post arch\"\ntest = \"test1\"\n+++\n", }, { path: filepath.Join(os.TempDir(), "archetypes", "product.md"), content: "+++\n+++\n", }, } { f, err := hugofs.Source().Create(v.path) if err != nil { return err } defer f.Close() _, err = f.Write([]byte(v.content)) if err != nil { return err } } return nil }
func touchFile(x ...string) { inpath := filepath.Join(x...) mkdir(filepath.Dir(inpath)) err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), hugofs.Source()) if err != nil { jww.FATAL.Fatalln(err) } }
func TestDoNewSite_force_empty_dir(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") hugofs.InitMemFs() hugofs.Source().MkdirAll(basepath, 777) err := doNewSite(basepath, true) assert.Nil(t, err) checkNewSiteInited(basepath, t) }
func getStaticSourceFs() afero.Fs { source := hugofs.Source() themeDir, err := helpers.GetThemeStaticDirPath() staticDir := helpers.GetStaticDirPath() + helpers.FilePathSeparator useTheme := true useStatic := true if err != nil { jww.WARN.Println(err) useTheme = false } else { if _, err := source.Stat(themeDir); os.IsNotExist(err) { jww.WARN.Println("Unable to find Theme Static Directory:", themeDir) useTheme = false } } if _, err := source.Stat(staticDir); os.IsNotExist(err) { jww.WARN.Println("Unable to find Static Directory:", staticDir) useStatic = false } if !useStatic && !useTheme { return nil } if !useStatic { jww.INFO.Println(themeDir, "is the only static directory available to sync from") return afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir)) } if !useTheme { jww.INFO.Println(staticDir, "is the only static directory available to sync from") return afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir)) } jww.INFO.Println("using a UnionFS for static directory comprised of:") jww.INFO.Println("Base:", themeDir) jww.INFO.Println("Overlay:", staticDir) base := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), themeDir)) overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), staticDir)) return afero.NewCopyOnWriteFs(base, overlay) }
func testRetryWhenDone() wd { cd := viper.GetString("cacheDir") viper.Set("cacheDir", helpers.GetTempDir("", hugofs.Source())) var tmpSleep time.Duration tmpSleep, resSleep = resSleep, time.Millisecond return wd{func() { viper.Set("cacheDir", cd) resSleep = tmpSleep }} }
func doNewSite(basepath string, force bool) error { dirs := []string{ filepath.Join(basepath, "layouts"), filepath.Join(basepath, "content"), filepath.Join(basepath, "archetypes"), filepath.Join(basepath, "static"), filepath.Join(basepath, "data"), filepath.Join(basepath, "themes"), } if exists, _ := helpers.Exists(basepath, hugofs.Source()); exists { if isDir, _ := helpers.IsDir(basepath, hugofs.Source()); !isDir { return errors.New(basepath + " already exists but not a directory") } isEmpty, _ := helpers.IsEmpty(basepath, hugofs.Source()) switch { case !isEmpty && !force: return errors.New(basepath + " already exists and is not empty") case !isEmpty && force: all := append(dirs, filepath.Join(basepath, "config."+configFormat)) for _, path := range all { if exists, _ := helpers.Exists(path, hugofs.Source()); exists { return errors.New(path + " already exists") } } } } for _, dir := range dirs { hugofs.Source().MkdirAll(dir, 0777) } createConfig(basepath, configFormat) jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath) jww.FEEDBACK.Println(nextStepsText()) return nil }
func TestNewContent(t *testing.T) { initViper() err := initFs() if err != nil { t.Fatalf("initialization error: %s", err) } cases := []struct { kind string path string resultStrings []string }{ {"post", "post/sample-1.md", []string{`title = "sample 1"`, `test = "test1"`}}, {"stump", "stump/sample-2.md", []string{`title = "sample 2"`}}, // no archetype file {"", "sample-3.md", []string{`title = "sample 3"`}}, // no archetype {"product", "product/sample-4.md", []string{`title = "sample 4"`}}, // empty archetype front matter } for i, c := range cases { err = create.NewContent(hugofs.Source(), c.kind, c.path) if err != nil { t.Errorf("[%d] NewContent: %s", i, err) } fname := filepath.Join(os.TempDir(), "content", filepath.FromSlash(c.path)) _, err = hugofs.Source().Stat(fname) if err != nil { t.Errorf("[%d] Stat: %s", i, err) } for _, v := range c.resultStrings { found, err := afero.FileContainsBytes(hugofs.Source(), fname, []byte(v)) if err != nil { t.Errorf("[%d] FileContainsBytes: %s", i, err) } if !found { t.Errorf("content missing from output: %q", v) } } } }
func getThemeDirPath(path string) (string, error) { if !ThemeSet() { return "", errors.New("No theme set") } themeDir := filepath.Join(GetThemeDir(), path) if _, err := hugofs.Source().Stat(themeDir); os.IsNotExist(err) { return "", fmt.Errorf("Unable to find %s directory for theme %s in %s", path, viper.GetString("theme"), themeDir) } return themeDir, nil }
// isThemeVsHugoVersionMismatch returns whether the current Hugo version is // less than the theme's min_version. func isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) { if !helpers.ThemeSet() { return } themeDir := helpers.GetThemeDir() fs := hugofs.Source() path := filepath.Join(themeDir, "theme.toml") exists, err := helpers.Exists(path, fs) if err != nil || !exists { return } f, err := fs.Open(path) if err != nil { return } defer f.Close() b, err := ioutil.ReadAll(f) if err != nil { return } c, err := parser.HandleTOMLMetaData(b) if err != nil { return } config := c.(map[string]interface{}) if minVersion, ok := config["min_version"]; ok { switch minVersion.(type) { case float32: return helpers.HugoVersionNumber < minVersion.(float32), fmt.Sprint(minVersion) case float64: return helpers.HugoVersionNumber < minVersion.(float64), fmt.Sprint(minVersion) default: return } } return }
// Undraft publishes the specified content by setting its draft status // to false and setting its publish date to now. If the specified content is // not a draft, it will log an error. func Undraft(cmd *cobra.Command, args []string) error { if err := InitializeConfig(); err != nil { return err } if len(args) < 1 { return newUserError("a piece of content needs to be specified") } location := args[0] // open the file f, err := hugofs.Source().Open(location) if err != nil { return err } // get the page from file p, err := parser.ReadFrom(f) f.Close() if err != nil { return err } w, err := undraftContent(p) if err != nil { return newSystemErrorF("an error occurred while undrafting %q: %s", location, err) } f, err = hugofs.Source().OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644) if err != nil { return newSystemErrorF("%q not be undrafted due to error opening file to save changes: %q\n", location, err) } defer f.Close() _, err = w.WriteTo(f) if err != nil { return newSystemErrorF("%q not be undrafted due to save error: %q\n", location, err) } return nil }
func checkNewSiteInited(basepath string, t *testing.T) { paths := []string{ filepath.Join(basepath, "layouts"), filepath.Join(basepath, "content"), filepath.Join(basepath, "archetypes"), filepath.Join(basepath, "static"), filepath.Join(basepath, "data"), filepath.Join(basepath, "config.toml"), } for _, path := range paths { _, err := hugofs.Source().Stat(path) assert.Nil(t, err) } }
// TODO: Consider calling doNewSite() instead? func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) error { fs := hugofs.Source() if exists, _ := helpers.Exists(targetDir, fs); exists { if isDir, _ := helpers.IsDir(targetDir, fs); !isDir { return errors.New("Target path \"" + targetDir + "\" already exists but not a directory") } isEmpty, _ := helpers.IsEmpty(targetDir, fs) if !isEmpty && !force { return errors.New("Target path \"" + targetDir + "\" already exists and is not empty") } } jekyllConfig := loadJekyllConfig(jekyllRoot) // Crude test to make sure at least one of _drafts/ and _posts/ exists // and is not empty. hasPostsOrDrafts := false postsDir := filepath.Join(jekyllRoot, "_posts") draftsDir := filepath.Join(jekyllRoot, "_drafts") for _, d := range []string{postsDir, draftsDir} { if exists, _ := helpers.Exists(d, fs); exists { if isDir, _ := helpers.IsDir(d, fs); isDir { if isEmpty, _ := helpers.IsEmpty(d, fs); !isEmpty { hasPostsOrDrafts = true } } } } if !hasPostsOrDrafts { return errors.New("Your Jekyll root contains neither posts nor drafts, aborting.") } mkdir(targetDir, "layouts") mkdir(targetDir, "content") mkdir(targetDir, "archetypes") mkdir(targetDir, "static") mkdir(targetDir, "data") mkdir(targetDir, "themes") createConfigFromJekyll(targetDir, "yaml", jekyllConfig) copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static")) return nil }
// getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one. // If you provide multiple parts they will be joined together to the final URL. // GetJSON returns nil or parsed JSON to use in a short code. func getJSON(urlParts ...string) interface{} { var v interface{} url := strings.Join(urlParts, "") for i := 0; i <= resRetries; i++ { c, err := resGetResource(url) if err != nil { jww.ERROR.Printf("Failed to get json resource %s with error message %s", url, err) return nil } err = json.Unmarshal(c, &v) if err != nil { jww.ERROR.Printf("Cannot read json from resource %s with error message %s", url, err) jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep) time.Sleep(resSleep) resDeleteCache(url, hugofs.Source()) continue } break } return v }