Update dependencies

This commit is contained in:
Andrey Nering
2017-06-04 16:06:04 -03:00
parent f98bf6c4b1
commit 09e6d5269d
130 changed files with 26814 additions and 19205 deletions

View File

@@ -1,3 +1,3 @@
Compatible with TOML version
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)

View File

@@ -1,17 +1,17 @@
## TOML parser and encoder for Go with reflection
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml`
reflection interface similar to Go's standard library `json` and `xml`
packages. This package also supports the `encoding.TextUnmarshaler` and
`encoding.TextMarshaler` interfaces so that you can define custom data
`encoding.TextMarshaler` interfaces so that you can define custom data
representations. (There is an example of this below.)
Spec: https://github.com/mojombo/toml
Spec: https://github.com/toml-lang/toml
Compatible with TOML version
[v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md)
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
Documentation: http://godoc.org/github.com/BurntSushi/toml
Documentation: https://godoc.org/github.com/BurntSushi/toml
Installation:
@@ -26,8 +26,7 @@ go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml
```
[![Build status](https://api.travis-ci.org/BurntSushi/toml.png)](https://travis-ci.org/BurntSushi/toml)
[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml)
### Testing
@@ -87,7 +86,7 @@ type TOML struct {
### Using the `encoding.TextUnmarshaler` interface
Here's an example that automatically parses duration strings into
Here's an example that automatically parses duration strings into
`time.Duration` values:
```toml
@@ -120,7 +119,7 @@ for _, s := range favorites.Song {
}
```
And you'll also need a `duration` type that satisfies the
And you'll also need a `duration` type that satisfies the
`encoding.TextUnmarshaler` interface:
```go
@@ -217,4 +216,3 @@ Note that a case insensitive match will be tried if an exact match can't be
found.
A working example of the above can be found in `_examples/example.{go,toml}`.

View File

@@ -4,7 +4,7 @@ files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the
MetaData type.
The specification implemented: https://github.com/mojombo/toml
The specification implemented: https://github.com/toml-lang/toml
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
whether a file is a valid TOML document. It can also be used to print the

View File

@@ -241,7 +241,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key)
if len(key) == 1 {
// Output an extra new line between top-level tables.
// Output an extra newline between top-level tables.
// (The newline isn't written if nothing else has been written though.)
enc.newline()
}

View File

@@ -30,24 +30,28 @@ const (
itemArrayTableEnd
itemKeyStart
itemCommentStart
itemInlineTableStart
itemInlineTableEnd
)
const (
eof = 0
tableStart = '['
tableEnd = ']'
arrayTableStart = '['
arrayTableEnd = ']'
tableSep = '.'
keySep = '='
arrayStart = '['
arrayEnd = ']'
arrayValTerm = ','
commentStart = '#'
stringStart = '"'
stringEnd = '"'
rawStringStart = '\''
rawStringEnd = '\''
eof = 0
comma = ','
tableStart = '['
tableEnd = ']'
arrayTableStart = '['
arrayTableEnd = ']'
tableSep = '.'
keySep = '='
arrayStart = '['
arrayEnd = ']'
commentStart = '#'
stringStart = '"'
stringEnd = '"'
rawStringStart = '\''
rawStringEnd = '\''
inlineTableStart = '{'
inlineTableEnd = '}'
)
type stateFn func(lx *lexer) stateFn
@@ -56,11 +60,18 @@ type lexer struct {
input string
start int
pos int
width int
line int
state stateFn
items chan item
// Allow for backing up up to three runes.
// This is necessary because TOML contains 3-rune tokens (""" and ''').
prevWidths [3]int
nprev int // how many of prevWidths are in use
// If we emit an eof, we can still back up, but it is not OK to call
// next again.
atEOF bool
// A stack of state functions used to maintain context.
// The idea is to reuse parts of the state machine in various places.
// For example, values can appear at the top level or within arbitrarily
@@ -88,7 +99,7 @@ func (lx *lexer) nextItem() item {
func lex(input string) *lexer {
lx := &lexer{
input: input + "\n",
input: input,
state: lexTop,
line: 1,
items: make(chan item, 10),
@@ -103,7 +114,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 {
return lx.errorf("BUG in lexer: no states to pop.")
return lx.errorf("BUG in lexer: no states to pop")
}
last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1]
@@ -125,16 +136,25 @@ func (lx *lexer) emitTrim(typ itemType) {
}
func (lx *lexer) next() (r rune) {
if lx.atEOF {
panic("next called after EOF")
}
if lx.pos >= len(lx.input) {
lx.width = 0
lx.atEOF = true
return eof
}
if lx.input[lx.pos] == '\n' {
lx.line++
}
r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:])
lx.pos += lx.width
lx.prevWidths[2] = lx.prevWidths[1]
lx.prevWidths[1] = lx.prevWidths[0]
if lx.nprev < 3 {
lx.nprev++
}
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
lx.prevWidths[0] = w
lx.pos += w
return r
}
@@ -143,9 +163,20 @@ func (lx *lexer) ignore() {
lx.start = lx.pos
}
// backup steps back one rune. Can be called only once per call of next.
// backup steps back one rune. Can be called only twice between calls to next.
func (lx *lexer) backup() {
lx.pos -= lx.width
if lx.atEOF {
lx.atEOF = false
return
}
if lx.nprev < 1 {
panic("backed up too far")
}
w := lx.prevWidths[0]
lx.prevWidths[0] = lx.prevWidths[1]
lx.prevWidths[1] = lx.prevWidths[2]
lx.nprev--
lx.pos -= w
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
lx.line--
}
@@ -182,7 +213,7 @@ func (lx *lexer) skip(pred func(rune) bool) {
// errorf stops all lexing by emitting an error and returning `nil`.
// Note that any value that is a character is escaped if it's a special
// character (new lines, tabs, etc.).
// character (newlines, tabs, etc.).
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
lx.items <- item{
itemError,
@@ -198,7 +229,6 @@ func lexTop(lx *lexer) stateFn {
if isWhitespace(r) || isNL(r) {
return lexSkip(lx, lexTop)
}
switch r {
case commentStart:
lx.push(lexTop)
@@ -207,7 +237,7 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart
case eof:
if lx.pos > lx.start {
return lx.errorf("Unexpected EOF.")
return lx.errorf("unexpected EOF")
}
lx.emit(itemEOF)
return nil
@@ -222,12 +252,12 @@ func lexTop(lx *lexer) stateFn {
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
// or a table.) It must see only whitespace, and will turn back to lexTop
// upon a new line. If it sees EOF, it will quit the lexer successfully.
// upon a newline. If it sees EOF, it will quit the lexer successfully.
func lexTopEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case r == commentStart:
// a comment will read to a new line for us.
// a comment will read to a newline for us.
lx.push(lexTop)
return lexCommentStart
case isWhitespace(r):
@@ -236,11 +266,11 @@ func lexTopEnd(lx *lexer) stateFn {
lx.ignore()
return lexTop
case r == eof:
lx.ignore()
return lexTop
lx.emit(itemEOF)
return nil
}
return lx.errorf("Expected a top-level item to end with a new line, "+
"comment or EOF, but got %q instead.", r)
return lx.errorf("expected a top-level item to end with a newline, "+
"comment, or EOF, but got %q instead", r)
}
// lexTable lexes the beginning of a table. Namely, it makes sure that
@@ -267,8 +297,8 @@ func lexTableEnd(lx *lexer) stateFn {
func lexArrayTableEnd(lx *lexer) stateFn {
if r := lx.next(); r != arrayTableEnd {
return lx.errorf("Expected end of table array name delimiter %q, "+
"but got %q instead.", arrayTableEnd, r)
return lx.errorf("expected end of table array name delimiter %q, "+
"but got %q instead", arrayTableEnd, r)
}
lx.emit(itemArrayTableEnd)
return lexTopEnd
@@ -278,11 +308,11 @@ func lexTableNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.peek(); {
case r == tableEnd || r == eof:
return lx.errorf("Unexpected end of table name. (Table names cannot " +
"be empty.)")
return lx.errorf("unexpected end of table name " +
"(table names cannot be empty)")
case r == tableSep:
return lx.errorf("Unexpected table separator. (Table names cannot " +
"be empty.)")
return lx.errorf("unexpected table separator " +
"(table names cannot be empty)")
case r == stringStart || r == rawStringStart:
lx.ignore()
lx.push(lexTableNameEnd)
@@ -317,8 +347,8 @@ func lexTableNameEnd(lx *lexer) stateFn {
case r == tableEnd:
return lx.pop()
default:
return lx.errorf("Expected '.' or ']' to end table name, but got %q "+
"instead.", r)
return lx.errorf("expected '.' or ']' to end table name, "+
"but got %q instead", r)
}
}
@@ -328,7 +358,7 @@ func lexKeyStart(lx *lexer) stateFn {
r := lx.peek()
switch {
case r == keySep:
return lx.errorf("Unexpected key separator %q.", keySep)
return lx.errorf("unexpected key separator %q", keySep)
case isWhitespace(r) || isNL(r):
lx.next()
return lexSkip(lx, lexKeyStart)
@@ -359,7 +389,7 @@ func lexBareKey(lx *lexer) stateFn {
lx.emit(itemText)
return lexKeyEnd
default:
return lx.errorf("Bare keys cannot contain %q.", r)
return lx.errorf("bare keys cannot contain %q", r)
}
}
@@ -372,7 +402,7 @@ func lexKeyEnd(lx *lexer) stateFn {
case isWhitespace(r):
return lexSkip(lx, lexKeyEnd)
default:
return lx.errorf("Expected key separator %q, but got %q instead.",
return lx.errorf("expected key separator %q, but got %q instead",
keySep, r)
}
}
@@ -381,9 +411,8 @@ func lexKeyEnd(lx *lexer) stateFn {
// lexValue will ignore whitespace.
// After a value is lexed, the last state on the next is popped and returned.
func lexValue(lx *lexer) stateFn {
// We allow whitespace to precede a value, but NOT new lines.
// In array syntax, the array states are responsible for ignoring new
// lines.
// We allow whitespace to precede a value, but NOT newlines.
// In array syntax, the array states are responsible for ignoring newlines.
r := lx.next()
switch {
case isWhitespace(r):
@@ -397,6 +426,10 @@ func lexValue(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemArray)
return lexArrayValue
case inlineTableStart:
lx.ignore()
lx.emit(itemInlineTableStart)
return lexInlineTableValue
case stringStart:
if lx.accept(stringStart) {
if lx.accept(stringStart) {
@@ -420,7 +453,7 @@ func lexValue(lx *lexer) stateFn {
case '+', '-':
return lexNumberStart
case '.': // special error case, be kind to users
return lx.errorf("Floats must start with a digit, not '.'.")
return lx.errorf("floats must start with a digit, not '.'")
}
if unicode.IsLetter(r) {
// Be permissive here; lexBool will give a nice error if the
@@ -430,11 +463,11 @@ func lexValue(lx *lexer) stateFn {
lx.backup()
return lexBool
}
return lx.errorf("Expected value but found %q instead.", r)
return lx.errorf("expected value but found %q instead", r)
}
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
// have already been consumed. All whitespace and new lines are ignored.
// have already been consumed. All whitespace and newlines are ignored.
func lexArrayValue(lx *lexer) stateFn {
r := lx.next()
switch {
@@ -443,10 +476,11 @@ func lexArrayValue(lx *lexer) stateFn {
case r == commentStart:
lx.push(lexArrayValue)
return lexCommentStart
case r == arrayValTerm:
return lx.errorf("Unexpected array value terminator %q.",
arrayValTerm)
case r == comma:
return lx.errorf("unexpected comma")
case r == arrayEnd:
// NOTE(caleb): The spec isn't clear about whether you can have
// a trailing comma or not, so we'll allow it.
return lexArrayEnd
}
@@ -455,8 +489,9 @@ func lexArrayValue(lx *lexer) stateFn {
return lexValue
}
// lexArrayValueEnd consumes the cruft between values of an array. Namely,
// it ignores whitespace and expects either a ',' or a ']'.
// lexArrayValueEnd consumes everything between the end of an array value and
// the next value (or the end of the array): it ignores whitespace and newlines
// and expects either a ',' or a ']'.
func lexArrayValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
@@ -465,31 +500,88 @@ func lexArrayValueEnd(lx *lexer) stateFn {
case r == commentStart:
lx.push(lexArrayValueEnd)
return lexCommentStart
case r == arrayValTerm:
case r == comma:
lx.ignore()
return lexArrayValue // move on to the next value
case r == arrayEnd:
return lexArrayEnd
}
return lx.errorf("Expected an array value terminator %q or an array "+
"terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r)
return lx.errorf(
"expected a comma or array terminator %q, but got %q instead",
arrayEnd, r,
)
}
// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has
// just been consumed.
// lexArrayEnd finishes the lexing of an array.
// It assumes that a ']' has just been consumed.
func lexArrayEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemArrayEnd)
return lx.pop()
}
// lexInlineTableValue consumes one key/value pair in an inline table.
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
func lexInlineTableValue(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValue)
case isNL(r):
return lx.errorf("newlines not allowed within inline tables")
case r == commentStart:
lx.push(lexInlineTableValue)
return lexCommentStart
case r == comma:
return lx.errorf("unexpected comma")
case r == inlineTableEnd:
return lexInlineTableEnd
}
lx.backup()
lx.push(lexInlineTableValueEnd)
return lexKeyStart
}
// lexInlineTableValueEnd consumes everything between the end of an inline table
// key/value pair and the next pair (or the end of the table):
// it ignores whitespace and expects either a ',' or a '}'.
func lexInlineTableValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r):
return lx.errorf("newlines not allowed within inline tables")
case r == commentStart:
lx.push(lexInlineTableValueEnd)
return lexCommentStart
case r == comma:
lx.ignore()
return lexInlineTableValue
case r == inlineTableEnd:
return lexInlineTableEnd
}
return lx.errorf("expected a comma or an inline table terminator %q, "+
"but got %q instead", inlineTableEnd, r)
}
// lexInlineTableEnd finishes the lexing of an inline table.
// It assumes that a '}' has just been consumed.
func lexInlineTableEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemInlineTableEnd)
return lx.pop()
}
// lexString consumes the inner contents of a string. It assumes that the
// beginning '"' has already been consumed and ignored.
func lexString(lx *lexer) stateFn {
r := lx.next()
switch {
case r == eof:
return lx.errorf("unexpected EOF")
case isNL(r):
return lx.errorf("Strings cannot contain new lines.")
return lx.errorf("strings cannot contain newlines")
case r == '\\':
lx.push(lexString)
return lexStringEscape
@@ -506,11 +598,12 @@ func lexString(lx *lexer) stateFn {
// lexMultilineString consumes the inner contents of a string. It assumes that
// the beginning '"""' has already been consumed and ignored.
func lexMultilineString(lx *lexer) stateFn {
r := lx.next()
switch {
case r == '\\':
switch lx.next() {
case eof:
return lx.errorf("unexpected EOF")
case '\\':
return lexMultilineStringEscape
case r == stringEnd:
case stringEnd:
if lx.accept(stringEnd) {
if lx.accept(stringEnd) {
lx.backup()
@@ -534,8 +627,10 @@ func lexMultilineString(lx *lexer) stateFn {
func lexRawString(lx *lexer) stateFn {
r := lx.next()
switch {
case r == eof:
return lx.errorf("unexpected EOF")
case isNL(r):
return lx.errorf("Strings cannot contain new lines.")
return lx.errorf("strings cannot contain newlines")
case r == rawStringEnd:
lx.backup()
lx.emit(itemRawString)
@@ -547,12 +642,13 @@ func lexRawString(lx *lexer) stateFn {
}
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
// a string. It assumes that the beginning "'" has already been consumed and
// a string. It assumes that the beginning "'''" has already been consumed and
// ignored.
func lexMultilineRawString(lx *lexer) stateFn {
r := lx.next()
switch {
case r == rawStringEnd:
switch lx.next() {
case eof:
return lx.errorf("unexpected EOF")
case rawStringEnd:
if lx.accept(rawStringEnd) {
if lx.accept(rawStringEnd) {
lx.backup()
@@ -605,10 +701,9 @@ func lexStringEscape(lx *lexer) stateFn {
case 'U':
return lexLongUnicodeEscape
}
return lx.errorf("Invalid escape character %q. Only the following "+
return lx.errorf("invalid escape character %q; only the following "+
"escape characters are allowed: "+
"\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+
"\\uXXXX and \\UXXXXXXXX.", r)
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
}
func lexShortUnicodeEscape(lx *lexer) stateFn {
@@ -616,8 +711,8 @@ func lexShortUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 4; i++ {
r = lx.next()
if !isHexadecimal(r) {
return lx.errorf("Expected four hexadecimal digits after '\\u', "+
"but got '%s' instead.", lx.current())
return lx.errorf(`expected four hexadecimal digits after '\u', `+
"but got %q instead", lx.current())
}
}
return lx.pop()
@@ -628,8 +723,8 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
for i := 0; i < 8; i++ {
r = lx.next()
if !isHexadecimal(r) {
return lx.errorf("Expected eight hexadecimal digits after '\\U', "+
"but got '%s' instead.", lx.current())
return lx.errorf(`expected eight hexadecimal digits after '\U', `+
"but got %q instead", lx.current())
}
}
return lx.pop()
@@ -647,9 +742,9 @@ func lexNumberOrDateStart(lx *lexer) stateFn {
case 'e', 'E':
return lexFloat
case '.':
return lx.errorf("Floats must start with a digit, not '.'.")
return lx.errorf("floats must start with a digit, not '.'")
}
return lx.errorf("Expected a digit but got %q.", r)
return lx.errorf("expected a digit but got %q", r)
}
// lexNumberOrDate consumes either an integer, float or datetime.
@@ -697,9 +792,9 @@ func lexNumberStart(lx *lexer) stateFn {
r := lx.next()
if !isDigit(r) {
if r == '.' {
return lx.errorf("Floats must start with a digit, not '.'.")
return lx.errorf("floats must start with a digit, not '.'")
}
return lx.errorf("Expected a digit but got %q.", r)
return lx.errorf("expected a digit but got %q", r)
}
return lexNumber
}
@@ -745,7 +840,7 @@ func lexBool(lx *lexer) stateFn {
var rs []rune
for {
r := lx.next()
if r == eof || isWhitespace(r) || isNL(r) {
if !unicode.IsLetter(r) {
lx.backup()
break
}
@@ -757,7 +852,7 @@ func lexBool(lx *lexer) stateFn {
lx.emit(itemBool)
return lx.pop()
}
return lx.errorf("Expected value but found %q instead.", s)
return lx.errorf("expected value but found %q instead", s)
}
// lexCommentStart begins the lexing of a comment. It will emit
@@ -769,7 +864,7 @@ func lexCommentStart(lx *lexer) stateFn {
}
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
// It will consume *up to* the first new line character, and pass control
// It will consume *up to* the first newline character, and pass control
// back to the last state on the stack.
func lexComment(lx *lexer) stateFn {
r := lx.peek()

View File

@@ -269,6 +269,41 @@ func (p *parser) value(it item) (interface{}, tomlType) {
types = append(types, typ)
}
return array, p.typeOfArray(types)
case itemInlineTableStart:
var (
hash = make(map[string]interface{})
outerContext = p.context
outerKey = p.currentKey
)
p.context = append(p.context, p.currentKey)
p.currentKey = ""
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
if it.typ != itemKeyStart {
p.bug("Expected key start but instead found %q, around line %d",
it.val, p.approxLine)
}
if it.typ == itemCommentStart {
p.expect(itemText)
continue
}
// retrieve key
k := p.next()
p.approxLine = k.line
kname := p.keyString(k)
// retrieve value
p.currentKey = kname
val, typ := p.value(p.next())
// make sure we keep metadata up to date
p.setType(kname, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[kname] = val
}
p.context = outerContext
p.currentKey = outerKey
return hash, tomlHash
}
p.bug("Unexpected value type: %s", it.typ)
panic("unreachable")

View File

@@ -1,3 +1,15 @@
# 1.3.0 (2017-05-02)
## Added
- #45: Added json (un)marshaling support (thanks @mh-cbon)
- Stability marker. See https://masterminds.github.io/stability/
## Fixed
- #51: Fix handling of single digit tilde constraint (thanks @dgodd)
## Changed
- #55: The godoc icon moved from png to svg
# 1.2.3 (2017-04-03)
## Fixed

View File

@@ -9,7 +9,7 @@ The `semver` package provides the ability to work with [Semantic Versions](http:
[![Stability:
Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html)
[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.png)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver)
[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.svg)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver)
## Parsing Semantic Versions

View File

@@ -175,7 +175,7 @@ func parseConstraint(c string) (*constraint, error) {
if isX(m[3]) {
ver = "0.0.0"
dirty = true
} else if isX(strings.TrimPrefix(m[4], ".")) {
} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
minorDirty = true
dirty = true
ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])

View File

@@ -2,6 +2,7 @@ package semver
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"regexp"
@@ -291,6 +292,31 @@ func (v *Version) Compare(o *Version) int {
return comparePrerelease(ps, po)
}
// UnmarshalJSON implements JSON.Unmarshaler interface.
func (v *Version) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
temp, err := NewVersion(s)
if err != nil {
return err
}
v.major = temp.major
v.minor = temp.minor
v.patch = temp.patch
v.pre = temp.pre
v.metadata = temp.metadata
v.original = temp.original
temp = nil
return nil
}
// MarshalJSON implements JSON.Marshaler interface.
func (v *Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}
func compareSegment(v, o int64) int {
if v < o {
return -1

View File

@@ -1,5 +1,7 @@
package sprig
import "github.com/imdario/mergo"
func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} {
d[key] = value
return d
@@ -72,3 +74,11 @@ func dict(v ...interface{}) map[string]interface{} {
}
return dict
}
func merge(dst map[string]interface{}, src map[string]interface{}) interface{} {
if err := mergo.Merge(&dst, src); err != nil {
// Swallow errors inside of a template.
return ""
}
return dst
}

View File

@@ -19,7 +19,7 @@ Date Functions
- date FORMAT TIME: Format a date, where a date is an integer type or a time.Time type, and
format is a time.Format formatting string.
- dateModify: Given a date, modify it with a duration: `date_modify "-1.5h" now`. If the duration doesn't
parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings.
parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings.
- now: Current time.Time, for feeding into date-related functions.
- htmlDate TIME: Format a date for use in the value field of an HTML "date" form element.
- dateInZone FORMAT TIME TZ: Like date, but takes three arguments: format, timestamp,
@@ -68,7 +68,7 @@ String Slice Functions:
- split: strings.Split, but as `split SEP STRING`. The results are returned
as a map with the indexes set to _N, where N is an integer starting from 0.
Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`)
- splitList: strings.Split, but as `split SEP STRING`. The results are returned
- splitList: strings.Split, but as `split SEP STRING`. The results are returned
as an array.
- toStrings: convert a list to a list of strings. 'list 1 2 3 | toStrings' produces '["1" "2" "3"]'
- sortAlpha: sort a list lexicographically.
@@ -114,7 +114,7 @@ File Paths:
- base: Return the last element of a path. https://golang.org/pkg/path#Base
- dir: Remove the last element of a path. https://golang.org/pkg/path#Dir
- clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.."
from "foo/../bar.html") https://golang.org/pkg/path#Clean
from "foo/../bar.html") https://golang.org/pkg/path#Clean
- ext: https://golang.org/pkg/path#Ext
- isAbs: https://golang.org/pkg/path#IsAbs

