package main import ( "bytes" "io/ioutil" "os" pathpkg "path" "sort" "strings" "time" ) // Dir represents a directory. type Dir struct { Title string // Directory title. Content string // Directory index content. Path string // Directory path. Pages []*Page // Pages in this directory. Dirs []*Dir // Subdirectories. index *Page // The index page. feed []byte // Atom feed. inputExt string // input file extension outputExt string // output file extension templateExt string // template file extension } // NewDir returns a new Dir with the given path. func NewDir(path string) *Dir { if path == "" { path = "/" } else { path = "/" + path + "/" } return &Dir{ Path: path, } } // read reads from a directory and indexes the files and directories within it. func (d *Dir) read(srcDir string, path string) error { entries, err := ioutil.ReadDir(pathpkg.Join(srcDir, path)) if err != nil { return err } for _, entry := range entries { name := entry.Name() // Ignore names that start with "_" if strings.HasPrefix(name, "_") { continue } path := pathpkg.Join(path, name) if entry.IsDir() { // Gather directory data dir := NewDir(path) dir.inputExt = d.inputExt dir.outputExt = d.outputExt dir.templateExt = d.templateExt if err := dir.read(srcDir, path); err != nil { return err } d.Dirs = append(d.Dirs, dir) } else { srcPath := pathpkg.Join(srcDir, path) content, err := ioutil.ReadFile(srcPath) if err != nil { return err } if ext := pathpkg.Ext(name); ext == d.inputExt { // Gather page data if strings.TrimSuffix(name, ext) == "index" { d.index = NewPage(d.Path, content) d.Title = d.index.Title d.Content = d.index.Content } else { d.Pages = append(d.Pages, NewPage(path, content)) } } } } return nil } // manipulate processes and manipulates the directory's contents. func (d *Dir) manipulate(cfg *Config) error { if d.templateExt != "" { // Create index if d.index != nil { var b strings.Builder tmpl := cfg.Templates.FindTemplate(d.Path, "index"+d.templateExt) if err := tmpl.Execute(&b, d); err != nil { return err } d.index.Content = b.String() } // Manipulate pages for i := range d.Pages { var b strings.Builder tmpl := cfg.Templates.FindTemplate(d.Path, "page"+d.templateExt) if err := tmpl.Execute(&b, d.Pages[i]); err != nil { return err } d.Pages[i].Content = b.String() } } // Feed represents a feed. type Feed struct { Title string // Feed title. Path string // Feed path. Updated time.Time // Last updated time. Entries []*Page // Feed entries. } // Create feeds if title, ok := cfg.Feeds[d.Path]; ok { var b bytes.Buffer feed := &Feed{ Title: title, Path: d.Path, Updated: time.Now(), Entries: d.Pages, } tmpl := cfg.Templates.FindTemplate(d.Path, "atom.xml") if err := tmpl.Execute(&b, feed); err != nil { return err } d.feed = b.Bytes() } // Manipulate subdirectories for _, d := range d.Dirs { if err := d.manipulate(cfg); err != nil { return err } } return nil } // Format represents an output format. type Format interface { Format(*Page) (path string, content []byte) } // write writes the Dir to the provided destination path. func (d *Dir) write(dstDir string, format Format) error { // Create the directory dirPath := pathpkg.Join(dstDir, d.Path) if err := os.MkdirAll(dirPath, 0755); err != nil { return err } // Write pages for _, page := range d.Pages { path, content := format.Format(page) dstPath := pathpkg.Join(dstDir, path) dir := pathpkg.Dir(dstPath) os.MkdirAll(dir, 0755) f, err := os.Create(dstPath) if err != nil { return err } if _, err := f.Write(content); err != nil { return err } } // Write the index file if d.index != nil { path, content := format.Format(d.index) dstPath := pathpkg.Join(dstDir, path) dir := pathpkg.Dir(dstPath) os.MkdirAll(dir, 0755) f, err := os.Create(dstPath) if err != nil { return err } if _, err := f.Write(content); err != nil { return err } } // Write the atom feed if d.feed != nil { const path = "atom.xml" dstPath := pathpkg.Join(dstDir, path) os.MkdirAll(dstDir, 0755) f, err := os.Create(dstPath) if err != nil { return err } if _, err := f.Write(d.feed); err != nil { return err } } // Write subdirectories for _, dir := range d.Dirs { dir.write(dstDir, format) } return nil } // sort sorts the directory's pages by date. func (d *Dir) sort() { sort.Slice(d.Pages, func(i, j int) bool { return d.Pages[i].Date.After(d.Pages[j].Date) }) // Sort subdirectories for _, d := range d.Dirs { d.sort() } }