github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/printf/printf.go (about) 1 // Package printf implements a parser for fmt.Printf-style format 2 // strings. 3 // 4 // It parses verbs according to the following syntax: 5 // Numeric -> '0'-'9' 6 // Letter -> 'a'-'z' | 'A'-'Z' 7 // Index -> '[' Numeric+ ']' 8 // Star -> '*' 9 // Star -> Index '*' 10 // 11 // Precision -> Numeric+ | Star 12 // Width -> Numeric+ | Star 13 // 14 // WidthAndPrecision -> Width '.' Precision 15 // WidthAndPrecision -> Width '.' 16 // WidthAndPrecision -> Width 17 // WidthAndPrecision -> '.' Precision 18 // WidthAndPrecision -> '.' 19 // 20 // Flag -> '+' | '-' | '#' | ' ' | '0' 21 // Verb -> Letter | '%' 22 // 23 // Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb 24 package printf 25 26 import ( 27 "errors" 28 "regexp" 29 "strconv" 30 "strings" 31 ) 32 33 // ErrInvalid is returned for invalid format strings or verbs. 34 var ErrInvalid = errors.New("invalid format string") 35 36 type Verb struct { 37 Letter rune 38 Flags string 39 40 Width Argument 41 Precision Argument 42 // Which value in the argument list the verb uses. 43 // -1 denotes the next argument, 44 // values > 0 denote explicit arguments. 45 // The value 0 denotes that no argument is consumed. This is the case for %%. 46 Value int 47 48 Raw string 49 } 50 51 // Argument is an implicit or explicit width or precision. 52 type Argument interface { 53 isArgument() 54 } 55 56 // The Default value, when no width or precision is provided. 57 type Default struct{} 58 59 // Zero is the implicit zero value. 60 // This value may only appear for precisions in format strings like %6.f 61 type Zero struct{} 62 63 // Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument. 64 type Star struct{ Index int } 65 66 // A Literal value, such as 6 in %6d. 67 type Literal int 68 69 func (Default) isArgument() {} 70 func (Zero) isArgument() {} 71 func (Star) isArgument() {} 72 func (Literal) isArgument() {} 73 74 // Parse parses f and returns a list of actions. 75 // An action may either be a literal string, or a Verb. 76 func Parse(f string) ([]interface{}, error) { 77 var out []interface{} 78 for len(f) > 0 { 79 if f[0] == '%' { 80 v, n, err := ParseVerb(f) 81 if err != nil { 82 return nil, err 83 } 84 f = f[n:] 85 out = append(out, v) 86 } else { 87 n := strings.IndexByte(f, '%') 88 if n > -1 { 89 out = append(out, f[:n]) 90 f = f[n:] 91 } else { 92 out = append(out, f) 93 f = "" 94 } 95 } 96 } 97 98 return out, nil 99 } 100 101 func atoi(s string) int { 102 n, _ := strconv.Atoi(s) 103 return n 104 } 105 106 // ParseVerb parses the verb at the beginning of f. 107 // It returns the verb, how much of the input was consumed, and an error, if any. 108 func ParseVerb(f string) (Verb, int, error) { 109 if len(f) < 2 { 110 return Verb{}, 0, ErrInvalid 111 } 112 const ( 113 flags = 1 114 115 width = 2 116 widthStar = 3 117 widthIndex = 5 118 119 dot = 6 120 prec = 7 121 precStar = 8 122 precIndex = 10 123 124 verbIndex = 11 125 verb = 12 126 ) 127 128 m := re.FindStringSubmatch(f) 129 if m == nil { 130 return Verb{}, 0, ErrInvalid 131 } 132 133 v := Verb{ 134 Letter: []rune(m[verb])[0], 135 Flags: m[flags], 136 Raw: m[0], 137 } 138 139 if m[width] != "" { 140 // Literal width 141 v.Width = Literal(atoi(m[width])) 142 } else if m[widthStar] != "" { 143 // Star width 144 if m[widthIndex] != "" { 145 v.Width = Star{atoi(m[widthIndex])} 146 } else { 147 v.Width = Star{-1} 148 } 149 } else { 150 // Default width 151 v.Width = Default{} 152 } 153 154 if m[dot] == "" { 155 // default precision 156 v.Precision = Default{} 157 } else { 158 if m[prec] != "" { 159 // Literal precision 160 v.Precision = Literal(atoi(m[prec])) 161 } else if m[precStar] != "" { 162 // Star precision 163 if m[precIndex] != "" { 164 v.Precision = Star{atoi(m[precIndex])} 165 } else { 166 v.Precision = Star{-1} 167 } 168 } else { 169 // Zero precision 170 v.Precision = Zero{} 171 } 172 } 173 174 if m[verb] == "%" { 175 v.Value = 0 176 } else if m[verbIndex] != "" { 177 v.Value = atoi(m[verbIndex]) 178 } else { 179 v.Value = -1 180 } 181 182 return v, len(m[0]), nil 183 } 184 185 const ( 186 flags = `([+#0 -]*)` 187 verb = `([a-zA-Z%])` 188 index = `(?:\[([0-9]+)\])` 189 star = `((` + index + `)?\*)` 190 width1 = `([0-9]+)` 191 width2 = star 192 width = `(?:` + width1 + `|` + width2 + `)` 193 precision = width 194 widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)` 195 ) 196 197 var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)