|
|
@@ -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 |
|
|
|
} |