summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go110
1 files changed, 89 insertions, 21 deletions
diff --git a/main.go b/main.go
index aafbfc1..7f49389 100644
--- a/main.go
+++ b/main.go
@@ -30,6 +30,11 @@ import (
const manifestName = ".weft-generated.json"
+const (
+ gitignoreStart = "# BEGIN weft generated outputs"
+ gitignoreEnd = "# END weft generated outputs"
+)
+
type Page struct {
SourcePath string
OutputPath string
@@ -54,12 +59,13 @@ type source struct {
}
type site struct {
- root string
- pages map[string][]*Page
- sources []source
- templates *template.Template
- fallback string
- warnings []string
+ root string
+ canonicalRoot string
+ pages map[string][]*Page
+ sources []source
+ templates *template.Template
+ fallback string
+ warnings []string
}
type manifest struct {
@@ -69,25 +75,24 @@ type manifest struct {
func main() {
flags := flag.NewFlagSet("weft", flag.ContinueOnError)
fallback := flags.String("fallback", "page", "fallback template for Markdown pages")
- flags.Usage = func() { fmt.Fprintln(flags.Output(), "usage: weft [-fallback template] <site-root>") }
+ manageGitignore := flags.Bool("gitignore", false, "manage generated outputs in the site .gitignore")
+ flags.Usage = func() {
+ fmt.Fprintln(flags.Output(), "usage: weft [-gitignore] [-fallback template] <site-root> <canonical-root>")
+ }
if err := flags.Parse(os.Args[1:]); err != nil {
os.Exit(2)
}
- if flags.NArg() != 1 {
+ if flags.NArg() != 2 {
flags.Usage()
os.Exit(2)
}
- if err := buildWithFallback(flags.Arg(0), *fallback); err != nil {
+ if err := buildWithFallback(flags.Arg(0), flags.Arg(1), *fallback, *manageGitignore); err != nil {
fmt.Fprintln(os.Stderr, "weft:", err)
os.Exit(1)
}
}
-func build(root string) error {
- return buildWithFallback(root, "page")
-}
-
-func buildWithFallback(root, fallback string) error {
+func buildWithFallback(root, canonicalRoot, fallback string, manageGitignore bool) error {
if strings.TrimSpace(fallback) == "" {
return errors.New("fallback template cannot be empty")
}
@@ -103,7 +108,7 @@ func buildWithFallback(root, fallback string) error {
return fmt.Errorf("site root is not a directory: %s", abs)
}
- s := &site{root: abs, fallback: fallback, pages: map[string][]*Page{}}
+ s := &site{root: abs, canonicalRoot: canonicalRoot, fallback: fallback, pages: map[string][]*Page{}}
if err := s.discover(); err != nil {
return err
}
@@ -117,7 +122,7 @@ func buildWithFallback(root, fallback string) error {
if err := s.validate(outputs); err != nil {
return err
}
- if err := s.write(outputs); err != nil {
+ if err := s.write(outputs, manageGitignore); err != nil {
return err
}
for _, warning := range s.warnings {
@@ -248,7 +253,7 @@ func (s *site) parsePage(rel, out string, info fs.FileInfo) (*Page, []string, er
}
return &Page{
SourcePath: rel, OutputPath: out, URL: urlPath,
- CanonicalURL: "https://tjp.lol" + urlPath, Title: title, Meta: meta,
+ CanonicalURL: s.canonicalRoot + urlPath, Title: title, Meta: meta,
ModTime: info.ModTime(), Sections: sections,
}, warnings, nil
}
@@ -392,7 +397,7 @@ func relativePath(from, target string) string {
if err != nil {
return target
}
- rel = filepath.ToSlash(rel)
+ rel = path.Clean(filepath.ToSlash(rel))
if directory {
return strings.TrimSuffix(rel, "/") + "/"
}
@@ -484,7 +489,7 @@ func (s *site) render() (map[string][]byte, error) {
current = src.page
name = s.markdownTemplate(src.path)
} else {
- current = &Page{SourcePath: src.path, OutputPath: src.output, URL: "/" + src.output, CanonicalURL: "https://tjp.lol/" + src.output, Meta: map[string]any{}}
+ current = &Page{SourcePath: src.path, OutputPath: src.output, URL: "/" + src.output, CanonicalURL: s.canonicalRoot + src.output, Meta: map[string]any{}}
}
if s.templates.Lookup(name) == nil {
return nil, fmt.Errorf("%s requires missing template %q", src.path, name)
@@ -617,7 +622,7 @@ func (s *site) readManifest() (map[string]bool, error) {
return tracked, nil
}
-func (s *site) write(outputs map[string][]byte) error {
+func (s *site) write(outputs map[string][]byte, manageGitignore bool) error {
tracked, err := s.readManifest()
if err != nil {
return err
@@ -661,6 +666,15 @@ func (s *site) write(outputs map[string][]byte) error {
if err := os.WriteFile(filepath.Join(stage, manifestName), manifestBytes, 0o644); err != nil {
return err
}
+ if manageGitignore {
+ contents, err := s.gitignore(paths)
+ if err != nil {
+ return err
+ }
+ if err := os.WriteFile(filepath.Join(stage, ".gitignore"), contents, 0o644); err != nil {
+ return err
+ }
+ }
var movedOld, installed []string
rollback := func() {
@@ -699,7 +713,11 @@ func (s *site) write(outputs map[string][]byte) error {
}
}
}
- for _, rel := range append(paths, manifestName) {
+ install := append(slices.Clone(paths), manifestName)
+ if manageGitignore {
+ install = append(install, ".gitignore")
+ }
+ for _, rel := range install {
if err := moveOld(rel); err != nil {
rollback()
return err
@@ -717,3 +735,53 @@ func (s *site) write(outputs map[string][]byte) error {
}
return nil
}
+
+func (s *site) gitignore(outputs []string) ([]byte, error) {
+ raw, err := os.ReadFile(filepath.Join(s.root, ".gitignore"))
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
+ return nil, err
+ }
+ var kept strings.Builder
+ inBlock, seen := false, false
+ for _, line := range strings.SplitAfter(string(raw), "\n") {
+ value := strings.TrimSuffix(strings.TrimSuffix(line, "\n"), "\r")
+ switch value {
+ case gitignoreStart:
+ if inBlock || seen {
+ return nil, errors.New("invalid managed block in .gitignore")
+ }
+ inBlock, seen = true, true
+ case gitignoreEnd:
+ if !inBlock {
+ return nil, errors.New("invalid managed block in .gitignore")
+ }
+ inBlock = false
+ default:
+ if !inBlock {
+ kept.WriteString(line)
+ }
+ }
+ }
+ if inBlock {
+ return nil, errors.New("unterminated managed block in .gitignore")
+ }
+ if kept.Len() > 0 && !strings.HasSuffix(kept.String(), "\n") {
+ kept.WriteByte('\n')
+ }
+ kept.WriteString(gitignoreStart + "\n")
+ for _, output := range outputs {
+ if strings.ContainsAny(output, "\r\n") {
+ return nil, fmt.Errorf("cannot add output to .gitignore: %q", output)
+ }
+ kept.WriteString("/" + escapeGitignore(output) + "\n")
+ }
+ kept.WriteString(gitignoreEnd + "\n")
+ return []byte(kept.String()), nil
+}
+
+func escapeGitignore(value string) string {
+ return strings.NewReplacer(
+ `\`, `\\`, " ", `\ `, "#", `\#`, "!", `\!`,
+ "[", `\[`, "]", `\]`, "*", `\*`, "?", `\?`,
+ ).Replace(value)
+}