View File

@@ -1,6 +1,7 @@
package sprig
import (
"errors"
"html/template"
"os"
"path"
@@ -10,6 +11,7 @@ import (
"time"
util "github.com/aokoli/goutils"
"github.com/huandu/xstrings"
)
// Produce the function map.
@@ -122,6 +124,9 @@ var genericMap = map[string]interface{}{
"randAscii": randAscii,
"randNumeric": randNumeric,
"swapcase": util.SwapCase,
"shuffle": xstrings.Shuffle,
"snakecase": xstrings.ToSnakeCase,
"camelcase": xstrings.ToCamelCase,
"wrap": func(l int, s string) string { return util.Wrap(s, l) },
"wrapWith": func(l int, sep, str string) string { return util.WrapCustom(str, l, sep, true) },
// Switch order so that "foobar" | contains "foo"
@@ -178,6 +183,9 @@ var genericMap = map[string]interface{}{
"biggest": max,
"max": max,
"min": min,
"ceil": ceil,
"floor": floor,
"round": round,
// string slices. Note that we reverse the order b/c that's better
// for template processing.
@@ -227,6 +235,7 @@ var genericMap = map[string]interface{}{
"keys": keys,
"pick": pick,
"omit": omit,
"merge": merge,
"append": push, "push": push,
"prepend": prepend,
@@ -249,4 +258,15 @@ var genericMap = map[string]interface{}{
// SemVer:
"semver": semver,
"semverCompare": semverCompare,
// Flow Control:
"fail": func(msg string) (string, error) { return "", errors.New(msg) },
// Regex
"regexMatch": regexMatch,
"regexFindAll": regexFindAll,
"regexFind": regexFind,
"regexReplaceAll": regexReplaceAll,
"regexReplaceAllLiteral": regexReplaceAllLiteral,
"regexSplit": regexSplit,
}

View File

@@ -1,8 +1,12 @@
hash: d366098dbe0d3a7bc5afdc55b91fb2a75e3443ff976e1a3b87a79e22cfead454
updated: 2017-04-07T09:31:43.889475111-06:00
hash: b9cc40bfd6dde74a94103b96700df1a9ab29a7fff5650216cf5a05f4fe72fb73
updated: 2017-05-02T16:01:04.617727646-06:00
imports:
- name: github.com/aokoli/goutils
version: 9c37978a95bd5c709a15883b6242714ea6709e64
- name: github.com/huandu/xstrings
version: 3959339b333561bf62a38b424fd41517c2c90f40
- name: github.com/imdario/mergo
version: 3e95a51e0639b4cf372f2ccf74c86749d747fbdc
- name: github.com/Masterminds/goutils
version: 45307ec16e3cd47cd841506c081f7afd8237d210
- name: github.com/Masterminds/semver
@@ -10,10 +14,20 @@ imports:
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/stretchr/testify
version: 4d4bfba8f1d1027c4fdbe371823030df51419987
- name: golang.org/x/crypto
version: 420870623a70591d5e0b187c77c95455a1224ca6
version: e3a8ff8ce36581f87a15341206f205b1da467059
subpackages:
- assert
- name: golang.org/x/crypto
version: d172538b2cfce0c13cee31e647d0367aa8cd2486
subpackages:
- scrypt
- pbkdf2
devImports: []
- scrypt
testImports:
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib

View File

@@ -10,3 +10,6 @@ import:
- package: github.com/Masterminds/semver
version: v1.2.2
- package: github.com/stretchr/testify
- package: github.com/imdario/mergo
version: ~0.2.2
- package: github.com/huandu/xstrings

View File

@@ -127,3 +127,33 @@ func untilStep(start, stop, step int) []int {
}
return v
}
func floor(a interface{}) float64 {
aa := toFloat64(a)
return math.Floor(aa)
}
func ceil(a interface{}) float64 {
aa := toFloat64(a)
return math.Ceil(aa)
}
func round(a interface{}, p int, r_opt ...float64) float64 {
roundOn := .5
if len(r_opt) > 0 {
roundOn = r_opt[0]
}
val := toFloat64(a)
places := toFloat64(p)
var round float64
pow := math.Pow(10, places)
digit := pow * val
_, div := math.Modf(digit)
if div >= roundOn {
round = math.Ceil(digit)
} else {
round = math.Floor(digit)
}
return round / pow
}

35
vendor/github.com/Masterminds/sprig/regex.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package sprig
import (
"regexp"
)
func regexMatch(regex string, s string) bool {
match, _ := regexp.MatchString(regex, s)
return match
}
func regexFindAll(regex string, s string, n int) []string {
r := regexp.MustCompile(regex)
return r.FindAllString(s, n)
}
func regexFind(regex string, s string) string {
r := regexp.MustCompile(regex)
return r.FindString(s)
}
func regexReplaceAll(regex string, s string, repl string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, repl)
}
func regexReplaceAllLiteral(regex string, s string, repl string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllLiteralString(s, repl)
}
func regexSplit(regex string, s string, n int) []string {
r := regexp.MustCompile(regex)
return r.Split(s, n)
}

8
vendor/github.com/aokoli/goutils/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 1.0.1 (2017-05-31)
## Fixed
- #21: Fix generation of alphanumeric strings (thanks @dbarranco)
# 1.0.0 (2014-04-30)
- Initial release.

View File

@@ -1,9 +1,10 @@
GoUtils
===========
[![Stability: Maintenance](https://masterminds.github.io/stability/maintenance.svg)](https://masterminds.github.io/stability/maintenance.html)
[![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils)
[![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils) [![Build Status](https://travis-ci.org/Masterminds/goutils.svg?branch=master)](https://travis-ci.org/Masterminds/goutils) [![Build status](https://ci.appveyor.com/api/projects/status/sc2b1ew0m7f0aiju?svg=true)](https://ci.appveyor.com/project/mattfarina/goutils)
GoUtils provides users with utility functions to manipulate strings in various ways. It is a Go implementation of some
GoUtils provides users with utility functions to manipulate strings in various ways. It is a Go implementation of some
string manipulation libraries of Java Apache Commons. GoUtils includes the following Java Apache Commons classes:
* WordUtils
* RandomStringUtils
@@ -13,11 +14,11 @@ string manipulation libraries of Java Apache Commons. GoUtils includes the follo
If you have Go set up on your system, from the GOPATH directory within the command line/terminal, enter this:
go get github.com/Masterminds/goutils
If you do not have Go set up on your system, please follow the [Go installation directions from the documenation](http://golang.org/doc/install), and then follow the instructions above to install GoUtils.
## Documentation
## Documentation
GoUtils doc is available here: [![GoDoc](https://godoc.org/github.com/Masterminds/goutils?status.png)](https://godoc.org/github.com/Masterminds/goutils)
@@ -25,12 +26,12 @@ GoUtils doc is available here: [![GoDoc](https://godoc.org/github.com/Mastermind
The code snippets below show examples of how to use GoUtils. Some functions return errors while others do not. The first instance below, which does not return an error, is the `Initials` function (located within the `wordutils.go` file).
package main
import (
"fmt"
"github.com/Masterminds/goutils"
)
func main() {
// EXAMPLE 1: A goutils function which returns no errors
@@ -40,35 +41,30 @@ The code snippets below show examples of how to use GoUtils. Some functions retu
Some functions return errors mainly due to illegal arguements used as parameters. The code example below illustrates how to deal with function that returns an error. In this instance, the function is the `Random` function (located within the `randomstringutils.go` file).
package main
import (
"fmt"
"github.com/Masterminds/goutils"
)
func main() {
// EXAMPLE 2: A goutils function which returns an error
rand1, err1 := goutils.Random (-1, 0, 0, true, true)
if err1 != nil {
if err1 != nil {
fmt.Println(err1) // Prints out error message because -1 was entered as the first parameter in goutils.Random(...)
} else {
fmt.Println(rand1)
fmt.Println(rand1)
}
}
## License
GoUtils is licensed under the Apache License, Version 2.0. Please check the LICENSE.txt file or visit http://www.apache.org/licenses/LICENSE-2.0 for a copy of the license.
GoUtils is licensed under the Apache License, Version 2.0. Please check the LICENSE.txt file or visit http://www.apache.org/licenses/LICENSE-2.0 for a copy of the license.
## Issue Reporting
Make suggestions or report issues using the Git issue tracker: https://github.com/Masterminds/goutils/issues
## Website
* [GoUtils webpage](http://Masterminds.github.io/goutils/)
## Mailing List
Contact [okolialex@gmail.com](mailto:okolialex@mail.com) to be added to the mailing list. You will get updates on the
status of the project and the potential direction it will be heading.

21
vendor/github.com/aokoli/goutils/appveyor.yml generated vendored Normal file
View File

@@ -0,0 +1,21 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\Masterminds\goutils
shallow_clone: true
environment:
GOPATH: C:\gopath
platform:
- x64
build: off
install:
- go version
- go env
test_script:
- go test -v
deploy: off

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"math"
"math/rand"
"regexp"
"time"
"unicode"
)
@@ -101,7 +102,24 @@ Returns:
error - an error stemming from an invalid parameter within underlying function, RandomSeed(...)
*/
func RandomAlphaNumeric(count int) (string, error) {
return Random(count, 0, 0, true, true)
RandomString, err := Random(count, 0, 0, true, true)
if err != nil {
return "", fmt.Errorf("Error: %s", err)
}
match, err := regexp.MatchString("([0-9]+)", RandomString)
if err != nil {
panic(err)
}
if !match {
//Get the position between 0 and the length of the string-1 to insert a random number
position := rand.Intn(count)
//Insert a random number between [0-9] in the position
RandomString = RandomString[:position] + string('0'+rand.Intn(10)) + RandomString[position+1:]
return RandomString, err
}
return RandomString, err
}
/*

23
vendor/github.com/huandu/xstrings/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,23 @@
# Contributing #
Thanks for your contribution in advance. No matter what you will contribute to this project, pull request or bug report or feature discussion, it's always highly appreciated.
## New API or feature ##
I want to speak more about how to add new functions to this package.
Package `xstring` is a collection of useful string functions which should be implemented in Go. It's a bit subject to say which function should be included and which should not. I set up following rules in order to make it clear and as objective as possible.
* Rule 1: Only string algorithm, which takes string as input, can be included.
* Rule 2: If a function has been implemented in package `string`, it must not be included.
* Rule 3: If a function is not language neutral, it must not be included.
* Rule 4: If a function is a part of standard library in other languages, it can be included.
* Rule 5: If a function is quite useful in some famous framework or library, it can be included.
New function must be discussed in project issues before submitting any code. If a pull request with new functions is sent without any ref issue, it will be rejected.
## Pull request ##
Pull request is always welcome. Just make sure you have run `go fmt` and all test cases passed before submit.
If the pull request is to add a new API or feature, don't forget to update README.md and add new API in function list.

22
vendor/github.com/huandu/xstrings/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Huan Du
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

114
vendor/github.com/huandu/xstrings/README.md generated vendored Normal file
View File

@@ -0,0 +1,114 @@
# xstrings #
[![Build Status](https://travis-ci.org/huandu/xstrings.png?branch=master)](https://travis-ci.org/huandu/xstrings)
[![GoDoc](https://godoc.org/github.com/huandu/xstrings?status.svg)](https://godoc.org/github.com/huandu/xstrings)
Go package [xstrings](https://godoc.org/github.com/huandu/xstrings) is a collection of string functions, which are widely used in other languages but absent in Go package [strings](http://golang.org/pkg/strings).
All functions are well tested and carefully tuned for performance.
## Propose a new function ##
Please review [contributing guideline](CONTRIBUTING.md) and [create new issue](https://github.com/huandu/xstrings/issues) to state why it should be included.
## Install ##
Use `go get` to install this library.
go get github.com/huandu/xstrings
## API document ##
See [GoDoc](https://godoc.org/github.com/huandu/xstrings) for full document.
## Function list ##
Go functions have a unique naming style. One, who has experience in other language but new in Go, may have difficulties to find out right string function to use.
Here is a list of functions in [strings](http://golang.org/pkg/strings) and [xstrings](https://godoc.org/github.com/huandu/xstrings) with enough extra information about how to map these functions to their friends in other languages. Hope this list could be helpful for fresh gophers.
### Package `xstrings` functions ###
*Keep this table sorted by Function in ascending order.*
| Function | Friends | # |
| -------- | ------- | --- |
| [Center](https://godoc.org/github.com/huandu/xstrings#Center) | `str.center` in Python; `String#center` in Ruby | [#30](https://github.com/huandu/xstrings/issues/30) |
| [Count](https://godoc.org/github.com/huandu/xstrings#Count) | `String#count` in Ruby | [#16](https://github.com/huandu/xstrings/issues/16) |
| [Delete](https://godoc.org/github.com/huandu/xstrings#Delete) | `String#delete` in Ruby | [#17](https://github.com/huandu/xstrings/issues/17) |
| [ExpandTabs](https://godoc.org/github.com/huandu/xstrings#ExpandTabs) | `str.expandtabs` in Python | [#27](https://github.com/huandu/xstrings/issues/27) |
| [FirstRuneToLower](https://godoc.org/github.com/huandu/xstrings#FirstRuneToLower) | `lcfirst` in PHP or Perl | [#15](https://github.com/huandu/xstrings/issues/15) |
| [FirstRuneToUpper](https://godoc.org/github.com/huandu/xstrings#FirstRuneToUpper) | `String#capitalize` in Ruby; `ucfirst` in PHP or Perl | [#15](https://github.com/huandu/xstrings/issues/15) |
| [Insert](https://godoc.org/github.com/huandu/xstrings#Insert) | `String#insert` in Ruby | [#18](https://github.com/huandu/xstrings/issues/18) |
| [LastPartition](https://godoc.org/github.com/huandu/xstrings#LastPartition) | `str.rpartition` in Python; `String#rpartition` in Ruby | [#19](https://github.com/huandu/xstrings/issues/19) |
| [LeftJustify](https://godoc.org/github.com/huandu/xstrings#LeftJustify) | `str.ljust` in Python; `String#ljust` in Ruby | [#28](https://github.com/huandu/xstrings/issues/28) |
| [Len](https://godoc.org/github.com/huandu/xstrings#Len) | `mb_strlen` in PHP | [#23](https://github.com/huandu/xstrings/issues/23) |
| [Partition](https://godoc.org/github.com/huandu/xstrings#Partition) | `str.partition` in Python; `String#partition` in Ruby | [#10](https://github.com/huandu/xstrings/issues/10) |
| [Reverse](https://godoc.org/github.com/huandu/xstrings#Reverse) | `String#reverse` in Ruby; `strrev` in PHP; `reverse` in Perl | [#7](https://github.com/huandu/xstrings/issues/7) |
| [RightJustify](https://godoc.org/github.com/huandu/xstrings#RightJustify) | `str.rjust` in Python; `String#rjust` in Ruby | [#29](https://github.com/huandu/xstrings/issues/29) |
| [RuneWidth](https://godoc.org/github.com/huandu/xstrings#RuneWidth) | - | [#27](https://github.com/huandu/xstrings/issues/27) |
| [Scrub](https://godoc.org/github.com/huandu/xstrings#Scrub) | `String#scrub` in Ruby | [#20](https://github.com/huandu/xstrings/issues/20) |
| [Shuffle](https://godoc.org/github.com/huandu/xstrings#Shuffle) | `str_shuffle` in PHP | [#13](https://github.com/huandu/xstrings/issues/13) |
| [ShuffleSource](https://godoc.org/github.com/huandu/xstrings#ShuffleSource) | `str_shuffle` in PHP | [#13](https://github.com/huandu/xstrings/issues/13) |
| [Slice](https://godoc.org/github.com/huandu/xstrings#Slice) | `mb_substr` in PHP | [#9](https://github.com/huandu/xstrings/issues/9) |
| [Squeeze](https://godoc.org/github.com/huandu/xstrings#Squeeze) | `String#squeeze` in Ruby | [#11](https://github.com/huandu/xstrings/issues/11) |
| [Successor](https://godoc.org/github.com/huandu/xstrings#Successor) | `String#succ` or `String#next` in Ruby | [#22](https://github.com/huandu/xstrings/issues/22) |
| [SwapCase](https://godoc.org/github.com/huandu/xstrings#SwapCase) | `str.swapcase` in Python; `String#swapcase` in Ruby | [#12](https://github.com/huandu/xstrings/issues/12) |
| [ToCamelCase](https://godoc.org/github.com/huandu/xstrings#ToCamelCase) | `String#camelize` in RoR | [#1](https://github.com/huandu/xstrings/issues/1) |
| [ToSnakeCase](https://godoc.org/github.com/huandu/xstrings#ToSnakeCase) | `String#underscore` in RoR | [#1](https://github.com/huandu/xstrings/issues/1) |
| [Translate](https://godoc.org/github.com/huandu/xstrings#Translate) | `str.translate` in Python; `String#tr` in Ruby; `strtr` in PHP; `tr///` in Perl | [#21](https://github.com/huandu/xstrings/issues/21) |
| [Width](https://godoc.org/github.com/huandu/xstrings#Width) | `mb_strwidth` in PHP | [#26](https://github.com/huandu/xstrings/issues/26) |
| [WordCount](https://godoc.org/github.com/huandu/xstrings#WordCount) | `str_word_count` in PHP | [#14](https://github.com/huandu/xstrings/issues/14) |
| [WordSplit](https://godoc.org/github.com/huandu/xstrings#WordSplit) | - | [#14](https://github.com/huandu/xstrings/issues/14) |
### Package `strings` functions ###
*Keep this table sorted by Function in ascending order.*
| Function | Friends |
| -------- | ------- |
| [Contains](http://golang.org/pkg/strings/#Contains) | `String#include?` in Ruby |
| [ContainsAny](http://golang.org/pkg/strings/#ContainsAny) | - |
| [ContainsRune](http://golang.org/pkg/strings/#ContainsRune) | - |
| [Count](http://golang.org/pkg/strings/#Count) | `str.count` in Python; `substr_count` in PHP |
| [EqualFold](http://golang.org/pkg/strings/#EqualFold) | `stricmp` in PHP; `String#casecmp` in Ruby |
| [Fields](http://golang.org/pkg/strings/#Fields) | `str.split` in Python; `split` in Perl; `String#split` in Ruby |
| [FieldsFunc](http://golang.org/pkg/strings/#FieldsFunc) | - |
| [HasPrefix](http://golang.org/pkg/strings/#HasPrefix) | `str.startswith` in Python; `String#start_with?` in Ruby |
| [HasSuffix](http://golang.org/pkg/strings/#HasSuffix) | `str.endswith` in Python; `String#end_with?` in Ruby |
| [Index](http://golang.org/pkg/strings/#Index) | `str.index` in Python; `String#index` in Ruby; `strpos` in PHP; `index` in Perl |
| [IndexAny](http://golang.org/pkg/strings/#IndexAny) | - |
| [IndexByte](http://golang.org/pkg/strings/#IndexByte) | - |
| [IndexFunc](http://golang.org/pkg/strings/#IndexFunc) | - |
| [IndexRune](http://golang.org/pkg/strings/#IndexRune) | - |
| [Join](http://golang.org/pkg/strings/#Join) | `str.join` in Python; `Array#join` in Ruby; `implode` in PHP; `join` in Perl |
| [LastIndex](http://golang.org/pkg/strings/#LastIndex) | `str.rindex` in Python; `String#rindex`; `strrpos` in PHP; `rindex` in Perl |
| [LastIndexAny](http://golang.org/pkg/strings/#LastIndexAny) | - |
| [LastIndexFunc](http://golang.org/pkg/strings/#LastIndexFunc) | - |
| [Map](http://golang.org/pkg/strings/#Map) | `String#each_codepoint` in Ruby |
| [Repeat](http://golang.org/pkg/strings/#Repeat) | operator `*` in Python and Ruby; `str_repeat` in PHP |
| [Replace](http://golang.org/pkg/strings/#Replace) | `str.replace` in Python; `String#sub` in Ruby; `str_replace` in PHP |
| [Split](http://golang.org/pkg/strings/#Split) | `str.split` in Python; `String#split` in Ruby; `explode` in PHP; `split` in Perl |
| [SplitAfter](http://golang.org/pkg/strings/#SplitAfter) | - |
| [SplitAfterN](http://golang.org/pkg/strings/#SplitAfterN) | - |
| [SplitN](http://golang.org/pkg/strings/#SplitN) | `str.split` in Python; `String#split` in Ruby; `explode` in PHP; `split` in Perl |
| [Title](http://golang.org/pkg/strings/#Title) | `str.title` in Python |
| [ToLower](http://golang.org/pkg/strings/#ToLower) | `str.lower` in Python; `String#downcase` in Ruby; `strtolower` in PHP; `lc` in Perl |
| [ToLowerSpecial](http://golang.org/pkg/strings/#ToLowerSpecial) | - |
| [ToTitle](http://golang.org/pkg/strings/#ToTitle) | - |
| [ToTitleSpecial](http://golang.org/pkg/strings/#ToTitleSpecial) | - |
| [ToUpper](http://golang.org/pkg/strings/#ToUpper) | `str.upper` in Python; `String#upcase` in Ruby; `strtoupper` in PHP; `uc` in Perl |
| [ToUpperSpecial](http://golang.org/pkg/strings/#ToUpperSpecial) | - |
| [Trim](http://golang.org/pkg/strings/#Trim) | `str.strip` in Python; `String#strip` in Ruby; `trim` in PHP |
| [TrimFunc](http://golang.org/pkg/strings/#TrimFunc) | - |
| [TrimLeft](http://golang.org/pkg/strings/#TrimLeft) | `str.lstrip` in Python; `String#lstrip` in Ruby; `ltrim` in PHP |
| [TrimLeftFunc](http://golang.org/pkg/strings/#TrimLeftFunc) | - |
| [TrimPrefix](http://golang.org/pkg/strings/#TrimPrefix) | - |
| [TrimRight](http://golang.org/pkg/strings/#TrimRight) | `str.rstrip` in Python; `String#rstrip` in Ruby; `rtrim` in PHP |
| [TrimRightFunc](http://golang.org/pkg/strings/#TrimRightFunc) | - |
| [TrimSpace](http://golang.org/pkg/strings/#TrimSpace) | `str.strip` in Python; `String#strip` in Ruby; `trim` in PHP |
| [TrimSuffix](http://golang.org/pkg/strings/#TrimSuffix) | `String#chomp` in Ruby; `chomp` in Perl |
## License ##
This library is licensed under MIT license. See LICENSE for details.

25
vendor/github.com/huandu/xstrings/common.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package xstrings
import (
"bytes"
)
const _BUFFER_INIT_GROW_SIZE_MAX = 2048
// Lazy initialize a buffer.
func allocBuffer(orig, cur string) *bytes.Buffer {
output := &bytes.Buffer{}
maxSize := len(orig) * 4
// Avoid to reserve too much memory at once.
if maxSize > _BUFFER_INIT_GROW_SIZE_MAX {
maxSize = _BUFFER_INIT_GROW_SIZE_MAX
}
output.Grow(maxSize)
output.WriteString(orig[:len(orig)-len(cur)])
return output
}

357
vendor/github.com/huandu/xstrings/convert.go generated vendored Normal file
View File

@@ -0,0 +1,357 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package xstrings
import (
"bytes"
"math/rand"
"unicode"
"unicode/utf8"
)
// ToCamelCase can convert all lower case characters behind underscores
// to upper case character.
// Underscore character will be removed in result except following cases.
// * More than 1 underscore.
// "a__b" => "A_B"
// * At the beginning of string.
// "_a" => "_A"
// * At the end of string.
// "ab_" => "Ab_"
func ToCamelCase(str string) string {
if len(str) == 0 {
return ""
}
buf := &bytes.Buffer{}
var r0, r1 rune
var size int
// leading '_' will appear in output.
for len(str) > 0 {
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
if r0 != '_' {
break
}
buf.WriteRune(r0)
}
if len(str) == 0 {
return buf.String()
}
buf.WriteRune(unicode.ToUpper(r0))
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
for len(str) > 0 {
r1 = r0
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
if r1 == '_' && r0 != '_' {
r0 = unicode.ToUpper(r0)
} else {
buf.WriteRune(r1)
}
}
buf.WriteRune(r0)
return buf.String()
}
// ToSnakeCase can convert all upper case characters in a string to
// underscore format.
//
// Some samples.
// "FirstName" => "first_name"
// "HTTPServer" => "http_server"
// "NoHTTPS" => "no_https"
// "GO_PATH" => "go_path"
// "GO PATH" => "go_path" // space is converted to underscore.
// "GO-PATH" => "go_path" // hyphen is converted to underscore.
func ToSnakeCase(str string) string {
if len(str) == 0 {
return ""
}
buf := &bytes.Buffer{}
var prev, r0, r1 rune
var size int
r0 = '_'
for len(str) > 0 {
prev = r0
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
switch {
case r0 == utf8.RuneError:
buf.WriteByte(byte(str[0]))
case unicode.IsUpper(r0):
if prev != '_' {
buf.WriteRune('_')
}
buf.WriteRune(unicode.ToLower(r0))
if len(str) == 0 {
break
}
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
if !unicode.IsUpper(r0) {
buf.WriteRune(r0)
break
}
// find next non-upper-case character and insert `_` properly.
// it's designed to convert `HTTPServer` to `http_server`.
// if there are more than 2 adjacent upper case characters in a word,
// treat them as an abbreviation plus a normal word.
for len(str) > 0 {
r1 = r0
r0, size = utf8.DecodeRuneInString(str)
str = str[size:]
if r0 == utf8.RuneError {
buf.WriteRune(unicode.ToLower(r1))
buf.WriteByte(byte(str[0]))
break
}
if !unicode.IsUpper(r0) {
if r0 == '_' || r0 == ' ' || r0 == '-' {
r0 = '_'
buf.WriteRune(unicode.ToLower(r1))
} else {
buf.WriteRune('_')
buf.WriteRune(unicode.ToLower(r1))
buf.WriteRune(r0)
}
break
}
buf.WriteRune(unicode.ToLower(r1))
}
if len(str) == 0 || r0 == '_' {
buf.WriteRune(unicode.ToLower(r0))
break
}
default:
if r0 == ' ' || r0 == '-' {
r0 = '_'
}
buf.WriteRune(r0)
}
}
return buf.String()
}
// SwapCase will swap characters case from upper to lower or lower to upper.
func SwapCase(str string) string {
var r rune
var size int
buf := &bytes.Buffer{}
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
switch {
case unicode.IsUpper(r):
buf.WriteRune(unicode.ToLower(r))
case unicode.IsLower(r):
buf.WriteRune(unicode.ToUpper(r))
default:
buf.WriteRune(r)
}
str = str[size:]
}
return buf.String()
}
// FirstRuneToUpper converts first rune to upper case if necessary.
func FirstRuneToUpper(str string) string {
if str == "" {
return str
}
r, size := utf8.DecodeRuneInString(str)
if !unicode.IsLower(r) {
return str
}
buf := &bytes.Buffer{}
buf.WriteRune(unicode.ToUpper(r))
buf.WriteString(str[size:])
return buf.String()
}
// FirstRuneToLower converts first rune to lower case if necessary.
func FirstRuneToLower(str string) string {
if str == "" {
return str
}
r, size := utf8.DecodeRuneInString(str)
if !unicode.IsUpper(r) {
return str
}
buf := &bytes.Buffer{}
buf.WriteRune(unicode.ToLower(r))
buf.WriteString(str[size:])
return buf.String()
}
// Shuffle randomizes runes in a string and returns the result.
// It uses default random source in `math/rand`.
func Shuffle(str string) string {
if str == "" {
return str
}
runes := []rune(str)
index := 0
for i := len(runes) - 1; i > 0; i-- {
index = rand.Intn(i + 1)
if i != index {
runes[i], runes[index] = runes[index], runes[i]
}
}
return string(runes)
}
// ShuffleSource randomizes runes in a string with given random source.
func ShuffleSource(str string, src rand.Source) string {
if str == "" {
return str
}
runes := []rune(str)
index := 0
r := rand.New(src)
for i := len(runes) - 1; i > 0; i-- {
index = r.Intn(i + 1)
if i != index {
runes[i], runes[index] = runes[index], runes[i]
}
}
return string(runes)
}
// Successor returns the successor to string.
//
// If there is one alphanumeric rune is found in string, increase the rune by 1.
// If increment generates a "carry", the rune to the left of it is incremented.
// This process repeats until there is no carry, adding an additional rune if necessary.
//
// If there is no alphanumeric rune, the rightmost rune will be increased by 1
// regardless whether the result is a valid rune or not.
//
// Only following characters are alphanumeric.
// * a - z
// * A - Z
// * 0 - 9
//
// Samples (borrowed from ruby's String#succ document):
// "abcd" => "abce"
// "THX1138" => "THX1139"
// "<<koala>>" => "<<koalb>>"
// "1999zzz" => "2000aaa"
// "ZZZ9999" => "AAAA0000"
// "***" => "**+"
func Successor(str string) string {
if str == "" {
return str
}
var r rune
var i int
carry := ' '
runes := []rune(str)
l := len(runes)
lastAlphanumeric := l
for i = l - 1; i >= 0; i-- {
r = runes[i]
if ('a' <= r && r <= 'y') ||
('A' <= r && r <= 'Y') ||
('0' <= r && r <= '8') {
runes[i]++
carry = ' '
lastAlphanumeric = i
break
}
switch r {
case 'z':
runes[i] = 'a'
carry = 'a'
lastAlphanumeric = i
case 'Z':
runes[i] = 'A'
carry = 'A'
lastAlphanumeric = i
case '9':
runes[i] = '0'
carry = '0'
lastAlphanumeric = i
}
}
// Needs to add one character for carry.
if i < 0 && carry != ' ' {
buf := &bytes.Buffer{}
buf.Grow(l + 4) // Reserve enough space for write.
if lastAlphanumeric != 0 {
buf.WriteString(str[:lastAlphanumeric])
}
buf.WriteRune(carry)
for _, r = range runes[lastAlphanumeric:] {
buf.WriteRune(r)
}
return buf.String()
}
// No alphanumeric character. Simply increase last rune's value.
if lastAlphanumeric == l {
runes[l-1]++
}
return string(runes)
}

120
vendor/github.com/huandu/xstrings/count.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package xstrings
import (
"unicode"
"unicode/utf8"
)
// Get str's utf8 rune length.
func Len(str string) int {
return utf8.RuneCountInString(str)
}
// Count number of words in a string.
//
// Word is defined as a locale dependent string containing alphabetic characters,
// which may also contain but not start with `'` and `-` characters.
func WordCount(str string) int {
var r rune
var size, n int
inWord := false
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
switch {
case isAlphabet(r):
if !inWord {
inWord = true
n++
}
case inWord && (r == '\'' || r == '-'):
// Still in word.
default:
inWord = false
}
str = str[size:]
}
return n
}
const minCJKCharacter = '\u3400'
// Checks r is a letter but not CJK character.
func isAlphabet(r rune) bool {
if !unicode.IsLetter(r) {
return false
}
switch {
// Quick check for non-CJK character.
case r < minCJKCharacter:
return true
// Common CJK characters.
case r >= '\u4E00' && r <= '\u9FCC':
return false
// Rare CJK characters.
case r >= '\u3400' && r <= '\u4D85':
return false
// Rare and historic CJK characters.
case r >= '\U00020000' && r <= '\U0002B81D':
return false
}
return true
}
// Width returns string width in monotype font.
// Multi-byte characters are usually twice the width of single byte characters.
//
// Algorithm comes from `mb_strwidth` in PHP.
// http://php.net/manual/en/function.mb-strwidth.php
func Width(str string) int {
var r rune
var size, n int
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
n += RuneWidth(r)
str = str[size:]
}
return n
}
// RuneWidth returns character width in monotype font.
// Multi-byte characters are usually twice the width of single byte characters.
//
// Algorithm comes from `mb_strwidth` in PHP.
// http://php.net/manual/en/function.mb-strwidth.php
func RuneWidth(r rune) int {
switch {
case r == utf8.RuneError || r < '\x20':
return 0
case '\x20' <= r && r < '\u2000':
return 1
case '\u2000' <= r && r < '\uFF61':
return 2
case '\uFF61' <= r && r < '\uFFA0':
return 1
case '\uFFA0' <= r:
return 2
}
return 0
}

8
vendor/github.com/huandu/xstrings/doc.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
// Package `xstrings` is to provide string algorithms which are useful but not included in `strings` package.
// See project home page for details. https://github.com/huandu/xstrings
//
// Package `xstrings` assumes all strings are encoded in utf8.
package xstrings

170
vendor/github.com/huandu/xstrings/format.go generated vendored Normal file
View File

@@ -0,0 +1,170 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package xstrings
import (
"bytes"
"unicode/utf8"
)
// ExpandTabs can expand tabs ('\t') rune in str to one or more spaces dpending on
// current column and tabSize.
// The column number is reset to zero after each newline ('\n') occurring in the str.
//
// ExpandTabs uses RuneWidth to decide rune's width.
// For example, CJK characters will be treated as two characters.
//
// If tabSize <= 0, ExpandTabs panics with error.
//
// Samples:
// ExpandTabs("a\tbc\tdef\tghij\tk", 4) => "a bc def ghij k"
// ExpandTabs("abcdefg\thij\nk\tl", 4) => "abcdefg hij\nk l"
// ExpandTabs("z中\t文\tw", 4) => "z中 文 w"
func ExpandTabs(str string, tabSize int) string {
if tabSize <= 0 {
panic("tab size must be positive")
}
var r rune
var i, size, column, expand int
var output *bytes.Buffer
orig := str
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
if r == '\t' {
expand = tabSize - column%tabSize
if output == nil {
output = allocBuffer(orig, str)
}
for i = 0; i < expand; i++ {
output.WriteByte(byte(' '))
}
column += expand
} else {
if r == '\n' {
column = 0
} else {
column += RuneWidth(r)
}
if output != nil {
output.WriteRune(r)
}
}
str = str[size:]
}
if output == nil {
return orig
}
return output.String()
}
// LeftJustify returns a string with pad string at right side if str's rune length is smaller than length.
// If str's rune length is larger than length, str itself will be returned.
//
// If pad is an empty string, str will be returned.
//
// Samples:
// LeftJustify("hello", 4, " ") => "hello"
// LeftJustify("hello", 10, " ") => "hello "
// LeftJustify("hello", 10, "123") => "hello12312"
func LeftJustify(str string, length int, pad string) string {
l := Len(str)
if l >= length || pad == "" {
return str
}
remains := length - l
padLen := Len(pad)
output := &bytes.Buffer{}
output.Grow(len(str) + (remains/padLen+1)*len(pad))
output.WriteString(str)
writePadString(output, pad, padLen, remains)
return output.String()
}
// RightJustify returns a string with pad string at left side if str's rune length is smaller than length.
// If str's rune length is larger than length, str itself will be returned.
//
// If pad is an empty string, str will be returned.
//
// Samples:
// RightJustify("hello", 4, " ") => "hello"
// RightJustify("hello", 10, " ") => " hello"
// RightJustify("hello", 10, "123") => "12312hello"
func RightJustify(str string, length int, pad string) string {
l := Len(str)
if l >= length || pad == "" {
return str
}
remains := length - l
padLen := Len(pad)
output := &bytes.Buffer{}
output.Grow(len(str) + (remains/padLen+1)*len(pad))
writePadString(output, pad, padLen, remains)
output.WriteString(str)
return output.String()
}
// Center returns a string with pad string at both side if str's rune length is smaller than length.
// If str's rune length is larger than length, str itself will be returned.
//
// If pad is an empty string, str will be returned.
//
// Samples:
// Center("hello", 4, " ") => "hello"
// Center("hello", 10, " ") => " hello "
// Center("hello", 10, "123") => "12hello123"
func Center(str string, length int, pad string) string {
l := Len(str)
if l >= length || pad == "" {
return str
}
remains := length - l
padLen := Len(pad)
output := &bytes.Buffer{}
output.Grow(len(str) + (remains/padLen+1)*len(pad))
writePadString(output, pad, padLen, remains/2)
output.WriteString(str)
writePadString(output, pad, padLen, (remains+1)/2)
return output.String()
}
func writePadString(output *bytes.Buffer, pad string, padLen, remains int) {
var r rune
var size int
repeats := remains / padLen
for i := 0; i < repeats; i++ {
output.WriteString(pad)
}
remains = remains % padLen
if remains != 0 {
for i := 0; i < remains; i++ {
r, size = utf8.DecodeRuneInString(pad)
output.WriteRune(r)
pad = pad[size:]
}
}
}

217
vendor/github.com/huandu/xstrings/manipulate.go generated vendored Normal file
View File

@@ -0,0 +1,217 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package xstrings
import (
"bytes"
"strings"
"unicode/utf8"
)
// Reverse a utf8 encoded string.
func Reverse(str string) string {
var size int
tail := len(str)
buf := make([]byte, tail)
s := buf
for len(str) > 0 {
_, size = utf8.DecodeRuneInString(str)
tail -= size
s = append(s[:tail], []byte(str[:size])...)
str = str[size:]
}
return string(buf)
}
// Slice a string by rune.
//
// Start must satisfy 0 <= start <= rune length.
//
// End can be positive, zero or negative.
// If end >= 0, start and end must satisfy start <= end <= rune length.
// If end < 0, it means slice to the end of string.
//
// Otherwise, Slice will panic as out of range.
func Slice(str string, start, end int) string {
var size, startPos, endPos int
origin := str
if start < 0 || end > len(str) || (end >= 0 && start > end) {
panic("out of range")
}
if end >= 0 {
end -= start
}
for start > 0 && len(str) > 0 {
_, size = utf8.DecodeRuneInString(str)
start--
startPos += size
str = str[size:]
}
if end < 0 {
return origin[startPos:]
}
endPos = startPos
for end > 0 && len(str) > 0 {
_, size = utf8.DecodeRuneInString(str)
end--
endPos += size
str = str[size:]
}
if len(str) == 0 && (start > 0 || end > 0) {
panic("out of range")
}
return origin[startPos:endPos]
}
// Partition splits a string by sep into three parts.
// The return value is a slice of strings with head, match and tail.
//
// If str contains sep, for example "hello" and "l", Partition returns
// "he", "l", "lo"
//
// If str doesn't contain sep, for example "hello" and "x", Partition returns
// "hello", "", ""
func Partition(str, sep string) (head, match, tail string) {
index := strings.Index(str, sep)
if index == -1 {
head = str
return
}
head = str[:index]
match = str[index : index+len(sep)]
tail = str[index+len(sep):]
return
}
// LastPartition splits a string by last instance of sep into three parts.
// The return value is a slice of strings with head, match and tail.
//
// If str contains sep, for example "hello" and "l", LastPartition returns
// "hel", "l", "o"
//
// If str doesn't contain sep, for example "hello" and "x", LastPartition returns
// "", "", "hello"
func LastPartition(str, sep string) (head, match, tail string) {
index := strings.LastIndex(str, sep)
if index == -1 {
tail = str
return
}
head = str[:index]
match = str[index : index+len(sep)]
tail = str[index+len(sep):]
return
}
// Insert src into dst at given rune index.
// Index is counted by runes instead of bytes.
//
// If index is out of range of dst, panic with out of range.
func Insert(dst, src string, index int) string {
return Slice(dst, 0, index) + src + Slice(dst, index, -1)
}
// Scrubs invalid utf8 bytes with repl string.
// Adjacent invalid bytes are replaced only once.
func Scrub(str, repl string) string {
var buf *bytes.Buffer
var r rune
var size, pos int
var hasError bool
origin := str
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
if r == utf8.RuneError {
if !hasError {
if buf == nil {
buf = &bytes.Buffer{}
}
buf.WriteString(origin[:pos])
hasError = true
}
} else if hasError {
hasError = false
buf.WriteString(repl)
origin = origin[pos:]
pos = 0
}
pos += size
str = str[size:]
}
if buf != nil {
buf.WriteString(origin)
return buf.String()
}
// No invalid byte.
return origin
}
// Splits a string into words. Returns a slice of words.
// If there is no word in a string, return nil.
//
// Word is defined as a locale dependent string containing alphabetic characters,
// which may also contain but not start with `'` and `-` characters.
func WordSplit(str string) []string {
var word string
var words []string
var r rune
var size, pos int
inWord := false
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
switch {
case isAlphabet(r):
if !inWord {
inWord = true
word = str
pos = 0
}
case inWord && (r == '\'' || r == '-'):
// Still in word.
default:
if inWord {
inWord = false
words = append(words, word[:pos])
}
}
pos += size
str = str[size:]
}
if inWord {
words = append(words, word[:pos])
}
return words
}

545
vendor/github.com/huandu/xstrings/translate.go generated vendored Normal file
View File

@@ -0,0 +1,545 @@
// Copyright 2015 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package xstrings
import (
"bytes"
"unicode"
"unicode/utf8"
)
type runeRangeMap struct {
FromLo rune // Lower bound of range map.
FromHi rune // An inclusive higher bound of range map.
ToLo rune
ToHi rune
}
type runeDict struct {
Dict [unicode.MaxASCII + 1]rune
}
type runeMap map[rune]rune
// Translator can translate string with pre-compiled from and to patterns.
// If a from/to pattern pair needs to be used more than once, it's recommended
// to create a Translator and reuse it.
type Translator struct {
quickDict *runeDict // A quick dictionary to look up rune by index. Only availabe for latin runes.
runeMap runeMap // Rune map for translation.
ranges []*runeRangeMap // Ranges of runes.
mappedRune rune // If mappedRune >= 0, all matched runes are translated to the mappedRune.
reverted bool // If to pattern is empty, all matched characters will be deleted.
hasPattern bool
}
// NewTranslator creates new Translator through a from/to pattern pair.
func NewTranslator(from, to string) *Translator {
tr := &Translator{}
if from == "" {
return tr
}
reverted := from[0] == '^'
deletion := len(to) == 0
if reverted {
from = from[1:]
}
var fromStart, fromEnd, fromRangeStep rune
var toStart, toEnd, toRangeStep rune
var fromRangeSize, toRangeSize rune
var singleRunes []rune
// Update the to rune range.
updateRange := func() {
// No more rune to read in the to rune pattern.
if toEnd == utf8.RuneError {
return
}
if toRangeStep == 0 {
to, toStart, toEnd, toRangeStep = nextRuneRange(to, toEnd)
return
}
// Current range is not empty. Consume 1 rune from start.
if toStart != toEnd {
toStart += toRangeStep
return
}
// No more rune. Repeat the last rune.
if to == "" {
toEnd = utf8.RuneError
return
}
// Both start and end are used. Read two more runes from the to pattern.
to, toStart, toEnd, toRangeStep = nextRuneRange(to, utf8.RuneError)
}
if deletion {
toStart = utf8.RuneError
toEnd = utf8.RuneError
} else {
// If from pattern is reverted, only the last rune in the to pattern will be used.
if reverted {
var size int
for len(to) > 0 {
toStart, size = utf8.DecodeRuneInString(to)
to = to[size:]
}
toEnd = utf8.RuneError
} else {
to, toStart, toEnd, toRangeStep = nextRuneRange(to, utf8.RuneError)
}
}
fromEnd = utf8.RuneError
for len(from) > 0 {
from, fromStart, fromEnd, fromRangeStep = nextRuneRange(from, fromEnd)
// fromStart is a single character. Just map it with a rune in the to pattern.
if fromRangeStep == 0 {
singleRunes = tr.addRune(fromStart, toStart, singleRunes)
updateRange()
continue
}
for toEnd != utf8.RuneError && fromStart != fromEnd {
// If mapped rune is a single character instead of a range, simply shift first
// rune in the range.
if toRangeStep == 0 {
singleRunes = tr.addRune(fromStart, toStart, singleRunes)
updateRange()
fromStart += fromRangeStep
continue
}
fromRangeSize = (fromEnd - fromStart) * fromRangeStep
toRangeSize = (toEnd - toStart) * toRangeStep
// Not enough runes in the to pattern. Need to read more.
if fromRangeSize > toRangeSize {
fromStart, toStart = tr.addRuneRange(fromStart, fromStart+toRangeSize*fromRangeStep, toStart, toEnd, singleRunes)
fromStart += fromRangeStep
updateRange()
// Edge case: If fromRangeSize == toRangeSize + 1, the last fromStart value needs be considered
// as a single rune.
if fromStart == fromEnd {
singleRunes = tr.addRune(fromStart, toStart, singleRunes)
updateRange()
}
continue
}
fromStart, toStart = tr.addRuneRange(fromStart, fromEnd, toStart, toStart+fromRangeSize*toRangeStep, singleRunes)
updateRange()
break
}
if fromStart == fromEnd {
fromEnd = utf8.RuneError
continue
}
fromStart, toStart = tr.addRuneRange(fromStart, fromEnd, toStart, toStart, singleRunes)
fromEnd = utf8.RuneError
}
if fromEnd != utf8.RuneError {
singleRunes = tr.addRune(fromEnd, toStart, singleRunes)
}
tr.reverted = reverted
tr.mappedRune = -1
tr.hasPattern = true
// Translate RuneError only if in deletion or reverted mode.
if deletion || reverted {
tr.mappedRune = toStart
}
return tr
}
func (tr *Translator) addRune(from, to rune, singleRunes []rune) []rune {
if from <= unicode.MaxASCII {
if tr.quickDict == nil {
tr.quickDict = &runeDict{}
}
tr.quickDict.Dict[from] = to
} else {
if tr.runeMap == nil {
tr.runeMap = make(runeMap)
}
tr.runeMap[from] = to
}
singleRunes = append(singleRunes, from)
return singleRunes
}
func (tr *Translator) addRuneRange(fromLo, fromHi, toLo, toHi rune, singleRunes []rune) (rune, rune) {
var r rune
var rrm *runeRangeMap
if fromLo < fromHi {
rrm = &runeRangeMap{
FromLo: fromLo,
FromHi: fromHi,
ToLo: toLo,
ToHi: toHi,
}
} else {
rrm = &runeRangeMap{
FromLo: fromHi,
FromHi: fromLo,
ToLo: toHi,
ToHi: toLo,
}
}
// If there is any single rune conflicts with this rune range, clear single rune record.
for _, r = range singleRunes {
if rrm.FromLo <= r && r <= rrm.FromHi {
if r <= unicode.MaxASCII {
tr.quickDict.Dict[r] = 0
} else {
delete(tr.runeMap, r)
}
}
}
tr.ranges = append(tr.ranges, rrm)
return fromHi, toHi
}
func nextRuneRange(str string, last rune) (remaining string, start, end rune, rangeStep rune) {
var r rune
var size int
remaining = str
escaping := false
isRange := false
for len(remaining) > 0 {
r, size = utf8.DecodeRuneInString(remaining)
remaining = remaining[size:]
// Parse special characters.
if !escaping {
if r == '\\' {
escaping = true
continue
}
if r == '-' {
// Ignore slash at beginning of string.
if last == utf8.RuneError {
continue
}
start = last
isRange = true
continue
}
}
escaping = false
if last != utf8.RuneError {
// This is a range which start and end are the same.
// Considier it as a normal character.
if isRange && last == r {
isRange = false
continue
}
start = last
end = r
if isRange {
if start < end {
rangeStep = 1
} else {
rangeStep = -1
}
}
return
}
last = r
}
start = last
end = utf8.RuneError
return
}
// Translate str with a from/to pattern pair.
//
// See comment in Translate function for usage and samples.
func (tr *Translator) Translate(str string) string {
if !tr.hasPattern || str == "" {
return str
}
var r rune
var size int
var needTr bool
orig := str
var output *bytes.Buffer
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
r, needTr = tr.TranslateRune(r)
if needTr && output == nil {
output = allocBuffer(orig, str)
}
if r != utf8.RuneError && output != nil {
output.WriteRune(r)
}
str = str[size:]
}
// No character is translated.
if output == nil {
return orig
}
return output.String()
}
// TranslateRune return translated rune and true if r matches the from pattern.
// If r doesn't match the pattern, original r is returned and translated is false.
func (tr *Translator) TranslateRune(r rune) (result rune, translated bool) {
switch {
case tr.quickDict != nil:
if r <= unicode.MaxASCII {
result = tr.quickDict.Dict[r]
if result != 0 {
translated = true
if tr.mappedRune >= 0 {
result = tr.mappedRune
}
break
}
}
fallthrough
case tr.runeMap != nil:
var ok bool
if result, ok = tr.runeMap[r]; ok {
translated = true
if tr.mappedRune >= 0 {
result = tr.mappedRune
}
break
}
fallthrough
default:
var rrm *runeRangeMap
ranges := tr.ranges
for i := len(ranges) - 1; i >= 0; i-- {
rrm = ranges[i]
if rrm.FromLo <= r && r <= rrm.FromHi {
translated = true
if tr.mappedRune >= 0 {
result = tr.mappedRune
break
}
if rrm.ToLo < rrm.ToHi {
result = rrm.ToLo + r - rrm.FromLo
} else if rrm.ToLo > rrm.ToHi {
// ToHi can be smaller than ToLo if range is from higher to lower.
result = rrm.ToLo - r + rrm.FromLo
} else {
result = rrm.ToLo
}
break
}
}
}
if tr.reverted {
if !translated {
result = tr.mappedRune
}
translated = !translated
}
if !translated {
result = r
}
return
}
// HasPattern returns true if Translator has one pattern at least.
func (tr *Translator) HasPattern() bool {
return tr.hasPattern
}
// Translate str with the characters defined in from replaced by characters defined in to.
//
// From and to are patterns representing a set of characters. Pattern is defined as following.
//
// * Special characters
// * '-' means a range of runes, e.g.
// * "a-z" means all characters from 'a' to 'z' inclusive;
// * "z-a" means all characters from 'z' to 'a' inclusive.
// * '^' as first character means a set of all runes excepted listed, e.g.
// * "^a-z" means all characters except 'a' to 'z' inclusive.
// * '\' escapes special characters.
// * Normal character represents itself, e.g. "abc" is a set including 'a', 'b' and 'c'.
//
// Translate will try to find a 1:1 mapping from from to to.
// If to is smaller than from, last rune in to will be used to map "out of range" characters in from.
//
// Note that '^' only works in the from pattern. It will be considered as a normal character in the to pattern.
//
// If the to pattern is an empty string, Translate works exactly the same as Delete.
//
// Samples:
// Translate("hello", "aeiou", "12345") => "h2ll4"
// Translate("hello", "a-z", "A-Z") => "HELLO"
// Translate("hello", "z-a", "a-z") => "svool"
// Translate("hello", "aeiou", "*") => "h*ll*"
// Translate("hello", "^l", "*") => "**ll*"
// Translate("hello ^ world", `\^lo`, "*") => "he*** * w*r*d"
func Translate(str, from, to string) string {
tr := NewTranslator(from, to)
return tr.Translate(str)
}
// Delete runes in str matching the pattern.
// Pattern is defined in Translate function.
//
// Samples:
// Delete("hello", "aeiou") => "hll"
// Delete("hello", "a-k") => "llo"
// Delete("hello", "^a-k") => "he"
func Delete(str, pattern string) string {
tr := NewTranslator(pattern, "")
return tr.Translate(str)
}
// Count how many runes in str match the pattern.
// Pattern is defined in Translate function.
//
// Samples:
// Count("hello", "aeiou") => 3
// Count("hello", "a-k") => 3
// Count("hello", "^a-k") => 2
func Count(str, pattern string) int {
if pattern == "" || str == "" {
return 0
}
var r rune
var size int
var matched bool
tr := NewTranslator(pattern, "")
cnt := 0
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
str = str[size:]
if _, matched = tr.TranslateRune(r); matched {
cnt++
}
}
return cnt
}
// Squeeze deletes adjacent repeated runes in str.
// If pattern is not empty, only runes matching the pattern will be squeezed.
//
// Samples:
// Squeeze("hello", "") => "helo"
// Squeeze("hello", "m-z") => "hello"
func Squeeze(str, pattern string) string {
var last, r rune
var size int
var skipSqueeze, matched bool
var tr *Translator
var output *bytes.Buffer
orig := str
last = -1
if len(pattern) > 0 {
tr = NewTranslator(pattern, "")
}
for len(str) > 0 {
r, size = utf8.DecodeRuneInString(str)
// Need to squeeze the str.
if last == r && !skipSqueeze {
if tr != nil {
if _, matched = tr.TranslateRune(r); !matched {
skipSqueeze = true
}
}
if output == nil {
output = allocBuffer(orig, str)
}
if skipSqueeze {
output.WriteRune(r)
}
} else {
if output != nil {
output.WriteRune(r)
}
last = r
}
str = str[size:]
}
if output == nil {
return orig
}
return output.String()
}

View File

@@ -65,15 +65,27 @@ If you were using Mergo **before** April 6th 2015, please check your project wor
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
```go
if err := mergo.Merge(&dst, src); err != nil {
// ...
}
```
Also, you can merge overwriting values using MergeWithOverwrite.
```go
if err := mergo.MergeWithOverwrite(&dst, src); err != nil {
// ...
}
```
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
```go
if err := mergo.Map(&dst, srcMap); err != nil {
// ...
}
```
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
@@ -97,11 +109,11 @@ type Foo struct {
func main() {
src := Foo{
A: "one",
B: 2,
}
dest := Foo{
A: "two",
B: 2,
}
mergo.Merge(&dest, src)

View File

@@ -19,7 +19,7 @@ func isBuiltin(name string) bool {
"echo", "printf", "break", "continue", "pwd", "cd",
"wait", "builtin", "trap", "type", "source", "command",
"pushd", "popd", "umask", "alias", "unalias", "fg", "bg",
"getopts", "eval":
"getopts", "eval", "test", "[":
return true
}
return false
@@ -207,6 +207,23 @@ func (r *Runner) builtinCode(pos syntax.Pos, name string, args []string) int {
r2.File = file
r2.Run()
return r2.exit
case "[":
if len(args) == 0 || args[len(args)-1] != "]" {
r.runErr(pos, "[: missing matching ]")
break
}
args = args[:len(args)-1]
fallthrough
case "test":
p := testParser{
rem: args,
err: func(format string, a ...interface{}) {
r.runErr(pos, format, a...)
},
}
p.next()
expr := p.classicTest("[", false)
return oneIf(r.bashTest(expr) == "")
case "trap", "source", "command", "pushd", "popd",
"umask", "alias", "unalias", "fg", "bg", "getopts":
r.runErr(pos, "unhandled builtin: %s", name)

View File

@@ -563,6 +563,7 @@ func (r *Runner) loopStmtsBroken(stmts []*syntax.Stmt) bool {
func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
var parts []string
var curBuf bytes.Buffer
allowEmpty := false
flush := func() {
if curBuf.Len() == 0 {
return
@@ -590,17 +591,18 @@ func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
}
curBuf.WriteString(s)
case *syntax.SglQuoted:
allowEmpty = true
curBuf.WriteString(x.Value)
case *syntax.DblQuoted:
// TODO: @ between double quotes but not alone
allowEmpty = true
if len(x.Parts) == 1 {
pe, ok := x.Parts[0].(*syntax.ParamExp)
if ok && pe.Param.Value == "@" {
for i, arg := range r.args {
pe, _ := x.Parts[0].(*syntax.ParamExp)
if elems := r.quotedElems(pe); elems != nil {
for i, elem := range elems {
if i > 0 {
flush()
}
curBuf.WriteString(arg)
curBuf.WriteString(elem)
}
continue
}
@@ -617,10 +619,10 @@ func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
}
case *syntax.CmdSubst:
r2 := *r
var outBuf bytes.Buffer
r2.Stdout = &outBuf
var buf bytes.Buffer
r2.Stdout = &buf
r2.stmts(x.Stmts)
val := strings.TrimRight(outBuf.String(), "\n")
val := strings.TrimRight(buf.String(), "\n")
if quoted {
curBuf.WriteString(val)
} else {
@@ -633,6 +635,9 @@ func (r *Runner) wordParts(wps []syntax.WordPart, quoted bool) []string {
}
}
flush()
if allowEmpty && len(parts) == 0 {
parts = append(parts, "")
}
return parts
}

View File

@@ -12,6 +12,29 @@ import (
"github.com/mvdan/sh/syntax"
)
func (r *Runner) quotedElems(pe *syntax.ParamExp) []string {
if pe == nil {
return nil
}
if pe.Param.Value == "@" {
return r.args
}
w, _ := pe.Index.(*syntax.Word)
if w == nil || len(w.Parts) != 1 {
return nil
}
l, _ := w.Parts[0].(*syntax.Lit)
if l == nil || l.Value != "@" {
return nil
}
val, _ := r.lookupVar(pe.Param.Value)
switch x := val.(type) {
case []string:
return x
}
return nil
}
func (r *Runner) paramExp(pe *syntax.ParamExp) string {
name := pe.Param.Value
var val varValue

182
vendor/github.com/mvdan/sh/interp/test_classic.go generated vendored Normal file
View File

@@ -0,0 +1,182 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"github.com/mvdan/sh/syntax"
)
const illegalTok = 0
type testParser struct {
eof bool
val string
rem []string
err func(format string, a ...interface{})
}
func (p *testParser) next() {
if p.eof || len(p.rem) == 0 {
p.eof = true
return
}
p.val = p.rem[0]
p.rem = p.rem[1:]
}
func (p *testParser) followWord(fval string) *syntax.Word {
if p.eof {
p.err("%s must be followed by a word", fval)
}
w := &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: p.val},
}}
p.next()
return w
}
func (p *testParser) classicTest(fval string, pastAndOr bool) syntax.TestExpr {
var left syntax.TestExpr
if pastAndOr {
left = p.testExprBase(fval)
} else {
left = p.classicTest(fval, true)
}
if left == nil || p.eof {
return left
}
opStr := p.val
op := testBinaryOp(p.val)
if op == illegalTok {
p.err("not a valid test operator: %s", p.val)
}
b := &syntax.BinaryTest{
Op: op,
X: left,
}
p.next()
switch b.Op {
case syntax.AndTest, syntax.OrTest:
if b.Y = p.classicTest(opStr, false); b.Y == nil {
p.err("%s must be followed by an expression", opStr)
}
default:
b.Y = p.followWord(opStr)
}
return b
}
func (p *testParser) testExprBase(fval string) syntax.TestExpr {
if p.eof {
return nil
}
op := testUnaryOp(p.val)
switch op {
case syntax.TsNot:
u := &syntax.UnaryTest{Op: op}
p.next()
u.X = p.classicTest(op.String(), false)
return u
case illegalTok:
return p.followWord(fval)
default:
u := &syntax.UnaryTest{Op: op}
p.next()
u.X = p.followWord(op.String())
return u
}
}
// testUnaryOp is an exact copy of syntax's.
func testUnaryOp(val string) syntax.UnTestOperator {
switch val {
case "!":
return syntax.TsNot
case "-e", "-a":
return syntax.TsExists
case "-f":
return syntax.TsRegFile
case "-d":
return syntax.TsDirect
case "-c":
return syntax.TsCharSp
case "-b":
return syntax.TsBlckSp
case "-p":
return syntax.TsNmPipe
case "-S":
return syntax.TsSocket
case "-L", "-h":
return syntax.TsSmbLink
case "-k":
return syntax.TsSticky
case "-g":
return syntax.TsGIDSet
case "-u":
return syntax.TsUIDSet
case "-G":
return syntax.TsGrpOwn
case "-O":
return syntax.TsUsrOwn
case "-N":
return syntax.TsModif
case "-r":
return syntax.TsRead
case "-w":
return syntax.TsWrite
case "-x":
return syntax.TsExec
case "-s":
return syntax.TsNoEmpty
case "-t":
return syntax.TsFdTerm
case "-z":
return syntax.TsEmpStr
case "-n":
return syntax.TsNempStr
case "-o":
return syntax.TsOptSet
case "-v":
return syntax.TsVarSet
case "-R":
return syntax.TsRefVar
default:
return illegalTok
}
}
// testBinaryOp is like syntax's, but with -a and -o, and without =~.
func testBinaryOp(val string) syntax.BinTestOperator {
switch val {
case "-a":
return syntax.AndTest
case "-o":
return syntax.OrTest
case "==", "=":
return syntax.TsMatch
case "!=":
return syntax.TsNoMatch
case "-nt":
return syntax.TsNewer
case "-ot":
return syntax.TsOlder
case "-ef":
return syntax.TsDevIno
case "-eq":
return syntax.TsEql
case "-ne":
return syntax.TsNeq
case "-le":
return syntax.TsLeq
case "-ge":
return syntax.TsGeq
case "-lt":
return syntax.TsLss
case "-gt":
return syntax.TsGtr
default:
return illegalTok
}
}

View File

@@ -28,6 +28,15 @@ func paramOps(r rune) bool {
return false
}
// these start a parameter expansion name
func paramNameOp(r rune) bool {
switch r {
case '}', ':', '+', '=', '%', '[', ']', '/', '^', ',', '@':
return false
}
return true
}
// tokenize these inside arithmetic expansions
func arithmOps(r rune) bool {
switch r {
@@ -86,14 +95,6 @@ retry:
return p.r
}
func (p *Parser) unrune(r rune, tok token) {
if p.r != utf8.RuneSelf {
p.npos -= utf8.RuneLen(p.r)
p.r = r
p.tok = tok
}
}
// fill reads more bytes from the input src into readBuf. Any bytes that
// had not yet been used at the end of the buffer are slid into the
// beginning of the buffer.
@@ -528,7 +529,7 @@ func (p *Parser) paramToken(r rune) token {
p.rune()
return leftBrack
case '/':
if p.rune() == '/' {
if p.rune() == '/' && p.quote != paramExpRepl {
p.rune()
return dblSlash
}
@@ -961,90 +962,90 @@ loop:
p.tok, p.val = _LitWord, p.endLit()
}
func testUnaryOp(val string) token {
func testUnaryOp(val string) UnTestOperator {
switch val {
case "!":
return exclMark
return TsNot
case "-e", "-a":
return tsExists
return TsExists
case "-f":
return tsRegFile
return TsRegFile
case "-d":
return tsDirect
return TsDirect
case "-c":
return tsCharSp
return TsCharSp
case "-b":
return tsBlckSp
return TsBlckSp
case "-p":
return tsNmPipe
return TsNmPipe
case "-S":
return tsSocket
return TsSocket
case "-L", "-h":
return tsSmbLink
return TsSmbLink
case "-k":
return tsSticky
return TsSticky
case "-g":
return tsGIDSet
return TsGIDSet
case "-u":
return tsUIDSet
return TsUIDSet
case "-G":
return tsGrpOwn
return TsGrpOwn
case "-O":
return tsUsrOwn
return TsUsrOwn
case "-N":
return tsModif
return TsModif
case "-r":
return tsRead
return TsRead
case "-w":
return tsWrite
return TsWrite
case "-x":
return tsExec
return TsExec
case "-s":
return tsNoEmpty
return TsNoEmpty
case "-t":
return tsFdTerm
return TsFdTerm
case "-z":
return tsEmpStr
return TsEmpStr
case "-n":
return tsNempStr
return TsNempStr
case "-o":
return tsOptSet
return TsOptSet
case "-v":
return tsVarSet
return TsVarSet
case "-R":
return tsRefVar
return TsRefVar
default:
return illegalTok
return 0
}
}
func testBinaryOp(val string) token {
func testBinaryOp(val string) BinTestOperator {
switch val {
case "==", "=":
return equal
return TsMatch
case "!=":
return nequal
return TsNoMatch
case "=~":
return tsReMatch
return TsReMatch
case "-nt":
return tsNewer
return TsNewer
case "-ot":
return tsOlder
return TsOlder
case "-ef":
return tsDevIno
return TsDevIno
case "-eq":
return tsEql
return TsEql
case "-ne":
return tsNeq
return TsNeq
case "-le":
return tsLeq
return TsLeq
case "-ge":
return tsGeq
return TsGeq
case "-lt":
return tsLss
return TsLss
case "-gt":
return tsGtr
return TsGtr
default:
return illegalTok
return 0
}
}

View File

@@ -111,17 +111,16 @@ func (c *Comment) End() Pos { return c.Hash + Pos(len(c.Text)) }
// Stmt represents a statement, otherwise known as a compound command.
// It is compromised of a command and other components that may come
// before or after it.
//
// The Coprocess field is particular to MirBSDKorn.
type Stmt struct {
Cmd Command
Position Pos
Semicolon Pos
Negated bool
Background bool
Coprocess bool
Assigns []*Assign
Redirs []*Redirect
Negated bool // ! stmt
Background bool // stmt &
Coprocess bool // mksh's |&
Assigns []*Assign // a=x b=y stmt
Redirs []*Redirect // stmt >a <b
}
func (s *Stmt) Pos() Pos { return s.Position }
@@ -174,13 +173,13 @@ func (*CoprocClause) commandNode() {}
// Assign represents an assignment to a variable.
type Assign struct {
Append bool
Naked bool
Append bool // +=
Naked bool // without '='
Name *Lit
Index ArithmExpr
Key *DblQuoted
Value *Word
Array *ArrayExpr
Index ArithmExpr // [i]
Key *DblQuoted // ["k"]
Value *Word // =val
Array *ArrayExpr // =(arr)
}
func (a *Assign) Pos() Pos { return a.Name.Pos() }
@@ -193,8 +192,7 @@ func (a *Assign) End() Pos {
}
if a.Index != nil {
return a.Index.End() + 2
}
if a.Key != nil {
} else if a.Key != nil {
return a.Key.End() + 2
}
if a.Naked {
@@ -205,10 +203,11 @@ func (a *Assign) End() Pos {
// Redirect represents an input/output redirection.
type Redirect struct {
OpPos Pos
Op RedirOperator
N *Lit
Word, Hdoc *Word
OpPos Pos
Op RedirOperator
N *Lit // N>
Word *Word // >word
Hdoc *Word // here-document body
}
func (r *Redirect) Pos() Pos {
@@ -330,10 +329,10 @@ func (b *BinaryCmd) End() Pos { return b.Y.End() }
// FuncDecl represents the declaration of a function.
type FuncDecl struct {
Position Pos
BashStyle bool
Name *Lit
Body *Stmt
Position Pos
RsrvWord bool // non-posix "function " style
Name *Lit
Body *Stmt
}
func (f *FuncDecl) Pos() Pos { return f.Position }
@@ -379,7 +378,7 @@ func (l *Lit) End() Pos { return l.ValueEnd }
// SglQuoted represents a string within single quotes.
type SglQuoted struct {
Position Pos
Dollar bool
Dollar bool // $''
Value string
}
@@ -395,7 +394,7 @@ func (q *SglQuoted) End() Pos {
// DblQuoted represents a list of nodes within double quotes.
type DblQuoted struct {
Position Pos
Dollar bool
Dollar bool // $""
Parts []WordPart
}
@@ -415,10 +414,8 @@ type CmdSubst struct {
Left, Right Pos
Stmts []*Stmt
// MirBSDTempFile is true for mksh's ${ foo;}
MirBSDTempFile bool
// MirBSDReplyvar is true for mksh's ${|foo;}
MirBSDReplyVar bool
TempFile bool // mksh's ${ foo;}
ReplyVar bool // mksh's ${|foo;}
}
func (c *CmdSubst) Pos() Pos { return c.Left }
@@ -427,16 +424,16 @@ func (c *CmdSubst) End() Pos { return c.Right + 1 }
// ParamExp represents a parameter expansion.
type ParamExp struct {
Dollar, Rbrace Pos
Short bool
Indirect bool
Length bool
Width bool
Short bool // $a instead of ${a}
Indirect bool // ${!a}
Length bool // ${#a}
Width bool // ${%a}
Param *Lit
Index ArithmExpr
Key *DblQuoted
Slice *Slice
Repl *Replace
Exp *Expansion
Index ArithmExpr // ${a[i]}
Key *DblQuoted // ${a["k"]}
Slice *Slice // ${a:x:y}
Repl *Replace // ${a/x/y}
Exp *Expansion // ${a:-b}, ${a#b}, etc
}
func (p *ParamExp) Pos() Pos { return p.Dollar }
@@ -446,8 +443,7 @@ func (p *ParamExp) End() Pos {
}
if p.Index != nil {
return p.Index.End() + 1
}
if p.Key != nil {
} else if p.Key != nil {
return p.Key.End() + 1
}
return p.Param.End()
@@ -480,8 +476,8 @@ type Expansion struct {
// ArithmExp represents an arithmetic expansion.
type ArithmExp struct {
Left, Right Pos
Bracket bool
Unsigned bool
Bracket bool // deprecated $[expr] form
Unsigned bool // mksh's $((# expr))
X ArithmExpr
}
@@ -498,7 +494,7 @@ func (a *ArithmExp) End() Pos {
// This node will never appear when in PosixConformant mode.
type ArithmCmd struct {
Left, Right Pos
Unsigned bool
Unsigned bool // mksh's ((# expr))
X ArithmExpr
}
@@ -651,7 +647,7 @@ func (p *ParenTest) End() Pos { return p.Rparen + 1 }
// This node will never appear when in PosixConformant mode.
type DeclClause struct {
Position Pos
Variant string
Variant string // "declare", "local", etc
Opts []*Word
Assigns []*Assign
}
@@ -684,8 +680,7 @@ type ArrayElem struct {
func (a *ArrayElem) Pos() Pos {
if a.Index != nil {
return a.Index.Pos()
}
if a.Key != nil {
} else if a.Key != nil {
return a.Key.Pos()
}
return a.Value.Pos()

View File

@@ -118,7 +118,7 @@ func (p *Parser) getPos() Pos { return Pos(p.offs + p.npos) }
func (p *Parser) lit(pos Pos, val string) *Lit {
if len(p.litBatch) == 0 {
p.litBatch = make([]Lit, 64)
p.litBatch = make([]Lit, 128)
}
l := &p.litBatch[0]
p.litBatch = p.litBatch[1:]
@@ -130,7 +130,7 @@ func (p *Parser) lit(pos Pos, val string) *Lit {
func (p *Parser) word(parts []WordPart) *Word {
if len(p.wordBatch) == 0 {
p.wordBatch = make([]Word, 32)
p.wordBatch = make([]Word, 64)
}
w := &p.wordBatch[0]
p.wordBatch = p.wordBatch[1:]
@@ -150,7 +150,7 @@ func (p *Parser) wps(wp WordPart) []WordPart {
func (p *Parser) stmt(pos Pos) *Stmt {
if len(p.stmtBatch) == 0 {
p.stmtBatch = make([]Stmt, 16)
p.stmtBatch = make([]Stmt, 64)
}
s := &p.stmtBatch[0]
p.stmtBatch = p.stmtBatch[1:]
@@ -160,7 +160,7 @@ func (p *Parser) stmt(pos Pos) *Stmt {
func (p *Parser) stList() []*Stmt {
if len(p.stListBatch) == 0 {
p.stListBatch = make([]*Stmt, 128)
p.stListBatch = make([]*Stmt, 256)
}
stmts := p.stListBatch[:0:4]
p.stListBatch = p.stListBatch[4:]
@@ -184,7 +184,7 @@ func (p *Parser) call(w *Word) *CallExpr {
return ce
}
type quoteState uint
type quoteState uint32
const (
noState quoteState = 1 << iota
@@ -557,9 +557,9 @@ func (p *Parser) wordPart() WordPart {
p.curErr(`"${ stmts;}" is a mksh feature`)
}
cs := &CmdSubst{
Left: p.pos,
MirBSDTempFile: p.r != '|',
MirBSDReplyVar: p.r == '|',
Left: p.pos,
TempFile: p.r != '|',
ReplyVar: p.r == '|',
}
old := p.preNested(subCmd)
p.rune() // don't tokenize '|'
@@ -792,6 +792,9 @@ func (p *Parser) arithmExpr(ftok token, fpos Pos, level int, compact, tern bool)
case _Lit, _LitWord:
p.curErr("not a valid arithmetic operator: %s", p.val)
return nil
case leftBrack:
p.curErr("[ must follow a name")
return nil
case rightParen, _EOF:
default:
if p.quote == arithmExpr {
@@ -845,7 +848,7 @@ func isArithName(left ArithmExpr) bool {
}
switch x := w.Parts[0].(type) {
case *Lit:
return validIdent(x.Value)
return ValidName(x.Value)
case *ParamExp:
return x.nakedIndex()
default:
@@ -905,10 +908,6 @@ func (p *Parser) arithmExprBase(compact bool) ArithmExpr {
p.postNested(old)
p.matched(left, leftBrack, rightBrack)
x = p.word(p.wps(pe))
case dollar:
x = p.word(p.wps(p.shortParamExp()))
case dollBrace:
x = p.word(p.wps(p.paramExp()))
case bckQuote:
if p.quote == arithmExprLet {
return nil
@@ -964,15 +963,18 @@ func (p *Parser) paramExp() *ParamExp {
pe := &ParamExp{Dollar: p.pos}
old := p.quote
p.quote = paramExpName
p.next()
if p.r == '#' {
p.tok = hash
p.pos = p.getPos()
p.rune()
} else {
p.next()
}
switch p.tok {
case at:
p.tok, p.val = _LitWord, "@"
case dblHash:
p.unrune('#', hash)
fallthrough
case hash:
if p.r != '}' {
if paramNameOp(p.r) {
pe.Length = true
p.next()
}
@@ -980,12 +982,12 @@ func (p *Parser) paramExp() *ParamExp {
if p.lang != LangMirBSDKorn {
p.posErr(pe.Pos(), `"${%%foo}" is a mksh feature`)
}
if p.r != '}' {
if paramNameOp(p.r) {
pe.Width = true
p.next()
}
case exclMark:
if p.r != '}' {
if paramNameOp(p.r) {
pe.Indirect = true
p.next()
}
@@ -1006,9 +1008,7 @@ func (p *Parser) paramExp() *ParamExp {
p.curErr("%s cannot be followed by a word", op)
}
default:
if !pe.Length {
p.posErr(pe.Dollar, "parameter expansion requires a literal")
}
p.curErr("parameter expansion requires a literal")
}
if p.tok == rightBrace {
pe.Rbrace = p.pos
@@ -1050,14 +1050,9 @@ func (p *Parser) paramExp() *ParamExp {
p.next()
pe.Repl.Orig = p.getWord()
p.quote = paramExpExp
switch p.tok {
case dblSlash:
p.unrune('/', slash)
p.next()
case slash:
p.next()
if p.got(slash) {
pe.Repl.With = p.getWord()
}
pe.Repl.With = p.getWord()
case colon:
if p.lang == LangPOSIX {
p.curErr("slicing is a bash feature")
@@ -1119,7 +1114,8 @@ func stopToken(tok token) bool {
return false
}
func validIdent(val string) bool {
// ValidName returns whether val is a valid name as per the POSIX spec.
func ValidName(val string) bool {
for i, c := range val {
switch {
case 'a' <= c && c <= 'z':
@@ -1138,7 +1134,7 @@ func (p *Parser) hasValidIdent() bool {
if p.val[end-1] == '+' && p.lang != LangPOSIX {
end--
}
if validIdent(p.val[:end]) {
if ValidName(p.val[:end]) {
return true
}
}
@@ -1387,57 +1383,51 @@ preLoop:
case "esac":
p.curErr(`%q can only be used to end a case`, p.val)
case "[[":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.testClause()
}
s.Cmd = p.testClause()
case "]]":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
p.curErr(`%s can only be used to close a test`,
p.val)
}
p.curErr(`%s can only be used to close a test`, p.val)
case "let":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.letClause()
}
s.Cmd = p.letClause()
case "function":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.bashFuncDecl()
}
s.Cmd = p.bashFuncDecl()
case "declare":
if p.lang != LangBash {
break
if p.lang == LangBash {
s.Cmd = p.declClause()
}
s.Cmd = p.declClause()
case "local", "export", "readonly", "typeset", "nameref":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.declClause()
}
s.Cmd = p.declClause()
case "time":
if p.lang == LangPOSIX {
break
if p.lang != LangPOSIX {
s.Cmd = p.timeClause()
}
s.Cmd = p.timeClause()
case "coproc":
if p.lang != LangBash {
break
if p.lang == LangBash {
s.Cmd = p.coprocClause()
}
s.Cmd = p.coprocClause()
}
if s.Cmd == nil {
name := p.lit(p.pos, p.val)
if p.next(); p.gotSameLine(leftParen) {
p.follow(name.ValuePos, "foo(", rightParen)
if p.lang == LangPOSIX && !validIdent(name.Value) {
p.posErr(name.Pos(), "invalid func name")
}
s.Cmd = p.funcDecl(name, name.ValuePos)
} else {
s.Cmd = p.callExpr(s, p.word(p.wps(name)))
if s.Cmd != nil {
break
}
name := p.lit(p.pos, p.val)
if p.next(); p.gotSameLine(leftParen) {
p.follow(name.ValuePos, "foo(", rightParen)
if p.lang == LangPOSIX && !ValidName(name.Value) {
p.posErr(name.Pos(), "invalid func name")
}
s.Cmd = p.funcDecl(name, name.ValuePos)
} else {
s.Cmd = p.callExpr(s, p.word(p.wps(name)))
}
case bckQuote:
if p.quote == subCmdBckquo {
@@ -1576,25 +1566,16 @@ func (p *Parser) loop(forPos Pos) Loop {
if p.tok == dblLeftParen {
cl := &CStyleLoop{Lparen: p.pos}
old := p.preNested(arithmExprCmd)
if p.next(); p.tok == dblSemicolon {
p.unrune(';', semicolon)
}
if p.tok != semicolon {
cl.Init = p.arithmExpr(dblLeftParen, cl.Lparen, 0, false, false)
}
p.next()
cl.Init = p.arithmExpr(dblLeftParen, cl.Lparen, 0, false, false)
scPos := p.pos
if p.tok == dblSemicolon {
p.unrune(';', semicolon)
}
p.follow(p.pos, "expression", semicolon)
if p.tok != semicolon {
if !p.got(dblSemicolon) {
p.follow(p.pos, "expr", semicolon)
cl.Cond = p.arithmExpr(semicolon, scPos, 0, false, false)
scPos = p.pos
p.follow(p.pos, "expr", semicolon)
}
scPos = p.pos
p.follow(p.pos, "expression", semicolon)
if p.tok != semicolon {
cl.Post = p.arithmExpr(semicolon, scPos, 0, false, false)
}
cl.Post = p.arithmExpr(semicolon, scPos, 0, false, false)
cl.Rparen = p.arithmEnd(dblLeftParen, cl.Lparen, old)
p.gotSameLine(semicolon)
return cl
@@ -1677,7 +1658,7 @@ func (p *Parser) testClause() *TestClause {
if p.next(); p.tok == _EOF || p.gotRsrv("]]") {
p.posErr(tc.Left, "test clause requires at least one expression")
}
tc.X = p.testExpr(illegalTok, tc.Left, 0)
tc.X = p.testExpr(illegalTok, tc.Left, false)
tc.Right = p.pos
if !p.gotRsrv("]]") {
p.matchingErr(tc.Left, "[[", "]]")
@@ -1685,26 +1666,23 @@ func (p *Parser) testClause() *TestClause {
return tc
}
func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr {
func (p *Parser) testExpr(ftok token, fpos Pos, pastAndOr bool) TestExpr {
var left TestExpr
if level > 1 {
if pastAndOr {
left = p.testExprBase(ftok, fpos)
} else {
left = p.testExpr(ftok, fpos, level+1)
left = p.testExpr(ftok, fpos, true)
}
if left == nil {
return left
}
var newLevel int
switch p.tok {
case andAnd, orOr:
case _LitWord:
if p.val == "]]" {
return left
}
fallthrough
case rdrIn, rdrOut:
newLevel = 1
case _EOF, rightParen:
return left
case _Lit:
@@ -1712,11 +1690,8 @@ func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr {
default:
p.curErr("not a valid test operator: %v", p.tok)
}
if newLevel < level {
return left
}
if p.tok == _LitWord {
if p.tok = testBinaryOp(p.val); p.tok == illegalTok {
if p.tok = token(testBinaryOp(p.val)); p.tok == illegalTok {
p.curErr("not a valid test operator: %s", p.val)
}
}
@@ -1728,7 +1703,7 @@ func (p *Parser) testExpr(ftok token, fpos Pos, level int) TestExpr {
switch b.Op {
case AndTest, OrTest:
p.next()
if b.Y = p.testExpr(token(b.Op), b.OpPos, newLevel); b.Y == nil {
if b.Y = p.testExpr(token(b.Op), b.OpPos, false); b.Y == nil {
p.followErrExp(b.OpPos, b.Op.String())
}
case TsReMatch:
@@ -1754,11 +1729,10 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
case _EOF:
return nil
case _LitWord:
op := testUnaryOp(p.val)
op := token(testUnaryOp(p.val))
switch op {
case illegalTok:
case tsRefVar, tsModif:
// TODO: check with man mksh
case tsRefVar, tsModif: // not available in mksh
if p.lang == LangBash {
p.tok = op
}
@@ -1770,7 +1744,7 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
case exclMark:
u := &UnaryTest{OpPos: p.pos, Op: TsNot}
p.next()
u.X = p.testExpr(token(u.Op), u.OpPos, 0)
u.X = p.testExpr(token(u.Op), u.OpPos, false)
return u
case tsExists, tsRegFile, tsDirect, tsCharSp, tsBlckSp, tsNmPipe,
tsSocket, tsSmbLink, tsSticky, tsGIDSet, tsUIDSet, tsGrpOwn,
@@ -1783,7 +1757,7 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
case leftParen:
pe := &ParenTest{Lparen: p.pos}
p.next()
if pe.X = p.testExpr(leftParen, pe.Lparen, 0); pe.X == nil {
if pe.X = p.testExpr(leftParen, pe.Lparen, false); pe.X == nil {
p.followErrExp(pe.Lparen, "(")
}
pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen)
@@ -1953,9 +1927,9 @@ func (p *Parser) callExpr(s *Stmt, w *Word) *CallExpr {
func (p *Parser) funcDecl(name *Lit, pos Pos) *FuncDecl {
fd := &FuncDecl{
Position: pos,
BashStyle: pos != name.ValuePos,
Name: name,
Position: pos,
RsrvWord: pos != name.ValuePos,
Name: name,
}
if fd.Body, _ = p.getStmt(false, false); fd.Body == nil {
p.followErr(fd.Pos(), "foo()", "a statement")

View File

@@ -289,13 +289,13 @@ func (p *Printer) wordPart(wp WordPart) {
case *CmdSubst:
p.incLines(x.Pos())
switch {
case x.MirBSDTempFile:
case x.TempFile:
p.WriteString("${")
p.wantSpace = true
p.nestedStmts(x.Stmts, x.Right)
p.wantSpace = false
p.semiRsrv("}", x.Right, true)
case x.MirBSDReplyVar:
case x.ReplyVar:
p.WriteString("${|")
p.nestedStmts(x.Stmts, x.Right)
p.wantSpace = false
@@ -380,9 +380,7 @@ func (p *Printer) paramExp(pe *ParamExp) {
case pe.Indirect:
p.WriteByte('!')
}
if pe.Param != nil {
p.WriteString(pe.Param.Value)
}
p.WriteString(pe.Param.Value)
p.wroteIndex(pe.Index, pe.Key)
if pe.Slice != nil {
p.WriteByte(':')
@@ -741,7 +739,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
}
p.nestedBinary = false
case *FuncDecl:
if x.BashStyle {
if x.RsrvWord {
p.WriteString("function ")
}
p.WriteString(x.Name.Value)

243
vendor/github.com/mvdan/sh/syntax/simplify.go generated vendored Normal file
View File

@@ -0,0 +1,243 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "bytes"
// Simplify simplifies a given program and returns whether any changes
// were made.
//
// This function is EXPERIMENTAL; it may change or disappear at any
// point until this notice is removed.
func Simplify(f *File) bool {
s := simplifier{}
Walk(f, s.visit)
return s.modified
}
type simplifier struct {
modified bool
}
func (s *simplifier) visit(node Node) bool {
switch x := node.(type) {
case *Assign:
if x.Index != nil {
x.Index = s.removeParensArithm(x.Index)
x.Index = s.inlineSimpleParams(x.Index)
}
case *ParamExp:
if x.Index != nil {
x.Index = s.removeParensArithm(x.Index)
x.Index = s.inlineSimpleParams(x.Index)
}
if x.Slice == nil {
break
}
if x.Slice.Offset != nil {
x.Slice.Offset = s.removeParensArithm(x.Slice.Offset)
x.Slice.Offset = s.inlineSimpleParams(x.Slice.Offset)
}
if x.Slice.Length != nil {
x.Slice.Length = s.removeParensArithm(x.Slice.Length)
x.Slice.Length = s.inlineSimpleParams(x.Slice.Length)
}
case *ArithmExp:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *ArithmCmd:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *ParenArithm:
x.X = s.removeParensArithm(x.X)
x.X = s.inlineSimpleParams(x.X)
case *BinaryArithm:
x.X = s.inlineSimpleParams(x.X)
x.Y = s.inlineSimpleParams(x.Y)
case *CmdSubst:
x.Stmts = s.inlineSubshell(x.Stmts)
case *Subshell:
x.Stmts = s.inlineSubshell(x.Stmts)
case *Word:
x.Parts = s.simplifyWord(x.Parts)
case *TestClause:
x.X = s.removeParensTest(x.X)
x.X = s.removeNegateTest(x.X)
case *ParenTest:
x.X = s.removeParensTest(x.X)
x.X = s.removeNegateTest(x.X)
case *BinaryTest:
x.X = s.unquoteParams(x.X)
x.X = s.removeNegateTest(x.X)
switch x.Op {
case TsMatch, TsNoMatch:
// unquoting enables globbing
default:
x.Y = s.unquoteParams(x.Y)
}
x.Y = s.removeNegateTest(x.Y)
case *UnaryTest:
x.X = s.unquoteParams(x.X)
}
return true
}
func (s *simplifier) simplifyWord(wps []WordPart) []WordPart {
parts:
for i, wp := range wps {
dq, _ := wp.(*DblQuoted)
if dq == nil || len(dq.Parts) != 1 {
break
}
lit, _ := dq.Parts[0].(*Lit)
if lit == nil {
break
}
var buf bytes.Buffer
escaped := false
for _, r := range lit.Value {
switch r {
case '\\':
escaped = !escaped
if escaped {
continue
}
case '\'':
continue parts
case '$', '"', '`':
escaped = false
default:
if escaped {
continue parts
}
escaped = false
}
buf.WriteRune(r)
}
newVal := buf.String()
if newVal == lit.Value {
break
}
s.modified = true
wps[i] = &SglQuoted{
Position: dq.Position,
Dollar: dq.Dollar,
Value: newVal,
}
}
return wps
}
func (s *simplifier) removeParensArithm(x ArithmExpr) ArithmExpr {
for {
par, _ := x.(*ParenArithm)
if par == nil {
return x
}
s.modified = true
x = par.X
}
}
func (s *simplifier) inlineSimpleParams(x ArithmExpr) ArithmExpr {
w, _ := x.(*Word)
if w == nil || len(w.Parts) != 1 {
return x
}
pe, _ := w.Parts[0].(*ParamExp)
if pe == nil || !ValidName(pe.Param.Value) {
return x
}
if pe.Indirect || pe.Length || pe.Width || pe.Key != nil ||
pe.Slice != nil || pe.Repl != nil || pe.Exp != nil {
return x
}
if pe.Index != nil {
s.modified = true
pe.Short = true
return w
}
s.modified = true
return &Word{Parts: []WordPart{pe.Param}}
}
func (s *simplifier) inlineSubshell(stmts []*Stmt) []*Stmt {
for len(stmts) == 1 {
st := stmts[0]
if st.Negated || st.Background || st.Coprocess ||
len(st.Assigns) > 0 || len(st.Redirs) > 0 {
break
}
sub, _ := st.Cmd.(*Subshell)
if sub == nil {
break
}
s.modified = true
stmts = sub.Stmts
}
return stmts
}
func (s *simplifier) unquoteParams(x TestExpr) TestExpr {
w, _ := x.(*Word)
if w == nil || len(w.Parts) != 1 {
return x
}
dq, _ := w.Parts[0].(*DblQuoted)
if dq == nil || len(dq.Parts) != 1 {
return x
}
if _, ok := dq.Parts[0].(*ParamExp); !ok {
return x
}
s.modified = true
w.Parts = dq.Parts
return w
}
func (s *simplifier) removeParensTest(x TestExpr) TestExpr {
for {
par, _ := x.(*ParenTest)
if par == nil {
return x
}
s.modified = true
x = par.X
}
}
func (s *simplifier) removeNegateTest(x TestExpr) TestExpr {
u, _ := x.(*UnaryTest)
if u == nil || u.Op != TsNot {
return x
}
switch y := u.X.(type) {
case *UnaryTest:
switch y.Op {
case TsEmpStr:
y.Op = TsNempStr
s.modified = true
return y
case TsNempStr:
y.Op = TsEmpStr
s.modified = true
return y
case TsNot:
s.modified = true
return y.X
}
case *BinaryTest:
switch y.Op {
case TsMatch:
y.Op = TsNoMatch
s.modified = true
return y
case TsNoMatch:
y.Op = TsMatch
s.modified = true
return y
}
}
return x
}

View File

@@ -46,6 +46,11 @@ func Walk(node Node, f func(Node) bool) {
if x.Value != nil {
Walk(x.Value, f)
}
if x.Index != nil {
Walk(x.Index, f)
} else if x.Key != nil {
Walk(x.Key, f)
}
if x.Array != nil {
Walk(x.Array, f)
}
@@ -109,11 +114,11 @@ func Walk(node Node, f func(Node) bool) {
case *CmdSubst:
walkStmts(x.Stmts, f)
case *ParamExp:
if x.Param != nil {
Walk(x.Param, f)
}
Walk(x.Param, f)
if x.Index != nil {
Walk(x.Index, f)
} else if x.Key != nil {
Walk(x.Key, f)
}
if x.Repl != nil {
if x.Repl.Orig != nil {
@@ -164,6 +169,8 @@ func Walk(node Node, f func(Node) bool) {
case *ArrayElem:
if x.Index != nil {
Walk(x.Index, f)
} else if x.Key != nil {
Walk(x.Key, f)
}
Walk(x.Value, f)
case *ExtGlob:

View File

@@ -246,6 +246,25 @@ It is possible to mark a flag as hidden, meaning it will still function as norma
flags.MarkHidden("secretFlag")
```
## Disable sorting of flags
`pflag` allows you to disable sorting of flags for help and usage message.
**Example**:
```go
flags.BoolP("verbose", "v", false, "verbose output")
flags.String("coolflag", "yeaah", "it's really cool flag")
flags.Int("usefulflag", 777, "sometimes it's very useful")
flags.SortFlags = false
flags.PrintDefaults()
```
**Output**:
```
-v, --verbose verbose output
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
```
## Supporting Go flags when using pflag
In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary
to support flags defined by third-party dependencies (e.g. `golang/glog`).
@@ -270,8 +289,8 @@ func main() {
You can see the full reference documentation of the pflag package
[at godoc.org][3], or through go's standard documentation system by
running `godoc -http=:6060` and browsing to
[http://localhost:6060/pkg/github.com/ogier/pflag][2] after
[http://localhost:6060/pkg/github.com/spf13/pflag][2] after
installation.
[2]: http://localhost:6060/pkg/github.com/ogier/pflag
[3]: http://godoc.org/github.com/ogier/pflag
[2]: http://localhost:6060/pkg/github.com/spf13/pflag
[3]: http://godoc.org/github.com/spf13/pflag

View File

@@ -83,7 +83,9 @@ func (f *FlagSet) CountP(name, shorthand string, usage string) *int {
return p
}
// Count like Count only the flag is placed on the CommandLine isntead of a given flag set
// Count defines a count flag with specified name, default value, and usage string.
// The return value is the address of an int variable that stores the value of the flag.
// A count flag will add 1 to its value evey time it is found on the command line
func Count(name string, usage string) *int {
return CommandLine.CountP(name, "", usage)
}

243
vendor/github.com/spf13/pflag/flag.go generated vendored
View File

@@ -16,9 +16,9 @@ pflag is a drop-in replacement of Go's native flag package. If you import
pflag under the name "flag" then all code should continue to function
with no changes.
import flag "github.com/ogier/pflag"
import flag "github.com/spf13/pflag"
There is one exception to this: if you directly instantiate the Flag struct
There is one exception to this: if you directly instantiate the Flag struct
there is one more field "Shorthand" that you will need to set.
Most code never instantiates this struct directly, and instead uses
functions such as String(), BoolVar(), and Var(), and is therefore
@@ -134,14 +134,21 @@ type FlagSet struct {
// a custom error handler.
Usage func()
// SortFlags is used to indicate, if user wants to have sorted flags in
// help/usage messages.
SortFlags bool
name string
parsed bool
actual map[NormalizedName]*Flag
orderedActual []*Flag
sortedActual []*Flag
formal map[NormalizedName]*Flag
orderedFormal []*Flag
sortedFormal []*Flag
shorthands map[byte]*Flag
args []string // arguments after flags
argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no --
exitOnError bool // does the program exit if there's an error?
errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor
interspersed bool // allow interspersed option/non-option args
@@ -156,7 +163,7 @@ type Flag struct {
Value Value // value as set
DefValue string // default value (as text); for usage message
Changed bool // If the user set the value (or if left to default)
NoOptDefVal string //default value (as text); if the flag is on the command line without any options
NoOptDefVal string // default value (as text); if the flag is on the command line without any options
Deprecated string // If this flag is deprecated, this string is the new or now thing to use
Hidden bool // used by cobra.Command to allow flags to be hidden from help/usage text
ShorthandDeprecated string // If the shorthand of this flag is deprecated, this string is the new or now thing to use
@@ -194,11 +201,13 @@ func sortFlags(flags map[NormalizedName]*Flag) []*Flag {
// "--getUrl" which may also be translated to "geturl" and everything will work.
func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) {
f.normalizeNameFunc = n
for k, v := range f.formal {
delete(f.formal, k)
nname := f.normalizeFlagName(string(k))
f.formal[nname] = v
f.sortedFormal = f.sortedFormal[:0]
for k, v := range f.orderedFormal {
delete(f.formal, NormalizedName(v.Name))
nname := f.normalizeFlagName(v.Name)
v.Name = string(nname)
f.formal[nname] = v
f.orderedFormal[k] = v
}
}
@@ -229,10 +238,25 @@ func (f *FlagSet) SetOutput(output io.Writer) {
f.output = output
}
// VisitAll visits the flags in lexicographical order, calling fn for each.
// VisitAll visits the flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits all flags, even those not set.
func (f *FlagSet) VisitAll(fn func(*Flag)) {
for _, flag := range sortFlags(f.formal) {
if len(f.formal) == 0 {
return
}
var flags []*Flag
if f.SortFlags {
if len(f.formal) != len(f.sortedFormal) {
f.sortedFormal = sortFlags(f.formal)
}
flags = f.sortedFormal
} else {
flags = f.orderedFormal
}
for _, flag := range flags {
fn(flag)
}
}
@@ -253,22 +277,39 @@ func (f *FlagSet) HasAvailableFlags() bool {
return false
}
// VisitAll visits the command-line flags in lexicographical order, calling
// fn for each. It visits all flags, even those not set.
// VisitAll visits the command-line flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits all flags, even those not set.
func VisitAll(fn func(*Flag)) {
CommandLine.VisitAll(fn)
}
// Visit visits the flags in lexicographical order, calling fn for each.
// Visit visits the flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits only those flags that have been set.
func (f *FlagSet) Visit(fn func(*Flag)) {
for _, flag := range sortFlags(f.actual) {
if len(f.actual) == 0 {
return
}
var flags []*Flag
if f.SortFlags {
if len(f.actual) != len(f.sortedActual) {
f.sortedActual = sortFlags(f.actual)
}
flags = f.sortedActual
} else {
flags = f.orderedActual
}
for _, flag := range flags {
fn(flag)
}
}
// Visit visits the command-line flags in lexicographical order, calling fn
// for each. It visits only those flags that have been set.
// Visit visits the command-line flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits only those flags that have been set.
func Visit(fn func(*Flag)) {
CommandLine.Visit(fn)
}
@@ -278,6 +319,22 @@ func (f *FlagSet) Lookup(name string) *Flag {
return f.lookup(f.normalizeFlagName(name))
}
// ShorthandLookup returns the Flag structure of the short handed flag,
// returning nil if none exists.
// It panics, if len(name) > 1.
func (f *FlagSet) ShorthandLookup(name string) *Flag {
if name == "" {
return nil
}
if len(name) > 1 {
msg := fmt.Sprintf("can not look up shorthand which is more than one ASCII character: %q", name)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
c := name[0]
return f.shorthands[c]
}
// lookup returns the Flag structure of the named flag, returning nil if none exists.
func (f *FlagSet) lookup(name NormalizedName) *Flag {
return f.formal[name]
@@ -319,7 +376,7 @@ func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error {
if flag == nil {
return fmt.Errorf("flag %q does not exist", name)
}
if len(usageMessage) == 0 {
if usageMessage == "" {
return fmt.Errorf("deprecated message for flag %q must be set", name)
}
flag.Deprecated = usageMessage
@@ -334,7 +391,7 @@ func (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) erro
if flag == nil {
return fmt.Errorf("flag %q does not exist", name)
}
if len(usageMessage) == 0 {
if usageMessage == "" {
return fmt.Errorf("deprecated message for flag %q must be set", name)
}
flag.ShorthandDeprecated = usageMessage
@@ -358,6 +415,12 @@ func Lookup(name string) *Flag {
return CommandLine.Lookup(name)
}
// ShorthandLookup returns the Flag structure of the short handed flag,
// returning nil if none exists.
func ShorthandLookup(name string) *Flag {
return CommandLine.ShorthandLookup(name)
}
// Set sets the value of the named flag.
func (f *FlagSet) Set(name, value string) error {
normalName := f.normalizeFlagName(name)
@@ -365,17 +428,28 @@ func (f *FlagSet) Set(name, value string) error {
if !ok {
return fmt.Errorf("no such flag -%v", name)
}
err := flag.Value.Set(value)
if err != nil {
return err
var flagName string
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
flagName = fmt.Sprintf("-%s, --%s", flag.Shorthand, flag.Name)
} else {
flagName = fmt.Sprintf("--%s", flag.Name)
}
return fmt.Errorf("invalid argument %q for %q flag: %v", value, flagName, err)
}
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
}
f.actual[normalName] = flag
f.orderedActual = append(f.orderedActual, flag)
flag.Changed = true
if len(flag.Deprecated) > 0 {
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
if flag.Deprecated != "" {
fmt.Fprintf(f.out(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
}
return nil
}
@@ -557,28 +631,28 @@ func wrap(i, w int, s string) string {
// for all flags in the FlagSet. Wrapped to `cols` columns (0 for no
// wrapping)
func (f *FlagSet) FlagUsagesWrapped(cols int) string {
x := new(bytes.Buffer)
buf := new(bytes.Buffer)
lines := make([]string, 0, len(f.formal))
maxlen := 0
f.VisitAll(func(flag *Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden {
if flag.Deprecated != "" || flag.Hidden {
return
}
line := ""
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
} else {
line = fmt.Sprintf(" --%s", flag.Name)
}
varname, usage := UnquoteUsage(flag)
if len(varname) > 0 {
if varname != "" {
line += " " + varname
}
if len(flag.NoOptDefVal) > 0 {
if flag.NoOptDefVal != "" {
switch flag.Value.Type() {
case "string":
line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
@@ -601,7 +675,7 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
line += usage
if !flag.defaultIsZeroValue() {
if flag.Value.Type() == "string" {
line += fmt.Sprintf(" (default \"%s\")", flag.DefValue)
line += fmt.Sprintf(" (default %q)", flag.DefValue)
} else {
line += fmt.Sprintf(" (default %s)", flag.DefValue)
}
@@ -614,10 +688,10 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
sidx := strings.Index(line, "\x00")
spacing := strings.Repeat(" ", maxlen-sidx)
// maxlen + 2 comes from + 1 for the \x00 and + 1 for the (deliberate) off-by-one in maxlen-sidx
fmt.Fprintln(x, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
fmt.Fprintln(buf, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
}
return x.String()
return buf.String()
}
// FlagUsages returns a string containing the usage information for all flags in
@@ -714,11 +788,10 @@ func (f *FlagSet) VarP(value Value, name, shorthand, usage string) {
// AddFlag will add the flag to the FlagSet
func (f *FlagSet) AddFlag(flag *Flag) {
// Call normalizeFlagName function only once
normalizedFlagName := f.normalizeFlagName(flag.Name)
_, alreadythere := f.formal[normalizedFlagName]
if alreadythere {
_, alreadyThere := f.formal[normalizedFlagName]
if alreadyThere {
msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name)
fmt.Fprintln(f.out(), msg)
panic(msg) // Happens only if flags are declared with identical names
@@ -729,28 +802,31 @@ func (f *FlagSet) AddFlag(flag *Flag) {
flag.Name = string(normalizedFlagName)
f.formal[normalizedFlagName] = flag
f.orderedFormal = append(f.orderedFormal, flag)
if len(flag.Shorthand) == 0 {
if flag.Shorthand == "" {
return
}
if len(flag.Shorthand) > 1 {
fmt.Fprintf(f.out(), "%s shorthand more than ASCII character: %s\n", f.name, flag.Shorthand)
panic("shorthand is more than one character")
msg := fmt.Sprintf("%q shorthand is more than one ASCII character", flag.Shorthand)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
if f.shorthands == nil {
f.shorthands = make(map[byte]*Flag)
}
c := flag.Shorthand[0]
old, alreadythere := f.shorthands[c]
if alreadythere {
fmt.Fprintf(f.out(), "%s shorthand reused: %q for %s already used for %s\n", f.name, c, flag.Name, old.Name)
panic("shorthand redefinition")
used, alreadyThere := f.shorthands[c]
if alreadyThere {
msg := fmt.Sprintf("unable to redefine %q shorthand in %q flagset: it's already used for %q flag", c, f.name, used.Name)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
f.shorthands[c] = flag
}
// AddFlagSet adds one FlagSet to another. If a flag is already present in f
// the flag from newSet will be ignored
// the flag from newSet will be ignored.
func (f *FlagSet) AddFlagSet(newSet *FlagSet) {
if newSet == nil {
return
@@ -798,34 +874,6 @@ func (f *FlagSet) usage() {
}
}
func (f *FlagSet) setFlag(flag *Flag, value string, origArg string) error {
if err := flag.Value.Set(value); err != nil {
return f.failf("invalid argument %q for %s: %v", value, origArg, err)
}
// mark as visited for Visit()
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
}
f.actual[f.normalizeFlagName(flag.Name)] = flag
flag.Changed = true
if len(flag.Deprecated) > 0 {
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
}
if len(flag.ShorthandDeprecated) > 0 && containsShorthand(origArg, flag.Shorthand) {
fmt.Fprintf(os.Stderr, "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
}
return nil
}
func containsShorthand(arg, shorthand string) bool {
// filter out flags --<flag_name>
if strings.HasPrefix(arg, "-") {
return false
}
arg = strings.SplitN(arg, "=", 2)[0]
return strings.Contains(arg, shorthand)
}
func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) {
a = args
name := s[2:]
@@ -833,10 +881,11 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
err = f.failf("bad flag syntax: %s", s)
return
}
split := strings.SplitN(name, "=", 2)
name = split[0]
flag, alreadythere := f.formal[f.normalizeFlagName(name)]
if !alreadythere {
flag, exists := f.formal[f.normalizeFlagName(name)]
if !exists {
if name == "help" { // special case for nice help message.
f.usage()
return a, ErrHelp
@@ -844,11 +893,12 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
err = f.failf("unknown flag: --%s", name)
return
}
var value string
if len(split) == 2 {
// '--flag=arg'
value = split[1]
} else if len(flag.NoOptDefVal) > 0 {
} else if flag.NoOptDefVal != "" {
// '--flag' (arg was optional)
value = flag.NoOptDefVal
} else if len(a) > 0 {
@@ -860,7 +910,8 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
err = f.failf("flag needs an argument: %s", s)
return
}
err = fn(flag, value, s)
err = fn(flag, value)
return
}
@@ -868,38 +919,49 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse
if strings.HasPrefix(shorthands, "test.") {
return
}
outArgs = args
outShorts = shorthands[1:]
c := shorthands[0]
flag, alreadythere := f.shorthands[c]
if !alreadythere {
flag, exists := f.shorthands[c]
if !exists {
if c == 'h' { // special case for nice help message.
f.usage()
err = ErrHelp
return
}
//TODO continue on error
err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
return
}
var value string
if len(shorthands) > 2 && shorthands[1] == '=' {
// '-f=arg'
value = shorthands[2:]
outShorts = ""
} else if len(flag.NoOptDefVal) > 0 {
} else if flag.NoOptDefVal != "" {
// '-f' (arg was optional)
value = flag.NoOptDefVal
} else if len(shorthands) > 1 {
// '-farg'
value = shorthands[1:]
outShorts = ""
} else if len(args) > 0 {
// '-f arg'
value = args[0]
outArgs = args[1:]
} else {
// '-f' (arg was required)
err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return
}
err = fn(flag, value, shorthands)
if flag.ShorthandDeprecated != "" {
fmt.Fprintf(f.out(), "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
}
err = fn(flag, value)
return
}
@@ -907,6 +969,7 @@ func (f *FlagSet) parseShortArg(s string, args []string, fn parseFunc) (a []stri
a = args
shorthands := s[1:]
// "shorthands" can be a series of shorthand letters of flags (e.g. "-vvv").
for len(shorthands) > 0 {
shorthands, a, err = f.parseSingleShortArg(shorthands, args, fn)
if err != nil {
@@ -954,13 +1017,18 @@ func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) {
// The return value will be ErrHelp if -help was set but not defined.
func (f *FlagSet) Parse(arguments []string) error {
f.parsed = true
f.args = make([]string, 0, len(arguments))
assign := func(flag *Flag, value, origArg string) error {
return f.setFlag(flag, value, origArg)
if len(arguments) < 0 {
return nil
}
err := f.parseArgs(arguments, assign)
f.args = make([]string, 0, len(arguments))
set := func(flag *Flag, value string) error {
return f.Set(flag.Name, value)
}
err := f.parseArgs(arguments, set)
if err != nil {
switch f.errorHandling {
case ContinueOnError:
@@ -974,7 +1042,7 @@ func (f *FlagSet) Parse(arguments []string) error {
return nil
}
type parseFunc func(flag *Flag, value, origArg string) error
type parseFunc func(flag *Flag, value string) error
// ParseAll parses flag definitions from the argument list, which should not
// include the command name. The arguments for fn are flag and value. Must be
@@ -985,11 +1053,7 @@ func (f *FlagSet) ParseAll(arguments []string, fn func(flag *Flag, value string)
f.parsed = true
f.args = make([]string, 0, len(arguments))
assign := func(flag *Flag, value, origArg string) error {
return fn(flag, value)
}
err := f.parseArgs(arguments, assign)
err := f.parseArgs(arguments, fn)
if err != nil {
switch f.errorHandling {
case ContinueOnError:
@@ -1036,14 +1100,15 @@ func Parsed() bool {
// CommandLine is the default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// NewFlagSet returns a new, empty flag set with the specified name and
// error handling property.
// NewFlagSet returns a new, empty flag set with the specified name,
// error handling property and SortFlags set to true.
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
argsLenAtDash: -1,
interspersed: true,
SortFlags: true,
}
return f
}