Add life report library and command-line client
This commit is contained in:
commit
0e4c211104
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*~
|
||||||
|
*.swp
|
110
lifereport.go
Normal file
110
lifereport.go
Normal file
@ -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
|
||||||
|
}
|
1
lifereport/.gitignore
vendored
Normal file
1
lifereport/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
lifereport
|
40
lifereport/report.go
Normal file
40
lifereport/report.go
Normal file
@ -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)
|
||||||
|
}
|
22
report.tmpl
Normal file
22
report.tmpl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>life report.</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="wrap">
|
||||||
|
<h1>Ep. {{.Num}}: {{.Month}}</h1>
|
||||||
|
{{if .Summary}}<p>{{.Summary}}</p>{{end}}
|
||||||
|
{{range .Sections}}<h2>My {{.Title}}</h2>
|
||||||
|
{{if .Summary}}
|
||||||
|
{{range .Summary}}<p>{{.}}</p>{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{if .Details}}<ul>
|
||||||
|
{{range .Details}}<li>{{.}}</li>{{end}}
|
||||||
|
</ul>{{end}}
|
||||||
|
<hr />
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
36
reports/template.json
Normal file
36
reports/template.json
Normal file
@ -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": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user