From: Fl_GUI Date: Sun, 26 Jan 2025 09:48:25 +0000 (+0100) Subject: initial commit X-Git-Url: https://git.openfl.eu/?a=commitdiff_plain;h=d9090ae072ff439163adb2a4642b2a1ade17bbc0;p=yampl.git initial commit --- d9090ae072ff439163adb2a4642b2a1ade17bbc0 diff --git a/examples/templates/name/main.html b/examples/templates/name/main.html new file mode 100644 index 0000000..e06a6ca --- /dev/null +++ b/examples/templates/name/main.html @@ -0,0 +1,12 @@ +{{ define "main" }} + + + + + + {{ range .Items }} + {{ template "item" . }} + {{ end }} + + +{{ end }} diff --git a/examples/templates/name/name.html b/examples/templates/name/name.html new file mode 100644 index 0000000..f7d4176 --- /dev/null +++ b/examples/templates/name/name.html @@ -0,0 +1,9 @@ +{{ define "item" }} +
+ {{ $color := "black" }} + {{ if .completed }} + {{ $color = "gray" }} + {{ end }} + {{ .name }} +
+{{ end }} diff --git a/examples/templates/yampl/details.html b/examples/templates/yampl/details.html new file mode 100644 index 0000000..9d882bd --- /dev/null +++ b/examples/templates/yampl/details.html @@ -0,0 +1,39 @@ +{{ define "item" }} + + + + + + + > back +
+ {{/* Choose an emoji */}} + {{ $symbol := "" }} + {{ $gray := false }} + {{ if eq .type "task" }} + {{ if .completed }} + {{ $symbol = "✅" }} + {{ else }} + {{ $symbol = "❎" }} + {{ end}} + {{ else if eq .type "bug" }} + {{ $symbol = "🐞" }} + {{ $gray = .completed }} + {{ else if eq .type "milestone" }} + {{ $symbol = "🏆" }} + {{ $gray = not .completed }} + {{ end }} + + {{/* Emoji tooltip */}} + {{ $completed := "" }} + {{ if .completed }} + {{ $completed = "completed" }} + {{ end }} + {{ $tooltip := printf "%s %s" $completed .type }} + +

{{ $symbol }} {{ .name }}

+

{{ .description }}

+
+ + +{{ end }} diff --git a/examples/templates/yampl/listitem.html b/examples/templates/yampl/listitem.html new file mode 100644 index 0000000..ea6d85a --- /dev/null +++ b/examples/templates/yampl/listitem.html @@ -0,0 +1,9 @@ +{{ define "listitem" }} +
+ {{ $color := "black" }} + {{ if .completed }} + {{ $color = "gray" }} + {{ end }} + {{ .name }} +
+{{ end }} diff --git a/examples/templates/yampl/main.html b/examples/templates/yampl/main.html new file mode 100644 index 0000000..d9559c5 --- /dev/null +++ b/examples/templates/yampl/main.html @@ -0,0 +1,23 @@ +{{ define "main" }} + + + + + + +
+ {{ with .Project }} +

{{ .projectname }}

+

{{ .projectdescription }}

+ {{ end }} +

backlog

