package main import ( "errors" "fmt" "html/template" "io/ioutil" "log" "net/http" "os" "os/exec" "path" "path/filepath" "regexp" "strings" ) type Page struct { Title string Body template.HTML BodyOrig string Concept template.HTML } var templates = template.Must(template.ParseFiles("templates/edit.html", "templates/view.html")) func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) { err := templates.ExecuteTemplate(w, tmpl+".html", p) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func (p *Page) save() error { filename := path.Join("data", p.Title+".kdb") return ioutil.WriteFile(filename, []byte(p.Body), 0600) } var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9\\-]+)(/?)$") func getTitle(w http.ResponseWriter, r *http.Request) (string, error) { m := validPath.FindStringSubmatch(r.URL.Path) if m == nil { http.NotFound(w, r) return "", errors.New("Invalid Page Title") } return m[2], nil // The title is the second subexpression. } func hasPage(word string) bool { if _, err := os.Stat(path.Join("data/", normalize(word)+".kdb")); err != nil { return false } return true } func loadPage(title string) (*Page, error) { filename := title + ".kdb" bodyb, err := ioutil.ReadFile(path.Join("data", filename)) if err != nil { return nil, err } body := template.HTMLEscapeString(string(bodyb)) concept := template.HTML(construct_graph(title)) for _, word := range strings.Fields(body) { if hasPage(word) { body = strings.ReplaceAll(body, word, fmt.Sprintf("%v", normalize(word), normalize(word))) } if strings.HasPrefix(word, "http") { body = strings.ReplaceAll(body, word, fmt.Sprintf("%v", word, word)) } if strings.HasPrefix(word, "data:image/png;base64,") { body = strings.ReplaceAll(body, word, fmt.Sprintf("", word)) } } return &Page{Title: title, Body: template.HTML(body), BodyOrig: string(bodyb), Concept: concept}, nil } func normalize(word string) string { if strings.HasPrefix(word, "typeof:") { word = word[7:] } else if strings.HasPrefix(word, "subtype:") { word = word[8:] } else if strings.HasPrefix(word, "instance:") { word = word[9:] } word = strings.TrimSpace(word) return word } func viewHandler(w http.ResponseWriter, r *http.Request) { title, err := getTitle(w, r) if err != nil { return } p, err := loadPage(title) if err != nil { http.Redirect(w, r, "/edit/"+title, http.StatusFound) return } renderTemplate(w, "view", p) } func editHandler(w http.ResponseWriter, r *http.Request) { title, err := getTitle(w, r) if err != nil { return } p, err := loadPage(title) if err != nil { p = &Page{Title: title} } renderTemplate(w, "edit", p) } func saveHandler(w http.ResponseWriter, r *http.Request) { title, err := getTitle(w, r) if err != nil { return } body := r.FormValue("body") p := &Page{Title: title, Body: template.HTML(body)} err = p.save() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+title, http.StatusFound) } func get_links(name string) (links []string) { filename := name + ".kdb" bodyb, err := ioutil.ReadFile(path.Join("data", filename)) if err != nil { return []string{} } body := template.HTMLEscapeString(string(bodyb)) for _, word := range strings.Fields(body) { if hasPage(word) { links = append(links, word) } } return } func construct_graph(word string) string { seen := make(map[string]bool) graph := "digraph {\n\""+word+"\";\n" graph = construct_graph_sub(graph, word, seen, 1) graph += "}" ioutil.WriteFile("tmp.dot", []byte(graph), 0640) subProcess := exec.Command("dot", "-Tsvg", "-otmp.svg", "tmp.dot") subProcess.Run() subProcess.Wait() data, _ := ioutil.ReadFile("tmp.svg") return "
"+strings.ReplaceAll(string(data), ` `, "")+"" } func construct_graph_sub(graph string, word string, seen map[string]bool, depth int) string { if depth >= 0 { } else { return graph } links := get_links(normalize(word)) if !seen[normalize(word)] { graph += fmt.Sprintf("\"%v\" [href=\"/view/%v\"];\n", normalize(word), normalize(word)) } seen[normalize(word)] = true seen[word] = true graph += "# " + word + "\n" for _, link := range links { if strings.HasPrefix(word, "typeof:") || strings.HasPrefix(word, "instance:") || strings.HasPrefix(word, "subtype:") { word = normalize(word) } if !seen[normalize(link)] { graph += fmt.Sprintf("\"%v\" [href=\"/view/%v\"];\n", normalize(link), normalize(link)) } if !seen[normalize(word)+"++"+normalize(link)] || !seen[normalize(link)+"++"+normalize(word)] { if strings.HasPrefix(link, "typeof:") { graph += fmt.Sprintf("\"%v\" -> \"%v\" [style=dotted]\n", link[7:], word) seen[word+"++"+link[7:]] = true seen[link[7:]+"++"+word] = true } else if strings.HasPrefix(link, "subtype:") { graph += fmt.Sprintf("\"%v\" -> \"%v\" [style=dotted]\n", word, link[8:]) seen[word+"++"+link[8:]] = true seen[link[8:]+"++"+word] = true } else if strings.HasPrefix(link, "instance:") { graph += fmt.Sprintf("\"%v\" -> \"%v\" [style=dotted]\n", word, link[9:]) seen[word+"++"+link[9:]] = true seen[link[9:]+"++"+word] = true } else { graph += fmt.Sprintf("\"%v\" -> \"%v\"\n", word, link) } seen[normalize(word)+"++"+normalize(link)] = true seen[normalize(link)+"++"+normalize(word)] = true } //} if !seen[link] { graph = construct_graph_sub(graph, link, seen, depth-1) } } return graph } func indexHandler(w http.ResponseWriter, r *http.Request) { root := "./data/" var files []string filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if path != "./data/" { files = append(files, path[5:]) } return nil }) body := "" for _, file := range files { name := strings.ReplaceAll(file, ".kdb", "") body += fmt.Sprintf("%v
", name, name) } renderTemplate(w, "view", &Page{Title: "Sigil KDB", Body: template.HTML(body), BodyOrig: ""}) } func main() { http.HandleFunc("/", indexHandler) http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) http.HandleFunc("/save/", saveHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }