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)