package main import ( "bufio" "fmt" "io" "os" "strconv" "strings" ) type Generator interface { Generate(TDL) } func semanticError(v ...interface{}) { v = append([]interface{}{"semantic error"}, v...) fmt.Fprintln(os.Stderr, v...) os.Exit(1) } func formatFloat(f float64) string { return strconv.FormatFloat(f, 'f', 2, 64) } func minOne(i int) int { if i < 1 { return 1 } else { return i } } type tableLine struct { cols [3]string } func newTableLine(first, second, third string) tableLine { return tableLine{[3]string{first, second, third}} } func columnLengths(lines []tableLine) [3]int { max := [3]int{0, 0, 0} for _, line := range lines { for i := 0; i < len(line.cols); i++ { if n := len([]rune(line.cols[i])); n > max[i] { max[i] = n } } } return max } func getTableFormat(lengths [3]int) string { format := "|" for _, length := range lengths { format += " %-" + strconv.Itoa(length) + "s |" } return format + "\n" } type StdGenerator struct { schedule TDL plates map[float64]int writer *bufio.Writer } func NewStdGenerator(writer io.Writer) *StdGenerator { return &StdGenerator{writer: bufio.NewWriter(writer)} } func (g *StdGenerator) write(v ...string) { for _, s := range v { g.writer.WriteString(s) } } func (g *StdGenerator) writeLine(v ...string) { g.write(v...) g.write("\n") } func (g *StdGenerator) writeTable(heading tableLine, lines []tableLine) { lengths := columnLengths(append(lines, heading)) formatString := getTableFormat(lengths) fmt.Fprintf(g.writer, formatString, heading.cols[0], heading.cols[1], heading.cols[2]) fmt.Fprintf(g.writer, formatString, strings.Repeat("-", lengths[0]), strings.Repeat("-", lengths[1]), strings.Repeat("-", lengths[2])) for _, line := range lines { fmt.Fprintf(g.writer, formatString, line.cols[0], line.cols[1], line.cols[2]) } } func (g *StdGenerator) init(schedule TDL) { g.schedule = schedule.Clone() g.plates = make(map[float64]int) for _, plate := range schedule.Plates { if _, ok := g.plates[plate.Weight]; ok { g.plates[plate.Weight] += plate.Amount } else { g.plates[plate.Weight] = plate.Amount } } if len(g.plates) == 0 { g.plates = map[float64]int{ 20.0: 26, 10.0: 2, 5.00: 2, 2.50: 2, 1.25: 2, } } for i := range g.schedule.Lifts { if g.schedule.Lifts[i].Bar == 0 { g.schedule.Lifts[i].Bar = 20 } } } func (g *StdGenerator) Generate(schedule TDL) { g.init(schedule) g.printMaxes() g.writeLine() g.printTrainingDays() g.printMaxes() g.writer.Flush() } func (g *StdGenerator) getLift(name string) *Lift { for i := range g.schedule.Lifts { if lift := &g.schedule.Lifts[i]; lift.Name == name { return lift } } semanticError("unknown lift '", name, "'") return nil } func (g *StdGenerator) increaseLift(lift *Lift, amount float64, relative bool) { if amount == 0 { amount = lift.Increment relative = lift.IncrementPercent } if relative { lift.Max *= amount / 100.0 } else { lift.Max += amount } } func (g *StdGenerator) getSetTemplate(name string) SetTemplate { for _, template := range g.schedule.SetTemplates { if template.Name == name { return template } } semanticError("unknown set template '", name, "'") return SetTemplate{} } func (g *StdGenerator) printMaxes() { g.writeLine("Maxes") g.writeLine("=====\n") for _, lift := range g.schedule.Lifts { g.writeLine(lift.Name, ": ", formatFloat(lift.Max)) } } func (g *StdGenerator) printTrainingDays() { for i, day := range g.schedule.TrainingDays { g.writeLine("Day ", strconv.Itoa(i+1)) g.writeLine("======\n") for _, item := range day.Items { g.printTrainingDayItem(item) } } } func (g *StdGenerator) printTrainingDayItem(item TrainingDayItem) { if item.Raw != "" { g.writeLine(item.Raw) } else { g.printLiftSchedule(item.LiftSchedule) } g.writeLine() } func (g *StdGenerator) printLiftSchedule(ls LiftSchedule) { lift := g.getLift(ls.LiftName) g.writeLine(lift.Name) g.writeLine("--------\n") heading := newTableLine("Set", "Plates", "Resulting") lines := []tableLine{} for _, item := range ls.Items { g.linesSetTemplateItem(&lines, lift, item) } g.writeTable(heading, lines) if ls.Increase { g.increaseLift(lift, ls.IncreaseAmount, ls.IncreasePercent) } } func (g *StdGenerator) linesSetTemplate(lines *[]tableLine, lift *Lift, template SetTemplate) { for _, item := range template.Items { g.linesSetTemplateItem(lines, lift, item) } return } func (g *StdGenerator) linesSetTemplateItem(lines *[]tableLine, lift *Lift, item SetTemplateItem) { for i := 0; i < minOne(item.Amount); i++ { if item.ReferenceName != "" { g.linesSetTemplate(lines, lift, g.getSetTemplate(item.ReferenceName)) } else { g.linesSet(lines, lift, item.Set) } } return } func (g *StdGenerator) linesSet(lines *[]tableLine, lift *Lift, set Set) { weight := lift.Max*set.Percentage/100.0 + set.PlusWeight plates, resulting := g.searchPlates(weight, lift.Bar) firstColumn := formatFloat(weight) + " x " + strconv.Itoa(set.Reps) if set.Amount > 1 { firstColumn += " x " + strconv.Itoa(set.Amount) } if set.Notice != "" { firstColumn += " " + set.Notice } *lines = append(*lines, newTableLine(firstColumn, plates, resulting)) return } func (g *StdGenerator) searchPlates(weight float64, bar float64) (string, string) { remaining := weight - bar if remaining <= 0 { return "", formatFloat(bar) } plateSequence := SearchPlates(g.plates, remaining) resulting := bar platesCounted := make(map[float64]int) for _, plate := range plateSequence { platesCounted[plate] = 0 resulting += 2 * plate } for _, plate := range plateSequence { platesCounted[plate] += 1 } plateString := "" for _, plate := range plateSequence { if platesCounted[plate] == 0 { continue } count := platesCounted[plate] platesCounted[plate] = 0 if plateString != "" { plateString += ", " } plateString += fmt.Sprint(plate) if count > 1 { plateString += "x" + strconv.Itoa(count) } } return plateString, formatFloat(resulting) }