printf.go raw

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