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. files map[string][]byte // Static files. index *Page // The index page. } // NewDir returns a new Dir with the given path. func NewDir(path string) *Dir { if path == "" { path = "/" } else { path = "/" + path + "/" } return &Dir{ Path: path, files: map[string][]byte{}, } } // 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) 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 pathpkg.Ext(name) == ".gmi" { // Gather page data if name == "index.gmi" { 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)) } } else { // Static file d.files[path] = content } } } return nil } // manipulate processes and manipulates the directory's contents. func (d *Dir) manipulate(cfg *Config) error { // Create index if d.index != nil { var b strings.Builder tmpl := cfg.Templates.FindTemplate(d.Path, "index.gmi") 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.gmi") 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.files[pathpkg.Join(d.Path, "atom.xml")] = b.Bytes() } // Manipulate subdirectories for _, d := range d.Dirs { if err := d.manipulate(cfg); err != nil { return err } } return nil } // write writes the Dir to the provided destination path. func (d *Dir) write(dstDir string, format outputFormat, cfg *Config) error { // Create the directory dirPath := pathpkg.Join(dstDir, d.Path) if err := os.MkdirAll(dirPath, 0755); err != nil { return err } // Write static files for path := range d.files { dstPath := pathpkg.Join(dstDir, path) f, err := os.Create(dstPath) if err != nil { return err } data := d.files[path] if _, err := f.Write(data); err != nil { return err } } // Write pages for _, page := range d.Pages { path, content := format(page, cfg) 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(d.index, cfg) 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 subdirectories for _, dir := range d.Dirs { dir.write(dstDir, format, cfg) } 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() } }