Browse Source

Add life report library and command-line client

master
Matt Baer 8 years ago
commit
0e4c211104
6 changed files with 211 additions and 0 deletions
  1. +2
    -0
      .gitignore
  2. +110
    -0
      lifereport.go
  3. +1
    -0
      lifereport/.gitignore
  4. +40
    -0
      lifereport/report.go
  5. +22
    -0
      report.tmpl
  6. +36
    -0
      reports/template.json

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
*~
*.swp

+ 110
- 0
lifereport.go View 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
- 0
lifereport/.gitignore View File

@@ -0,0 +1 @@
lifereport

+ 40
- 0
lifereport/report.go View 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
- 0
report.tmpl View 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
- 0
reports/template.json View 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…
Cancel
Save