Repos / s4g / c572b9ebea
commit c572b9ebea0cfb5967120ddd27dca28eb9a95137
Author: Nhân <hi@imnhan.com>
Date:   Sat Jul 22 14:21:26 2023 +0700

    implement redirects

diff --git a/docs/manifest.txt b/docs/manifest.txt
index 207b13f..c0da3ca 100644
--- a/docs/manifest.txt
+++ b/docs/manifest.txt
@@ -2,4 +2,6 @@ about/index.html
 feed.xml
 index.html
 mfws.html
+mfws/index.html
+scale.html
 scale/index.html
\ No newline at end of file
diff --git a/docs/mfws/index.html b/docs/mfws/index.html
new file mode 100644
index 0000000..d3ca5e5
--- /dev/null
+++ b/docs/mfws/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Redirecting to /webmaker2000/mfws.html</title>
+    <meta http-equiv="Refresh" content="0; URL=/webmaker2000/mfws.html" />
+  </head>
+  <body>
+    The page you're looking for has been moved to <a href="/webmaker2000/mfws.html">/webmaker2000/mfws.html</a>.
+  </body>
+</html>
diff --git a/docs/redirects.txt b/docs/redirects.txt
new file mode 100644
index 0000000..c20f396
--- /dev/null
+++ b/docs/redirects.txt
@@ -0,0 +1,2 @@
+mfws/index.html -> mfws.html
+scale.html -> scale/
diff --git a/docs/scale.html b/docs/scale.html
new file mode 100644
index 0000000..df77cf9
--- /dev/null
+++ b/docs/scale.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Redirecting to /webmaker2000/scale/</title>
+    <meta http-equiv="Refresh" content="0; URL=/webmaker2000/scale/" />
+  </head>
+  <body>
+    The page you're looking for has been moved to <a href="/webmaker2000/scale/">/webmaker2000/scale/</a>.
+  </body>
+</html>
diff --git a/main.go b/main.go
index be06e87..b429160 100644
--- a/main.go
+++ b/main.go
@@ -28,6 +28,7 @@
 const SiteExt = ".wbmkr2k"
 const SiteFileName = "website" + SiteExt
 const FeedPath = "feed.xml"
+const RedirectsPath = "redirects.txt"
 
 func main() {
 	invalidCommand := func() {
@@ -255,6 +256,15 @@ func regenerate(fsys writablefs.FS) (site *SiteMetadata, err error) {
 		fmt.Println("Generated", FeedPath)
 	}
 
+	redirects, uerr := generateRedirects(fsys, RedirectsPath, site.Root)
+	if uerr != nil {
+		return nil, fmt.Errorf("generate redirects: %w", uerr)
+	}
+	for _, p := range redirects {
+		generatedFiles[p] = true
+		fmt.Println("Generated", p)
+	}
+
 	DeleteOldGeneratedFiles(fsys, generatedFiles)
 	WriteManifest(fsys, generatedFiles)
 
diff --git a/redirects.go b/redirects.go
new file mode 100644
index 0000000..5c1c54e
--- /dev/null
+++ b/redirects.go
@@ -0,0 +1,102 @@
+package main
+
+import (
+	"bufio"
+	"bytes"
+	"fmt"
+	"html/template"
+	"io/fs"
+	"path/filepath"
+	"strings"
+
+	"go.imnhan.com/webmaker2000/errs"
+	"go.imnhan.com/webmaker2000/writablefs"
+)
+
+// Returns list of generated files
+func generateRedirects(fsys writablefs.FS, path string, root string) ([]string, *errs.UserErr) {
+	f, err := fsys.Open(path)
+	if err != nil {
+		panic(err)
+	}
+
+	var sources, dests []string
+
+	s := bufio.NewScanner(f)
+	lineNo := 0
+	for s.Scan() {
+		lineNo++
+		line := strings.TrimSpace(s.Text())
+		if line == "" || line[0] == '#' {
+			continue
+		}
+		src, dest, found := strings.Cut(line, "->")
+		if !found {
+			return nil, &errs.UserErr{
+				File: path,
+				Line: lineNo,
+				Msg:  fmt.Sprintf(`Expected "src -> dest", found "%s"`, line),
+			}
+		}
+
+		src = strings.TrimPrefix(strings.TrimSpace(src), "/")
+		dest = strings.TrimPrefix(strings.TrimSpace(dest), "/")
+
+		if strings.HasSuffix(src, "/") {
+			return nil, &errs.UserErr{
+				File: path,
+				Line: lineNo,
+				Msg:  fmt.Sprintf(`Source must not end with a "/" (found "%s")`, line),
+			}
+		}
+
+		srcStat, err := fs.Stat(fsys, src)
+		if err == nil {
+			if srcStat.IsDir() {
+				return nil, &errs.UserErr{
+					File: path,
+					Line: lineNo,
+					Msg:  fmt.Sprintf(`Source must not be a folder (found "%s")`, line),
+				}
+			}
+		}
+
+		sources = append(sources, src)
+		dests = append(dests, dest)
+	}
+
+	for i, src := range sources {
+		srcDir := filepath.Dir(src)
+		err := fsys.MkdirAll(srcDir)
+		if err != nil {
+			panic(err)
+		}
+
+		var srcBuf bytes.Buffer
+		err = srcTmpl.Execute(&srcBuf, root+dests[i])
+		if err != nil {
+			panic(err)
+		}
+
+		err = fsys.WriteFile(src, srcBuf.Bytes())
+		if err != nil {
+			panic(err)
+		}
+
+		fmt.Printf("Redirect: %s -> %s\n", src, dests[i])
+	}
+
+	return sources, nil
+}
+
+var srcTmpl = template.Must(template.New("src").Parse(`<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Redirecting to {{.}}</title>
+    <meta http-equiv="Refresh" content="0; URL={{.}}" />
+  </head>
+  <body>
+    The page you're looking for has been moved to <a href="{{.}}">{{.}}</a>.
+  </body>
+</html>
+`))
diff --git a/writablefs/writablefs.go b/writablefs/writablefs.go
index d99a1f6..de6ca63 100644
--- a/writablefs/writablefs.go
+++ b/writablefs/writablefs.go
@@ -10,6 +10,7 @@ type FS interface {
 	fs.FS
 	WriteFile(path string, content []byte) error
 	RemoveAll(path string) error
+	MkdirAll(path string) error
 	Path() string
 }
 
@@ -36,3 +37,8 @@ func (w writeDirFS) WriteFile(path string, content []byte) error {
 func (w writeDirFS) Path() string {
 	return string(w)
 }
+
+func (w writeDirFS) MkdirAll(path string) error {
+	fullPath := filepath.Join(string(w), path)
+	return os.MkdirAll(fullPath, 0755)
+}