diff --git a/README.md b/README.md index a588c32..9be3688 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ kiln A kiln site is organized in the following way: ``` -src/ Site source -templates/ Templates - page.gmi Page template - section.gmi Section template -dst/ Site destination +src/ Site source +templates/ Templates + page.gmi Page template + directory.gmi Directory template +dst/ Site destination ``` Running `kiln` takes the contents in `src`, runs them through the templates in @@ -46,13 +46,14 @@ Page templates are provided with the following information: - `Permalink`: Permalink to the page - `Content`: The contents of the file (including the title) -## Sections +## Directories -Section templates are provided with the following information: +Directory templates are provided with the following information: -- `Path`: Relative path to the section -- `Permalink`: Permalink to the section -- `Pages`: The pages in this section +- `Path`: Relative path to the directory +- `Permalink`: Permalink to the directory +- `Pages`: The pages in this directory +- `Directories`: The subdirectories of this directory ## Templates @@ -61,4 +62,4 @@ Templates are located in the `templates` directory. There are currently two supported templates: - `page.gmi`: The template used for pages -- `section.gmi`: The template used for sections +- `directory.gmi`: The template used for directories diff --git a/kiln.go b/kiln.go index 7af17fc..dfeb437 100644 --- a/kiln.go +++ b/kiln.go @@ -11,12 +11,13 @@ import ( ) type Site struct { - Files map[string][]byte - Dirs map[string][]string - Pages map[string]*Page - Templates *template.Template + Static map[string][]byte // Static files + Directory *Directory // Site directory + Templates *template.Template // Templates } +// LoadSite loads and returns a Site. +// It reads site content from the specified source directory. func LoadSite(srcDir string) (*Site, error) { tmpl, err := template.New("templates").ParseGlob("templates/*.gmi") if err != nil { @@ -24,58 +25,31 @@ func LoadSite(srcDir string) (*Site, error) { } site := &Site{ - Files: map[string][]byte{}, - Dirs: map[string][]string{}, - Pages: map[string]*Page{}, + Static: map[string][]byte{}, + Directory: NewDirectory(""), Templates: tmpl, } - if err := site.ReadDir(srcDir, ""); err != nil { + if err := site.Directory.Read(site, srcDir, ""); err != nil { return nil, err } return site, nil } -// ReadDir reads a directory and indexes the files and directories within it. -func (s *Site) ReadDir(srcDir string, dstDir string) error { - entries, err := ioutil.ReadDir(srcDir) - if err != nil { - return err - } - - for _, entry := range entries { - name := entry.Name() - srcPath := filepath.Join(srcDir, name) - dstPath := filepath.Join(dstDir, name) - - if entry.IsDir() { - s.Dirs[dstPath] = []string{} - s.ReadDir(srcPath, dstPath) - } else { - content, err := ioutil.ReadFile(srcPath) - if err != nil { - return err - } - - s.Files[dstPath] = content - s.Dirs[dstDir] = append(s.Dirs[dstDir], dstPath) - } - } - return nil -} - // Write writes the contents of the Index to the provided destination directory. func (s *Site) Write(dstDir string) error { // Empty the destination directory if err := os.RemoveAll(dstDir); err != nil { return err } + // Create the destination directory if err := os.MkdirAll(dstDir, 0755); err != nil { return err } - for path := range s.Files { + // Write the static files + for path := range s.Static { // Create any parent directories if dir := filepath.Dir(path); dir != "." { dstPath := filepath.Join(dstDir, dir) @@ -90,51 +64,47 @@ func (s *Site) Write(dstDir string) error { if err != nil { return err } - data := s.Files[path] + data := s.Static[path] if _, err := f.Write(data); err != nil { return err } } - return nil + + // Write the directory + return s.Directory.Write(dstDir) } // Manipulate processes and manipulates the site's content. -func (s *Site) Manipulate() error { - for path := range s.Files { - switch filepath.Ext(path) { - case ".gmi": - // Gather page data - content := string(s.Files[path]) - page := NewPage(path, content) - - builder := &strings.Builder{} - tmpl := s.Templates.Lookup("page.gmi") - if err := tmpl.Execute(builder, page); err != nil { - return err - } - page.Content = builder.String() - s.Pages[path] = page - s.Files[path] = []byte(page.Content) - } - } - - for dir := range s.Dirs { - // Gather section data - section := NewSection(dir) - for _, path := range s.Dirs[dir] { - switch filepath.Ext(path) { - case ".gmi": - section.Pages = append(section.Pages, s.Pages[path]) - } - } - - dstPath := filepath.Join(dir, "index.gmi") +func (s *Site) Manipulate(dir *Directory) error { + // Write the directory index file, if it doesn't exist + if dir.Index == nil { + path := filepath.Join(dir.Path, "index.gmi") builder := &strings.Builder{} - tmpl := s.Templates.Lookup("section.gmi") - if err := tmpl.Execute(builder, section); err != nil { + tmpl := s.Templates.Lookup("directory.gmi") + if err := tmpl.Execute(builder, dir); err != nil { return err } - s.Files[dstPath] = []byte(builder.String()) + content := builder.String() + permalink := filepath.Dir(path) + if permalink == "." { + permalink = "" + } + page := &Page{ + Path: path, + Permalink: "/" + permalink, + Content: content, + } + dir.Index = page + } + + // Manipulate pages + for i := range dir.Pages { + builder := &strings.Builder{} + tmpl := s.Templates.Lookup("page.gmi") + if err := tmpl.Execute(builder, dir.Pages[i]); err != nil { + return err + } + dir.Pages[i].Content = builder.String() } return nil @@ -158,55 +128,158 @@ type Page struct { var titleRE = regexp.MustCompile("^# ?([^#\r\n]+)") func NewPage(path string, content string) *Page { - page := &Page{ - Path: path, - Permalink: "/" + path, - Content: content, - } - // Try to parse the date from the page filename + var date time.Time const layout = "2006-01-02" base := filepath.Base(path) if len(base) >= len(layout) { dateStr := base[:len(layout)] - if date, err := time.Parse(layout, dateStr); err == nil { - page.Date = date + if time, err := time.Parse(layout, dateStr); err == nil { + date = time + } + // Remove the date from the path + base = base[len(layout):] + if len(base) > 0 { + // Remove a leading dash + if base[0] == '-' { + base = base[1:] + } + if len(base) > 0 { + dir := filepath.Dir(path) + if dir == "." { + dir = "" + } + path = filepath.Join(dir, base) + } } } // Try to parse the title from the contents + var title string if submatches := titleRE.FindStringSubmatch(content); submatches != nil { - page.Title = submatches[1] + title = submatches[1] + // Remove the title from the contents + content = content[len(submatches[0]):] } - return page + return &Page{ + Path: path, + Permalink: "/" + path, + Title: title, + Date: date, + Content: content, + } } -// Section represents a section (i.e., a directory). -type Section struct { - // The path to this section. +// Directory represents a directory of pages. +type Directory struct { + // The path to this directory. Path string - // The permalink to this section. + // The permalink to this directory. Permalink string - // The pages in this section. + // The pages in this directory. Pages []*Page + // The subdirectories of this directory. + Directories []*Directory + // The index file (index.gmi). + Index *Page } // NewSection returns a new Section with the given path. -func NewSection(path string) *Section { +func NewDirectory(path string) *Directory { var permalink string if path == "" { permalink = "/" } else { permalink = "/" + path + "/" } - return &Section{ + return &Directory{ Path: path, Permalink: permalink, } } -// Sort sorts pages by date. -func (s *Section) Sort() { - // TODO: Implement this +// Read reads from a directory and indexes the files and directories within it. +func (d *Directory) Read(site *Site, srcDir string, path string) error { + entries, err := ioutil.ReadDir(srcDir) + if err != nil { + return err + } + + for _, entry := range entries { + name := entry.Name() + path := filepath.Join(path, name) + srcPath := filepath.Join(srcDir, path) + + if entry.IsDir() { + // Gather directory data + dir := NewDirectory(path) + dir.Read(site, srcPath, path) + d.Directories = append(d.Directories, dir) + } else { + content, err := ioutil.ReadFile(srcPath) + if err != nil { + return err + } + + switch filepath.Ext(name) { + case ".gmi", ".gemini": + // Gather page data + content := string(content) + page := NewPage(path, content) + + if name == "index.gmi" { + d.Index = page + } else { + d.Pages = append(d.Pages, page) + } + + default: + // Static file + site.Static[path] = content + } + } + } + return nil +} + +// Write writes the Directory to the provided destination path. +func (d *Directory) Write(dstDir string) error { + // Create the directory + dirPath := filepath.Join(dstDir, d.Path) + if err := os.MkdirAll(dirPath, 0755); err != nil { + return err + } + + // Write the files + for _, page := range d.Pages { + dstPath := filepath.Join(dstDir, page.Path) + f, err := os.Create(dstPath) + if err != nil { + return err + } + data := []byte(page.Content) + if _, err := f.Write(data); err != nil { + return err + } + } + + // Write the index file + if d.Index != nil { + dstPath := filepath.Join(dstDir, d.Index.Path) + f, err := os.Create(dstPath) + if err != nil { + return err + } + data := []byte(d.Index.Content) + if _, err := f.Write(data); err != nil { + return err + } + } + + // Write subdirectories + for _, dir := range d.Directories { + dir.Write(dstDir) + } + return nil } diff --git a/main.go b/main.go index 8e3da6c..249bb40 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ func run() error { if err != nil { return err } - if err := site.Manipulate(); err != nil { + if err := site.Manipulate(site.Directory); err != nil { return err } if err := site.Write("dst"); err != nil {