+
+
+ {{ range .Items }} + {{ template "listitem" . }} + {{ end }} +
+
+ + +{{ end }} diff --git a/examples/toml/empty.toml b/examples/toml/empty.toml new file mode 100644 index 0000000..dd88931 --- /dev/null +++ b/examples/toml/empty.toml @@ -0,0 +1,3 @@ +projectName = "void" + +items = [] diff --git a/examples/toml/yampl.toml b/examples/toml/yampl.toml new file mode 100644 index 0000000..25cce17 --- /dev/null +++ b/examples/toml/yampl.toml @@ -0,0 +1,92 @@ +[project] +projectname = "yampl" +projectdescription = ''' +A toml-to-html site generator for project management. +''' + +[[items]] +name = "hello world" +description = ''' +Have an program. +''' +type = "milestone" +completed = true + +[[items]] +name = "command line utility" +description = ''' +Run the program once as command line utility. +It acts as a conversion script. +It turns the project file and template into a html file. +''' +type = "task" +completed = true + +[[items]] +name = "http server" +description = ''' +Run the program as a http service. +On request it reads the project file and runs the detemplating. +''' +type = "task" +completed = true + +[[items]] +name = "project repo" +description = ''' +set up a git page for yampl. +''' +type = "task" +completed = false + +[[items]] +name = "static links to details" +description = ''' +Links are based on index of the item, but this index can change. +Figure out static routing, perhaps based on name. +''' +type = "bug" +completed = false + +[[items]] +name = "hosted" +description = ''' +Host yampl for yampl on project.openfl.eu/yamlp +''' +type = "milestone" +completed = false + +[[items]] +name = "git hook" +description = ''' +On push to main branch of yampl, the toml file must be checked out into the working directory. +The yampl service should pick up that new file. +''' +type = "task" +completed = false + +[[items]] +name = "watch project file" +description = ''' +Run the program as a http service, with websocket connection to the frontend. +On project file change, reload the file, and update clients with new content. +''' +type = "task" +completed = false + +[[items]] +name = "auto hosted" +description = ''' +Have the hosting be completely automatic, with automatic updates of the project file +and the container. +''' +type = "milestone" +completed = false + +[[items]] +name = "heat death of the universe" +description = ''' +survive. +''' +type = "milestone" +completed = false diff --git a/file.go b/file.go new file mode 100644 index 0000000..d99b529 --- /dev/null +++ b/file.go @@ -0,0 +1,29 @@ +package main + +import ( + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" +) + +var InvalidFilepathError = errors.New("invalid file.") + +func getFile(filepath string) (io.ReadCloser, error) { + dirname, filename := path.Split(filepath) + if dirname == "" { + return nil, fmt.Errorf("%w: the file '%s' could not be read", InvalidFilepathError, filepath) + } + dirFS := os.DirFS(dirname) + fileInfo, err := fs.Stat(dirFS, filename) + if err != nil { + return nil, fmt.Errorf("%w: could not check the file %s", InvalidFilepathError, filepath) + } + if !fileInfo.Mode().IsRegular() { + return nil, fmt.Errorf("%w: the file '%s' is not a text file", InvalidFilepathError, filepath) + } + + return os.Open(filepath) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6c4c8db --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module go.openfl.eu/yampl + +go 1.23.5 + +require github.com/BurntSushi/toml v1.4.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8bc10f6 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= diff --git a/main.go b/main.go new file mode 100644 index 0000000..311d998 --- /dev/null +++ b/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "strconv" + + "github.com/BurntSushi/toml" +) + +type ProjectData struct { + Project Project + Items []ProjectItems +} + +type Project any +type ProjectItems map[string]any + +var tomlfilepath = flag.String("toml", "", "path from current working directory to project file") +var tempdirpath = flag.String("templ", "", "path from current working directory to a directory containing template files") +var staticdirpath = flag.String("static", "", "path from current working direcotry to a directory containing static files to serve over http") + +func listRespond(w http.ResponseWriter, r *http.Request) { + templ, err := getTemplate(*tempdirpath) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + tomlfile, err := getFile(*tomlfilepath) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + var data ProjectData + _, err = toml.NewDecoder(tomlfile).Decode(&data) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + for i, it := range data.Items { + it["_index"] = i + it["_url"] = fmt.Sprintf("/items/%d", i) + } + + templ.ExecuteTemplate(w, "main", data) +} + +func itemRespond(w http.ResponseWriter, r *http.Request) { + templ, err := getTemplate(*tempdirpath) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + tomlfile, err := getFile(*tomlfilepath) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + var data ProjectData + _, err = toml.NewDecoder(tomlfile).Decode(&data) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + index, err := strconv.Atoi(r.PathValue("index")) + if err != nil || index < 0 || len(data.Items) < index { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + templ.ExecuteTemplate(w, "item", data.Items[index]) +} + +func main() { + flag.Parse() + if *tomlfilepath == "" || *tempdirpath == "" { + flag.Usage() + os.Exit(-1) + } + + http.HandleFunc("GET /{$}", listRespond) + http.HandleFunc("GET /items/{index}", itemRespond) + if *staticdirpath != "" { + log.Println("serving static content") + http.Handle("GET /static/", http.StripPrefix("/static", http.FileServerFS(os.DirFS(*staticdirpath)))) + } + + fmt.Println("listening on localhost:8081") + panic(http.ListenAndServe(":8081", nil)) +} diff --git a/out.html b/out.html new file mode 100644 index 0000000..1e134bf --- /dev/null +++ b/out.html @@ -0,0 +1,43 @@ + + + + + + + + +
+ + + + + command line utility +
+ + + +
+ + + http server +
+ + + +
+ + + watch project file +
+ + + +
+ + + heat death of the universe +
+ + + + diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..45b983b --- /dev/null +++ b/static/index.html @@ -0,0 +1 @@ +hi diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..936cbf1 --- /dev/null +++ b/static/style.css @@ -0,0 +1,41 @@ +body { + background-color: white; + display: flex; + justify-content: center; + font-family: monospace; + background-color: blanchedalmond; +} +.column { + max-width: 50%; +} + +.items { +} + +.link { + color: black; + text-decoration: none;; +} + +.link :hover { + text-decoration: underline; +} + +h2.centered { + text-align: center; +} + +.bgtask { + background-color: lightblue; +} +.bgbug { + background-color: crimson; +} + +.bgmilestone { + background-color: goldenrod; +} + +.grayscale { + filter: grayscale(100%); +} \ No newline at end of file diff --git a/template.go b/template.go new file mode 100644 index 0000000..9c416c8 --- /dev/null +++ b/template.go @@ -0,0 +1,11 @@ +package main + +import ( + "html/template" + "os" +) + +func getTemplate(templpath string) (*template.Template, error) { + templfs := os.DirFS(templpath) + return template.ParseFS(templfs, "*.html") +}