435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
198 lines
4.2 KiB
Go
198 lines
4.2 KiB
Go
// Package printf implements a parser for fmt.Printf-style format
|
|
// strings.
|
|
//
|
|
// It parses verbs according to the following syntax:
|
|
// Numeric -> '0'-'9'
|
|
// Letter -> 'a'-'z' | 'A'-'Z'
|
|
// Index -> '[' Numeric+ ']'
|
|
// Star -> '*'
|
|
// Star -> Index '*'
|
|
//
|
|
// Precision -> Numeric+ | Star
|
|
// Width -> Numeric+ | Star
|
|
//
|
|
// WidthAndPrecision -> Width '.' Precision
|
|
// WidthAndPrecision -> Width '.'
|
|
// WidthAndPrecision -> Width
|
|
// WidthAndPrecision -> '.' Precision
|
|
// WidthAndPrecision -> '.'
|
|
//
|
|
// Flag -> '+' | '-' | '#' | ' ' | '0'
|
|
// Verb -> Letter | '%'
|
|
//
|
|
// Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb
|
|
package printf
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ErrInvalid is returned for invalid format strings or verbs.
|
|
var ErrInvalid = errors.New("invalid format string")
|
|
|
|
type Verb struct {
|
|
Letter rune
|
|
Flags string
|
|
|
|
Width Argument
|
|
Precision Argument
|
|
// Which value in the argument list the verb uses.
|
|
// -1 denotes the next argument,
|
|
// values > 0 denote explicit arguments.
|
|
// The value 0 denotes that no argument is consumed. This is the case for %%.
|
|
Value int
|
|
|
|
Raw string
|
|
}
|
|
|
|
// Argument is an implicit or explicit width or precision.
|
|
type Argument interface {
|
|
isArgument()
|
|
}
|
|
|
|
// The Default value, when no width or precision is provided.
|
|
type Default struct{}
|
|
|
|
// Zero is the implicit zero value.
|
|
// This value may only appear for precisions in format strings like %6.f
|
|
type Zero struct{}
|
|
|
|
// Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument.
|
|
type Star struct{ Index int }
|
|
|
|
// A Literal value, such as 6 in %6d.
|
|
type Literal int
|
|
|
|
func (Default) isArgument() {}
|
|
func (Zero) isArgument() {}
|
|
func (Star) isArgument() {}
|
|
func (Literal) isArgument() {}
|
|
|
|
// Parse parses f and returns a list of actions.
|
|
// An action may either be a literal string, or a Verb.
|
|
func Parse(f string) ([]interface{}, error) {
|
|
var out []interface{}
|
|
for len(f) > 0 {
|
|
if f[0] == '%' {
|
|
v, n, err := ParseVerb(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f = f[n:]
|
|
out = append(out, v)
|
|
} else {
|
|
n := strings.IndexByte(f, '%')
|
|
if n > -1 {
|
|
out = append(out, f[:n])
|
|
f = f[n:]
|
|
} else {
|
|
out = append(out, f)
|
|
f = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func atoi(s string) int {
|
|
n, _ := strconv.Atoi(s)
|
|
return n
|
|
}
|
|
|
|
// ParseVerb parses the verb at the beginning of f.
|
|
// It returns the verb, how much of the input was consumed, and an error, if any.
|
|
func ParseVerb(f string) (Verb, int, error) {
|
|
if len(f) < 2 {
|
|
return Verb{}, 0, ErrInvalid
|
|
}
|
|
const (
|
|
flags = 1
|
|
|
|
width = 2
|
|
widthStar = 3
|
|
widthIndex = 5
|
|
|
|
dot = 6
|
|
prec = 7
|
|
precStar = 8
|
|
precIndex = 10
|
|
|
|
verbIndex = 11
|
|
verb = 12
|
|
)
|
|
|
|
m := re.FindStringSubmatch(f)
|
|
if m == nil {
|
|
return Verb{}, 0, ErrInvalid
|
|
}
|
|
|
|
v := Verb{
|
|
Letter: []rune(m[verb])[0],
|
|
Flags: m[flags],
|
|
Raw: m[0],
|
|
}
|
|
|
|
if m[width] != "" {
|
|
// Literal width
|
|
v.Width = Literal(atoi(m[width]))
|
|
} else if m[widthStar] != "" {
|
|
// Star width
|
|
if m[widthIndex] != "" {
|
|
v.Width = Star{atoi(m[widthIndex])}
|
|
} else {
|
|
v.Width = Star{-1}
|
|
}
|
|
} else {
|
|
// Default width
|
|
v.Width = Default{}
|
|
}
|
|
|
|
if m[dot] == "" {
|
|
// default precision
|
|
v.Precision = Default{}
|
|
} else {
|
|
if m[prec] != "" {
|
|
// Literal precision
|
|
v.Precision = Literal(atoi(m[prec]))
|
|
} else if m[precStar] != "" {
|
|
// Star precision
|
|
if m[precIndex] != "" {
|
|
v.Precision = Star{atoi(m[precIndex])}
|
|
} else {
|
|
v.Precision = Star{-1}
|
|
}
|
|
} else {
|
|
// Zero precision
|
|
v.Precision = Zero{}
|
|
}
|
|
}
|
|
|
|
if m[verb] == "%" {
|
|
v.Value = 0
|
|
} else if m[verbIndex] != "" {
|
|
v.Value = atoi(m[verbIndex])
|
|
} else {
|
|
v.Value = -1
|
|
}
|
|
|
|
return v, len(m[0]), nil
|
|
}
|
|
|
|
const (
|
|
flags = `([+#0 -]*)`
|
|
verb = `([a-zA-Z%])`
|
|
index = `(?:\[([0-9]+)\])`
|
|
star = `((` + index + `)?\*)`
|
|
width1 = `([0-9]+)`
|
|
width2 = star
|
|
width = `(?:` + width1 + `|` + width2 + `)`
|
|
precision = width
|
|
widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)`
|
|
)
|
|
|
|
var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb)
|