-
Notifications
You must be signed in to change notification settings - Fork 2
/
format.go
125 lines (117 loc) · 2.42 KB
/
format.go
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
package main
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
"unicode"
)
var BadFormatStr = fmt.Errorf("bad format string")
func FormatHelp(keys []KeyDesc) string {
return `
<fmtstr> syntax:
$<var>: The value of a preset or user-set variable. Must be followed
by something other than a letter, number or _.
Will error if the variable is not defined without a default value.
${<var>}: Alternative syntax.
[...]: Only displays the text if all variables used inside are defined.
For example to only display the '::' if there are unmet deps. use:
$path[ :: $unmet]
\...: Standard backslash escaping.
preset variables:
` + KeysHelp(keys)
}
func (v *Todo) Format(fmtorig string) ([]byte, error) {
buf, fmt, err := v.format(fmtorig)
if err != nil && err != defaultUsed {
return nil, err
}
if len(fmt) != 0 {
return nil, BadFormatStr
}
return buf.Bytes(), nil
}
var defaultUsed = errors.New("default used")
func (v *Todo) format(orig string) (buf bytes.Buffer, str string, err error) {
str = orig
for len(str) > 0 {
switch str[0] {
case '\\':
if len(str) >= 2 && asciiIsSymbol(str[1]) {
buf.WriteByte(str[1])
str = str[2:]
} else {
ch, _, s, e := strconv.UnquoteChar(str, 0)
if e != nil {
err = BadFormatStr
return
}
str = s
buf.WriteRune(ch)
}
case '[':
b, s, e := v.format(str[1:])
if e == BadFormatStr {
err = e
return
}
if e == nil {
buf.Write(b.Bytes())
}
str = s
case ']':
str = str[1:]
return
case '$':
key := ""
str = str[1:]
if len(str) == 0 {
buf.WriteByte('$')
return
}
if str[0] == '{' {
i := strings.IndexByte(str, '}')
if i == -1 {
err = BadFormatStr
return
}
key = str[1:i]
str = str[i+1:]
} else {
i := len(str)
for j, ch := range str {
if ch != '_' && !unicode.IsLetter(ch) {
i = j
break
}
}
key = str[:i]
str = str[i:]
}
if key == "" {
buf.WriteByte('$')
continue
}
val, ok, e := v.Get(key)
if e != nil {
err = e
continue
}
if !ok {
if err == nil {
err = defaultUsed
}
continue
}
buf.WriteString(val)
default:
buf.WriteByte(str[0])
str = str[1:]
}
}
return
}
func asciiIsSymbol(ch byte) bool {
return '!' <= ch && ch <= '/' || ':' <= ch && ch <= '@' || '[' <= ch && ch <= '`' || '{' <= ch && ch <= '~'
}