package layout import ( "bufio" "io" "log" "strconv" "strings" ) /********************************************************************************** The syntax for the layout specification is: ``` (rowspan:)?widget(/weight)? ``` 1. Each line is a row 2. Empty lines are skipped 3. Spaces are compressed 4. Legal widget names are: cpu, disk, mem, temp, batt, net, procs 5. Names are not case sensitive 4. The simplest row is a single widget, by name, e.g. ``` cpu ``` 5. Widgets with no weights have a weight of 1. 6. If multiple widgets are put on a row with no weights, they will all have the same width. 7. Weights are integers 8. A widget will have a width proportional to its weight divided by the total weight count of the row. E.g., ``` cpu net disk/2 mem/4 ``` The first row will have two widgets: the CPU and network widgets; each will be 50% of the total width wide. The second row will have two widgets: disk and memory; the first will be 2/6 ~= 33% wide, and the second will be 5/7 ~= 67% wide (or, memory will be twice as wide as disk). 9. If prefixed by a number and colon, the widget will span that number of rows downward. E.g. ``` 2:cpu mem ``` The CPU widget will be twice as high as the memory widget. Similarly, ``` mem 2:cpu net ``` memory and network will be in the same row as CPU, one over the other, and each half as high as CPU. 10. Negative, 0, or non-integer weights will be recorded as "1". Same for row spans. 11. Unrecognized widgets will cause the application to abort. 12. In rows with multi-row spanning widgets **and** weights, weights in lower rows are ignored. Put the weight on the widgets in that row, not in later (spanned) rows. 13. Widgets are filled in top down, left-to-right order. 14. The larges row span in a row defines the top-level row span; all smaller row spans constitude sub-rows in the row. For example, `cpu mem/3 net/5` means that net/5 will be 5 rows tall overall, and mem will compose 3 of them. If following rows do not have enough widgets to fill the gaps, spacers will be used. 15. Lines beginning with "#" will be ignored. It must be the first character of the line. **********************************************************************************/ func ParseLayout(i io.Reader) layout { r := bufio.NewScanner(i) rv := layout{Rows: make([][]widgetRule, 0)} var lineNo int for r.Scan() { l := strings.TrimSpace(r.Text()) if l == "" || l[0] == '#' { continue } row := make([]widgetRule, 0) ws := strings.Fields(l) weightTotal := 0 for _, w := range ws { wr := widgetRule{Weight: 1} ks := strings.Split(w, "/") rs := strings.Split(ks[0], ":") var wid string if len(rs) > 1 { v, e := strconv.Atoi(rs[0]) if e != nil { ln := strconv.Itoa(lineNo) log.Printf(tr.Value("layout.error.format", "INT:STRING/INT", ln, rs[0], w)) v = 1 } if v < 1 { v = 1 } wr.Height = v wid = rs[1] } else { wr.Height = 1 wid = rs[0] } wr.Widget = strings.ToLower(wid) if len(ks) > 1 { weight, e := strconv.Atoi(ks[1]) if e != nil { ln := strconv.Itoa(lineNo) log.Printf(tr.Value("layout.error.format", "STRING/INT", ln, ks[1], w)) weight = 1 } if weight < 1 { weight = 1 } wr.Weight = float64(weight) if len(ks) > 2 { ln := strconv.Itoa(lineNo) log.Printf(tr.Value("layout.error.slashes", ln, w)) } weightTotal += weight } else { weightTotal++ } row = append(row, wr) } // Prevent tricksy users from breaking their own computers if weightTotal <= 1 { weightTotal = 1 } for i, w := range row { row[i].Weight = w.Weight / float64(weightTotal) } rv.Rows = append(rv.Rows, row) } return rv }