summaryrefslogtreecommitdiffstats
path: root/layout/parser.go
blob: f35171e7ca17df2609d3de7a1b5133a00f52dcc7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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 {
					log.Printf("Layout error on line %d: format must be INT:STRING/INT. Error parsing %s as a int. Word was %s. Using a row height of 1.", lineNo, 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 {
					log.Printf("Layout error on line %d: format must be STRING/INT. Error parsing %s as a int. Word was %s. Using a weight of 1 for widget.", lineNo, ks[1], w)
					weight = 1
				}
				if weight < 1 {
					weight = 1
				}
				wr.Weight = float64(weight)
				if len(ks) > 2 {
					log.Printf("Layout warning on line %d: too many '/' in word %s; ignoring extra junk.", lineNo, w)
				}
				weightTotal += weight
			} else {
				weightTotal += 1
			}
			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
}