From 0e4c2111046f83f939858a36b0d19b96318cbd37 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 7 Mar 2016 13:10:52 -0500 Subject: [PATCH] Add life report library and command-line client --- .gitignore | 2 + lifereport.go | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ lifereport/.gitignore | 1 + lifereport/report.go | 40 ++++++++++++++++++ report.tmpl | 22 ++++++++++ reports/template.json | 36 +++++++++++++++++ 6 files changed, 211 insertions(+) create mode 100644 .gitignore create mode 100644 lifereport.go create mode 100644 lifereport/.gitignore create mode 100644 lifereport/report.go create mode 100644 report.tmpl create mode 100644 reports/template.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b72f9be --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +*.swp diff --git a/lifereport.go b/lifereport.go new file mode 100644 index 0000000..c75b0c5 --- /dev/null +++ b/lifereport.go @@ -0,0 +1,110 @@ +// report parses life report JSON and +package report + +import ( + "encoding/json" + "fmt" + "html/template" + "os" + "path" + "strconv" + "strings" +) + +type ( + // Report represents a life report. It contains summaries and lists on + // various areas of a given person's life for the given time period -- + // in this case, a month. + Report struct { + Num int + Month string + Year int `json:"year"` + Summary string `json:"summary"` + Sections []Section + } + + // Section represents an area of life to report on. This contains a + // title to describe the area, a set of paragraphs representing a + // summary, and a set of details representing a list of more specific + // items. + Section struct { + Title string `json:"title"` + Summary []template.HTML `json:"summary"` + Details []template.HTML `json:"details"` + } +) + +// ParseReport takes a file and generates a life report from it. It +// assumes the file is named in the format NNN-month.json where NNN is the +// number of the report, usually named with leading zeroes so they're +// ordered correctly on your filesystem. +func ParseReport(f *os.File) (*Report, error) { + var err error + r := Report{} + + // Determine report number and month from filename in the format + // NNN-month.json + baseName := path.Base(f.Name()) + fd := strings.Split(baseName[:strings.Index(baseName, ".json")], "-") + if len(fd) >= 2 { + r.Num, err = strconv.Atoi(fd[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to parse report number: %v\n", err) + r.Num = 1 + } + r.Month = strings.Title(fd[1]) + } else { + r.Num = 1 + r.Month = "First" + } + + // Parse JSON into a map + var d map[string]interface{} + jsonParser := json.NewDecoder(f) + if err = jsonParser.Decode(&d); err != nil { + return nil, err + } + + // Populate Report struct from the map + for k, v := range d { + // Handle consistent properties here, like year + if k == "year" { + r.Year = int(v.(float64)) + continue + } else if k == "summary" { + r.Summary = v.(string) + continue + } + + // All other properties will be the names of sections. The property name + // will be used as the default title for any given section, unless + // overridden by the object's actual `title` property. + s := Section{ + Title: k, + } + m := v.(map[string]interface{}) + if m["title"] != nil { + s.Title = m["title"].(string) + } + + // Populate section summary + if m["summary"] != nil { + sums := m["summary"].([]interface{}) + for _, sum := range sums { + s.Summary = append(s.Summary, template.HTML(sum.(string))) + } + } + + // Populate section details + if m["details"] != nil { + dets := m["details"].([]interface{}) + for _, det := range dets { + s.Details = append(s.Details, template.HTML(det.(string))) + } + } + + r.Sections = append(r.Sections, s) + } + + return &r, nil +} diff --git a/lifereport/.gitignore b/lifereport/.gitignore new file mode 100644 index 0000000..669e54c --- /dev/null +++ b/lifereport/.gitignore @@ -0,0 +1 @@ +lifereport diff --git a/lifereport/report.go b/lifereport/report.go new file mode 100644 index 0000000..648f645 --- /dev/null +++ b/lifereport/report.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "html/template" + "os" + "path" + + report "github.com/thebaer/life-report" +) + +func main() { + if len(os.Args) < 2 { + fmt.Print(`usage: lifereport [directory] + directory: path to a folder of reports +`) + return + } + + dir := os.Args[1] + f, err := os.Open(path.Join(dir, "template.json")) + if err != nil { + fmt.Fprintf(os.Stdout, "Unable to open file: %v\n", err) + return + } + + r, err := report.ParseReport(f) + if err != nil { + fmt.Fprintf(os.Stdout, "Unable to parse report: %v\n", err) + return + } + f.Close() + + t, err := template.ParseFiles("../report.tmpl") + if err != nil { + fmt.Fprintf(os.Stdout, "Unable to parse template: %v\n", err) + return + } + t.Execute(os.Stdout, r) +} diff --git a/report.tmpl b/report.tmpl new file mode 100644 index 0000000..03afc41 --- /dev/null +++ b/report.tmpl @@ -0,0 +1,22 @@ + + + + life report. + + + +
+

Ep. {{.Num}}: {{.Month}}

+ {{if .Summary}}

{{.Summary}}

{{end}} + {{range .Sections}}

My {{.Title}}

+ {{if .Summary}} + {{range .Summary}}

{{.}}

{{end}} + {{end}} + {{if .Details}}{{end}} +
+ {{end}} +
+ + diff --git a/reports/template.json b/reports/template.json new file mode 100644 index 0000000..294e0d4 --- /dev/null +++ b/reports/template.json @@ -0,0 +1,36 @@ +{ + "year": 2016, + "health": { + "title": "health / diet", + "details": [ + ] + }, + "money": { + "summary": [ + ] + }, + "travels": { + "details": [ + ] + }, + "studies": { + "details": [ + ] + }, + "music": { + "details": [ + ] + }, + "books": { + "details": [ + ] + }, + "distractions": { + "details": [ + ] + }, + "projects": { + "details": [ + ] + } +}