package main import ( "bytes" "io" "io/ioutil" "log" "os" "os/exec" 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. } // 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, task *Task) error { return d.read(srcDir, "", task) } func (d *Dir) read(srcDir, path string, task *Task) 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) if err := dir.read(srcDir, path, task); err != nil { return err } d.Dirs = append(d.Dirs, dir) } else if ext := pathpkg.Ext(name); ext == task.InputExt { srcPath := pathpkg.Join(srcDir, path) content, err := ioutil.ReadFile(srcPath) if err != nil { return err } if cmd := task.PreProcess; cmd != "" { content = RunProcessCmd(cmd, bytes.NewReader(content)) } // 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 } // Process processes the directory's contents. func (d *Dir) Process(cfg *Config, task *Task) error { if task.TemplateExt != "" { // Create index if d.index != nil { var b strings.Builder tmpl := cfg.Templates.FindTemplate(d.Path, "index"+task.TemplateExt) if err := tmpl.Execute(&b, d); err != nil { return err } d.index.Content = b.String() } // Process pages for i := range d.Pages { var b strings.Builder tmpl := cfg.Templates.FindTemplate(d.Path, "page"+task.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() } // Process subdirectories for _, d := range d.Dirs { if err := d.Process(cfg, task); err != nil { return err } } return nil } // Write writes the directory's contents to the provided destination path. func (d *Dir) Write(dstDir string, task *Task) error { // Create the directory dirPath := pathpkg.Join(dstDir, d.Path) if err := os.MkdirAll(dirPath, 0755); err != nil { return err } // Write pages pages := d.Pages if d.index != nil { pages = append(pages, d.index) } for _, page := range pages { path := task.OutputPath(page.Path) var content []byte if cmd := task.PostProcess; cmd != "" { content = RunProcessCmd(cmd, strings.NewReader(page.Content)) } else { content = []byte(page.Content) } dstPath := pathpkg.Join(dstDir, path) dir := pathpkg.Dir(dstPath) os.MkdirAll(dir, 0755) if err := os.WriteFile(dstPath, content, 0644); 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) if err := os.WriteFile(dstPath, d.feed, 0644); err != nil { return err } } // Write subdirectories for _, dir := range d.Dirs { dir.Write(dstDir, task) } 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() } } // RunProcessCmd runs a process command. func RunProcessCmd(command string, input io.Reader) []byte { split := strings.Split(command, " ") cmd := exec.Command(split[0], split[1:]...) cmd.Stdin = input cmd.Stderr = os.Stderr output, err := cmd.Output() if err != nil { log.Fatal(err) } return output }