// A website generator tool package main import ( "tildegit.org/nihilazo/go-gemtext" "encoding/json" "fmt" "io/ioutil" "io" "os" "path/filepath" ) // TODO replace these hardcoded paths with config var root string = "/mnt/term/home/nico/website/wiki" var geminiPrefix string = "gemini://breadpunk.club/~bagel" var htmlPrefix string = "https://itwont.work" var geminiOutputDir string = filepath.Join(root, "public_gemini") var htmlOutputDir string = filepath.Join(root, "public_html") var inputDir string = filepath.Join(root, "content") var tagFile string = filepath.Join(root, "tags.json") type filePathInfo struct { Path string New bool // True if the file is new } type pageInfo struct { Path string Title string } // RemoveIndex is a reslicing function. func RemoveIndex(s []pageInfo, index int) []pageInfo { return append(s[:index], s[index+1:]...) } var tagData map[string][]pageInfo = make(map[string][]pageInfo) var files []filePathInfo // Files to process (those that are new or have been edited) func copy(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) if err != nil { return 0, err } if !sourceFileStat.Mode().IsRegular() { return 0, fmt.Errorf("%s is not a regular file", src) } source, err := os.Open(src) if err != nil { return 0, err } defer source.Close() destination, err := os.Create(dst) if err != nil { return 0, err } defer destination.Close() nBytes, err := io.Copy(destination, source) return nBytes, err } func processPage(f filePathInfo) error { // Delete any existing tag references to this file if filepath.Ext(f.Path) == ".gmi" { for i,d := range tagData { // For each tag in tagdata if len(d) != 0 { for j, _ := range d { // for each file in the tag if files[j].Path == f.Path { tagData[i] = RemoveIndex(d,j) } } } } // Open the file and parse the gemtext data, err := ioutil.ReadFile(f.Path) if err != nil { return err } parse, err := gemtext.ParsePage(string(data)) if err != nil { return err } // TODO get tag data from files // TODO render and write HTML and gemini } else if filepath.Ext(f.Path) == ".tpl" { // TODO handle template files } else { // Non-gemtext files rel, err := filepath.Rel(inputDir, f.Path) if err != nil { return err } copy(f.Path, filepath.Join(htmlOutputDir, rel)) copy(f.Path, filepath.Join(geminiOutputDir, rel)) } return nil } // walkInputDir is called when walking the input directory, finds what files are new or have been edited, adds them to files func walkInputDir(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { rel, err := filepath.Rel(inputDir, path) if err != nil { return err } outPath := filepath.Join(geminiOutputDir, rel) outFileInfo, err := os.Stat(outPath) if err != nil { if os.IsNotExist(err) { files = append(files, filePathInfo{Path: path, New: true}) } else { return err } } else { if info.ModTime().After(outFileInfo.ModTime()) { files = append(files, filePathInfo{Path: path}) } } } return nil } func main() { loadingJson := true if _, err := os.Stat(tagFile); err != nil { if os.IsNotExist(err) { loadingJson = false fmt.Println("Not loading tags file, it doesn't exist yet") } else { fmt.Println(err) return } } tagFileData, err := ioutil.ReadFile(tagFile) if loadingJson { err = json.Unmarshal([]byte(tagFileData), &tagData) if err != nil { fmt.Println(err) return } } err = filepath.Walk(inputDir, walkInputDir) // walks the tree, creates the files slice and updates tagData if err != nil { fmt.Println(err) return } for _, file := range files { err := processPage(file) // Process all the updated files. if err != nil { fmt.Println(err) return } } // TODO tag pages json, err := json.Marshal(tagData) err = ioutil.WriteFile(tagFile, json, 0644) if err != nil { fmt.Println(err) return } }