ref: master
./roadmap.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
package main import ( "embed" "errors" "fmt" "html/template" "io/fs" "log" "net/http" "os" "path/filepath" "slices" "strings" "github.com/rveen/ogdl" ) //go:embed templates var templatesFs embed.FS type Task struct { Path string Summary string Status string Assignee string Tags string Statuses []string Description string Priority int64 Milestone string Subtasks map[string][]Task } func (t Task) SubtasksMap(status string) []Task { slices.SortFunc(t.Subtasks[status], func(a, b Task) int { return int(a.Priority) - int(b.Priority) }) return t.Subtasks[status] } func main() { base := "" if len(os.Args) < 2 { // TODO error os.Exit(1) } else { base = os.Args[1] } base = filepath.Clean(base) router(base) } func router(base string) { http.HandleFunc("/favicon.ico", func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(404) }) http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { status := renderTasks(base, filepath.Clean(r.URL.Path), rw) if status != 200 { rw.WriteHeader(status) } }) http.ListenAndServe(":8080", nil) } func renderTasks(base, path string, rw http.ResponseWriter) int { if path == "/" { t, err := root(base) if err != nil { log.Printf("while getting task %s: %v", path, err) return 500 // TODO get status from error } tmpl, err := template.ParseFS(templatesFs, "templates/root.html") // TODO create template if err != nil { log.Printf("while parsing template: %v", err) return 500 } tmpl.Execute(rw, t) return 200 } else { t, err := task(base, path) if err != nil { log.Printf("while getting task %s: %v", path, err) return 500 // TODO get status from error } tmpl, err := template.ParseFS(templatesFs, "templates/task.html") if err != nil { log.Printf("while parsing template: %v", err) return 500 } tmpl.Execute(rw, t) return 200 } } func root(base string) ([]Task, error) { tasks := []Task{} dirEntries, err := os.ReadDir(base) if err != nil { log.Printf("while reading dir: %v", err) os.Exit(1) } for _, dirEntry := range dirEntries { if !dirEntry.IsDir() { task, err := readTask(base, dirEntry.Name()) if err != nil { return []Task{}, fmt.Errorf("while reading dir: %v", err) } tasks = append(tasks, task) } } return tasks, nil } func task(base, path string) (Task, error) { middleTasks := strings.Split(path, "/")[1:] middleTasks = middleTasks[:len(middleTasks)-1] statuses := []string{} middlePath := "" for _, name := range middleTasks { middlePath = middlePath + "/" + name t, err := readTask(base, middlePath+".ogdl") if err != nil { return Task{}, fmt.Errorf("while reading middle task %s: %v", path, err) } if len(t.Statuses) > 0 { statuses = t.Statuses } } task, err := readTask(base, path+".ogdl") if err != nil { return Task{}, fmt.Errorf("while reading task %s: %v", path, err) } if len(task.Statuses) == 0 { task.Statuses = statuses } dirEntries, err := os.ReadDir(filepath.Join(base, path)) if err != nil { if errors.Is(err, fs.ErrNotExist) { return task, nil } return Task{}, fmt.Errorf("while reading dir: %v", err) } task.Subtasks = map[string][]Task{} for _, dirEntry := range dirEntries { if !dirEntry.IsDir() { subtask, err := readTask(base, filepath.Join(path, dirEntry.Name())) if err != nil { return Task{}, fmt.Errorf("while reading subtask %s: %v", path, err) } subtasks := task.Subtasks[subtask.Status] subtasks = append(subtasks, subtask) task.Subtasks[subtask.Status] = subtasks } } return task, nil } func readTask(base, path string) (Task, error) { g := ogdl.FromFile(filepath.Join(base, path)) if g == nil { return Task{}, fmt.Errorf("no such task: %s", path) } summary := g.String() g = g.Out[0] tags := strings.Join(getArray(g.Get("tags")), ", ") return Task{ Path: strings.ReplaceAll(path, ".ogdl", ""), Summary: summary, Status: g.Get("status").String(), Statuses: getArray(g.Get("statuses")), // TODO inherit Assignee: g.Get("assignee").String(), Description: g.Get("description").String(), Tags: tags, Priority: g.Get("priority").Int64(), Milestone: g.Get("milestone").String(), }, nil } func getArray(g *ogdl.Graph) []string { if g.Len() < 1 { return []string{} } array := make([]string, g.Len()) for i, o := range g.Out { array[i] = o.ThisString() } return array } |