vendor dependencies
This commit is contained in:
parent
ead4eceb48
commit
102feb4b94
|
@ -123,3 +123,8 @@ f6c17b524822278a87e3b3bd809fec33b51f5b46 github.com/emirpasic/gods/utils
|
||||||
411e09b969b1170a9f0c467558eb4c4c110d9c77 google.golang.org/genproto/googleapis/rpc/status
|
411e09b969b1170a9f0c467558eb4c4c110d9c77 google.golang.org/genproto/googleapis/rpc/status
|
||||||
0eb507a2ca07f13baf499f89d66cc566bf644643 (dirty) google.golang.org/grpc/codes
|
0eb507a2ca07f13baf499f89d66cc566bf644643 (dirty) google.golang.org/grpc/codes
|
||||||
0eb507a2ca07f13baf499f89d66cc566bf644643 (dirty) google.golang.org/grpc/status
|
0eb507a2ca07f13baf499f89d66cc566bf644643 (dirty) google.golang.org/grpc/status
|
||||||
|
a0175ee3bccc567396460bf5acd36800cb10c49c github.com/alecthomas/template
|
||||||
|
a0175ee3bccc567396460bf5acd36800cb10c49c github.com/alecthomas/template/parse
|
||||||
|
2efee857e7cfd4f3d0138cc3cbb1b4966962b93a github.com/alecthomas/units
|
||||||
|
3acf1b3de25d89c7688c63bb45f6b07f566555ec github.com/dickeyxxx/netrc
|
||||||
|
7f0871f2e17818990e4eed73f9b5c2f429501228 gopkg.in/alecthomas/kingpin.v2
|
||||||
|
|
|
@ -0,0 +1,406 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package template implements data-driven templates for generating textual output.
|
||||||
|
|
||||||
|
To generate HTML output, see package html/template, which has the same interface
|
||||||
|
as this package but automatically secures HTML output against certain attacks.
|
||||||
|
|
||||||
|
Templates are executed by applying them to a data structure. Annotations in the
|
||||||
|
template refer to elements of the data structure (typically a field of a struct
|
||||||
|
or a key in a map) to control execution and derive values to be displayed.
|
||||||
|
Execution of the template walks the structure and sets the cursor, represented
|
||||||
|
by a period '.' and called "dot", to the value at the current location in the
|
||||||
|
structure as execution proceeds.
|
||||||
|
|
||||||
|
The input text for a template is UTF-8-encoded text in any format.
|
||||||
|
"Actions"--data evaluations or control structures--are delimited by
|
||||||
|
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||||
|
Actions may not span newlines, although comments can.
|
||||||
|
|
||||||
|
Once parsed, a template may be executed safely in parallel.
|
||||||
|
|
||||||
|
Here is a trivial example that prints "17 items are made of wool".
|
||||||
|
|
||||||
|
type Inventory struct {
|
||||||
|
Material string
|
||||||
|
Count uint
|
||||||
|
}
|
||||||
|
sweaters := Inventory{"wool", 17}
|
||||||
|
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
err = tmpl.Execute(os.Stdout, sweaters)
|
||||||
|
if err != nil { panic(err) }
|
||||||
|
|
||||||
|
More intricate examples appear below.
|
||||||
|
|
||||||
|
Actions
|
||||||
|
|
||||||
|
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||||
|
data, defined in detail below.
|
||||||
|
|
||||||
|
*/
|
||||||
|
// {{/* a comment */}}
|
||||||
|
// A comment; discarded. May contain newlines.
|
||||||
|
// Comments do not nest and must start and end at the
|
||||||
|
// delimiters, as shown here.
|
||||||
|
/*
|
||||||
|
|
||||||
|
{{pipeline}}
|
||||||
|
The default textual representation of the value of the pipeline
|
||||||
|
is copied to the output.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{end}}
|
||||||
|
If the value of the pipeline is empty, no output is generated;
|
||||||
|
otherwise, T1 is executed. The empty values are false, 0, any
|
||||||
|
nil pointer or interface value, and any array, slice, map, or
|
||||||
|
string of length zero.
|
||||||
|
Dot is unaffected.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
If the value of the pipeline is empty, T0 is executed;
|
||||||
|
otherwise, T1 is executed. Dot is unaffected.
|
||||||
|
|
||||||
|
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||||
|
To simplify the appearance of if-else chains, the else action
|
||||||
|
of an if may include another if directly; the effect is exactly
|
||||||
|
the same as writing
|
||||||
|
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||||
|
|
||||||
|
{{range pipeline}} T1 {{end}}
|
||||||
|
The value of the pipeline must be an array, slice, map, or channel.
|
||||||
|
If the value of the pipeline has length zero, nothing is output;
|
||||||
|
otherwise, dot is set to the successive elements of the array,
|
||||||
|
slice, or map and T1 is executed. If the value is a map and the
|
||||||
|
keys are of basic type with a defined order ("comparable"), the
|
||||||
|
elements will be visited in sorted key order.
|
||||||
|
|
||||||
|
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
The value of the pipeline must be an array, slice, map, or channel.
|
||||||
|
If the value of the pipeline has length zero, dot is unaffected and
|
||||||
|
T0 is executed; otherwise, dot is set to the successive elements
|
||||||
|
of the array, slice, or map and T1 is executed.
|
||||||
|
|
||||||
|
{{template "name"}}
|
||||||
|
The template with the specified name is executed with nil data.
|
||||||
|
|
||||||
|
{{template "name" pipeline}}
|
||||||
|
The template with the specified name is executed with dot set
|
||||||
|
to the value of the pipeline.
|
||||||
|
|
||||||
|
{{with pipeline}} T1 {{end}}
|
||||||
|
If the value of the pipeline is empty, no output is generated;
|
||||||
|
otherwise, dot is set to the value of the pipeline and T1 is
|
||||||
|
executed.
|
||||||
|
|
||||||
|
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||||
|
If the value of the pipeline is empty, dot is unaffected and T0
|
||||||
|
is executed; otherwise, dot is set to the value of the pipeline
|
||||||
|
and T1 is executed.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
|
||||||
|
An argument is a simple value, denoted by one of the following.
|
||||||
|
|
||||||
|
- A boolean, string, character, integer, floating-point, imaginary
|
||||||
|
or complex constant in Go syntax. These behave like Go's untyped
|
||||||
|
constants, although raw strings may not span newlines.
|
||||||
|
- The keyword nil, representing an untyped Go nil.
|
||||||
|
- The character '.' (period):
|
||||||
|
.
|
||||||
|
The result is the value of dot.
|
||||||
|
- A variable name, which is a (possibly empty) alphanumeric string
|
||||||
|
preceded by a dollar sign, such as
|
||||||
|
$piOver2
|
||||||
|
or
|
||||||
|
$
|
||||||
|
The result is the value of the variable.
|
||||||
|
Variables are described below.
|
||||||
|
- The name of a field of the data, which must be a struct, preceded
|
||||||
|
by a period, such as
|
||||||
|
.Field
|
||||||
|
The result is the value of the field. Field invocations may be
|
||||||
|
chained:
|
||||||
|
.Field1.Field2
|
||||||
|
Fields can also be evaluated on variables, including chaining:
|
||||||
|
$x.Field1.Field2
|
||||||
|
- The name of a key of the data, which must be a map, preceded
|
||||||
|
by a period, such as
|
||||||
|
.Key
|
||||||
|
The result is the map element value indexed by the key.
|
||||||
|
Key invocations may be chained and combined with fields to any
|
||||||
|
depth:
|
||||||
|
.Field1.Key1.Field2.Key2
|
||||||
|
Although the key must be an alphanumeric identifier, unlike with
|
||||||
|
field names they do not need to start with an upper case letter.
|
||||||
|
Keys can also be evaluated on variables, including chaining:
|
||||||
|
$x.key1.key2
|
||||||
|
- The name of a niladic method of the data, preceded by a period,
|
||||||
|
such as
|
||||||
|
.Method
|
||||||
|
The result is the value of invoking the method with dot as the
|
||||||
|
receiver, dot.Method(). Such a method must have one return value (of
|
||||||
|
any type) or two return values, the second of which is an error.
|
||||||
|
If it has two and the returned error is non-nil, execution terminates
|
||||||
|
and an error is returned to the caller as the value of Execute.
|
||||||
|
Method invocations may be chained and combined with fields and keys
|
||||||
|
to any depth:
|
||||||
|
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||||
|
Methods can also be evaluated on variables, including chaining:
|
||||||
|
$x.Method1.Field
|
||||||
|
- The name of a niladic function, such as
|
||||||
|
fun
|
||||||
|
The result is the value of invoking the function, fun(). The return
|
||||||
|
types and values behave as in methods. Functions and function
|
||||||
|
names are described below.
|
||||||
|
- A parenthesized instance of one the above, for grouping. The result
|
||||||
|
may be accessed by a field or map key invocation.
|
||||||
|
print (.F1 arg1) (.F2 arg2)
|
||||||
|
(.StructValuedMethod "arg").Field
|
||||||
|
|
||||||
|
Arguments may evaluate to any type; if they are pointers the implementation
|
||||||
|
automatically indirects to the base type when required.
|
||||||
|
If an evaluation yields a function value, such as a function-valued
|
||||||
|
field of a struct, the function is not invoked automatically, but it
|
||||||
|
can be used as a truth value for an if action and the like. To invoke
|
||||||
|
it, use the call function, defined below.
|
||||||
|
|
||||||
|
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||||
|
value (argument) or a function or method call, possibly with multiple arguments:
|
||||||
|
|
||||||
|
Argument
|
||||||
|
The result is the value of evaluating the argument.
|
||||||
|
.Method [Argument...]
|
||||||
|
The method can be alone or the last element of a chain but,
|
||||||
|
unlike methods in the middle of a chain, it can take arguments.
|
||||||
|
The result is the value of calling the method with the
|
||||||
|
arguments:
|
||||||
|
dot.Method(Argument1, etc.)
|
||||||
|
functionName [Argument...]
|
||||||
|
The result is the value of calling the function associated
|
||||||
|
with the name:
|
||||||
|
function(Argument1, etc.)
|
||||||
|
Functions and function names are described below.
|
||||||
|
|
||||||
|
Pipelines
|
||||||
|
|
||||||
|
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||||
|
characters '|'. In a chained pipeline, the result of the each command is
|
||||||
|
passed as the last argument of the following command. The output of the final
|
||||||
|
command in the pipeline is the value of the pipeline.
|
||||||
|
|
||||||
|
The output of a command will be either one value or two values, the second of
|
||||||
|
which has type error. If that second value is present and evaluates to
|
||||||
|
non-nil, execution terminates and the error is returned to the caller of
|
||||||
|
Execute.
|
||||||
|
|
||||||
|
Variables
|
||||||
|
|
||||||
|
A pipeline inside an action may initialize a variable to capture the result.
|
||||||
|
The initialization has syntax
|
||||||
|
|
||||||
|
$variable := pipeline
|
||||||
|
|
||||||
|
where $variable is the name of the variable. An action that declares a
|
||||||
|
variable produces no output.
|
||||||
|
|
||||||
|
If a "range" action initializes a variable, the variable is set to the
|
||||||
|
successive elements of the iteration. Also, a "range" may declare two
|
||||||
|
variables, separated by a comma:
|
||||||
|
|
||||||
|
range $index, $element := pipeline
|
||||||
|
|
||||||
|
in which case $index and $element are set to the successive values of the
|
||||||
|
array/slice index or map key and element, respectively. Note that if there is
|
||||||
|
only one variable, it is assigned the element; this is opposite to the
|
||||||
|
convention in Go range clauses.
|
||||||
|
|
||||||
|
A variable's scope extends to the "end" action of the control structure ("if",
|
||||||
|
"with", or "range") in which it is declared, or to the end of the template if
|
||||||
|
there is no such control structure. A template invocation does not inherit
|
||||||
|
variables from the point of its invocation.
|
||||||
|
|
||||||
|
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||||
|
to the starting value of dot.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
Here are some example one-line templates demonstrating pipelines and variables.
|
||||||
|
All produce the quoted word "output":
|
||||||
|
|
||||||
|
{{"\"output\""}}
|
||||||
|
A string constant.
|
||||||
|
{{`"output"`}}
|
||||||
|
A raw string constant.
|
||||||
|
{{printf "%q" "output"}}
|
||||||
|
A function call.
|
||||||
|
{{"output" | printf "%q"}}
|
||||||
|
A function call whose final argument comes from the previous
|
||||||
|
command.
|
||||||
|
{{printf "%q" (print "out" "put")}}
|
||||||
|
A parenthesized argument.
|
||||||
|
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||||
|
A more elaborate call.
|
||||||
|
{{"output" | printf "%s" | printf "%q"}}
|
||||||
|
A longer chain.
|
||||||
|
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||||
|
A with action using dot.
|
||||||
|
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||||
|
A with action that creates and uses a variable.
|
||||||
|
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||||
|
A with action that uses the variable in another action.
|
||||||
|
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||||
|
The same, but pipelined.
|
||||||
|
|
||||||
|
Functions
|
||||||
|
|
||||||
|
During execution functions are found in two function maps: first in the
|
||||||
|
template, then in the global function map. By default, no functions are defined
|
||||||
|
in the template but the Funcs method can be used to add them.
|
||||||
|
|
||||||
|
Predefined global functions are named as follows.
|
||||||
|
|
||||||
|
and
|
||||||
|
Returns the boolean AND of its arguments by returning the
|
||||||
|
first empty argument or the last argument, that is,
|
||||||
|
"and x y" behaves as "if x then y else x". All the
|
||||||
|
arguments are evaluated.
|
||||||
|
call
|
||||||
|
Returns the result of calling the first argument, which
|
||||||
|
must be a function, with the remaining arguments as parameters.
|
||||||
|
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||||
|
Y is a func-valued field, map entry, or the like.
|
||||||
|
The first argument must be the result of an evaluation
|
||||||
|
that yields a value of function type (as distinct from
|
||||||
|
a predefined function such as print). The function must
|
||||||
|
return either one or two result values, the second of which
|
||||||
|
is of type error. If the arguments don't match the function
|
||||||
|
or the returned error value is non-nil, execution stops.
|
||||||
|
html
|
||||||
|
Returns the escaped HTML equivalent of the textual
|
||||||
|
representation of its arguments.
|
||||||
|
index
|
||||||
|
Returns the result of indexing its first argument by the
|
||||||
|
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||||
|
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||||
|
js
|
||||||
|
Returns the escaped JavaScript equivalent of the textual
|
||||||
|
representation of its arguments.
|
||||||
|
len
|
||||||
|
Returns the integer length of its argument.
|
||||||
|
not
|
||||||
|
Returns the boolean negation of its single argument.
|
||||||
|
or
|
||||||
|
Returns the boolean OR of its arguments by returning the
|
||||||
|
first non-empty argument or the last argument, that is,
|
||||||
|
"or x y" behaves as "if x then x else y". All the
|
||||||
|
arguments are evaluated.
|
||||||
|
print
|
||||||
|
An alias for fmt.Sprint
|
||||||
|
printf
|
||||||
|
An alias for fmt.Sprintf
|
||||||
|
println
|
||||||
|
An alias for fmt.Sprintln
|
||||||
|
urlquery
|
||||||
|
Returns the escaped value of the textual representation of
|
||||||
|
its arguments in a form suitable for embedding in a URL query.
|
||||||
|
|
||||||
|
The boolean functions take any zero value to be false and a non-zero
|
||||||
|
value to be true.
|
||||||
|
|
||||||
|
There is also a set of binary comparison operators defined as
|
||||||
|
functions:
|
||||||
|
|
||||||
|
eq
|
||||||
|
Returns the boolean truth of arg1 == arg2
|
||||||
|
ne
|
||||||
|
Returns the boolean truth of arg1 != arg2
|
||||||
|
lt
|
||||||
|
Returns the boolean truth of arg1 < arg2
|
||||||
|
le
|
||||||
|
Returns the boolean truth of arg1 <= arg2
|
||||||
|
gt
|
||||||
|
Returns the boolean truth of arg1 > arg2
|
||||||
|
ge
|
||||||
|
Returns the boolean truth of arg1 >= arg2
|
||||||
|
|
||||||
|
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||||
|
arguments and compares the second and subsequent to the first,
|
||||||
|
returning in effect
|
||||||
|
|
||||||
|
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||||
|
|
||||||
|
(Unlike with || in Go, however, eq is a function call and all the
|
||||||
|
arguments will be evaluated.)
|
||||||
|
|
||||||
|
The comparison functions work on basic types only (or named basic
|
||||||
|
types, such as "type Celsius float32"). They implement the Go rules
|
||||||
|
for comparison of values, except that size and exact type are
|
||||||
|
ignored, so any integer value, signed or unsigned, may be compared
|
||||||
|
with any other integer value. (The arithmetic value is compared,
|
||||||
|
not the bit pattern, so all negative integers are less than all
|
||||||
|
unsigned integers.) However, as usual, one may not compare an int
|
||||||
|
with a float32 and so on.
|
||||||
|
|
||||||
|
Associated templates
|
||||||
|
|
||||||
|
Each template is named by a string specified when it is created. Also, each
|
||||||
|
template is associated with zero or more other templates that it may invoke by
|
||||||
|
name; such associations are transitive and form a name space of templates.
|
||||||
|
|
||||||
|
A template may use a template invocation to instantiate another associated
|
||||||
|
template; see the explanation of the "template" action above. The name must be
|
||||||
|
that of a template associated with the template that contains the invocation.
|
||||||
|
|
||||||
|
Nested template definitions
|
||||||
|
|
||||||
|
When parsing a template, another template may be defined and associated with the
|
||||||
|
template being parsed. Template definitions must appear at the top level of the
|
||||||
|
template, much like global variables in a Go program.
|
||||||
|
|
||||||
|
The syntax of such definitions is to surround each template declaration with a
|
||||||
|
"define" and "end" action.
|
||||||
|
|
||||||
|
The define action names the template being created by providing a string
|
||||||
|
constant. Here is a simple example:
|
||||||
|
|
||||||
|
`{{define "T1"}}ONE{{end}}
|
||||||
|
{{define "T2"}}TWO{{end}}
|
||||||
|
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||||
|
{{template "T3"}}`
|
||||||
|
|
||||||
|
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||||
|
when it is executed. Finally it invokes T3. If executed this template will
|
||||||
|
produce the text
|
||||||
|
|
||||||
|
ONE TWO
|
||||||
|
|
||||||
|
By construction, a template may reside in only one association. If it's
|
||||||
|
necessary to have a template addressable from multiple associations, the
|
||||||
|
template definition must be parsed multiple times to create distinct *Template
|
||||||
|
values, or must be copied with the Clone or AddParseTree method.
|
||||||
|
|
||||||
|
Parse may be called multiple times to assemble the various associated templates;
|
||||||
|
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||||
|
related templates stored in files.
|
||||||
|
|
||||||
|
A template may be executed directly or through ExecuteTemplate, which executes
|
||||||
|
an associated template identified by name. To invoke our example above, we
|
||||||
|
might write,
|
||||||
|
|
||||||
|
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execution failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
or to invoke a particular template explicitly by name,
|
||||||
|
|
||||||
|
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("execution failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
package template
|
|
@ -0,0 +1,845 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// state represents the state of an execution. It's not part of the
|
||||||
|
// template so that multiple executions of the same template
|
||||||
|
// can execute in parallel.
|
||||||
|
type state struct {
|
||||||
|
tmpl *Template
|
||||||
|
wr io.Writer
|
||||||
|
node parse.Node // current node, for errors
|
||||||
|
vars []variable // push-down stack of variable values.
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||||
|
type variable struct {
|
||||||
|
name string
|
||||||
|
value reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// push pushes a new variable on the stack.
|
||||||
|
func (s *state) push(name string, value reflect.Value) {
|
||||||
|
s.vars = append(s.vars, variable{name, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark returns the length of the variable stack.
|
||||||
|
func (s *state) mark() int {
|
||||||
|
return len(s.vars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop pops the variable stack up to the mark.
|
||||||
|
func (s *state) pop(mark int) {
|
||||||
|
s.vars = s.vars[0:mark]
|
||||||
|
}
|
||||||
|
|
||||||
|
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||||
|
func (s *state) setVar(n int, value reflect.Value) {
|
||||||
|
s.vars[len(s.vars)-n].value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// varValue returns the value of the named variable.
|
||||||
|
func (s *state) varValue(name string) reflect.Value {
|
||||||
|
for i := s.mark() - 1; i >= 0; i-- {
|
||||||
|
if s.vars[i].name == name {
|
||||||
|
return s.vars[i].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.errorf("undefined variable: %s", name)
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
var zero reflect.Value
|
||||||
|
|
||||||
|
// at marks the state to be on node n, for error reporting.
|
||||||
|
func (s *state) at(node parse.Node) {
|
||||||
|
s.node = node
|
||||||
|
}
|
||||||
|
|
||||||
|
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||||
|
// so it can be used safely inside a Printf format string.
|
||||||
|
func doublePercent(str string) string {
|
||||||
|
if strings.Contains(str, "%") {
|
||||||
|
str = strings.Replace(str, "%", "%%", -1)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf formats the error and terminates processing.
|
||||||
|
func (s *state) errorf(format string, args ...interface{}) {
|
||||||
|
name := doublePercent(s.tmpl.Name())
|
||||||
|
if s.node == nil {
|
||||||
|
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||||
|
} else {
|
||||||
|
location, context := s.tmpl.ErrorContext(s.node)
|
||||||
|
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// errRecover is the handler that turns panics into returns from the top
|
||||||
|
// level of Parse.
|
||||||
|
func errRecover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
switch err := e.(type) {
|
||||||
|
case runtime.Error:
|
||||||
|
panic(e)
|
||||||
|
case error:
|
||||||
|
*errp = err
|
||||||
|
default:
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteTemplate applies the template associated with t that has the given name
|
||||||
|
// to the specified data object and writes the output to wr.
|
||||||
|
// If an error occurs executing the template or writing its output,
|
||||||
|
// execution stops, but partial results may already have been written to
|
||||||
|
// the output writer.
|
||||||
|
// A template may be executed safely in parallel.
|
||||||
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||||
|
tmpl := t.tmpl[name]
|
||||||
|
if tmpl == nil {
|
||||||
|
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||||
|
}
|
||||||
|
return tmpl.Execute(wr, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute applies a parsed template to the specified data object,
|
||||||
|
// and writes the output to wr.
|
||||||
|
// If an error occurs executing the template or writing its output,
|
||||||
|
// execution stops, but partial results may already have been written to
|
||||||
|
// the output writer.
|
||||||
|
// A template may be executed safely in parallel.
|
||||||
|
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||||
|
defer errRecover(&err)
|
||||||
|
value := reflect.ValueOf(data)
|
||||||
|
state := &state{
|
||||||
|
tmpl: t,
|
||||||
|
wr: wr,
|
||||||
|
vars: []variable{{"$", value}},
|
||||||
|
}
|
||||||
|
t.init()
|
||||||
|
if t.Tree == nil || t.Root == nil {
|
||||||
|
var b bytes.Buffer
|
||||||
|
for name, tmpl := range t.tmpl {
|
||||||
|
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, "%q", name)
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
if b.Len() > 0 {
|
||||||
|
s = "; defined templates are: " + b.String()
|
||||||
|
}
|
||||||
|
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
||||||
|
}
|
||||||
|
state.walk(value, t.Root)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk functions step through the major pieces of the template structure,
|
||||||
|
// generating output as they go.
|
||||||
|
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||||
|
s.at(node)
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *parse.ActionNode:
|
||||||
|
// Do not pop variables so they persist until next end.
|
||||||
|
// Also, if the action declares variables, don't print the result.
|
||||||
|
val := s.evalPipeline(dot, node.Pipe)
|
||||||
|
if len(node.Pipe.Decl) == 0 {
|
||||||
|
s.printValue(node, val)
|
||||||
|
}
|
||||||
|
case *parse.IfNode:
|
||||||
|
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||||
|
case *parse.ListNode:
|
||||||
|
for _, node := range node.Nodes {
|
||||||
|
s.walk(dot, node)
|
||||||
|
}
|
||||||
|
case *parse.RangeNode:
|
||||||
|
s.walkRange(dot, node)
|
||||||
|
case *parse.TemplateNode:
|
||||||
|
s.walkTemplate(dot, node)
|
||||||
|
case *parse.TextNode:
|
||||||
|
if _, err := s.wr.Write(node.Text); err != nil {
|
||||||
|
s.errorf("%s", err)
|
||||||
|
}
|
||||||
|
case *parse.WithNode:
|
||||||
|
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||||
|
default:
|
||||||
|
s.errorf("unknown node: %s", node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||||
|
// are identical in behavior except that 'with' sets dot.
|
||||||
|
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||||
|
defer s.pop(s.mark())
|
||||||
|
val := s.evalPipeline(dot, pipe)
|
||||||
|
truth, ok := isTrue(val)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("if/with can't use %v", val)
|
||||||
|
}
|
||||||
|
if truth {
|
||||||
|
if typ == parse.NodeWith {
|
||||||
|
s.walk(val, list)
|
||||||
|
} else {
|
||||||
|
s.walk(dot, list)
|
||||||
|
}
|
||||||
|
} else if elseList != nil {
|
||||||
|
s.walk(dot, elseList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||||
|
// and whether the value has a meaningful truth value.
|
||||||
|
func isTrue(val reflect.Value) (truth, ok bool) {
|
||||||
|
if !val.IsValid() {
|
||||||
|
// Something like var x interface{}, never set. It's a form of nil.
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
truth = val.Len() > 0
|
||||||
|
case reflect.Bool:
|
||||||
|
truth = val.Bool()
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
truth = val.Complex() != 0
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||||
|
truth = !val.IsNil()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
truth = val.Int() != 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
truth = val.Float() != 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
truth = val.Uint() != 0
|
||||||
|
case reflect.Struct:
|
||||||
|
truth = true // Struct values are always true.
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return truth, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||||
|
s.at(r)
|
||||||
|
defer s.pop(s.mark())
|
||||||
|
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||||
|
// mark top of stack before any variables in the body are pushed.
|
||||||
|
mark := s.mark()
|
||||||
|
oneIteration := func(index, elem reflect.Value) {
|
||||||
|
// Set top var (lexically the second if there are two) to the element.
|
||||||
|
if len(r.Pipe.Decl) > 0 {
|
||||||
|
s.setVar(1, elem)
|
||||||
|
}
|
||||||
|
// Set next var (lexically the first if there are two) to the index.
|
||||||
|
if len(r.Pipe.Decl) > 1 {
|
||||||
|
s.setVar(2, index)
|
||||||
|
}
|
||||||
|
s.walk(elem, r.List)
|
||||||
|
s.pop(mark)
|
||||||
|
}
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Map:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, key := range sortKeys(val.MapKeys()) {
|
||||||
|
oneIteration(key, val.MapIndex(key))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Chan:
|
||||||
|
if val.IsNil() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for ; ; i++ {
|
||||||
|
elem, ok := val.Recv()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
oneIteration(reflect.ValueOf(i), elem)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case reflect.Invalid:
|
||||||
|
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||||
|
default:
|
||||||
|
s.errorf("range can't iterate over %v", val)
|
||||||
|
}
|
||||||
|
if r.ElseList != nil {
|
||||||
|
s.walk(dot, r.ElseList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||||
|
s.at(t)
|
||||||
|
tmpl := s.tmpl.tmpl[t.Name]
|
||||||
|
if tmpl == nil {
|
||||||
|
s.errorf("template %q not defined", t.Name)
|
||||||
|
}
|
||||||
|
// Variables declared by the pipeline persist.
|
||||||
|
dot = s.evalPipeline(dot, t.Pipe)
|
||||||
|
newState := *s
|
||||||
|
newState.tmpl = tmpl
|
||||||
|
// No dynamic scoping: template invocations inherit no variables.
|
||||||
|
newState.vars = []variable{{"$", dot}}
|
||||||
|
newState.walk(dot, tmpl.Root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||||
|
// values from the data structure by examining fields, calling methods, and so on.
|
||||||
|
// The printing of those values happens only through walk functions.
|
||||||
|
|
||||||
|
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||||
|
// pipeline has a variable declaration, the variable will be pushed on the
|
||||||
|
// stack. Callers should therefore pop the stack after they are finished
|
||||||
|
// executing commands depending on the pipeline value.
|
||||||
|
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||||
|
if pipe == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.at(pipe)
|
||||||
|
for _, cmd := range pipe.Cmds {
|
||||||
|
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||||
|
// If the object has type interface{}, dig down one level to the thing inside.
|
||||||
|
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||||
|
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, variable := range pipe.Decl {
|
||||||
|
s.push(variable.Ident[0], value)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||||
|
if len(args) > 1 || final.IsValid() {
|
||||||
|
s.errorf("can't give argument to non-function %s", args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||||
|
firstWord := cmd.Args[0]
|
||||||
|
switch n := firstWord.(type) {
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||||
|
case *parse.ChainNode:
|
||||||
|
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
// Must be a function.
|
||||||
|
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||||
|
return s.evalPipeline(dot, n)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||||
|
}
|
||||||
|
s.at(firstWord)
|
||||||
|
s.notAFunction(cmd.Args, final)
|
||||||
|
switch word := firstWord.(type) {
|
||||||
|
case *parse.BoolNode:
|
||||||
|
return reflect.ValueOf(word.True)
|
||||||
|
case *parse.DotNode:
|
||||||
|
return dot
|
||||||
|
case *parse.NilNode:
|
||||||
|
s.errorf("nil is not a command")
|
||||||
|
case *parse.NumberNode:
|
||||||
|
return s.idealConstant(word)
|
||||||
|
case *parse.StringNode:
|
||||||
|
return reflect.ValueOf(word.Text)
|
||||||
|
}
|
||||||
|
s.errorf("can't evaluate command %q", firstWord)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// idealConstant is called to return the value of a number in a context where
|
||||||
|
// we don't know the type. In that case, the syntax of the number tells us
|
||||||
|
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||||
|
// a uint ideal constant in this situation - the value must be of int type.
|
||||||
|
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||||
|
// These are ideal constants but we don't know the type
|
||||||
|
// and we have no context. (If it was a method argument,
|
||||||
|
// we'd know what we need.) The syntax guides us to some extent.
|
||||||
|
s.at(constant)
|
||||||
|
switch {
|
||||||
|
case constant.IsComplex:
|
||||||
|
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||||
|
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||||
|
return reflect.ValueOf(constant.Float64)
|
||||||
|
case constant.IsInt:
|
||||||
|
n := int(constant.Int64)
|
||||||
|
if int64(n) != constant.Int64 {
|
||||||
|
s.errorf("%s overflows int", constant.Text)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(n)
|
||||||
|
case constant.IsUint:
|
||||||
|
s.errorf("%s overflows int", constant.Text)
|
||||||
|
}
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexConstant(s string) bool {
|
||||||
|
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(field)
|
||||||
|
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(chain)
|
||||||
|
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||||
|
pipe := s.evalArg(dot, nil, chain.Node)
|
||||||
|
if len(chain.Field) == 0 {
|
||||||
|
s.errorf("internal error: no fields in evalChainNode")
|
||||||
|
}
|
||||||
|
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||||
|
s.at(variable)
|
||||||
|
value := s.varValue(variable.Ident[0])
|
||||||
|
if len(variable.Ident) == 1 {
|
||||||
|
s.notAFunction(args, final)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||||
|
// dot is the environment in which to evaluate arguments, while
|
||||||
|
// receiver is the value being walked along the chain.
|
||||||
|
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
n := len(ident)
|
||||||
|
for i := 0; i < n-1; i++ {
|
||||||
|
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
||||||
|
}
|
||||||
|
// Now if it's a method, it gets the arguments.
|
||||||
|
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
s.at(node)
|
||||||
|
name := node.Ident
|
||||||
|
function, ok := findFunction(name, s.tmpl)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("%q is not a defined function", name)
|
||||||
|
}
|
||||||
|
return s.evalCall(dot, function, cmd, name, args, final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||||
|
// The 'final' argument represents the return value from the preceding
|
||||||
|
// value of the pipeline, if any.
|
||||||
|
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||||
|
if !receiver.IsValid() {
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
typ := receiver.Type()
|
||||||
|
receiver, _ = indirect(receiver)
|
||||||
|
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||||
|
// we see all methods of T and *T.
|
||||||
|
ptr := receiver
|
||||||
|
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
||||||
|
ptr = ptr.Addr()
|
||||||
|
}
|
||||||
|
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||||
|
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||||
|
}
|
||||||
|
hasArgs := len(args) > 1 || final.IsValid()
|
||||||
|
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
||||||
|
receiver, isNil := indirect(receiver)
|
||||||
|
if isNil {
|
||||||
|
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||||
|
}
|
||||||
|
switch receiver.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||||
|
if ok {
|
||||||
|
field := receiver.FieldByIndex(tField.Index)
|
||||||
|
if tField.PkgPath != "" { // field is unexported
|
||||||
|
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||||
|
}
|
||||||
|
// If it's a function, we must call it.
|
||||||
|
if hasArgs {
|
||||||
|
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
// If it's a map, attempt to use the field name as a key.
|
||||||
|
nameVal := reflect.ValueOf(fieldName)
|
||||||
|
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||||
|
if hasArgs {
|
||||||
|
s.errorf("%s is not a method but has arguments", fieldName)
|
||||||
|
}
|
||||||
|
return receiver.MapIndex(nameVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||||
|
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||||
|
// as the function itself.
|
||||||
|
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||||
|
if args != nil {
|
||||||
|
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||||
|
}
|
||||||
|
typ := fun.Type()
|
||||||
|
numIn := len(args)
|
||||||
|
if final.IsValid() {
|
||||||
|
numIn++
|
||||||
|
}
|
||||||
|
numFixed := len(args)
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||||
|
if numIn < numFixed {
|
||||||
|
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||||
|
}
|
||||||
|
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||||
|
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||||
|
}
|
||||||
|
if !goodFunc(typ) {
|
||||||
|
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||||
|
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||||
|
}
|
||||||
|
// Build the arg list.
|
||||||
|
argv := make([]reflect.Value, numIn)
|
||||||
|
// Args must be evaluated. Fixed args first.
|
||||||
|
i := 0
|
||||||
|
for ; i < numFixed && i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||||
|
}
|
||||||
|
// Now the ... args.
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||||
|
for ; i < len(args); i++ {
|
||||||
|
argv[i] = s.evalArg(dot, argType, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add final value if necessary.
|
||||||
|
if final.IsValid() {
|
||||||
|
t := typ.In(typ.NumIn() - 1)
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
argv[i] = s.validateType(final, t)
|
||||||
|
}
|
||||||
|
result := fun.Call(argv)
|
||||||
|
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||||
|
if len(result) == 2 && !result[1].IsNil() {
|
||||||
|
s.at(node)
|
||||||
|
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||||
|
}
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||||
|
func canBeNil(typ reflect.Type) bool {
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateType guarantees that the value is valid and assignable to the type.
|
||||||
|
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||||
|
if !value.IsValid() {
|
||||||
|
if typ == nil || canBeNil(typ) {
|
||||||
|
// An untyped nil interface{}. Accept as a proper nil value.
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
s.errorf("invalid value; expected %s", typ)
|
||||||
|
}
|
||||||
|
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||||
|
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||||
|
value = value.Elem()
|
||||||
|
if value.Type().AssignableTo(typ) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
}
|
||||||
|
// Does one dereference or indirection work? We could do more, as we
|
||||||
|
// do with method receivers, but that gets messy and method receivers
|
||||||
|
// are much more constrained, so it makes more sense there than here.
|
||||||
|
// Besides, one is almost always all you need.
|
||||||
|
switch {
|
||||||
|
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||||
|
value = value.Elem()
|
||||||
|
if !value.IsValid() {
|
||||||
|
s.errorf("dereference of nil pointer of type %s", typ)
|
||||||
|
}
|
||||||
|
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||||
|
value = value.Addr()
|
||||||
|
default:
|
||||||
|
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
switch arg := n.(type) {
|
||||||
|
case *parse.DotNode:
|
||||||
|
return s.validateType(dot, typ)
|
||||||
|
case *parse.NilNode:
|
||||||
|
if canBeNil(typ) {
|
||||||
|
return reflect.Zero(typ)
|
||||||
|
}
|
||||||
|
s.errorf("cannot assign nil to %s", typ)
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
return s.evalFunction(dot, arg, arg, nil, zero)
|
||||||
|
case *parse.ChainNode:
|
||||||
|
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
||||||
|
}
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return s.evalBool(typ, n)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return s.evalComplex(typ, n)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return s.evalFloat(typ, n)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return s.evalInteger(typ, n)
|
||||||
|
case reflect.Interface:
|
||||||
|
if typ.NumMethod() == 0 {
|
||||||
|
return s.evalEmptyInterface(dot, n)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
return s.evalString(typ, n)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return s.evalUnsignedInteger(typ, n)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.BoolNode); ok {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetBool(n.True)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected bool; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.StringNode); ok {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetString(n.Text)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected string; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetInt(n.Int64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected integer; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetUint(n.Uint64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected unsigned integer; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetFloat(n.Float64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected float; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||||
|
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||||
|
value := reflect.New(typ).Elem()
|
||||||
|
value.SetComplex(n.Complex128)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
s.errorf("expected complex; found %s", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||||
|
s.at(n)
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *parse.BoolNode:
|
||||||
|
return reflect.ValueOf(n.True)
|
||||||
|
case *parse.DotNode:
|
||||||
|
return dot
|
||||||
|
case *parse.FieldNode:
|
||||||
|
return s.evalFieldNode(dot, n, nil, zero)
|
||||||
|
case *parse.IdentifierNode:
|
||||||
|
return s.evalFunction(dot, n, n, nil, zero)
|
||||||
|
case *parse.NilNode:
|
||||||
|
// NilNode is handled in evalArg, the only place that calls here.
|
||||||
|
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||||
|
case *parse.NumberNode:
|
||||||
|
return s.idealConstant(n)
|
||||||
|
case *parse.StringNode:
|
||||||
|
return reflect.ValueOf(n.Text)
|
||||||
|
case *parse.VariableNode:
|
||||||
|
return s.evalVariableNode(dot, n, nil, zero)
|
||||||
|
case *parse.PipeNode:
|
||||||
|
return s.evalPipeline(dot, n)
|
||||||
|
}
|
||||||
|
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||||
|
panic("not reached")
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||||
|
// We indirect through pointers and empty interfaces (only) because
|
||||||
|
// non-empty interfaces have methods we might need.
|
||||||
|
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||||
|
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||||
|
if v.IsNil() {
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printValue writes the textual representation of the value to the output of
|
||||||
|
// the template.
|
||||||
|
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||||
|
s.at(n)
|
||||||
|
iface, ok := printableValue(v)
|
||||||
|
if !ok {
|
||||||
|
s.errorf("can't print %s of type %s", n, v.Type())
|
||||||
|
}
|
||||||
|
fmt.Fprint(s.wr, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printableValue returns the, possibly indirected, interface value inside v that
|
||||||
|
// is best for a call to formatted printer.
|
||||||
|
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||||
|
}
|
||||||
|
if !v.IsValid() {
|
||||||
|
return "<no value>", true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||||
|
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||||
|
v = v.Addr()
|
||||||
|
} else {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.Interface(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types to help sort the keys in a map for reproducible output.
|
||||||
|
|
||||||
|
type rvs []reflect.Value
|
||||||
|
|
||||||
|
func (x rvs) Len() int { return len(x) }
|
||||||
|
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
type rvInts struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
||||||
|
|
||||||
|
type rvUints struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
||||||
|
|
||||||
|
type rvFloats struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
||||||
|
|
||||||
|
type rvStrings struct{ rvs }
|
||||||
|
|
||||||
|
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
||||||
|
|
||||||
|
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||||
|
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||||
|
if len(v) <= 1 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
switch v[0].Kind() {
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
sort.Sort(rvFloats{v})
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
sort.Sort(rvInts{v})
|
||||||
|
case reflect.String:
|
||||||
|
sort.Sort(rvStrings{v})
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
sort.Sort(rvUints{v})
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
|
@ -0,0 +1,598 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||||
|
// Each function must have either a single return value, or two return values of
|
||||||
|
// which the second has type error. In that case, if the second (error)
|
||||||
|
// return value evaluates to non-nil during execution, execution terminates and
|
||||||
|
// Execute returns that error.
|
||||||
|
type FuncMap map[string]interface{}
|
||||||
|
|
||||||
|
var builtins = FuncMap{
|
||||||
|
"and": and,
|
||||||
|
"call": call,
|
||||||
|
"html": HTMLEscaper,
|
||||||
|
"index": index,
|
||||||
|
"js": JSEscaper,
|
||||||
|
"len": length,
|
||||||
|
"not": not,
|
||||||
|
"or": or,
|
||||||
|
"print": fmt.Sprint,
|
||||||
|
"printf": fmt.Sprintf,
|
||||||
|
"println": fmt.Sprintln,
|
||||||
|
"urlquery": URLQueryEscaper,
|
||||||
|
|
||||||
|
// Comparisons
|
||||||
|
"eq": eq, // ==
|
||||||
|
"ge": ge, // >=
|
||||||
|
"gt": gt, // >
|
||||||
|
"le": le, // <=
|
||||||
|
"lt": lt, // <
|
||||||
|
"ne": ne, // !=
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtinFuncs = createValueFuncs(builtins)
|
||||||
|
|
||||||
|
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||||
|
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||||
|
m := make(map[string]reflect.Value)
|
||||||
|
addValueFuncs(m, funcMap)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||||
|
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||||
|
for name, fn := range in {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
if v.Kind() != reflect.Func {
|
||||||
|
panic("value for " + name + " not a function")
|
||||||
|
}
|
||||||
|
if !goodFunc(v.Type()) {
|
||||||
|
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||||
|
}
|
||||||
|
out[name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||||
|
// call addValueFuncs first.
|
||||||
|
func addFuncs(out, in FuncMap) {
|
||||||
|
for name, fn := range in {
|
||||||
|
out[name] = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// goodFunc checks that the function or method has the right result signature.
|
||||||
|
func goodFunc(typ reflect.Type) bool {
|
||||||
|
// We allow functions with 1 result or 2 results where the second is an error.
|
||||||
|
switch {
|
||||||
|
case typ.NumOut() == 1:
|
||||||
|
return true
|
||||||
|
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// findFunction looks for a function in the template, and global map.
|
||||||
|
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||||
|
if tmpl != nil && tmpl.common != nil {
|
||||||
|
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||||
|
return fn, true
|
||||||
|
}
|
||||||
|
return reflect.Value{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexing.
|
||||||
|
|
||||||
|
// index returns the result of indexing its first argument by the following
|
||||||
|
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||||
|
// indexed item must be a map, slice, or array.
|
||||||
|
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
||||||
|
v := reflect.ValueOf(item)
|
||||||
|
for _, i := range indices {
|
||||||
|
index := reflect.ValueOf(i)
|
||||||
|
var isNil bool
|
||||||
|
if v, isNil = indirect(v); isNil {
|
||||||
|
return nil, fmt.Errorf("index of nil pointer")
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.String:
|
||||||
|
var x int64
|
||||||
|
switch index.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
x = index.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
x = int64(index.Uint())
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||||
|
}
|
||||||
|
if x < 0 || x >= int64(v.Len()) {
|
||||||
|
return nil, fmt.Errorf("index out of range: %d", x)
|
||||||
|
}
|
||||||
|
v = v.Index(int(x))
|
||||||
|
case reflect.Map:
|
||||||
|
if !index.IsValid() {
|
||||||
|
index = reflect.Zero(v.Type().Key())
|
||||||
|
}
|
||||||
|
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||||
|
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
||||||
|
}
|
||||||
|
if x := v.MapIndex(index); x.IsValid() {
|
||||||
|
v = x
|
||||||
|
} else {
|
||||||
|
v = reflect.Zero(v.Type().Elem())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length
|
||||||
|
|
||||||
|
// length returns the length of the item, with an error if it has no defined length.
|
||||||
|
func length(item interface{}) (int, error) {
|
||||||
|
v, isNil := indirect(reflect.ValueOf(item))
|
||||||
|
if isNil {
|
||||||
|
return 0, fmt.Errorf("len of nil pointer")
|
||||||
|
}
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function invocation
|
||||||
|
|
||||||
|
// call returns the result of evaluating the first argument as a function.
|
||||||
|
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||||
|
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
||||||
|
v := reflect.ValueOf(fn)
|
||||||
|
typ := v.Type()
|
||||||
|
if typ.Kind() != reflect.Func {
|
||||||
|
return nil, fmt.Errorf("non-function of type %s", typ)
|
||||||
|
}
|
||||||
|
if !goodFunc(typ) {
|
||||||
|
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||||
|
}
|
||||||
|
numIn := typ.NumIn()
|
||||||
|
var dddType reflect.Type
|
||||||
|
if typ.IsVariadic() {
|
||||||
|
if len(args) < numIn-1 {
|
||||||
|
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||||
|
}
|
||||||
|
dddType = typ.In(numIn - 1).Elem()
|
||||||
|
} else {
|
||||||
|
if len(args) != numIn {
|
||||||
|
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argv := make([]reflect.Value, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
value := reflect.ValueOf(arg)
|
||||||
|
// Compute the expected type. Clumsy because of variadics.
|
||||||
|
var argType reflect.Type
|
||||||
|
if !typ.IsVariadic() || i < numIn-1 {
|
||||||
|
argType = typ.In(i)
|
||||||
|
} else {
|
||||||
|
argType = dddType
|
||||||
|
}
|
||||||
|
if !value.IsValid() && canBeNil(argType) {
|
||||||
|
value = reflect.Zero(argType)
|
||||||
|
}
|
||||||
|
if !value.Type().AssignableTo(argType) {
|
||||||
|
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
||||||
|
}
|
||||||
|
argv[i] = value
|
||||||
|
}
|
||||||
|
result := v.Call(argv)
|
||||||
|
if len(result) == 2 && !result[1].IsNil() {
|
||||||
|
return result[0].Interface(), result[1].Interface().(error)
|
||||||
|
}
|
||||||
|
return result[0].Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean logic.
|
||||||
|
|
||||||
|
func truth(a interface{}) bool {
|
||||||
|
t, _ := isTrue(reflect.ValueOf(a))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// and computes the Boolean AND of its arguments, returning
|
||||||
|
// the first false argument it encounters, or the last argument.
|
||||||
|
func and(arg0 interface{}, args ...interface{}) interface{} {
|
||||||
|
if !truth(arg0) {
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
arg0 = args[i]
|
||||||
|
if !truth(arg0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
|
||||||
|
// or computes the Boolean OR of its arguments, returning
|
||||||
|
// the first true argument it encounters, or the last argument.
|
||||||
|
func or(arg0 interface{}, args ...interface{}) interface{} {
|
||||||
|
if truth(arg0) {
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
arg0 = args[i]
|
||||||
|
if truth(arg0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arg0
|
||||||
|
}
|
||||||
|
|
||||||
|
// not returns the Boolean negation of its argument.
|
||||||
|
func not(arg interface{}) (truth bool) {
|
||||||
|
truth, _ = isTrue(reflect.ValueOf(arg))
|
||||||
|
return !truth
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison.
|
||||||
|
|
||||||
|
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadComparisonType = errors.New("invalid type for comparison")
|
||||||
|
errBadComparison = errors.New("incompatible types for comparison")
|
||||||
|
errNoComparison = errors.New("missing argument for comparison")
|
||||||
|
)
|
||||||
|
|
||||||
|
type kind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
invalidKind kind = iota
|
||||||
|
boolKind
|
||||||
|
complexKind
|
||||||
|
intKind
|
||||||
|
floatKind
|
||||||
|
integerKind
|
||||||
|
stringKind
|
||||||
|
uintKind
|
||||||
|
)
|
||||||
|
|
||||||
|
func basicKind(v reflect.Value) (kind, error) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return boolKind, nil
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return intKind, nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return uintKind, nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return floatKind, nil
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return complexKind, nil
|
||||||
|
case reflect.String:
|
||||||
|
return stringKind, nil
|
||||||
|
}
|
||||||
|
return invalidKind, errBadComparisonType
|
||||||
|
}
|
||||||
|
|
||||||
|
// eq evaluates the comparison a == b || a == c || ...
|
||||||
|
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||||
|
v1 := reflect.ValueOf(arg1)
|
||||||
|
k1, err := basicKind(v1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(arg2) == 0 {
|
||||||
|
return false, errNoComparison
|
||||||
|
}
|
||||||
|
for _, arg := range arg2 {
|
||||||
|
v2 := reflect.ValueOf(arg)
|
||||||
|
k2, err := basicKind(v2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
truth := false
|
||||||
|
if k1 != k2 {
|
||||||
|
// Special case: Can compare integer values regardless of type's sign.
|
||||||
|
switch {
|
||||||
|
case k1 == intKind && k2 == uintKind:
|
||||||
|
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||||
|
case k1 == uintKind && k2 == intKind:
|
||||||
|
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||||
|
default:
|
||||||
|
return false, errBadComparison
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch k1 {
|
||||||
|
case boolKind:
|
||||||
|
truth = v1.Bool() == v2.Bool()
|
||||||
|
case complexKind:
|
||||||
|
truth = v1.Complex() == v2.Complex()
|
||||||
|
case floatKind:
|
||||||
|
truth = v1.Float() == v2.Float()
|
||||||
|
case intKind:
|
||||||
|
truth = v1.Int() == v2.Int()
|
||||||
|
case stringKind:
|
||||||
|
truth = v1.String() == v2.String()
|
||||||
|
case uintKind:
|
||||||
|
truth = v1.Uint() == v2.Uint()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if truth {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ne evaluates the comparison a != b.
|
||||||
|
func ne(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// != is the inverse of ==.
|
||||||
|
equal, err := eq(arg1, arg2)
|
||||||
|
return !equal, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// lt evaluates the comparison a < b.
|
||||||
|
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
v1 := reflect.ValueOf(arg1)
|
||||||
|
k1, err := basicKind(v1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
v2 := reflect.ValueOf(arg2)
|
||||||
|
k2, err := basicKind(v2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
truth := false
|
||||||
|
if k1 != k2 {
|
||||||
|
// Special case: Can compare integer values regardless of type's sign.
|
||||||
|
switch {
|
||||||
|
case k1 == intKind && k2 == uintKind:
|
||||||
|
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||||
|
case k1 == uintKind && k2 == intKind:
|
||||||
|
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||||
|
default:
|
||||||
|
return false, errBadComparison
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch k1 {
|
||||||
|
case boolKind, complexKind:
|
||||||
|
return false, errBadComparisonType
|
||||||
|
case floatKind:
|
||||||
|
truth = v1.Float() < v2.Float()
|
||||||
|
case intKind:
|
||||||
|
truth = v1.Int() < v2.Int()
|
||||||
|
case stringKind:
|
||||||
|
truth = v1.String() < v2.String()
|
||||||
|
case uintKind:
|
||||||
|
truth = v1.Uint() < v2.Uint()
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return truth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// le evaluates the comparison <= b.
|
||||||
|
func le(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// <= is < or ==.
|
||||||
|
lessThan, err := lt(arg1, arg2)
|
||||||
|
if lessThan || err != nil {
|
||||||
|
return lessThan, err
|
||||||
|
}
|
||||||
|
return eq(arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// gt evaluates the comparison a > b.
|
||||||
|
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// > is the inverse of <=.
|
||||||
|
lessOrEqual, err := le(arg1, arg2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !lessOrEqual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ge evaluates the comparison a >= b.
|
||||||
|
func ge(arg1, arg2 interface{}) (bool, error) {
|
||||||
|
// >= is the inverse of <.
|
||||||
|
lessThan, err := lt(arg1, arg2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !lessThan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlQuot = []byte(""") // shorter than """
|
||||||
|
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||||
|
htmlAmp = []byte("&")
|
||||||
|
htmlLt = []byte("<")
|
||||||
|
htmlGt = []byte(">")
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||||
|
func HTMLEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i, c := range b {
|
||||||
|
var html []byte
|
||||||
|
switch c {
|
||||||
|
case '"':
|
||||||
|
html = htmlQuot
|
||||||
|
case '\'':
|
||||||
|
html = htmlApos
|
||||||
|
case '&':
|
||||||
|
html = htmlAmp
|
||||||
|
case '<':
|
||||||
|
html = htmlLt
|
||||||
|
case '>':
|
||||||
|
html = htmlGt
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
w.Write(html)
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||||
|
func HTMLEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexAny(s, `'"&<>`) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
HTMLEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func HTMLEscaper(args ...interface{}) string {
|
||||||
|
return HTMLEscapeString(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// JavaScript escaping.
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsLowUni = []byte(`\u00`)
|
||||||
|
hex = []byte("0123456789ABCDEF")
|
||||||
|
|
||||||
|
jsBackslash = []byte(`\\`)
|
||||||
|
jsApos = []byte(`\'`)
|
||||||
|
jsQuot = []byte(`\"`)
|
||||||
|
jsLt = []byte(`\x3C`)
|
||||||
|
jsGt = []byte(`\x3E`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||||
|
func JSEscape(w io.Writer, b []byte) {
|
||||||
|
last := 0
|
||||||
|
for i := 0; i < len(b); i++ {
|
||||||
|
c := b[i]
|
||||||
|
|
||||||
|
if !jsIsSpecial(rune(c)) {
|
||||||
|
// fast path: nothing to do
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w.Write(b[last:i])
|
||||||
|
|
||||||
|
if c < utf8.RuneSelf {
|
||||||
|
// Quotes, slashes and angle brackets get quoted.
|
||||||
|
// Control characters get written as \u00XX.
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
w.Write(jsBackslash)
|
||||||
|
case '\'':
|
||||||
|
w.Write(jsApos)
|
||||||
|
case '"':
|
||||||
|
w.Write(jsQuot)
|
||||||
|
case '<':
|
||||||
|
w.Write(jsLt)
|
||||||
|
case '>':
|
||||||
|
w.Write(jsGt)
|
||||||
|
default:
|
||||||
|
w.Write(jsLowUni)
|
||||||
|
t, b := c>>4, c&0x0f
|
||||||
|
w.Write(hex[t : t+1])
|
||||||
|
w.Write(hex[b : b+1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unicode rune.
|
||||||
|
r, size := utf8.DecodeRune(b[i:])
|
||||||
|
if unicode.IsPrint(r) {
|
||||||
|
w.Write(b[i : i+size])
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "\\u%04X", r)
|
||||||
|
}
|
||||||
|
i += size - 1
|
||||||
|
}
|
||||||
|
last = i + 1
|
||||||
|
}
|
||||||
|
w.Write(b[last:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||||
|
func JSEscapeString(s string) string {
|
||||||
|
// Avoid allocation if we can.
|
||||||
|
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
JSEscape(&b, []byte(s))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsIsSpecial(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '\\', '\'', '"', '<', '>':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return r < ' ' || utf8.RuneSelf <= r
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||||
|
// representation of its arguments.
|
||||||
|
func JSEscaper(args ...interface{}) string {
|
||||||
|
return JSEscapeString(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||||
|
// its arguments in a form suitable for embedding in a URL query.
|
||||||
|
func URLQueryEscaper(args ...interface{}) string {
|
||||||
|
return url.QueryEscape(evalArgs(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||||
|
// fmt.Sprint(args...)
|
||||||
|
// except that each argument is indirected (if a pointer), as required,
|
||||||
|
// using the same rules as the default string evaluation during template
|
||||||
|
// execution.
|
||||||
|
func evalArgs(args []interface{}) string {
|
||||||
|
ok := false
|
||||||
|
var s string
|
||||||
|
// Fast path for simple common case.
|
||||||
|
if len(args) == 1 {
|
||||||
|
s, ok = args[0].(string)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
for i, arg := range args {
|
||||||
|
a, ok := printableValue(reflect.ValueOf(arg))
|
||||||
|
if ok {
|
||||||
|
args[i] = a
|
||||||
|
} // else left fmt do its thing
|
||||||
|
}
|
||||||
|
s = fmt.Sprint(args...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Helper functions to make constructing templates easier.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Functions and methods to parse templates.
|
||||||
|
|
||||||
|
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||||
|
// and panics if the error is non-nil. It is intended for use in variable
|
||||||
|
// initializations such as
|
||||||
|
// var t = template.Must(template.New("name").Parse("text"))
|
||||||
|
func Must(t *Template, err error) *Template {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles creates a new Template and parses the template definitions from
|
||||||
|
// the named files. The returned template's name will have the (base) name and
|
||||||
|
// (parsed) contents of the first file. There must be at least one file.
|
||||||
|
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||||
|
func ParseFiles(filenames ...string) (*Template, error) {
|
||||||
|
return parseFiles(nil, filenames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles parses the named files and associates the resulting templates with
|
||||||
|
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||||
|
// otherwise it is t. There must be at least one file.
|
||||||
|
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||||
|
return parseFiles(t, filenames...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFiles is the helper for the method and function. If the argument
|
||||||
|
// template is nil, it is created from the first file.
|
||||||
|
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
// Not really a problem, but be consistent.
|
||||||
|
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||||
|
}
|
||||||
|
for _, filename := range filenames {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s := string(b)
|
||||||
|
name := filepath.Base(filename)
|
||||||
|
// First template becomes return value if not already defined,
|
||||||
|
// and we use that one for subsequent New calls to associate
|
||||||
|
// all the templates together. Also, if this file has the same name
|
||||||
|
// as t, this file becomes the contents of t, so
|
||||||
|
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||||
|
// works. Otherwise we create a new template associated with t.
|
||||||
|
var tmpl *Template
|
||||||
|
if t == nil {
|
||||||
|
t = New(name)
|
||||||
|
}
|
||||||
|
if name == t.Name() {
|
||||||
|
tmpl = t
|
||||||
|
} else {
|
||||||
|
tmpl = t.New(name)
|
||||||
|
}
|
||||||
|
_, err = tmpl.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGlob creates a new Template and parses the template definitions from the
|
||||||
|
// files identified by the pattern, which must match at least one file. The
|
||||||
|
// returned template will have the (base) name and (parsed) contents of the
|
||||||
|
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||||
|
// ParseFiles with the list of files matched by the pattern.
|
||||||
|
func ParseGlob(pattern string) (*Template, error) {
|
||||||
|
return parseGlob(nil, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGlob parses the template definitions in the files identified by the
|
||||||
|
// pattern and associates the resulting templates with t. The pattern is
|
||||||
|
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||||
|
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||||
|
// pattern.
|
||||||
|
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||||
|
return parseGlob(t, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGlob is the implementation of the function and method ParseGlob.
|
||||||
|
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||||
|
filenames, err := filepath.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||||
|
}
|
||||||
|
return parseFiles(t, filenames...)
|
||||||
|
}
|
|
@ -0,0 +1,556 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// item represents a token or text string returned from the scanner.
|
||||||
|
type item struct {
|
||||||
|
typ itemType // The type of this item.
|
||||||
|
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||||
|
val string // The value of this item.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i item) String() string {
|
||||||
|
switch {
|
||||||
|
case i.typ == itemEOF:
|
||||||
|
return "EOF"
|
||||||
|
case i.typ == itemError:
|
||||||
|
return i.val
|
||||||
|
case i.typ > itemKeyword:
|
||||||
|
return fmt.Sprintf("<%s>", i.val)
|
||||||
|
case len(i.val) > 10:
|
||||||
|
return fmt.Sprintf("%.10q...", i.val)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", i.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemType identifies the type of lex items.
|
||||||
|
type itemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
itemError itemType = iota // error occurred; value is text of error
|
||||||
|
itemBool // boolean constant
|
||||||
|
itemChar // printable ASCII character; grab bag for comma etc.
|
||||||
|
itemCharConstant // character constant
|
||||||
|
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||||
|
itemColonEquals // colon-equals (':=') introducing a declaration
|
||||||
|
itemEOF
|
||||||
|
itemField // alphanumeric identifier starting with '.'
|
||||||
|
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||||
|
itemLeftDelim // left action delimiter
|
||||||
|
itemLeftParen // '(' inside action
|
||||||
|
itemNumber // simple number, including imaginary
|
||||||
|
itemPipe // pipe symbol
|
||||||
|
itemRawString // raw quoted string (includes quotes)
|
||||||
|
itemRightDelim // right action delimiter
|
||||||
|
itemElideNewline // elide newline after right delim
|
||||||
|
itemRightParen // ')' inside action
|
||||||
|
itemSpace // run of spaces separating arguments
|
||||||
|
itemString // quoted string (includes quotes)
|
||||||
|
itemText // plain text
|
||||||
|
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||||
|
// Keywords appear after all the rest.
|
||||||
|
itemKeyword // used only to delimit the keywords
|
||||||
|
itemDot // the cursor, spelled '.'
|
||||||
|
itemDefine // define keyword
|
||||||
|
itemElse // else keyword
|
||||||
|
itemEnd // end keyword
|
||||||
|
itemIf // if keyword
|
||||||
|
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||||
|
itemRange // range keyword
|
||||||
|
itemTemplate // template keyword
|
||||||
|
itemWith // with keyword
|
||||||
|
)
|
||||||
|
|
||||||
|
var key = map[string]itemType{
|
||||||
|
".": itemDot,
|
||||||
|
"define": itemDefine,
|
||||||
|
"else": itemElse,
|
||||||
|
"end": itemEnd,
|
||||||
|
"if": itemIf,
|
||||||
|
"range": itemRange,
|
||||||
|
"nil": itemNil,
|
||||||
|
"template": itemTemplate,
|
||||||
|
"with": itemWith,
|
||||||
|
}
|
||||||
|
|
||||||
|
const eof = -1
|
||||||
|
|
||||||
|
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||||
|
type stateFn func(*lexer) stateFn
|
||||||
|
|
||||||
|
// lexer holds the state of the scanner.
|
||||||
|
type lexer struct {
|
||||||
|
name string // the name of the input; used only for error reports
|
||||||
|
input string // the string being scanned
|
||||||
|
leftDelim string // start of action
|
||||||
|
rightDelim string // end of action
|
||||||
|
state stateFn // the next lexing function to enter
|
||||||
|
pos Pos // current position in the input
|
||||||
|
start Pos // start position of this item
|
||||||
|
width Pos // width of last rune read from input
|
||||||
|
lastPos Pos // position of most recent item returned by nextItem
|
||||||
|
items chan item // channel of scanned items
|
||||||
|
parenDepth int // nesting depth of ( ) exprs
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next rune in the input.
|
||||||
|
func (l *lexer) next() rune {
|
||||||
|
if int(l.pos) >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.width = Pos(w)
|
||||||
|
l.pos += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next rune in the input.
|
||||||
|
func (l *lexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one rune. Can only be called once per call of next.
|
||||||
|
func (l *lexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
// emit passes an item back to the client.
|
||||||
|
func (l *lexer) emit(t itemType) {
|
||||||
|
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore skips over the pending input before this point.
|
||||||
|
func (l *lexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept consumes the next rune if it's from the valid set.
|
||||||
|
func (l *lexer) accept(valid string) bool {
|
||||||
|
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// acceptRun consumes a run of runes from the valid set.
|
||||||
|
func (l *lexer) acceptRun(valid string) {
|
||||||
|
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lineNumber reports which line we're on, based on the position of
|
||||||
|
// the previous item returned by nextItem. Doing it this way
|
||||||
|
// means we don't have to worry about peek double counting.
|
||||||
|
func (l *lexer) lineNumber() int {
|
||||||
|
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf returns an error token and terminates the scan by passing
|
||||||
|
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||||
|
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||||
|
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextItem returns the next item from the input.
|
||||||
|
func (l *lexer) nextItem() item {
|
||||||
|
item := <-l.items
|
||||||
|
l.lastPos = item.pos
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// lex creates a new scanner for the input string.
|
||||||
|
func lex(name, input, left, right string) *lexer {
|
||||||
|
if left == "" {
|
||||||
|
left = leftDelim
|
||||||
|
}
|
||||||
|
if right == "" {
|
||||||
|
right = rightDelim
|
||||||
|
}
|
||||||
|
l := &lexer{
|
||||||
|
name: name,
|
||||||
|
input: input,
|
||||||
|
leftDelim: left,
|
||||||
|
rightDelim: right,
|
||||||
|
items: make(chan item),
|
||||||
|
}
|
||||||
|
go l.run()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// run runs the state machine for the lexer.
|
||||||
|
func (l *lexer) run() {
|
||||||
|
for l.state = lexText; l.state != nil; {
|
||||||
|
l.state = l.state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// state functions
|
||||||
|
|
||||||
|
const (
|
||||||
|
leftDelim = "{{"
|
||||||
|
rightDelim = "}}"
|
||||||
|
leftComment = "/*"
|
||||||
|
rightComment = "*/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lexText scans until an opening action delimiter, "{{".
|
||||||
|
func lexText(l *lexer) stateFn {
|
||||||
|
for {
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(itemText)
|
||||||
|
}
|
||||||
|
return lexLeftDelim
|
||||||
|
}
|
||||||
|
if l.next() == eof {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Correctly reached EOF.
|
||||||
|
if l.pos > l.start {
|
||||||
|
l.emit(itemText)
|
||||||
|
}
|
||||||
|
l.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexLeftDelim scans the left delimiter, which is known to be present.
|
||||||
|
func lexLeftDelim(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(l.leftDelim))
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
||||||
|
return lexComment
|
||||||
|
}
|
||||||
|
l.emit(itemLeftDelim)
|
||||||
|
l.parenDepth = 0
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexComment scans a comment. The left comment marker is known to be present.
|
||||||
|
func lexComment(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(leftComment))
|
||||||
|
i := strings.Index(l.input[l.pos:], rightComment)
|
||||||
|
if i < 0 {
|
||||||
|
return l.errorf("unclosed comment")
|
||||||
|
}
|
||||||
|
l.pos += Pos(i + len(rightComment))
|
||||||
|
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||||
|
return l.errorf("comment ends before closing delimiter")
|
||||||
|
|
||||||
|
}
|
||||||
|
l.pos += Pos(len(l.rightDelim))
|
||||||
|
l.ignore()
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRightDelim scans the right delimiter, which is known to be present.
|
||||||
|
func lexRightDelim(l *lexer) stateFn {
|
||||||
|
l.pos += Pos(len(l.rightDelim))
|
||||||
|
l.emit(itemRightDelim)
|
||||||
|
if l.peek() == '\\' {
|
||||||
|
l.pos++
|
||||||
|
l.emit(itemElideNewline)
|
||||||
|
}
|
||||||
|
return lexText
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexInsideAction scans the elements inside action delimiters.
|
||||||
|
func lexInsideAction(l *lexer) stateFn {
|
||||||
|
// Either number, quoted string, or identifier.
|
||||||
|
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||||
|
// Pipe symbols separate and are emitted.
|
||||||
|
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||||
|
if l.parenDepth == 0 {
|
||||||
|
return lexRightDelim
|
||||||
|
}
|
||||||
|
return l.errorf("unclosed left paren")
|
||||||
|
}
|
||||||
|
switch r := l.next(); {
|
||||||
|
case r == eof || isEndOfLine(r):
|
||||||
|
return l.errorf("unclosed action")
|
||||||
|
case isSpace(r):
|
||||||
|
return lexSpace
|
||||||
|
case r == ':':
|
||||||
|
if l.next() != '=' {
|
||||||
|
return l.errorf("expected :=")
|
||||||
|
}
|
||||||
|
l.emit(itemColonEquals)
|
||||||
|
case r == '|':
|
||||||
|
l.emit(itemPipe)
|
||||||
|
case r == '"':
|
||||||
|
return lexQuote
|
||||||
|
case r == '`':
|
||||||
|
return lexRawQuote
|
||||||
|
case r == '$':
|
||||||
|
return lexVariable
|
||||||
|
case r == '\'':
|
||||||
|
return lexChar
|
||||||
|
case r == '.':
|
||||||
|
// special look-ahead for ".field" so we don't break l.backup().
|
||||||
|
if l.pos < Pos(len(l.input)) {
|
||||||
|
r := l.input[l.pos]
|
||||||
|
if r < '0' || '9' < r {
|
||||||
|
return lexField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallthrough // '.' can start a number.
|
||||||
|
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||||
|
l.backup()
|
||||||
|
return lexNumber
|
||||||
|
case isAlphaNumeric(r):
|
||||||
|
l.backup()
|
||||||
|
return lexIdentifier
|
||||||
|
case r == '(':
|
||||||
|
l.emit(itemLeftParen)
|
||||||
|
l.parenDepth++
|
||||||
|
return lexInsideAction
|
||||||
|
case r == ')':
|
||||||
|
l.emit(itemRightParen)
|
||||||
|
l.parenDepth--
|
||||||
|
if l.parenDepth < 0 {
|
||||||
|
return l.errorf("unexpected right paren %#U", r)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||||
|
l.emit(itemChar)
|
||||||
|
return lexInsideAction
|
||||||
|
default:
|
||||||
|
return l.errorf("unrecognized character in action: %#U", r)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexSpace scans a run of space characters.
|
||||||
|
// One space has already been seen.
|
||||||
|
func lexSpace(l *lexer) stateFn {
|
||||||
|
for isSpace(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
}
|
||||||
|
l.emit(itemSpace)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexIdentifier scans an alphanumeric.
|
||||||
|
func lexIdentifier(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch r := l.next(); {
|
||||||
|
case isAlphaNumeric(r):
|
||||||
|
// absorb.
|
||||||
|
default:
|
||||||
|
l.backup()
|
||||||
|
word := l.input[l.start:l.pos]
|
||||||
|
if !l.atTerminator() {
|
||||||
|
return l.errorf("bad character %#U", r)
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case key[word] > itemKeyword:
|
||||||
|
l.emit(key[word])
|
||||||
|
case word[0] == '.':
|
||||||
|
l.emit(itemField)
|
||||||
|
case word == "true", word == "false":
|
||||||
|
l.emit(itemBool)
|
||||||
|
default:
|
||||||
|
l.emit(itemIdentifier)
|
||||||
|
}
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexField scans a field: .Alphanumeric.
|
||||||
|
// The . has been scanned.
|
||||||
|
func lexField(l *lexer) stateFn {
|
||||||
|
return lexFieldOrVariable(l, itemField)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexVariable scans a Variable: $Alphanumeric.
|
||||||
|
// The $ has been scanned.
|
||||||
|
func lexVariable(l *lexer) stateFn {
|
||||||
|
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||||
|
l.emit(itemVariable)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
return lexFieldOrVariable(l, itemVariable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||||
|
// The . or $ has been scanned.
|
||||||
|
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||||
|
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||||
|
if typ == itemVariable {
|
||||||
|
l.emit(itemVariable)
|
||||||
|
} else {
|
||||||
|
l.emit(itemDot)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
var r rune
|
||||||
|
for {
|
||||||
|
r = l.next()
|
||||||
|
if !isAlphaNumeric(r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !l.atTerminator() {
|
||||||
|
return l.errorf("bad character %#U", r)
|
||||||
|
}
|
||||||
|
l.emit(typ)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// atTerminator reports whether the input is at valid termination character to
|
||||||
|
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||||
|
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||||
|
// day to implement arithmetic.
|
||||||
|
func (l *lexer) atTerminator() bool {
|
||||||
|
r := l.peek()
|
||||||
|
if isSpace(r) || isEndOfLine(r) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case eof, '.', ',', '|', ':', ')', '(':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||||
|
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||||
|
// bad choice of delimiter.
|
||||||
|
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexChar scans a character constant. The initial quote is already
|
||||||
|
// scanned. Syntax checking is done by the parser.
|
||||||
|
func lexChar(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != eof && r != '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated character constant")
|
||||||
|
case '\'':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemCharConstant)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||||
|
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||||
|
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||||
|
// strconv) will notice.
|
||||||
|
func lexNumber(l *lexer) stateFn {
|
||||||
|
if !l.scanNumber() {
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||||
|
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||||
|
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(itemComplex)
|
||||||
|
} else {
|
||||||
|
l.emit(itemNumber)
|
||||||
|
}
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lexer) scanNumber() bool {
|
||||||
|
// Optional leading sign.
|
||||||
|
l.accept("+-")
|
||||||
|
// Is it hex?
|
||||||
|
digits := "0123456789"
|
||||||
|
if l.accept("0") && l.accept("xX") {
|
||||||
|
digits = "0123456789abcdefABCDEF"
|
||||||
|
}
|
||||||
|
l.acceptRun(digits)
|
||||||
|
if l.accept(".") {
|
||||||
|
l.acceptRun(digits)
|
||||||
|
}
|
||||||
|
if l.accept("eE") {
|
||||||
|
l.accept("+-")
|
||||||
|
l.acceptRun("0123456789")
|
||||||
|
}
|
||||||
|
// Is it imaginary?
|
||||||
|
l.accept("i")
|
||||||
|
// Next thing mustn't be alphanumeric.
|
||||||
|
if isAlphaNumeric(l.peek()) {
|
||||||
|
l.next()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexQuote scans a quoted string.
|
||||||
|
func lexQuote(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != eof && r != '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated quoted string")
|
||||||
|
case '"':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemString)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRawQuote scans a raw quoted string.
|
||||||
|
func lexRawQuote(l *lexer) stateFn {
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case eof, '\n':
|
||||||
|
return l.errorf("unterminated raw quoted string")
|
||||||
|
case '`':
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(itemRawString)
|
||||||
|
return lexInsideAction
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSpace reports whether r is a space character.
|
||||||
|
func isSpace(r rune) bool {
|
||||||
|
return r == ' ' || r == '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEndOfLine reports whether r is an end-of-line character.
|
||||||
|
func isEndOfLine(r rune) bool {
|
||||||
|
return r == '\r' || r == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||||
|
func isAlphaNumeric(r rune) bool {
|
||||||
|
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||||
|
}
|
|
@ -0,0 +1,834 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Parse nodes.
|
||||||
|
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||||
|
|
||||||
|
// A Node is an element in the parse tree. The interface is trivial.
|
||||||
|
// The interface contains an unexported method so that only
|
||||||
|
// types local to this package can satisfy it.
|
||||||
|
type Node interface {
|
||||||
|
Type() NodeType
|
||||||
|
String() string
|
||||||
|
// Copy does a deep copy of the Node and all its components.
|
||||||
|
// To avoid type assertions, some XxxNodes also have specialized
|
||||||
|
// CopyXxx methods that return *XxxNode.
|
||||||
|
Copy() Node
|
||||||
|
Position() Pos // byte position of start of node in full original input string
|
||||||
|
// tree returns the containing *Tree.
|
||||||
|
// It is unexported so all implementations of Node are in this package.
|
||||||
|
tree() *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeType identifies the type of a parse tree node.
|
||||||
|
type NodeType int
|
||||||
|
|
||||||
|
// Pos represents a byte position in the original input text from which
|
||||||
|
// this template was parsed.
|
||||||
|
type Pos int
|
||||||
|
|
||||||
|
func (p Pos) Position() Pos {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns itself and provides an easy default implementation
|
||||||
|
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||||
|
func (t NodeType) Type() NodeType {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NodeText NodeType = iota // Plain text.
|
||||||
|
NodeAction // A non-control action such as a field evaluation.
|
||||||
|
NodeBool // A boolean constant.
|
||||||
|
NodeChain // A sequence of field accesses.
|
||||||
|
NodeCommand // An element of a pipeline.
|
||||||
|
NodeDot // The cursor, dot.
|
||||||
|
nodeElse // An else action. Not added to tree.
|
||||||
|
nodeEnd // An end action. Not added to tree.
|
||||||
|
NodeField // A field or method name.
|
||||||
|
NodeIdentifier // An identifier; always a function name.
|
||||||
|
NodeIf // An if action.
|
||||||
|
NodeList // A list of Nodes.
|
||||||
|
NodeNil // An untyped nil constant.
|
||||||
|
NodeNumber // A numerical constant.
|
||||||
|
NodePipe // A pipeline of commands.
|
||||||
|
NodeRange // A range action.
|
||||||
|
NodeString // A string constant.
|
||||||
|
NodeTemplate // A template invocation action.
|
||||||
|
NodeVariable // A $ variable.
|
||||||
|
NodeWith // A with action.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nodes.
|
||||||
|
|
||||||
|
// ListNode holds a sequence of nodes.
|
||||||
|
type ListNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Nodes []Node // The element nodes in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newList(pos Pos) *ListNode {
|
||||||
|
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) append(n Node) {
|
||||||
|
l.Nodes = append(l.Nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) tree() *Tree {
|
||||||
|
return l.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) String() string {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
for _, n := range l.Nodes {
|
||||||
|
fmt.Fprint(b, n)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) CopyList() *ListNode {
|
||||||
|
if l == nil {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
n := l.tr.newList(l.Pos)
|
||||||
|
for _, elem := range l.Nodes {
|
||||||
|
n.append(elem.Copy())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ListNode) Copy() Node {
|
||||||
|
return l.CopyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextNode holds plain text.
|
||||||
|
type TextNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Text []byte // The text; may span newlines.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||||
|
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) String() string {
|
||||||
|
return fmt.Sprintf(textFormat, t.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) tree() *Tree {
|
||||||
|
return t.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TextNode) Copy() Node {
|
||||||
|
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeNode holds a pipeline with optional declaration
|
||||||
|
type PipeNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Decl []*VariableNode // Variable declarations in lexical order.
|
||||||
|
Cmds []*CommandNode // The commands in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
||||||
|
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) append(command *CommandNode) {
|
||||||
|
p.Cmds = append(p.Cmds, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) String() string {
|
||||||
|
s := ""
|
||||||
|
if len(p.Decl) > 0 {
|
||||||
|
for i, v := range p.Decl {
|
||||||
|
if i > 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += v.String()
|
||||||
|
}
|
||||||
|
s += " := "
|
||||||
|
}
|
||||||
|
for i, c := range p.Cmds {
|
||||||
|
if i > 0 {
|
||||||
|
s += " | "
|
||||||
|
}
|
||||||
|
s += c.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) tree() *Tree {
|
||||||
|
return p.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||||
|
if p == nil {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
var decl []*VariableNode
|
||||||
|
for _, d := range p.Decl {
|
||||||
|
decl = append(decl, d.Copy().(*VariableNode))
|
||||||
|
}
|
||||||
|
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
||||||
|
for _, c := range p.Cmds {
|
||||||
|
n.append(c.Copy().(*CommandNode))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PipeNode) Copy() Node {
|
||||||
|
return p.CopyPipe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionNode holds an action (something bounded by delimiters).
|
||||||
|
// Control actions have their own nodes; ActionNode represents simple
|
||||||
|
// ones such as field evaluations and parenthesized pipelines.
|
||||||
|
type ActionNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Pipe *PipeNode // The pipeline in the action.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||||
|
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) String() string {
|
||||||
|
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) tree() *Tree {
|
||||||
|
return a.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ActionNode) Copy() Node {
|
||||||
|
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||||
|
type CommandNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||||
|
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) append(arg Node) {
|
||||||
|
c.Args = append(c.Args, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for i, arg := range c.Args {
|
||||||
|
if i > 0 {
|
||||||
|
s += " "
|
||||||
|
}
|
||||||
|
if arg, ok := arg.(*PipeNode); ok {
|
||||||
|
s += "(" + arg.String() + ")"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s += arg.String()
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) tree() *Tree {
|
||||||
|
return c.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandNode) Copy() Node {
|
||||||
|
if c == nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
n := c.tr.newCommand(c.Pos)
|
||||||
|
for _, c := range c.Args {
|
||||||
|
n.append(c.Copy())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentifierNode holds an identifier.
|
||||||
|
type IdentifierNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident string // The identifier's name.
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||||
|
func NewIdentifier(ident string) *IdentifierNode {
|
||||||
|
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||||
|
// Chained for convenience.
|
||||||
|
// TODO: fix one day?
|
||||||
|
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||||
|
i.Pos = pos
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||||
|
// Chained for convenience.
|
||||||
|
// TODO: fix one day?
|
||||||
|
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||||
|
i.tr = t
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) String() string {
|
||||||
|
return i.Ident
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) tree() *Tree {
|
||||||
|
return i.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierNode) Copy() Node {
|
||||||
|
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VariableNode holds a list of variable names, possibly with chained field
|
||||||
|
// accesses. The dollar sign is part of the (first) name.
|
||||||
|
type VariableNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident []string // Variable name and fields in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||||
|
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for i, id := range v.Ident {
|
||||||
|
if i > 0 {
|
||||||
|
s += "."
|
||||||
|
}
|
||||||
|
s += id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) tree() *Tree {
|
||||||
|
return v.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VariableNode) Copy() Node {
|
||||||
|
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DotNode holds the special identifier '.'.
|
||||||
|
type DotNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||||
|
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) Type() NodeType {
|
||||||
|
// Override method on embedded NodeType for API compatibility.
|
||||||
|
// TODO: Not really a problem; could change API without effect but
|
||||||
|
// api tool complains.
|
||||||
|
return NodeDot
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) String() string {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) tree() *Tree {
|
||||||
|
return d.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DotNode) Copy() Node {
|
||||||
|
return d.tr.newDot(d.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||||
|
type NilNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||||
|
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) Type() NodeType {
|
||||||
|
// Override method on embedded NodeType for API compatibility.
|
||||||
|
// TODO: Not really a problem; could change API without effect but
|
||||||
|
// api tool complains.
|
||||||
|
return NodeNil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) String() string {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) tree() *Tree {
|
||||||
|
return n.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NilNode) Copy() Node {
|
||||||
|
return n.tr.newNil(n.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldNode holds a field (identifier starting with '.').
|
||||||
|
// The names may be chained ('.x.y').
|
||||||
|
// The period is dropped from each ident.
|
||||||
|
type FieldNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Ident []string // The identifiers in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||||
|
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) String() string {
|
||||||
|
s := ""
|
||||||
|
for _, id := range f.Ident {
|
||||||
|
s += "." + id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) tree() *Tree {
|
||||||
|
return f.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FieldNode) Copy() Node {
|
||||||
|
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||||
|
// The names may be chained ('.x.y').
|
||||||
|
// The periods are dropped from each ident.
|
||||||
|
type ChainNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Node Node
|
||||||
|
Field []string // The identifiers in lexical order.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||||
|
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||||
|
func (c *ChainNode) Add(field string) {
|
||||||
|
if len(field) == 0 || field[0] != '.' {
|
||||||
|
panic("no dot in field")
|
||||||
|
}
|
||||||
|
field = field[1:] // Remove leading dot.
|
||||||
|
if field == "" {
|
||||||
|
panic("empty field")
|
||||||
|
}
|
||||||
|
c.Field = append(c.Field, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) String() string {
|
||||||
|
s := c.Node.String()
|
||||||
|
if _, ok := c.Node.(*PipeNode); ok {
|
||||||
|
s = "(" + s + ")"
|
||||||
|
}
|
||||||
|
for _, field := range c.Field {
|
||||||
|
s += "." + field
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) tree() *Tree {
|
||||||
|
return c.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChainNode) Copy() Node {
|
||||||
|
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolNode holds a boolean constant.
|
||||||
|
type BoolNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
True bool // The value of the boolean constant.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||||
|
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) String() string {
|
||||||
|
if b.True {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) tree() *Tree {
|
||||||
|
return b.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoolNode) Copy() Node {
|
||||||
|
return b.tr.newBool(b.Pos, b.True)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||||
|
// The value is parsed and stored under all the types that can represent the value.
|
||||||
|
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||||
|
type NumberNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
IsInt bool // Number has an integral value.
|
||||||
|
IsUint bool // Number has an unsigned integral value.
|
||||||
|
IsFloat bool // Number has a floating-point value.
|
||||||
|
IsComplex bool // Number is complex.
|
||||||
|
Int64 int64 // The signed integer value.
|
||||||
|
Uint64 uint64 // The unsigned integer value.
|
||||||
|
Float64 float64 // The floating-point value.
|
||||||
|
Complex128 complex128 // The complex value.
|
||||||
|
Text string // The original textual representation from the input.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||||
|
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||||
|
switch typ {
|
||||||
|
case itemCharConstant:
|
||||||
|
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tail != "'" {
|
||||||
|
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||||
|
}
|
||||||
|
n.Int64 = int64(rune)
|
||||||
|
n.IsInt = true
|
||||||
|
n.Uint64 = uint64(rune)
|
||||||
|
n.IsUint = true
|
||||||
|
n.Float64 = float64(rune) // odd but those are the rules.
|
||||||
|
n.IsFloat = true
|
||||||
|
return n, nil
|
||||||
|
case itemComplex:
|
||||||
|
// fmt.Sscan can parse the pair, so let it do the work.
|
||||||
|
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n.IsComplex = true
|
||||||
|
n.simplifyComplex()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
// Imaginary constants can only be complex unless they are zero.
|
||||||
|
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||||
|
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsComplex = true
|
||||||
|
n.Complex128 = complex(0, f)
|
||||||
|
n.simplifyComplex()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Do integer test first so we get 0x123 etc.
|
||||||
|
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||||
|
if err == nil {
|
||||||
|
n.IsUint = true
|
||||||
|
n.Uint64 = u
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseInt(text, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsInt = true
|
||||||
|
n.Int64 = i
|
||||||
|
if i == 0 {
|
||||||
|
n.IsUint = true // in case of -0.
|
||||||
|
n.Uint64 = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If an integer extraction succeeded, promote the float.
|
||||||
|
if n.IsInt {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = float64(n.Int64)
|
||||||
|
} else if n.IsUint {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = float64(n.Uint64)
|
||||||
|
} else {
|
||||||
|
f, err := strconv.ParseFloat(text, 64)
|
||||||
|
if err == nil {
|
||||||
|
n.IsFloat = true
|
||||||
|
n.Float64 = f
|
||||||
|
// If a floating-point extraction succeeded, extract the int if needed.
|
||||||
|
if !n.IsInt && float64(int64(f)) == f {
|
||||||
|
n.IsInt = true
|
||||||
|
n.Int64 = int64(f)
|
||||||
|
}
|
||||||
|
if !n.IsUint && float64(uint64(f)) == f {
|
||||||
|
n.IsUint = true
|
||||||
|
n.Uint64 = uint64(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||||
|
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||||
|
// These all require that the imaginary part be zero.
|
||||||
|
func (n *NumberNode) simplifyComplex() {
|
||||||
|
n.IsFloat = imag(n.Complex128) == 0
|
||||||
|
if n.IsFloat {
|
||||||
|
n.Float64 = real(n.Complex128)
|
||||||
|
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||||
|
if n.IsInt {
|
||||||
|
n.Int64 = int64(n.Float64)
|
||||||
|
}
|
||||||
|
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||||
|
if n.IsUint {
|
||||||
|
n.Uint64 = uint64(n.Float64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) String() string {
|
||||||
|
return n.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) tree() *Tree {
|
||||||
|
return n.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NumberNode) Copy() Node {
|
||||||
|
nn := new(NumberNode)
|
||||||
|
*nn = *n // Easy, fast, correct.
|
||||||
|
return nn
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringNode holds a string constant. The value has been "unquoted".
|
||||||
|
type StringNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Quoted string // The original text of the string, with quotes.
|
||||||
|
Text string // The string, after quote processing.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||||
|
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) String() string {
|
||||||
|
return s.Quoted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) tree() *Tree {
|
||||||
|
return s.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StringNode) Copy() Node {
|
||||||
|
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endNode represents an {{end}} action.
|
||||||
|
// It does not appear in the final parse tree.
|
||||||
|
type endNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||||
|
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) String() string {
|
||||||
|
return "{{end}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) tree() *Tree {
|
||||||
|
return e.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endNode) Copy() Node {
|
||||||
|
return e.tr.newEnd(e.Pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||||
|
type elseNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||||
|
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) Type() NodeType {
|
||||||
|
return nodeElse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) String() string {
|
||||||
|
return "{{else}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) tree() *Tree {
|
||||||
|
return e.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *elseNode) Copy() Node {
|
||||||
|
return e.tr.newElse(e.Pos, e.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BranchNode is the common representation of if, range, and with.
|
||||||
|
type BranchNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Pipe *PipeNode // The pipeline to be evaluated.
|
||||||
|
List *ListNode // What to execute if the value is non-empty.
|
||||||
|
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) String() string {
|
||||||
|
name := ""
|
||||||
|
switch b.NodeType {
|
||||||
|
case NodeIf:
|
||||||
|
name = "if"
|
||||||
|
case NodeRange:
|
||||||
|
name = "range"
|
||||||
|
case NodeWith:
|
||||||
|
name = "with"
|
||||||
|
default:
|
||||||
|
panic("unknown branch type")
|
||||||
|
}
|
||||||
|
if b.ElseList != nil {
|
||||||
|
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) tree() *Tree {
|
||||||
|
return b.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BranchNode) Copy() Node {
|
||||||
|
switch b.NodeType {
|
||||||
|
case NodeIf:
|
||||||
|
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
case NodeRange:
|
||||||
|
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
case NodeWith:
|
||||||
|
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||||
|
default:
|
||||||
|
panic("unknown branch type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfNode represents an {{if}} action and its commands.
|
||||||
|
type IfNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||||
|
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IfNode) Copy() Node {
|
||||||
|
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeNode represents a {{range}} action and its commands.
|
||||||
|
type RangeNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||||
|
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RangeNode) Copy() Node {
|
||||||
|
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNode represents a {{with}} action and its commands.
|
||||||
|
type WithNode struct {
|
||||||
|
BranchNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||||
|
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WithNode) Copy() Node {
|
||||||
|
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateNode represents a {{template}} action.
|
||||||
|
type TemplateNode struct {
|
||||||
|
NodeType
|
||||||
|
Pos
|
||||||
|
tr *Tree
|
||||||
|
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||||
|
Name string // The name of the template (unquoted).
|
||||||
|
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||||
|
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) String() string {
|
||||||
|
if t.Pipe == nil {
|
||||||
|
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) tree() *Tree {
|
||||||
|
return t.tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TemplateNode) Copy() Node {
|
||||||
|
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||||
|
}
|
|
@ -0,0 +1,700 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package parse builds parse trees for templates as defined by text/template
|
||||||
|
// and html/template. Clients should use those packages to construct templates
|
||||||
|
// rather than this one, which provides shared internal data structures not
|
||||||
|
// intended for general use.
|
||||||
|
package parse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tree is the representation of a single parsed template.
|
||||||
|
type Tree struct {
|
||||||
|
Name string // name of the template represented by the tree.
|
||||||
|
ParseName string // name of the top-level template during parsing, for error messages.
|
||||||
|
Root *ListNode // top-level root of the tree.
|
||||||
|
text string // text parsed to create the template (or its parent)
|
||||||
|
// Parsing only; cleared after parse.
|
||||||
|
funcs []map[string]interface{}
|
||||||
|
lex *lexer
|
||||||
|
token [3]item // three-token lookahead for parser.
|
||||||
|
peekCount int
|
||||||
|
vars []string // variables defined at the moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||||
|
func (t *Tree) Copy() *Tree {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Tree{
|
||||||
|
Name: t.Name,
|
||||||
|
ParseName: t.ParseName,
|
||||||
|
Root: t.Root.CopyList(),
|
||||||
|
text: t.text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||||
|
// templates described in the argument string. The top-level template will be
|
||||||
|
// given the specified name. If an error is encountered, parsing stops and an
|
||||||
|
// empty map is returned with the error.
|
||||||
|
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
||||||
|
treeSet = make(map[string]*Tree)
|
||||||
|
t := New(name)
|
||||||
|
t.text = text
|
||||||
|
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns the next token.
|
||||||
|
func (t *Tree) next() item {
|
||||||
|
if t.peekCount > 0 {
|
||||||
|
t.peekCount--
|
||||||
|
} else {
|
||||||
|
t.token[0] = t.lex.nextItem()
|
||||||
|
}
|
||||||
|
return t.token[t.peekCount]
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup backs the input stream up one token.
|
||||||
|
func (t *Tree) backup() {
|
||||||
|
t.peekCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup2 backs the input stream up two tokens.
|
||||||
|
// The zeroth token is already there.
|
||||||
|
func (t *Tree) backup2(t1 item) {
|
||||||
|
t.token[1] = t1
|
||||||
|
t.peekCount = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup3 backs the input stream up three tokens
|
||||||
|
// The zeroth token is already there.
|
||||||
|
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||||
|
t.token[1] = t1
|
||||||
|
t.token[2] = t2
|
||||||
|
t.peekCount = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next token.
|
||||||
|
func (t *Tree) peek() item {
|
||||||
|
if t.peekCount > 0 {
|
||||||
|
return t.token[t.peekCount-1]
|
||||||
|
}
|
||||||
|
t.peekCount = 1
|
||||||
|
t.token[0] = t.lex.nextItem()
|
||||||
|
return t.token[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextNonSpace returns the next non-space token.
|
||||||
|
func (t *Tree) nextNonSpace() (token item) {
|
||||||
|
for {
|
||||||
|
token = t.next()
|
||||||
|
if token.typ != itemSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekNonSpace returns but does not consume the next non-space token.
|
||||||
|
func (t *Tree) peekNonSpace() (token item) {
|
||||||
|
for {
|
||||||
|
token = t.next()
|
||||||
|
if token.typ != itemSpace {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing.
|
||||||
|
|
||||||
|
// New allocates a new parse tree with the given name.
|
||||||
|
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||||
|
return &Tree{
|
||||||
|
Name: name,
|
||||||
|
funcs: funcs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||||
|
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||||
|
// which can occur in old code.
|
||||||
|
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||||
|
pos := int(n.Position())
|
||||||
|
tree := n.tree()
|
||||||
|
if tree == nil {
|
||||||
|
tree = t
|
||||||
|
}
|
||||||
|
text := tree.text[:pos]
|
||||||
|
byteNum := strings.LastIndex(text, "\n")
|
||||||
|
if byteNum == -1 {
|
||||||
|
byteNum = pos // On first line.
|
||||||
|
} else {
|
||||||
|
byteNum++ // After the newline.
|
||||||
|
byteNum = pos - byteNum
|
||||||
|
}
|
||||||
|
lineNum := 1 + strings.Count(text, "\n")
|
||||||
|
context = n.String()
|
||||||
|
if len(context) > 20 {
|
||||||
|
context = fmt.Sprintf("%.20s...", context)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf formats the error and terminates processing.
|
||||||
|
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||||
|
t.Root = nil
|
||||||
|
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
||||||
|
panic(fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// error terminates processing.
|
||||||
|
func (t *Tree) error(err error) {
|
||||||
|
t.errorf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// expect consumes the next token and guarantees it has the required type.
|
||||||
|
func (t *Tree) expect(expected itemType, context string) item {
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
if token.typ != expected {
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||||
|
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
if token.typ != expected1 && token.typ != expected2 {
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// unexpected complains about the token and terminates processing.
|
||||||
|
func (t *Tree) unexpected(token item, context string) {
|
||||||
|
t.errorf("unexpected %s in %s", token, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||||
|
func (t *Tree) recover(errp *error) {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
if _, ok := e.(runtime.Error); ok {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
t.stopParse()
|
||||||
|
}
|
||||||
|
*errp = e.(error)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// startParse initializes the parser, using the lexer.
|
||||||
|
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
||||||
|
t.Root = nil
|
||||||
|
t.lex = lex
|
||||||
|
t.vars = []string{"$"}
|
||||||
|
t.funcs = funcs
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopParse terminates parsing.
|
||||||
|
func (t *Tree) stopParse() {
|
||||||
|
t.lex = nil
|
||||||
|
t.vars = nil
|
||||||
|
t.funcs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the template definition string to construct a representation of
|
||||||
|
// the template for execution. If either action delimiter string is empty, the
|
||||||
|
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||||
|
// the treeSet map.
|
||||||
|
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||||
|
defer t.recover(&err)
|
||||||
|
t.ParseName = t.Name
|
||||||
|
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
||||||
|
t.text = text
|
||||||
|
t.parse(treeSet)
|
||||||
|
t.add(treeSet)
|
||||||
|
t.stopParse()
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// add adds tree to the treeSet.
|
||||||
|
func (t *Tree) add(treeSet map[string]*Tree) {
|
||||||
|
tree := treeSet[t.Name]
|
||||||
|
if tree == nil || IsEmptyTree(tree.Root) {
|
||||||
|
treeSet[t.Name] = t
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !IsEmptyTree(t.Root) {
|
||||||
|
t.errorf("template: multiple definition of template %q", t.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||||
|
func IsEmptyTree(n Node) bool {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case nil:
|
||||||
|
return true
|
||||||
|
case *ActionNode:
|
||||||
|
case *IfNode:
|
||||||
|
case *ListNode:
|
||||||
|
for _, node := range n.Nodes {
|
||||||
|
if !IsEmptyTree(node) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case *RangeNode:
|
||||||
|
case *TemplateNode:
|
||||||
|
case *TextNode:
|
||||||
|
return len(bytes.TrimSpace(n.Text)) == 0
|
||||||
|
case *WithNode:
|
||||||
|
default:
|
||||||
|
panic("unknown node: " + n.String())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse is the top-level parser for a template, essentially the same
|
||||||
|
// as itemList except it also parses {{define}} actions.
|
||||||
|
// It runs to EOF.
|
||||||
|
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||||
|
t.Root = t.newList(t.peek().pos)
|
||||||
|
for t.peek().typ != itemEOF {
|
||||||
|
if t.peek().typ == itemLeftDelim {
|
||||||
|
delim := t.next()
|
||||||
|
if t.nextNonSpace().typ == itemDefine {
|
||||||
|
newT := New("definition") // name will be updated once we know it.
|
||||||
|
newT.text = t.text
|
||||||
|
newT.ParseName = t.ParseName
|
||||||
|
newT.startParse(t.funcs, t.lex)
|
||||||
|
newT.parseDefinition(treeSet)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.backup2(delim)
|
||||||
|
}
|
||||||
|
n := t.textOrAction()
|
||||||
|
if n.Type() == nodeEnd {
|
||||||
|
t.errorf("unexpected %s", n)
|
||||||
|
}
|
||||||
|
t.Root.append(n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||||
|
// installs the definition in the treeSet map. The "define" keyword has already
|
||||||
|
// been scanned.
|
||||||
|
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||||
|
const context = "define clause"
|
||||||
|
name := t.expectOneOf(itemString, itemRawString, context)
|
||||||
|
var err error
|
||||||
|
t.Name, err = strconv.Unquote(name.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
t.expect(itemRightDelim, context)
|
||||||
|
var end Node
|
||||||
|
t.Root, end = t.itemList()
|
||||||
|
if end.Type() != nodeEnd {
|
||||||
|
t.errorf("unexpected %s in %s", end, context)
|
||||||
|
}
|
||||||
|
t.add(treeSet)
|
||||||
|
t.stopParse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemList:
|
||||||
|
// textOrAction*
|
||||||
|
// Terminates at {{end}} or {{else}}, returned separately.
|
||||||
|
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||||
|
list = t.newList(t.peekNonSpace().pos)
|
||||||
|
for t.peekNonSpace().typ != itemEOF {
|
||||||
|
n := t.textOrAction()
|
||||||
|
switch n.Type() {
|
||||||
|
case nodeEnd, nodeElse:
|
||||||
|
return list, n
|
||||||
|
}
|
||||||
|
list.append(n)
|
||||||
|
}
|
||||||
|
t.errorf("unexpected EOF")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// textOrAction:
|
||||||
|
// text | action
|
||||||
|
func (t *Tree) textOrAction() Node {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemElideNewline:
|
||||||
|
return t.elideNewline()
|
||||||
|
case itemText:
|
||||||
|
return t.newText(token.pos, token.val)
|
||||||
|
case itemLeftDelim:
|
||||||
|
return t.action()
|
||||||
|
default:
|
||||||
|
t.unexpected(token, "input")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// elideNewline:
|
||||||
|
// Remove newlines trailing rightDelim if \\ is present.
|
||||||
|
func (t *Tree) elideNewline() Node {
|
||||||
|
token := t.peek()
|
||||||
|
if token.typ != itemText {
|
||||||
|
t.unexpected(token, "input")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.next()
|
||||||
|
stripped := strings.TrimLeft(token.val, "\n\r")
|
||||||
|
diff := len(token.val) - len(stripped)
|
||||||
|
if diff > 0 {
|
||||||
|
// This is a bit nasty. We mutate the token in-place to remove
|
||||||
|
// preceding newlines.
|
||||||
|
token.pos += Pos(diff)
|
||||||
|
token.val = stripped
|
||||||
|
}
|
||||||
|
return t.newText(token.pos, token.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action:
|
||||||
|
// control
|
||||||
|
// command ("|" command)*
|
||||||
|
// Left delim is past. Now get actions.
|
||||||
|
// First word could be a keyword such as range.
|
||||||
|
func (t *Tree) action() (n Node) {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemElse:
|
||||||
|
return t.elseControl()
|
||||||
|
case itemEnd:
|
||||||
|
return t.endControl()
|
||||||
|
case itemIf:
|
||||||
|
return t.ifControl()
|
||||||
|
case itemRange:
|
||||||
|
return t.rangeControl()
|
||||||
|
case itemTemplate:
|
||||||
|
return t.templateControl()
|
||||||
|
case itemWith:
|
||||||
|
return t.withControl()
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
// Do not pop variables; they persist until "end".
|
||||||
|
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipeline:
|
||||||
|
// declarations? command ('|' command)*
|
||||||
|
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||||
|
var decl []*VariableNode
|
||||||
|
pos := t.peekNonSpace().pos
|
||||||
|
// Are there declarations?
|
||||||
|
for {
|
||||||
|
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||||
|
t.next()
|
||||||
|
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||||
|
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||||
|
// argument variable rather than a declaration. So remember the token
|
||||||
|
// adjacent to the variable so we can push it back if necessary.
|
||||||
|
tokenAfterVariable := t.peek()
|
||||||
|
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
||||||
|
t.nextNonSpace()
|
||||||
|
variable := t.newVariable(v.pos, v.val)
|
||||||
|
decl = append(decl, variable)
|
||||||
|
t.vars = append(t.vars, v.val)
|
||||||
|
if next.typ == itemChar && next.val == "," {
|
||||||
|
if context == "range" && len(decl) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.errorf("too many declarations in %s", context)
|
||||||
|
}
|
||||||
|
} else if tokenAfterVariable.typ == itemSpace {
|
||||||
|
t.backup3(v, tokenAfterVariable)
|
||||||
|
} else {
|
||||||
|
t.backup2(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
||||||
|
for {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemRightDelim, itemRightParen:
|
||||||
|
if len(pipe.Cmds) == 0 {
|
||||||
|
t.errorf("missing value for %s", context)
|
||||||
|
}
|
||||||
|
if token.typ == itemRightParen {
|
||||||
|
t.backup()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||||
|
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||||
|
t.backup()
|
||||||
|
pipe.append(t.command())
|
||||||
|
default:
|
||||||
|
t.unexpected(token, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||||
|
defer t.popVars(len(t.vars))
|
||||||
|
line = t.lex.lineNumber()
|
||||||
|
pipe = t.pipeline(context)
|
||||||
|
var next Node
|
||||||
|
list, next = t.itemList()
|
||||||
|
switch next.Type() {
|
||||||
|
case nodeEnd: //done
|
||||||
|
case nodeElse:
|
||||||
|
if allowElseIf {
|
||||||
|
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||||
|
// the elseControl will have left the "if" token pending. Treat
|
||||||
|
// {{if a}}_{{else if b}}_{{end}}
|
||||||
|
// as
|
||||||
|
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||||
|
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||||
|
// is assumed. This technique works even for long if-else-if chains.
|
||||||
|
// TODO: Should we allow else-if in with and range?
|
||||||
|
if t.peek().typ == itemIf {
|
||||||
|
t.next() // Consume the "if" token.
|
||||||
|
elseList = t.newList(next.Position())
|
||||||
|
elseList.append(t.ifControl())
|
||||||
|
// Do not consume the next item - only one {{end}} required.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseList, next = t.itemList()
|
||||||
|
if next.Type() != nodeEnd {
|
||||||
|
t.errorf("expected end; found %s", next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pipe.Position(), line, pipe, list, elseList
|
||||||
|
}
|
||||||
|
|
||||||
|
// If:
|
||||||
|
// {{if pipeline}} itemList {{end}}
|
||||||
|
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// If keyword is past.
|
||||||
|
func (t *Tree) ifControl() Node {
|
||||||
|
return t.newIf(t.parseControl(true, "if"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range:
|
||||||
|
// {{range pipeline}} itemList {{end}}
|
||||||
|
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// Range keyword is past.
|
||||||
|
func (t *Tree) rangeControl() Node {
|
||||||
|
return t.newRange(t.parseControl(false, "range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// With:
|
||||||
|
// {{with pipeline}} itemList {{end}}
|
||||||
|
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||||
|
// If keyword is past.
|
||||||
|
func (t *Tree) withControl() Node {
|
||||||
|
return t.newWith(t.parseControl(false, "with"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// End:
|
||||||
|
// {{end}}
|
||||||
|
// End keyword is past.
|
||||||
|
func (t *Tree) endControl() Node {
|
||||||
|
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else:
|
||||||
|
// {{else}}
|
||||||
|
// Else keyword is past.
|
||||||
|
func (t *Tree) elseControl() Node {
|
||||||
|
// Special case for "else if".
|
||||||
|
peek := t.peekNonSpace()
|
||||||
|
if peek.typ == itemIf {
|
||||||
|
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||||
|
return t.newElse(peek.pos, t.lex.lineNumber())
|
||||||
|
}
|
||||||
|
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template:
|
||||||
|
// {{template stringValue pipeline}}
|
||||||
|
// Template keyword is past. The name must be something that can evaluate
|
||||||
|
// to a string.
|
||||||
|
func (t *Tree) templateControl() Node {
|
||||||
|
var name string
|
||||||
|
token := t.nextNonSpace()
|
||||||
|
switch token.typ {
|
||||||
|
case itemString, itemRawString:
|
||||||
|
s, err := strconv.Unquote(token.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
name = s
|
||||||
|
default:
|
||||||
|
t.unexpected(token, "template invocation")
|
||||||
|
}
|
||||||
|
var pipe *PipeNode
|
||||||
|
if t.nextNonSpace().typ != itemRightDelim {
|
||||||
|
t.backup()
|
||||||
|
// Do not pop variables; they persist until "end".
|
||||||
|
pipe = t.pipeline("template")
|
||||||
|
}
|
||||||
|
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
||||||
|
}
|
||||||
|
|
||||||
|
// command:
|
||||||
|
// operand (space operand)*
|
||||||
|
// space-separated arguments up to a pipeline character or right delimiter.
|
||||||
|
// we consume the pipe character but leave the right delim to terminate the action.
|
||||||
|
func (t *Tree) command() *CommandNode {
|
||||||
|
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||||
|
for {
|
||||||
|
t.peekNonSpace() // skip leading spaces.
|
||||||
|
operand := t.operand()
|
||||||
|
if operand != nil {
|
||||||
|
cmd.append(operand)
|
||||||
|
}
|
||||||
|
switch token := t.next(); token.typ {
|
||||||
|
case itemSpace:
|
||||||
|
continue
|
||||||
|
case itemError:
|
||||||
|
t.errorf("%s", token.val)
|
||||||
|
case itemRightDelim, itemRightParen:
|
||||||
|
t.backup()
|
||||||
|
case itemPipe:
|
||||||
|
default:
|
||||||
|
t.errorf("unexpected %s in operand; missing space?", token)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if len(cmd.Args) == 0 {
|
||||||
|
t.errorf("empty command")
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// operand:
|
||||||
|
// term .Field*
|
||||||
|
// An operand is a space-separated component of a command,
|
||||||
|
// a term possibly followed by field accesses.
|
||||||
|
// A nil return means the next item is not an operand.
|
||||||
|
func (t *Tree) operand() Node {
|
||||||
|
node := t.term()
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if t.peek().typ == itemField {
|
||||||
|
chain := t.newChain(t.peek().pos, node)
|
||||||
|
for t.peek().typ == itemField {
|
||||||
|
chain.Add(t.next().val)
|
||||||
|
}
|
||||||
|
// Compatibility with original API: If the term is of type NodeField
|
||||||
|
// or NodeVariable, just put more fields on the original.
|
||||||
|
// Otherwise, keep the Chain node.
|
||||||
|
// TODO: Switch to Chains always when we can.
|
||||||
|
switch node.Type() {
|
||||||
|
case NodeField:
|
||||||
|
node = t.newField(chain.Position(), chain.String())
|
||||||
|
case NodeVariable:
|
||||||
|
node = t.newVariable(chain.Position(), chain.String())
|
||||||
|
default:
|
||||||
|
node = chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// term:
|
||||||
|
// literal (number, string, nil, boolean)
|
||||||
|
// function (identifier)
|
||||||
|
// .
|
||||||
|
// .Field
|
||||||
|
// $
|
||||||
|
// '(' pipeline ')'
|
||||||
|
// A term is a simple "expression".
|
||||||
|
// A nil return means the next item is not a term.
|
||||||
|
func (t *Tree) term() Node {
|
||||||
|
switch token := t.nextNonSpace(); token.typ {
|
||||||
|
case itemError:
|
||||||
|
t.errorf("%s", token.val)
|
||||||
|
case itemIdentifier:
|
||||||
|
if !t.hasFunction(token.val) {
|
||||||
|
t.errorf("function %q not defined", token.val)
|
||||||
|
}
|
||||||
|
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||||
|
case itemDot:
|
||||||
|
return t.newDot(token.pos)
|
||||||
|
case itemNil:
|
||||||
|
return t.newNil(token.pos)
|
||||||
|
case itemVariable:
|
||||||
|
return t.useVar(token.pos, token.val)
|
||||||
|
case itemField:
|
||||||
|
return t.newField(token.pos, token.val)
|
||||||
|
case itemBool:
|
||||||
|
return t.newBool(token.pos, token.val == "true")
|
||||||
|
case itemCharConstant, itemComplex, itemNumber:
|
||||||
|
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
return number
|
||||||
|
case itemLeftParen:
|
||||||
|
pipe := t.pipeline("parenthesized pipeline")
|
||||||
|
if token := t.next(); token.typ != itemRightParen {
|
||||||
|
t.errorf("unclosed right paren: unexpected %s", token)
|
||||||
|
}
|
||||||
|
return pipe
|
||||||
|
case itemString, itemRawString:
|
||||||
|
s, err := strconv.Unquote(token.val)
|
||||||
|
if err != nil {
|
||||||
|
t.error(err)
|
||||||
|
}
|
||||||
|
return t.newString(token.pos, token.val, s)
|
||||||
|
}
|
||||||
|
t.backup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasFunction reports if a function name exists in the Tree's maps.
|
||||||
|
func (t *Tree) hasFunction(name string) bool {
|
||||||
|
for _, funcMap := range t.funcs {
|
||||||
|
if funcMap == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if funcMap[name] != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// popVars trims the variable list to the specified length
|
||||||
|
func (t *Tree) popVars(n int) {
|
||||||
|
t.vars = t.vars[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
// useVar returns a node for a variable reference. It errors if the
|
||||||
|
// variable is not defined.
|
||||||
|
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||||
|
v := t.newVariable(pos, name)
|
||||||
|
for _, varName := range t.vars {
|
||||||
|
if varName == v.Ident[0] {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.errorf("undefined variable %q", v.Ident[0])
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template/parse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// common holds the information shared by related templates.
|
||||||
|
type common struct {
|
||||||
|
tmpl map[string]*Template
|
||||||
|
// We use two maps, one for parsing and one for execution.
|
||||||
|
// This separation makes the API cleaner since it doesn't
|
||||||
|
// expose reflection to the client.
|
||||||
|
parseFuncs FuncMap
|
||||||
|
execFuncs map[string]reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template is the representation of a parsed template. The *parse.Tree
|
||||||
|
// field is exported only for use by html/template and should be treated
|
||||||
|
// as unexported by all other clients.
|
||||||
|
type Template struct {
|
||||||
|
name string
|
||||||
|
*parse.Tree
|
||||||
|
*common
|
||||||
|
leftDelim string
|
||||||
|
rightDelim string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new template with the given name.
|
||||||
|
func New(name string) *Template {
|
||||||
|
return &Template{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the template.
|
||||||
|
func (t *Template) Name() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// New allocates a new template associated with the given one and with the same
|
||||||
|
// delimiters. The association, which is transitive, allows one template to
|
||||||
|
// invoke another with a {{template}} action.
|
||||||
|
func (t *Template) New(name string) *Template {
|
||||||
|
t.init()
|
||||||
|
return &Template{
|
||||||
|
name: name,
|
||||||
|
common: t.common,
|
||||||
|
leftDelim: t.leftDelim,
|
||||||
|
rightDelim: t.rightDelim,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Template) init() {
|
||||||
|
if t.common == nil {
|
||||||
|
t.common = new(common)
|
||||||
|
t.tmpl = make(map[string]*Template)
|
||||||
|
t.parseFuncs = make(FuncMap)
|
||||||
|
t.execFuncs = make(map[string]reflect.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a duplicate of the template, including all associated
|
||||||
|
// templates. The actual representation is not copied, but the name space of
|
||||||
|
// associated templates is, so further calls to Parse in the copy will add
|
||||||
|
// templates to the copy but not to the original. Clone can be used to prepare
|
||||||
|
// common templates and use them with variant definitions for other templates
|
||||||
|
// by adding the variants after the clone is made.
|
||||||
|
func (t *Template) Clone() (*Template, error) {
|
||||||
|
nt := t.copy(nil)
|
||||||
|
nt.init()
|
||||||
|
nt.tmpl[t.name] = nt
|
||||||
|
for k, v := range t.tmpl {
|
||||||
|
if k == t.name { // Already installed.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The associated templates share nt's common structure.
|
||||||
|
tmpl := v.copy(nt.common)
|
||||||
|
nt.tmpl[k] = tmpl
|
||||||
|
}
|
||||||
|
for k, v := range t.parseFuncs {
|
||||||
|
nt.parseFuncs[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range t.execFuncs {
|
||||||
|
nt.execFuncs[k] = v
|
||||||
|
}
|
||||||
|
return nt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy returns a shallow copy of t, with common set to the argument.
|
||||||
|
func (t *Template) copy(c *common) *Template {
|
||||||
|
nt := New(t.name)
|
||||||
|
nt.Tree = t.Tree
|
||||||
|
nt.common = c
|
||||||
|
nt.leftDelim = t.leftDelim
|
||||||
|
nt.rightDelim = t.rightDelim
|
||||||
|
return nt
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddParseTree creates a new template with the name and parse tree
|
||||||
|
// and associates it with t.
|
||||||
|
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||||
|
if t.common != nil && t.tmpl[name] != nil {
|
||||||
|
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
||||||
|
}
|
||||||
|
nt := t.New(name)
|
||||||
|
nt.Tree = tree
|
||||||
|
t.tmpl[name] = nt
|
||||||
|
return nt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates returns a slice of the templates associated with t, including t
|
||||||
|
// itself.
|
||||||
|
func (t *Template) Templates() []*Template {
|
||||||
|
if t.common == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Return a slice so we don't expose the map.
|
||||||
|
m := make([]*Template, 0, len(t.tmpl))
|
||||||
|
for _, v := range t.tmpl {
|
||||||
|
m = append(m, v)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delims sets the action delimiters to the specified strings, to be used in
|
||||||
|
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||||
|
// definitions will inherit the settings. An empty delimiter stands for the
|
||||||
|
// corresponding default: {{ or }}.
|
||||||
|
// The return value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Delims(left, right string) *Template {
|
||||||
|
t.leftDelim = left
|
||||||
|
t.rightDelim = right
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funcs adds the elements of the argument map to the template's function map.
|
||||||
|
// It panics if a value in the map is not a function with appropriate return
|
||||||
|
// type. However, it is legal to overwrite elements of the map. The return
|
||||||
|
// value is the template, so calls can be chained.
|
||||||
|
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||||
|
t.init()
|
||||||
|
addValueFuncs(t.execFuncs, funcMap)
|
||||||
|
addFuncs(t.parseFuncs, funcMap)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup returns the template with the given name that is associated with t,
|
||||||
|
// or nil if there is no such template.
|
||||||
|
func (t *Template) Lookup(name string) *Template {
|
||||||
|
if t.common == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return t.tmpl[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a string into a template. Nested template definitions will be
|
||||||
|
// associated with the top-level template t. Parse may be called multiple times
|
||||||
|
// to parse definitions of templates to associate with t. It is an error if a
|
||||||
|
// resulting template is non-empty (contains content other than template
|
||||||
|
// definitions) and would replace a non-empty template with the same name.
|
||||||
|
// (In multiple calls to Parse with the same receiver template, only one call
|
||||||
|
// can contain text other than space, comments, and template definitions.)
|
||||||
|
func (t *Template) Parse(text string) (*Template, error) {
|
||||||
|
t.init()
|
||||||
|
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||||
|
for name, tree := range trees {
|
||||||
|
// If the name we parsed is the name of this template, overwrite this template.
|
||||||
|
// The associate method checks it's not a redefinition.
|
||||||
|
tmpl := t
|
||||||
|
if name != t.name {
|
||||||
|
tmpl = t.New(name)
|
||||||
|
}
|
||||||
|
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
||||||
|
if replace, err := t.associate(tmpl, tree); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if replace {
|
||||||
|
tmpl.Tree = tree
|
||||||
|
}
|
||||||
|
tmpl.leftDelim = t.leftDelim
|
||||||
|
tmpl.rightDelim = t.rightDelim
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// associate installs the new template into the group of templates associated
|
||||||
|
// with t. It is an error to reuse a name except to overwrite an empty
|
||||||
|
// template. The two are already known to share the common structure.
|
||||||
|
// The boolean return value reports wither to store this tree as t.Tree.
|
||||||
|
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
||||||
|
if new.common != t.common {
|
||||||
|
panic("internal error: associate not common")
|
||||||
|
}
|
||||||
|
name := new.name
|
||||||
|
if old := t.tmpl[name]; old != nil {
|
||||||
|
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
||||||
|
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
||||||
|
if newIsEmpty {
|
||||||
|
// Whether old is empty or not, new is empty; no reason to replace old.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if !oldIsEmpty {
|
||||||
|
return false, fmt.Errorf("template: redefinition of template %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.tmpl[name] = new
|
||||||
|
return true, nil
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package units
|
||||||
|
|
||||||
|
// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte,
|
||||||
|
// etc.).
|
||||||
|
type Base2Bytes int64
|
||||||
|
|
||||||
|
// Base-2 byte units.
|
||||||
|
const (
|
||||||
|
Kibibyte Base2Bytes = 1024
|
||||||
|
KiB = Kibibyte
|
||||||
|
Mebibyte = Kibibyte * 1024
|
||||||
|
MiB = Mebibyte
|
||||||
|
Gibibyte = Mebibyte * 1024
|
||||||
|
GiB = Gibibyte
|
||||||
|
Tebibyte = Gibibyte * 1024
|
||||||
|
TiB = Tebibyte
|
||||||
|
Pebibyte = Tebibyte * 1024
|
||||||
|
PiB = Pebibyte
|
||||||
|
Exbibyte = Pebibyte * 1024
|
||||||
|
EiB = Exbibyte
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bytesUnitMap = MakeUnitMap("iB", "B", 1024)
|
||||||
|
oldBytesUnitMap = MakeUnitMap("B", "B", 1024)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB
|
||||||
|
// and KiB are both 1024.
|
||||||
|
func ParseBase2Bytes(s string) (Base2Bytes, error) {
|
||||||
|
n, err := ParseUnit(s, bytesUnitMap)
|
||||||
|
if err != nil {
|
||||||
|
n, err = ParseUnit(s, oldBytesUnitMap)
|
||||||
|
}
|
||||||
|
return Base2Bytes(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Base2Bytes) String() string {
|
||||||
|
return ToString(int64(b), 1024, "iB", "B")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricBytesUnitMap = MakeUnitMap("B", "B", 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetricBytes are SI byte units (1000 bytes in a kilobyte).
|
||||||
|
type MetricBytes SI
|
||||||
|
|
||||||
|
// SI base-10 byte units.
|
||||||
|
const (
|
||||||
|
Kilobyte MetricBytes = 1000
|
||||||
|
KB = Kilobyte
|
||||||
|
Megabyte = Kilobyte * 1000
|
||||||
|
MB = Megabyte
|
||||||
|
Gigabyte = Megabyte * 1000
|
||||||
|
GB = Gigabyte
|
||||||
|
Terabyte = Gigabyte * 1000
|
||||||
|
TB = Terabyte
|
||||||
|
Petabyte = Terabyte * 1000
|
||||||
|
PB = Petabyte
|
||||||
|
Exabyte = Petabyte * 1000
|
||||||
|
EB = Exabyte
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes.
|
||||||
|
func ParseMetricBytes(s string) (MetricBytes, error) {
|
||||||
|
n, err := ParseUnit(s, metricBytesUnitMap)
|
||||||
|
return MetricBytes(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MetricBytes) String() string {
|
||||||
|
return ToString(int64(m), 1000, "B", "B")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseStrictBytes supports both iB and B suffixes for base 2 and metric,
|
||||||
|
// respectively. That is, KiB represents 1024 and KB represents 1000.
|
||||||
|
func ParseStrictBytes(s string) (int64, error) {
|
||||||
|
n, err := ParseUnit(s, bytesUnitMap)
|
||||||
|
if err != nil {
|
||||||
|
n, err = ParseUnit(s, metricBytesUnitMap)
|
||||||
|
}
|
||||||
|
return int64(n), err
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Package units provides helpful unit multipliers and functions for Go.
|
||||||
|
//
|
||||||
|
// The goal of this package is to have functionality similar to the time [1] package.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// [1] http://golang.org/pkg/time/
|
||||||
|
//
|
||||||
|
// It allows for code like this:
|
||||||
|
//
|
||||||
|
// n, err := ParseBase2Bytes("1KB")
|
||||||
|
// // n == 1024
|
||||||
|
// n = units.Mebibyte * 512
|
||||||
|
package units
|
|
@ -0,0 +1,26 @@
|
||||||
|
package units
|
||||||
|
|
||||||
|
// SI units.
|
||||||
|
type SI int64
|
||||||
|
|
||||||
|
// SI unit multiples.
|
||||||
|
const (
|
||||||
|
Kilo SI = 1000
|
||||||
|
Mega = Kilo * 1000
|
||||||
|
Giga = Mega * 1000
|
||||||
|
Tera = Giga * 1000
|
||||||
|
Peta = Tera * 1000
|
||||||
|
Exa = Peta * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 {
|
||||||
|
return map[string]float64{
|
||||||
|
shortSuffix: 1,
|
||||||
|
"K" + suffix: float64(scale),
|
||||||
|
"M" + suffix: float64(scale * scale),
|
||||||
|
"G" + suffix: float64(scale * scale * scale),
|
||||||
|
"T" + suffix: float64(scale * scale * scale * scale),
|
||||||
|
"P" + suffix: float64(scale * scale * scale * scale * scale),
|
||||||
|
"E" + suffix: float64(scale * scale * scale * scale * scale * scale),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
package units
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
siUnits = []string{"", "K", "M", "G", "T", "P", "E"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToString(n int64, scale int64, suffix, baseSuffix string) string {
|
||||||
|
mn := len(siUnits)
|
||||||
|
out := make([]string, mn)
|
||||||
|
for i, m := range siUnits {
|
||||||
|
if n%scale != 0 || i == 0 && n == 0 {
|
||||||
|
s := suffix
|
||||||
|
if i == 0 {
|
||||||
|
s = baseSuffix
|
||||||
|
}
|
||||||
|
out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s)
|
||||||
|
}
|
||||||
|
n /= scale
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(out, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123
|
||||||
|
var errLeadingInt = errors.New("units: bad [0-9]*") // never printed
|
||||||
|
|
||||||
|
// leadingInt consumes the leading [0-9]* from s.
|
||||||
|
func leadingInt(s string) (x int64, rem string, err error) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if x >= (1<<63-10)/10 {
|
||||||
|
// overflow
|
||||||
|
return 0, "", errLeadingInt
|
||||||
|
}
|
||||||
|
x = x*10 + int64(c) - '0'
|
||||||
|
}
|
||||||
|
return x, s[i:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUnit(s string, unitMap map[string]float64) (int64, error) {
|
||||||
|
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
||||||
|
orig := s
|
||||||
|
f := float64(0)
|
||||||
|
neg := false
|
||||||
|
|
||||||
|
// Consume [-+]?
|
||||||
|
if s != "" {
|
||||||
|
c := s[0]
|
||||||
|
if c == '-' || c == '+' {
|
||||||
|
neg = c == '-'
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Special case: if all that is left is "0", this is zero.
|
||||||
|
if s == "0" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
for s != "" {
|
||||||
|
g := float64(0) // this element of the sequence
|
||||||
|
|
||||||
|
var x int64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// The next character must be [0-9.]
|
||||||
|
if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
// Consume [0-9]*
|
||||||
|
pl := len(s)
|
||||||
|
x, s, err = leadingInt(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
g = float64(x)
|
||||||
|
pre := pl != len(s) // whether we consumed anything before a period
|
||||||
|
|
||||||
|
// Consume (\.[0-9]*)?
|
||||||
|
post := false
|
||||||
|
if s != "" && s[0] == '.' {
|
||||||
|
s = s[1:]
|
||||||
|
pl := len(s)
|
||||||
|
x, s, err = leadingInt(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
scale := 1.0
|
||||||
|
for n := pl - len(s); n > 0; n-- {
|
||||||
|
scale *= 10
|
||||||
|
}
|
||||||
|
g += float64(x) / scale
|
||||||
|
post = pl != len(s)
|
||||||
|
}
|
||||||
|
if !pre && !post {
|
||||||
|
// no digits (e.g. ".s" or "-.s")
|
||||||
|
return 0, errors.New("units: invalid " + orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume unit.
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c == '.' || ('0' <= c && c <= '9') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u := s[:i]
|
||||||
|
s = s[i:]
|
||||||
|
unit, ok := unitMap[u]
|
||||||
|
if !ok {
|
||||||
|
return 0, errors.New("units: unknown unit " + u + " in " + orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
f += g * unit
|
||||||
|
}
|
||||||
|
|
||||||
|
if neg {
|
||||||
|
f = -f
|
||||||
|
}
|
||||||
|
if f < float64(-1<<63) || f > float64(1<<63-1) {
|
||||||
|
return 0, errors.New("units: overflow parsing unit")
|
||||||
|
}
|
||||||
|
return int64(f), nil
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
package netrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidNetrc means there was an error parsing the netrc file
|
||||||
|
var ErrInvalidNetrc = errors.New("Invalid netrc")
|
||||||
|
|
||||||
|
// Netrc file
|
||||||
|
type Netrc struct {
|
||||||
|
Path string
|
||||||
|
machines []*Machine
|
||||||
|
tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine from the netrc file
|
||||||
|
type Machine struct {
|
||||||
|
Name string
|
||||||
|
IsDefault bool
|
||||||
|
tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the netrc file at the given path
|
||||||
|
// It returns a Netrc instance
|
||||||
|
func Parse(path string) (*Netrc, error) {
|
||||||
|
file, err := read(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netrc, err := parse(lex(file))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netrc.Path = path
|
||||||
|
return netrc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine gets a machine by name
|
||||||
|
func (n *Netrc) Machine(name string) *Machine {
|
||||||
|
for _, m := range n.machines {
|
||||||
|
if m.Name == name {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMachine adds a machine
|
||||||
|
func (n *Netrc) AddMachine(name, login, password string) {
|
||||||
|
machine := n.Machine(name)
|
||||||
|
if machine == nil {
|
||||||
|
machine = &Machine{}
|
||||||
|
n.machines = append(n.machines, machine)
|
||||||
|
}
|
||||||
|
machine.Name = name
|
||||||
|
machine.tokens = []string{"machine ", name, "\n"}
|
||||||
|
machine.Set("login", login)
|
||||||
|
machine.Set("password", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMachine remove a machine
|
||||||
|
func (n *Netrc) RemoveMachine(name string) {
|
||||||
|
for i, machine := range n.machines {
|
||||||
|
if machine.Name == name {
|
||||||
|
n.machines = append(n.machines[:i], n.machines[i+1:]...)
|
||||||
|
// continue removing but start over since the indexes changed
|
||||||
|
n.RemoveMachine(name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render out the netrc file to a string
|
||||||
|
func (n *Netrc) Render() string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
for _, token := range n.tokens {
|
||||||
|
b.WriteString(token)
|
||||||
|
}
|
||||||
|
for _, machine := range n.machines {
|
||||||
|
for _, token := range machine.tokens {
|
||||||
|
b.WriteString(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the file to disk
|
||||||
|
func (n *Netrc) Save() error {
|
||||||
|
body := []byte(n.Render())
|
||||||
|
if filepath.Ext(n.Path) == ".gpg" {
|
||||||
|
cmd := exec.Command("gpg", "-a", "--batch", "--default-recipient-self", "-e")
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stdin.Write(body)
|
||||||
|
stdin.Close()
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
body, err = cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(n.Path, body, 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func read(path string) (io.Reader, error) {
|
||||||
|
if filepath.Ext(path) == ".gpg" {
|
||||||
|
cmd := exec.Command("gpg", "--batch", "--quiet", "--decrypt", path)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stdout, nil
|
||||||
|
}
|
||||||
|
return os.Open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lex(file io.Reader) []string {
|
||||||
|
commentRe := regexp.MustCompile("\\s*#")
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
scanner.Split(func(data []byte, eof bool) (int, []byte, error) {
|
||||||
|
if eof && len(data) == 0 {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
inWhitespace := unicode.IsSpace(rune(data[0]))
|
||||||
|
for i, c := range data {
|
||||||
|
if c == '#' {
|
||||||
|
// line has a comment
|
||||||
|
i = commentRe.FindIndex(data)[0]
|
||||||
|
if i == 0 {
|
||||||
|
// currently in a comment
|
||||||
|
i = bytes.IndexByte(data, '\n')
|
||||||
|
if i == -1 {
|
||||||
|
// no newline at end
|
||||||
|
if !eof {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
i = len(data)
|
||||||
|
}
|
||||||
|
for i < len(data) {
|
||||||
|
if !unicode.IsSpace(rune(data[i])) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i, data[0:i], nil
|
||||||
|
}
|
||||||
|
if unicode.IsSpace(rune(c)) != inWhitespace {
|
||||||
|
return i, data[0:i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eof {
|
||||||
|
return len(data), data, nil
|
||||||
|
}
|
||||||
|
return 0, nil, nil
|
||||||
|
})
|
||||||
|
tokens := make([]string, 0, 100)
|
||||||
|
for scanner.Scan() {
|
||||||
|
tokens = append(tokens, scanner.Text())
|
||||||
|
}
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(tokens []string) (*Netrc, error) {
|
||||||
|
n := &Netrc{}
|
||||||
|
n.machines = make([]*Machine, 0, 20)
|
||||||
|
var machine *Machine
|
||||||
|
for i, token := range tokens {
|
||||||
|
// group tokens into machines
|
||||||
|
if token == "machine" || token == "default" {
|
||||||
|
// start new group
|
||||||
|
machine = &Machine{}
|
||||||
|
n.machines = append(n.machines, machine)
|
||||||
|
if token == "default" {
|
||||||
|
machine.IsDefault = true
|
||||||
|
machine.Name = "default"
|
||||||
|
} else {
|
||||||
|
machine.Name = tokens[i+2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if machine == nil {
|
||||||
|
n.tokens = append(n.tokens, token)
|
||||||
|
} else {
|
||||||
|
machine.tokens = append(machine.tokens, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a property from a machine
|
||||||
|
func (m *Machine) Get(name string) string {
|
||||||
|
i := 4
|
||||||
|
if m.IsDefault {
|
||||||
|
i = 2
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if i+2 >= len(m.tokens) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if m.tokens[i] == name {
|
||||||
|
return m.tokens[i+2]
|
||||||
|
}
|
||||||
|
i = i + 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a property on the machine
|
||||||
|
func (m *Machine) Set(name, value string) {
|
||||||
|
i := 4
|
||||||
|
if m.IsDefault {
|
||||||
|
i = 2
|
||||||
|
}
|
||||||
|
for i+2 < len(m.tokens) {
|
||||||
|
if m.tokens[i] == name {
|
||||||
|
m.tokens[i+2] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i = i + 4
|
||||||
|
}
|
||||||
|
m.tokens = append(m.tokens, " ", name, " ", value, "\n")
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
// Action callback executed at various stages after all values are populated.
|
||||||
|
// The application, commands, arguments and flags all have corresponding
|
||||||
|
// actions.
|
||||||
|
type Action func(*ParseContext) error
|
||||||
|
|
||||||
|
type actionMixin struct {
|
||||||
|
actions []Action
|
||||||
|
preActions []Action
|
||||||
|
}
|
||||||
|
|
||||||
|
type actionApplier interface {
|
||||||
|
applyActions(*ParseContext) error
|
||||||
|
applyPreActions(*ParseContext) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *actionMixin) addAction(action Action) {
|
||||||
|
a.actions = append(a.actions, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *actionMixin) addPreAction(action Action) {
|
||||||
|
a.preActions = append(a.preActions, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *actionMixin) applyActions(context *ParseContext) error {
|
||||||
|
for _, action := range a.actions {
|
||||||
|
if err := action(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *actionMixin) applyPreActions(context *ParseContext) error {
|
||||||
|
for _, preAction := range a.preActions {
|
||||||
|
if err := preAction(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,685 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCommandNotSpecified = fmt.Errorf("command not specified")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApplicationValidator func(*Application) error
|
||||||
|
|
||||||
|
// An Application contains the definitions of flags, arguments and commands
|
||||||
|
// for an application.
|
||||||
|
type Application struct {
|
||||||
|
cmdMixin
|
||||||
|
initialized bool
|
||||||
|
|
||||||
|
Name string
|
||||||
|
Help string
|
||||||
|
|
||||||
|
author string
|
||||||
|
version string
|
||||||
|
errorWriter io.Writer // Destination for errors.
|
||||||
|
usageWriter io.Writer // Destination for usage
|
||||||
|
usageTemplate string
|
||||||
|
validator ApplicationValidator
|
||||||
|
terminate func(status int) // See Terminate()
|
||||||
|
noInterspersed bool // can flags be interspersed with args (or must they come first)
|
||||||
|
defaultEnvars bool
|
||||||
|
completion bool
|
||||||
|
|
||||||
|
// Help flag. Exposed for user customisation.
|
||||||
|
HelpFlag *FlagClause
|
||||||
|
// Help command. Exposed for user customisation. May be nil.
|
||||||
|
HelpCommand *CmdClause
|
||||||
|
// Version flag. Exposed for user customisation. May be nil.
|
||||||
|
VersionFlag *FlagClause
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Kingpin application instance.
|
||||||
|
func New(name, help string) *Application {
|
||||||
|
a := &Application{
|
||||||
|
Name: name,
|
||||||
|
Help: help,
|
||||||
|
errorWriter: os.Stderr, // Left for backwards compatibility purposes.
|
||||||
|
usageWriter: os.Stderr,
|
||||||
|
usageTemplate: DefaultUsageTemplate,
|
||||||
|
terminate: os.Exit,
|
||||||
|
}
|
||||||
|
a.flagGroup = newFlagGroup()
|
||||||
|
a.argGroup = newArgGroup()
|
||||||
|
a.cmdGroup = newCmdGroup(a)
|
||||||
|
a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).")
|
||||||
|
a.HelpFlag.Bool()
|
||||||
|
a.Flag("help-long", "Generate long help.").Hidden().PreAction(a.generateLongHelp).Bool()
|
||||||
|
a.Flag("help-man", "Generate a man page.").Hidden().PreAction(a.generateManPage).Bool()
|
||||||
|
a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().BoolVar(&a.completion)
|
||||||
|
a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).Bool()
|
||||||
|
a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).Bool()
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) generateLongHelp(c *ParseContext) error {
|
||||||
|
a.Writer(os.Stdout)
|
||||||
|
if err := a.UsageForContextWithTemplate(c, 2, LongHelpTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.terminate(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) generateManPage(c *ParseContext) error {
|
||||||
|
a.Writer(os.Stdout)
|
||||||
|
if err := a.UsageForContextWithTemplate(c, 2, ManPageTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.terminate(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) generateBashCompletionScript(c *ParseContext) error {
|
||||||
|
a.Writer(os.Stdout)
|
||||||
|
if err := a.UsageForContextWithTemplate(c, 2, BashCompletionTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.terminate(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) generateZSHCompletionScript(c *ParseContext) error {
|
||||||
|
a.Writer(os.Stdout)
|
||||||
|
if err := a.UsageForContextWithTemplate(c, 2, ZshCompletionTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.terminate(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultEnvars configures all flags (that do not already have an associated
|
||||||
|
// envar) to use a default environment variable in the form "<app>_<flag>".
|
||||||
|
//
|
||||||
|
// For example, if the application is named "foo" and a flag is named "bar-
|
||||||
|
// waz" the environment variable: "FOO_BAR_WAZ".
|
||||||
|
func (a *Application) DefaultEnvars() *Application {
|
||||||
|
a.defaultEnvars = true
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate specifies the termination handler. Defaults to os.Exit(status).
|
||||||
|
// If nil is passed, a no-op function will be used.
|
||||||
|
func (a *Application) Terminate(terminate func(int)) *Application {
|
||||||
|
if terminate == nil {
|
||||||
|
terminate = func(int) {}
|
||||||
|
}
|
||||||
|
a.terminate = terminate
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer specifies the writer to use for usage and errors. Defaults to os.Stderr.
|
||||||
|
// DEPRECATED: See ErrorWriter and UsageWriter.
|
||||||
|
func (a *Application) Writer(w io.Writer) *Application {
|
||||||
|
a.errorWriter = w
|
||||||
|
a.usageWriter = w
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorWriter sets the io.Writer to use for errors.
|
||||||
|
func (a *Application) ErrorWriter(w io.Writer) *Application {
|
||||||
|
a.errorWriter = w
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageWriter sets the io.Writer to use for errors.
|
||||||
|
func (a *Application) UsageWriter(w io.Writer) *Application {
|
||||||
|
a.usageWriter = w
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageTemplate specifies the text template to use when displaying usage
|
||||||
|
// information. The default is UsageTemplate.
|
||||||
|
func (a *Application) UsageTemplate(template string) *Application {
|
||||||
|
a.usageTemplate = template
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sets a validation function to run when parsing.
|
||||||
|
func (a *Application) Validate(validator ApplicationValidator) *Application {
|
||||||
|
a.validator = validator
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseContext parses the given command line and returns the fully populated
|
||||||
|
// ParseContext.
|
||||||
|
func (a *Application) ParseContext(args []string) (*ParseContext, error) {
|
||||||
|
return a.parseContext(false, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseContext, error) {
|
||||||
|
if err := a.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
context := tokenize(args, ignoreDefault)
|
||||||
|
err := parse(context, a)
|
||||||
|
return context, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses command-line arguments. It returns the selected command and an
|
||||||
|
// error. The selected command will be a space separated subcommand, if
|
||||||
|
// subcommands have been configured.
|
||||||
|
//
|
||||||
|
// This will populate all flag and argument values, call all callbacks, and so
|
||||||
|
// on.
|
||||||
|
func (a *Application) Parse(args []string) (command string, err error) {
|
||||||
|
|
||||||
|
context, parseErr := a.ParseContext(args)
|
||||||
|
selected := []string{}
|
||||||
|
var setValuesErr error
|
||||||
|
|
||||||
|
if context == nil {
|
||||||
|
// Since we do not throw error immediately, there could be a case
|
||||||
|
// where a context returns nil. Protect against that.
|
||||||
|
return "", parseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = a.setDefaults(context); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, setValuesErr = a.setValues(context)
|
||||||
|
|
||||||
|
if err = a.applyPreActions(context, !a.completion); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.completion {
|
||||||
|
a.generateBashCompletion(context)
|
||||||
|
a.terminate(0)
|
||||||
|
} else {
|
||||||
|
if parseErr != nil {
|
||||||
|
return "", parseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
a.maybeHelp(context)
|
||||||
|
if !context.EOL() {
|
||||||
|
return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
|
||||||
|
}
|
||||||
|
|
||||||
|
if setValuesErr != nil {
|
||||||
|
return "", setValuesErr
|
||||||
|
}
|
||||||
|
|
||||||
|
command, err = a.execute(context, selected)
|
||||||
|
if err == ErrCommandNotSpecified {
|
||||||
|
a.writeUsage(context, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return command, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) writeUsage(context *ParseContext, err error) {
|
||||||
|
if err != nil {
|
||||||
|
a.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
if err := a.UsageForContext(context); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
a.terminate(1)
|
||||||
|
} else {
|
||||||
|
a.terminate(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) maybeHelp(context *ParseContext) {
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if flag, ok := element.Clause.(*FlagClause); ok && flag == a.HelpFlag {
|
||||||
|
// Re-parse the command-line ignoring defaults, so that help works correctly.
|
||||||
|
context, _ = a.parseContext(true, context.rawArgs)
|
||||||
|
a.writeUsage(context, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version adds a --version flag for displaying the application version.
|
||||||
|
func (a *Application) Version(version string) *Application {
|
||||||
|
a.version = version
|
||||||
|
a.VersionFlag = a.Flag("version", "Show application version.").PreAction(func(*ParseContext) error {
|
||||||
|
fmt.Fprintln(a.usageWriter, version)
|
||||||
|
a.terminate(0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
a.VersionFlag.Bool()
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author sets the author output by some help templates.
|
||||||
|
func (a *Application) Author(author string) *Application {
|
||||||
|
a.author = author
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action callback to call when all values are populated and parsing is
|
||||||
|
// complete, but before any command, flag or argument actions.
|
||||||
|
//
|
||||||
|
// All Action() callbacks are called in the order they are encountered on the
|
||||||
|
// command line.
|
||||||
|
func (a *Application) Action(action Action) *Application {
|
||||||
|
a.addAction(action)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action called after parsing completes but before validation and execution.
|
||||||
|
func (a *Application) PreAction(action Action) *Application {
|
||||||
|
a.addPreAction(action)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command adds a new top-level command.
|
||||||
|
func (a *Application) Command(name, help string) *CmdClause {
|
||||||
|
return a.addCommand(name, help)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interspersed control if flags can be interspersed with positional arguments
|
||||||
|
//
|
||||||
|
// true (the default) means that they can, false means that all the flags must appear before the first positional arguments.
|
||||||
|
func (a *Application) Interspersed(interspersed bool) *Application {
|
||||||
|
a.noInterspersed = !interspersed
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) defaultEnvarPrefix() string {
|
||||||
|
if a.defaultEnvars {
|
||||||
|
return a.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) init() error {
|
||||||
|
if a.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if a.cmdGroup.have() && a.argGroup.have() {
|
||||||
|
return fmt.Errorf("can't mix top-level Arg()s with Command()s")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have subcommands, add a help command at the top-level.
|
||||||
|
if a.cmdGroup.have() {
|
||||||
|
var command []string
|
||||||
|
a.HelpCommand = a.Command("help", "Show help.").PreAction(func(context *ParseContext) error {
|
||||||
|
a.Usage(command)
|
||||||
|
a.terminate(0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
a.HelpCommand.Arg("command", "Show help on command.").StringsVar(&command)
|
||||||
|
// Make help first command.
|
||||||
|
l := len(a.commandOrder)
|
||||||
|
a.commandOrder = append(a.commandOrder[l-1:l], a.commandOrder[:l-1]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.flagGroup.init(a.defaultEnvarPrefix()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := a.cmdGroup.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := a.argGroup.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, cmd := range a.commands {
|
||||||
|
if err := cmd.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flagGroups := []*flagGroup{a.flagGroup}
|
||||||
|
for _, cmd := range a.commandOrder {
|
||||||
|
if err := checkDuplicateFlags(cmd, flagGroups); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.initialized = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively check commands for duplicate flags.
|
||||||
|
func checkDuplicateFlags(current *CmdClause, flagGroups []*flagGroup) error {
|
||||||
|
// Check for duplicates.
|
||||||
|
for _, flags := range flagGroups {
|
||||||
|
for _, flag := range current.flagOrder {
|
||||||
|
if flag.shorthand != 0 {
|
||||||
|
if _, ok := flags.short[string(flag.shorthand)]; ok {
|
||||||
|
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := flags.long[flag.name]; ok {
|
||||||
|
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flagGroups = append(flagGroups, current.flagGroup)
|
||||||
|
// Check subcommands.
|
||||||
|
for _, subcmd := range current.commandOrder {
|
||||||
|
if err := checkDuplicateFlags(subcmd, flagGroups); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) execute(context *ParseContext, selected []string) (string, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = a.validateRequired(context); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = a.applyValidators(context); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = a.applyActions(context); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
command := strings.Join(selected, " ")
|
||||||
|
if command == "" && a.cmdGroup.have() {
|
||||||
|
return "", ErrCommandNotSpecified
|
||||||
|
}
|
||||||
|
return command, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) setDefaults(context *ParseContext) error {
|
||||||
|
flagElements := map[string]*ParseElement{}
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||||
|
flagElements[flag.name] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
argElements := map[string]*ParseElement{}
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||||
|
argElements[arg.name] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check required flags and set defaults.
|
||||||
|
for _, flag := range context.flags.long {
|
||||||
|
if flagElements[flag.name] == nil {
|
||||||
|
if err := flag.setDefault(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range context.arguments.args {
|
||||||
|
if argElements[arg.name] == nil {
|
||||||
|
if err := arg.setDefault(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) validateRequired(context *ParseContext) error {
|
||||||
|
flagElements := map[string]*ParseElement{}
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||||
|
flagElements[flag.name] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
argElements := map[string]*ParseElement{}
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||||
|
argElements[arg.name] = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check required flags and set defaults.
|
||||||
|
for _, flag := range context.flags.long {
|
||||||
|
if flagElements[flag.name] == nil {
|
||||||
|
// Check required flags were provided.
|
||||||
|
if flag.needsValue() {
|
||||||
|
return fmt.Errorf("required flag --%s not provided", flag.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range context.arguments.args {
|
||||||
|
if argElements[arg.name] == nil {
|
||||||
|
if arg.needsValue() {
|
||||||
|
return fmt.Errorf("required argument '%s' not provided", arg.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) setValues(context *ParseContext) (selected []string, err error) {
|
||||||
|
// Set all arg and flag values.
|
||||||
|
var (
|
||||||
|
lastCmd *CmdClause
|
||||||
|
flagSet = map[string]struct{}{}
|
||||||
|
)
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
switch clause := element.Clause.(type) {
|
||||||
|
case *FlagClause:
|
||||||
|
if _, ok := flagSet[clause.name]; ok {
|
||||||
|
if v, ok := clause.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||||
|
return nil, fmt.Errorf("flag '%s' cannot be repeated", clause.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = clause.value.Set(*element.Value); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
flagSet[clause.name] = struct{}{}
|
||||||
|
|
||||||
|
case *ArgClause:
|
||||||
|
if err = clause.value.Set(*element.Value); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case *CmdClause:
|
||||||
|
if clause.validator != nil {
|
||||||
|
if err = clause.validator(clause); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selected = append(selected, clause.name)
|
||||||
|
lastCmd = clause
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastCmd != nil && len(lastCmd.commands) > 0 {
|
||||||
|
return nil, fmt.Errorf("must select a subcommand of '%s'", lastCmd.FullCommand())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) applyValidators(context *ParseContext) (err error) {
|
||||||
|
// Call command validation functions.
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if cmd, ok := element.Clause.(*CmdClause); ok && cmd.validator != nil {
|
||||||
|
if err = cmd.validator(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.validator != nil {
|
||||||
|
err = a.validator(a)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) applyPreActions(context *ParseContext, dispatch bool) error {
|
||||||
|
if err := a.actionMixin.applyPreActions(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Dispatch to actions.
|
||||||
|
if dispatch {
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if applier, ok := element.Clause.(actionApplier); ok {
|
||||||
|
if err := applier.applyPreActions(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) applyActions(context *ParseContext) error {
|
||||||
|
if err := a.actionMixin.applyActions(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Dispatch to actions.
|
||||||
|
for _, element := range context.Elements {
|
||||||
|
if applier, ok := element.Clause.(actionApplier); ok {
|
||||||
|
if err := applier.applyActions(context); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf prints an error message to w in the format "<appname>: error: <message>".
|
||||||
|
func (a *Application) Errorf(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(a.errorWriter, a.Name+": error: "+format+"\n", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf writes a formatted error to w then terminates with exit status 1.
|
||||||
|
func (a *Application) Fatalf(format string, args ...interface{}) {
|
||||||
|
a.Errorf(format, args...)
|
||||||
|
a.terminate(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalUsage prints an error message followed by usage information, then
|
||||||
|
// exits with a non-zero status.
|
||||||
|
func (a *Application) FatalUsage(format string, args ...interface{}) {
|
||||||
|
a.Errorf(format, args...)
|
||||||
|
// Force usage to go to error output.
|
||||||
|
a.usageWriter = a.errorWriter
|
||||||
|
a.Usage([]string{})
|
||||||
|
a.terminate(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalUsageContext writes a printf formatted error message to w, then usage
|
||||||
|
// information for the given ParseContext, before exiting.
|
||||||
|
func (a *Application) FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||||
|
a.Errorf(format, args...)
|
||||||
|
if err := a.UsageForContext(context); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
a.terminate(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||||
|
// with the given formatted string, if any.
|
||||||
|
func (a *Application) FatalIfError(err error, format string, args ...interface{}) {
|
||||||
|
if err != nil {
|
||||||
|
prefix := ""
|
||||||
|
if format != "" {
|
||||||
|
prefix = fmt.Sprintf(format, args...) + ": "
|
||||||
|
}
|
||||||
|
a.Errorf(prefix+"%s", err)
|
||||||
|
a.terminate(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) completionOptions(context *ParseContext) []string {
|
||||||
|
args := context.rawArgs
|
||||||
|
|
||||||
|
var (
|
||||||
|
currArg string
|
||||||
|
prevArg string
|
||||||
|
target cmdMixin
|
||||||
|
)
|
||||||
|
|
||||||
|
numArgs := len(args)
|
||||||
|
if numArgs > 1 {
|
||||||
|
args = args[1:]
|
||||||
|
currArg = args[len(args)-1]
|
||||||
|
}
|
||||||
|
if numArgs > 2 {
|
||||||
|
prevArg = args[len(args)-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
target = a.cmdMixin
|
||||||
|
if context.SelectedCommand != nil {
|
||||||
|
// A subcommand was in use. We will use it as the target
|
||||||
|
target = context.SelectedCommand.cmdMixin
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currArg != "" && strings.HasPrefix(currArg, "--")) || strings.HasPrefix(prevArg, "--") {
|
||||||
|
// Perform completion for A flag. The last/current argument started with "-"
|
||||||
|
var (
|
||||||
|
flagName string // The name of a flag if given (could be half complete)
|
||||||
|
flagValue string // The value assigned to a flag (if given) (could be half complete)
|
||||||
|
)
|
||||||
|
|
||||||
|
if strings.HasPrefix(prevArg, "--") && !strings.HasPrefix(currArg, "--") {
|
||||||
|
// Matches: ./myApp --flag value
|
||||||
|
// Wont Match: ./myApp --flag --
|
||||||
|
flagName = prevArg[2:] // Strip the "--"
|
||||||
|
flagValue = currArg
|
||||||
|
} else if strings.HasPrefix(currArg, "--") {
|
||||||
|
// Matches: ./myApp --flag --
|
||||||
|
// Matches: ./myApp --flag somevalue --
|
||||||
|
// Matches: ./myApp --
|
||||||
|
flagName = currArg[2:] // Strip the "--"
|
||||||
|
}
|
||||||
|
|
||||||
|
options, flagMatched, valueMatched := target.FlagCompletion(flagName, flagValue)
|
||||||
|
if valueMatched {
|
||||||
|
// Value Matched. Show cmdCompletions
|
||||||
|
return target.CmdCompletion(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add top level flags if we're not at the top level and no match was found.
|
||||||
|
if context.SelectedCommand != nil && !flagMatched {
|
||||||
|
topOptions, topFlagMatched, topValueMatched := a.FlagCompletion(flagName, flagValue)
|
||||||
|
if topValueMatched {
|
||||||
|
// Value Matched. Back to cmdCompletions
|
||||||
|
return target.CmdCompletion(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
if topFlagMatched {
|
||||||
|
// Top level had a flag which matched the input. Return it's options.
|
||||||
|
options = topOptions
|
||||||
|
} else {
|
||||||
|
// Add top level flags
|
||||||
|
options = append(options, topOptions...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform completion for sub commands and arguments.
|
||||||
|
return target.CmdCompletion(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) generateBashCompletion(context *ParseContext) {
|
||||||
|
options := a.completionOptions(context)
|
||||||
|
fmt.Printf("%s", strings.Join(options, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func envarTransform(name string) string {
|
||||||
|
return strings.ToUpper(envarTransformRegexp.ReplaceAllString(name, "_"))
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type argGroup struct {
|
||||||
|
args []*ArgClause
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArgGroup() *argGroup {
|
||||||
|
return &argGroup{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *argGroup) have() bool {
|
||||||
|
return len(a.args) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArg gets an argument definition.
|
||||||
|
//
|
||||||
|
// This allows existing arguments to be modified after definition but before parsing. Useful for
|
||||||
|
// modular applications.
|
||||||
|
func (a *argGroup) GetArg(name string) *ArgClause {
|
||||||
|
for _, arg := range a.args {
|
||||||
|
if arg.name == name {
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *argGroup) Arg(name, help string) *ArgClause {
|
||||||
|
arg := newArg(name, help)
|
||||||
|
a.args = append(a.args, arg)
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *argGroup) init() error {
|
||||||
|
required := 0
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
previousArgMustBeLast := false
|
||||||
|
for i, arg := range a.args {
|
||||||
|
if previousArgMustBeLast {
|
||||||
|
return fmt.Errorf("Args() can't be followed by another argument '%s'", arg.name)
|
||||||
|
}
|
||||||
|
if arg.consumesRemainder() {
|
||||||
|
previousArgMustBeLast = true
|
||||||
|
}
|
||||||
|
if _, ok := seen[arg.name]; ok {
|
||||||
|
return fmt.Errorf("duplicate argument '%s'", arg.name)
|
||||||
|
}
|
||||||
|
seen[arg.name] = struct{}{}
|
||||||
|
if arg.required && required != i {
|
||||||
|
return fmt.Errorf("required arguments found after non-required")
|
||||||
|
}
|
||||||
|
if arg.required {
|
||||||
|
required++
|
||||||
|
}
|
||||||
|
if err := arg.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArgClause struct {
|
||||||
|
actionMixin
|
||||||
|
parserMixin
|
||||||
|
completionsMixin
|
||||||
|
envarMixin
|
||||||
|
name string
|
||||||
|
help string
|
||||||
|
defaultValues []string
|
||||||
|
required bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArg(name, help string) *ArgClause {
|
||||||
|
a := &ArgClause{
|
||||||
|
name: name,
|
||||||
|
help: help,
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) setDefault() error {
|
||||||
|
if a.HasEnvarValue() {
|
||||||
|
if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() {
|
||||||
|
// Use the value as-is
|
||||||
|
return a.value.Set(a.GetEnvarValue())
|
||||||
|
}
|
||||||
|
for _, value := range a.GetSplitEnvarValue() {
|
||||||
|
if err := a.value.Set(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.defaultValues) > 0 {
|
||||||
|
for _, defaultValue := range a.defaultValues {
|
||||||
|
if err := a.value.Set(defaultValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) needsValue() bool {
|
||||||
|
haveDefault := len(a.defaultValues) > 0
|
||||||
|
return a.required && !(haveDefault || a.HasEnvarValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) consumesRemainder() bool {
|
||||||
|
if r, ok := a.value.(remainderArg); ok {
|
||||||
|
return r.IsCumulative()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required arguments must be input by the user. They can not have a Default() value provided.
|
||||||
|
func (a *ArgClause) Required() *ArgClause {
|
||||||
|
a.required = true
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values for this argument. They *must* be parseable by the value of the argument.
|
||||||
|
func (a *ArgClause) Default(values ...string) *ArgClause {
|
||||||
|
a.defaultValues = values
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||||
|
// if it is set. Several default values can be provided by using new lines to
|
||||||
|
// separate them.
|
||||||
|
func (a *ArgClause) Envar(name string) *ArgClause {
|
||||||
|
a.envar = name
|
||||||
|
a.noEnvar = false
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||||
|
// Most useful in conjunction with app.DefaultEnvars().
|
||||||
|
func (a *ArgClause) NoEnvar() *ArgClause {
|
||||||
|
a.envar = ""
|
||||||
|
a.noEnvar = true
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) Action(action Action) *ArgClause {
|
||||||
|
a.addAction(action)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) PreAction(action Action) *ArgClause {
|
||||||
|
a.addPreAction(action)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// HintAction registers a HintAction (function) for the arg to provide completions
|
||||||
|
func (a *ArgClause) HintAction(action HintAction) *ArgClause {
|
||||||
|
a.addHintAction(action)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// HintOptions registers any number of options for the flag to provide completions
|
||||||
|
func (a *ArgClause) HintOptions(options ...string) *ArgClause {
|
||||||
|
a.addHintAction(func() []string {
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) init() error {
|
||||||
|
if a.required && len(a.defaultValues) > 0 {
|
||||||
|
return fmt.Errorf("required argument '%s' with unusable default value", a.name)
|
||||||
|
}
|
||||||
|
if a.value == nil {
|
||||||
|
return fmt.Errorf("no parser defined for arg '%s'", a.name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmdMixin struct {
|
||||||
|
*flagGroup
|
||||||
|
*argGroup
|
||||||
|
*cmdGroup
|
||||||
|
actionMixin
|
||||||
|
}
|
||||||
|
|
||||||
|
// CmdCompletion returns completion options for arguments, if that's where
|
||||||
|
// parsing left off, or commands if there aren't any unsatisfied args.
|
||||||
|
func (c *cmdMixin) CmdCompletion(context *ParseContext) []string {
|
||||||
|
var options []string
|
||||||
|
|
||||||
|
// Count args already satisfied - we won't complete those, and add any
|
||||||
|
// default commands' alternatives, since they weren't listed explicitly
|
||||||
|
// and the user may want to explicitly list something else.
|
||||||
|
argsSatisfied := 0
|
||||||
|
for _, el := range context.Elements {
|
||||||
|
switch clause := el.Clause.(type) {
|
||||||
|
case *ArgClause:
|
||||||
|
if el.Value != nil && *el.Value != "" {
|
||||||
|
argsSatisfied++
|
||||||
|
}
|
||||||
|
case *CmdClause:
|
||||||
|
options = append(options, clause.completionAlts...)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if argsSatisfied < len(c.argGroup.args) {
|
||||||
|
// Since not all args have been satisfied, show options for the current one
|
||||||
|
options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...)
|
||||||
|
} else {
|
||||||
|
// If all args are satisfied, then go back to completing commands
|
||||||
|
for _, cmd := range c.cmdGroup.commandOrder {
|
||||||
|
if !cmd.hidden {
|
||||||
|
options = append(options, cmd.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) {
|
||||||
|
// Check if flagName matches a known flag.
|
||||||
|
// If it does, show the options for the flag
|
||||||
|
// Otherwise, show all flags
|
||||||
|
|
||||||
|
options := []string{}
|
||||||
|
|
||||||
|
for _, flag := range c.flagGroup.flagOrder {
|
||||||
|
// Loop through each flag and determine if a match exists
|
||||||
|
if flag.name == flagName {
|
||||||
|
// User typed entire flag. Need to look for flag options.
|
||||||
|
options = flag.resolveCompletions()
|
||||||
|
if len(options) == 0 {
|
||||||
|
// No Options to Choose From, Assume Match.
|
||||||
|
return options, true, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop options to find if the user specified value matches
|
||||||
|
isPrefix := false
|
||||||
|
matched := false
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
if flagValue == opt {
|
||||||
|
matched = true
|
||||||
|
} else if strings.HasPrefix(opt, flagValue) {
|
||||||
|
isPrefix = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matched Flag Directly
|
||||||
|
// Flag Value Not Prefixed, and Matched Directly
|
||||||
|
return options, true, !isPrefix && matched
|
||||||
|
}
|
||||||
|
|
||||||
|
if !flag.hidden {
|
||||||
|
options = append(options, "--"+flag.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No Flag directly matched.
|
||||||
|
return options, false, false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type cmdGroup struct {
|
||||||
|
app *Application
|
||||||
|
parent *CmdClause
|
||||||
|
commands map[string]*CmdClause
|
||||||
|
commandOrder []*CmdClause
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) defaultSubcommand() *CmdClause {
|
||||||
|
for _, cmd := range c.commandOrder {
|
||||||
|
if cmd.isDefault {
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) cmdNames() []string {
|
||||||
|
names := make([]string, 0, len(c.commandOrder))
|
||||||
|
for _, cmd := range c.commandOrder {
|
||||||
|
names = append(names, cmd.name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArg gets a command definition.
|
||||||
|
//
|
||||||
|
// This allows existing commands to be modified after definition but before parsing. Useful for
|
||||||
|
// modular applications.
|
||||||
|
func (c *cmdGroup) GetCommand(name string) *CmdClause {
|
||||||
|
return c.commands[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCmdGroup(app *Application) *cmdGroup {
|
||||||
|
return &cmdGroup{
|
||||||
|
app: app,
|
||||||
|
commands: make(map[string]*CmdClause),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) flattenedCommands() (out []*CmdClause) {
|
||||||
|
for _, cmd := range c.commandOrder {
|
||||||
|
if len(cmd.commands) == 0 {
|
||||||
|
out = append(out, cmd)
|
||||||
|
}
|
||||||
|
out = append(out, cmd.flattenedCommands()...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) addCommand(name, help string) *CmdClause {
|
||||||
|
cmd := newCommand(c.app, name, help)
|
||||||
|
c.commands[name] = cmd
|
||||||
|
c.commandOrder = append(c.commandOrder, cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) init() error {
|
||||||
|
seen := map[string]bool{}
|
||||||
|
if c.defaultSubcommand() != nil && !c.have() {
|
||||||
|
return fmt.Errorf("default subcommand %q provided but no subcommands defined", c.defaultSubcommand().name)
|
||||||
|
}
|
||||||
|
defaults := []string{}
|
||||||
|
for _, cmd := range c.commandOrder {
|
||||||
|
if cmd.isDefault {
|
||||||
|
defaults = append(defaults, cmd.name)
|
||||||
|
}
|
||||||
|
if seen[cmd.name] {
|
||||||
|
return fmt.Errorf("duplicate command %q", cmd.name)
|
||||||
|
}
|
||||||
|
seen[cmd.name] = true
|
||||||
|
for _, alias := range cmd.aliases {
|
||||||
|
if seen[alias] {
|
||||||
|
return fmt.Errorf("alias duplicates existing command %q", alias)
|
||||||
|
}
|
||||||
|
c.commands[alias] = cmd
|
||||||
|
}
|
||||||
|
if err := cmd.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(defaults) > 1 {
|
||||||
|
return fmt.Errorf("more than one default subcommand exists: %s", strings.Join(defaults, ", "))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) have() bool {
|
||||||
|
return len(c.commands) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmdClauseValidator func(*CmdClause) error
|
||||||
|
|
||||||
|
// A CmdClause is a single top-level command. It encapsulates a set of flags
|
||||||
|
// and either subcommands or positional arguments.
|
||||||
|
type CmdClause struct {
|
||||||
|
cmdMixin
|
||||||
|
app *Application
|
||||||
|
name string
|
||||||
|
aliases []string
|
||||||
|
help string
|
||||||
|
isDefault bool
|
||||||
|
validator CmdClauseValidator
|
||||||
|
hidden bool
|
||||||
|
completionAlts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommand(app *Application, name, help string) *CmdClause {
|
||||||
|
c := &CmdClause{
|
||||||
|
app: app,
|
||||||
|
name: name,
|
||||||
|
help: help,
|
||||||
|
}
|
||||||
|
c.flagGroup = newFlagGroup()
|
||||||
|
c.argGroup = newArgGroup()
|
||||||
|
c.cmdGroup = newCmdGroup(app)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an Alias for this command.
|
||||||
|
func (c *CmdClause) Alias(name string) *CmdClause {
|
||||||
|
c.aliases = append(c.aliases, name)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sets a validation function to run when parsing.
|
||||||
|
func (c *CmdClause) Validate(validator CmdClauseValidator) *CmdClause {
|
||||||
|
c.validator = validator
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdClause) FullCommand() string {
|
||||||
|
out := []string{c.name}
|
||||||
|
for p := c.parent; p != nil; p = p.parent {
|
||||||
|
out = append([]string{p.name}, out...)
|
||||||
|
}
|
||||||
|
return strings.Join(out, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command adds a new sub-command.
|
||||||
|
func (c *CmdClause) Command(name, help string) *CmdClause {
|
||||||
|
cmd := c.addCommand(name, help)
|
||||||
|
cmd.parent = c
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default makes this command the default if commands don't match.
|
||||||
|
func (c *CmdClause) Default() *CmdClause {
|
||||||
|
c.isDefault = true
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdClause) Action(action Action) *CmdClause {
|
||||||
|
c.addAction(action)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdClause) PreAction(action Action) *CmdClause {
|
||||||
|
c.addPreAction(action)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdClause) init() error {
|
||||||
|
if err := c.flagGroup.init(c.app.defaultEnvarPrefix()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.argGroup.have() && c.cmdGroup.have() {
|
||||||
|
return fmt.Errorf("can't mix Arg()s with Command()s")
|
||||||
|
}
|
||||||
|
if err := c.argGroup.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.cmdGroup.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdClause) Hidden() *CmdClause {
|
||||||
|
c.hidden = true
|
||||||
|
return c
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
// HintAction is a function type who is expected to return a slice of possible
|
||||||
|
// command line arguments.
|
||||||
|
type HintAction func() []string
|
||||||
|
type completionsMixin struct {
|
||||||
|
hintActions []HintAction
|
||||||
|
builtinHintActions []HintAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *completionsMixin) addHintAction(action HintAction) {
|
||||||
|
a.hintActions = append(a.hintActions, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow adding of HintActions which are added internally, ie, EnumVar
|
||||||
|
func (a *completionsMixin) addHintActionBuiltin(action HintAction) {
|
||||||
|
a.builtinHintActions = append(a.builtinHintActions, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *completionsMixin) resolveCompletions() []string {
|
||||||
|
var hints []string
|
||||||
|
|
||||||
|
options := a.builtinHintActions
|
||||||
|
if len(a.hintActions) > 0 {
|
||||||
|
// User specified their own hintActions. Use those instead.
|
||||||
|
options = a.hintActions
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hintAction := range options {
|
||||||
|
hints = append(hints, hintAction()...)
|
||||||
|
}
|
||||||
|
return hints
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Package kingpin provides command line interfaces like this:
|
||||||
|
//
|
||||||
|
// $ chat
|
||||||
|
// usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||||
|
//
|
||||||
|
// Flags:
|
||||||
|
// --debug enable debug mode
|
||||||
|
// --help Show help.
|
||||||
|
// --server=127.0.0.1 server address
|
||||||
|
//
|
||||||
|
// Commands:
|
||||||
|
// help <command>
|
||||||
|
// Show help for a command.
|
||||||
|
//
|
||||||
|
// post [<flags>] <channel>
|
||||||
|
// Post a message to a channel.
|
||||||
|
//
|
||||||
|
// register <nick> <name>
|
||||||
|
// Register a new user.
|
||||||
|
//
|
||||||
|
// $ chat help post
|
||||||
|
// usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||||
|
//
|
||||||
|
// Post a message to a channel.
|
||||||
|
//
|
||||||
|
// Flags:
|
||||||
|
// --image=IMAGE image to post
|
||||||
|
//
|
||||||
|
// Args:
|
||||||
|
// <channel> channel to post to
|
||||||
|
// [<text>] text to post
|
||||||
|
// $ chat post --image=~/Downloads/owls.jpg pics
|
||||||
|
//
|
||||||
|
// From code like this:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import "gopkg.in/alecthomas/kingpin.v2"
|
||||||
|
//
|
||||||
|
// var (
|
||||||
|
// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool()
|
||||||
|
// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP()
|
||||||
|
//
|
||||||
|
// register = kingpin.Command("register", "Register a new user.")
|
||||||
|
// registerNick = register.Arg("nick", "nickname for user").Required().String()
|
||||||
|
// registerName = register.Arg("name", "name of user").Required().String()
|
||||||
|
//
|
||||||
|
// post = kingpin.Command("post", "Post a message to a channel.")
|
||||||
|
// postImage = post.Flag("image", "image to post").ExistingFile()
|
||||||
|
// postChannel = post.Arg("channel", "channel to post to").Required().String()
|
||||||
|
// postText = post.Arg("text", "text to post").String()
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// switch kingpin.Parse() {
|
||||||
|
// // Register user
|
||||||
|
// case "register":
|
||||||
|
// println(*registerNick)
|
||||||
|
//
|
||||||
|
// // Post message
|
||||||
|
// case "post":
|
||||||
|
// if *postImage != nil {
|
||||||
|
// }
|
||||||
|
// if *postText != "" {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
package kingpin
|
|
@ -0,0 +1,45 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
envVarValuesSeparator = "\r?\n"
|
||||||
|
envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$")
|
||||||
|
envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator)
|
||||||
|
)
|
||||||
|
|
||||||
|
type envarMixin struct {
|
||||||
|
envar string
|
||||||
|
noEnvar bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envarMixin) HasEnvarValue() bool {
|
||||||
|
return e.GetEnvarValue() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envarMixin) GetEnvarValue() string {
|
||||||
|
if e.noEnvar || e.envar == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return os.Getenv(e.envar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *envarMixin) GetSplitEnvarValue() []string {
|
||||||
|
values := make([]string, 0)
|
||||||
|
|
||||||
|
envarValue := e.GetEnvarValue()
|
||||||
|
if envarValue == "" {
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by new line to extract multiple values, if any.
|
||||||
|
trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "")
|
||||||
|
for _, value := range envVarValuesSplitter.Split(trimmed, -1) {
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
|
@ -0,0 +1,308 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flagGroup struct {
|
||||||
|
short map[string]*FlagClause
|
||||||
|
long map[string]*FlagClause
|
||||||
|
flagOrder []*FlagClause
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFlagGroup() *flagGroup {
|
||||||
|
return &flagGroup{
|
||||||
|
short: map[string]*FlagClause{},
|
||||||
|
long: map[string]*FlagClause{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFlag gets a flag definition.
|
||||||
|
//
|
||||||
|
// This allows existing flags to be modified after definition but before parsing. Useful for
|
||||||
|
// modular applications.
|
||||||
|
func (f *flagGroup) GetFlag(name string) *FlagClause {
|
||||||
|
return f.long[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag defines a new flag with the given long name and help.
|
||||||
|
func (f *flagGroup) Flag(name, help string) *FlagClause {
|
||||||
|
flag := newFlag(name, help)
|
||||||
|
f.long[name] = flag
|
||||||
|
f.flagOrder = append(f.flagOrder, flag)
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
||||||
|
if err := f.checkDuplicates(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, flag := range f.long {
|
||||||
|
if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" {
|
||||||
|
flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name)
|
||||||
|
}
|
||||||
|
if err := flag.init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flag.shorthand != 0 {
|
||||||
|
f.short[string(flag.shorthand)] = flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flagGroup) checkDuplicates() error {
|
||||||
|
seenShort := map[rune]bool{}
|
||||||
|
seenLong := map[string]bool{}
|
||||||
|
for _, flag := range f.flagOrder {
|
||||||
|
if flag.shorthand != 0 {
|
||||||
|
if _, ok := seenShort[flag.shorthand]; ok {
|
||||||
|
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||||
|
}
|
||||||
|
seenShort[flag.shorthand] = true
|
||||||
|
}
|
||||||
|
if _, ok := seenLong[flag.name]; ok {
|
||||||
|
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||||
|
}
|
||||||
|
seenLong[flag.name] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) {
|
||||||
|
var token *Token
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
token = context.Peek()
|
||||||
|
switch token.Type {
|
||||||
|
case TokenEOL:
|
||||||
|
break loop
|
||||||
|
|
||||||
|
case TokenLong, TokenShort:
|
||||||
|
flagToken := token
|
||||||
|
defaultValue := ""
|
||||||
|
var flag *FlagClause
|
||||||
|
var ok bool
|
||||||
|
invert := false
|
||||||
|
|
||||||
|
name := token.Value
|
||||||
|
if token.Type == TokenLong {
|
||||||
|
flag, ok = f.long[name]
|
||||||
|
if !ok {
|
||||||
|
if strings.HasPrefix(name, "no-") {
|
||||||
|
name = name[3:]
|
||||||
|
invert = true
|
||||||
|
}
|
||||||
|
flag, ok = f.long[name]
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flag, ok = f.short[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown short flag '%s'", flagToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Next()
|
||||||
|
|
||||||
|
fb, ok := flag.value.(boolFlag)
|
||||||
|
if ok && fb.IsBoolFlag() {
|
||||||
|
if invert {
|
||||||
|
defaultValue = "false"
|
||||||
|
} else {
|
||||||
|
defaultValue = "true"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if invert {
|
||||||
|
context.Push(token)
|
||||||
|
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||||
|
}
|
||||||
|
token = context.Peek()
|
||||||
|
if token.Type != TokenArg {
|
||||||
|
context.Push(token)
|
||||||
|
return nil, fmt.Errorf("expected argument for flag '%s'", flagToken)
|
||||||
|
}
|
||||||
|
context.Next()
|
||||||
|
defaultValue = token.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
context.matchedFlag(flag, defaultValue)
|
||||||
|
return flag, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlagClause is a fluid interface used to build flags.
|
||||||
|
type FlagClause struct {
|
||||||
|
parserMixin
|
||||||
|
actionMixin
|
||||||
|
completionsMixin
|
||||||
|
envarMixin
|
||||||
|
name string
|
||||||
|
shorthand rune
|
||||||
|
help string
|
||||||
|
defaultValues []string
|
||||||
|
placeholder string
|
||||||
|
hidden bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFlag(name, help string) *FlagClause {
|
||||||
|
f := &FlagClause{
|
||||||
|
name: name,
|
||||||
|
help: help,
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagClause) setDefault() error {
|
||||||
|
if f.HasEnvarValue() {
|
||||||
|
if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||||
|
// Use the value as-is
|
||||||
|
return f.value.Set(f.GetEnvarValue())
|
||||||
|
} else {
|
||||||
|
for _, value := range f.GetSplitEnvarValue() {
|
||||||
|
if err := f.value.Set(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(f.defaultValues) > 0 {
|
||||||
|
for _, defaultValue := range f.defaultValues {
|
||||||
|
if err := f.value.Set(defaultValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagClause) needsValue() bool {
|
||||||
|
haveDefault := len(f.defaultValues) > 0
|
||||||
|
return f.required && !(haveDefault || f.HasEnvarValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagClause) init() error {
|
||||||
|
if f.required && len(f.defaultValues) > 0 {
|
||||||
|
return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name)
|
||||||
|
}
|
||||||
|
if f.value == nil {
|
||||||
|
return fmt.Errorf("no type defined for --%s (eg. .String())", f.name)
|
||||||
|
}
|
||||||
|
if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 {
|
||||||
|
return fmt.Errorf("invalid default for '--%s', expecting single value", f.name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch to the given function after the flag is parsed and validated.
|
||||||
|
func (f *FlagClause) Action(action Action) *FlagClause {
|
||||||
|
f.addAction(action)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagClause) PreAction(action Action) *FlagClause {
|
||||||
|
f.addPreAction(action)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// HintAction registers a HintAction (function) for the flag to provide completions
|
||||||
|
func (a *FlagClause) HintAction(action HintAction) *FlagClause {
|
||||||
|
a.addHintAction(action)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// HintOptions registers any number of options for the flag to provide completions
|
||||||
|
func (a *FlagClause) HintOptions(options ...string) *FlagClause {
|
||||||
|
a.addHintAction(func() []string {
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FlagClause) EnumVar(target *string, options ...string) {
|
||||||
|
a.parserMixin.EnumVar(target, options...)
|
||||||
|
a.addHintActionBuiltin(func() []string {
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FlagClause) Enum(options ...string) (target *string) {
|
||||||
|
a.addHintActionBuiltin(func() []string {
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
return a.parserMixin.Enum(options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values for this flag. They *must* be parseable by the value of the flag.
|
||||||
|
func (f *FlagClause) Default(values ...string) *FlagClause {
|
||||||
|
f.defaultValues = values
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED: Use Envar(name) instead.
|
||||||
|
func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause {
|
||||||
|
return f.Envar(envar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||||
|
// if it is set. Several default values can be provided by using new lines to
|
||||||
|
// separate them.
|
||||||
|
func (f *FlagClause) Envar(name string) *FlagClause {
|
||||||
|
f.envar = name
|
||||||
|
f.noEnvar = false
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||||
|
// Most useful in conjunction with app.DefaultEnvars().
|
||||||
|
func (f *FlagClause) NoEnvar() *FlagClause {
|
||||||
|
f.envar = ""
|
||||||
|
f.noEnvar = true
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlaceHolder sets the place-holder string used for flag values in the help. The
|
||||||
|
// default behaviour is to use the value provided by Default() if provided,
|
||||||
|
// then fall back on the capitalized flag name.
|
||||||
|
func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause {
|
||||||
|
f.placeholder = placeholder
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hidden hides a flag from usage but still allows it to be used.
|
||||||
|
func (f *FlagClause) Hidden() *FlagClause {
|
||||||
|
f.hidden = true
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required makes the flag required. You can not provide a Default() value to a Required() flag.
|
||||||
|
func (f *FlagClause) Required() *FlagClause {
|
||||||
|
f.required = true
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short sets the short flag name.
|
||||||
|
func (f *FlagClause) Short(name rune) *FlagClause {
|
||||||
|
f.shorthand = name
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool makes this flag a boolean flag.
|
||||||
|
func (f *FlagClause) Bool() (target *bool) {
|
||||||
|
target = new(bool)
|
||||||
|
f.SetValue(newBoolValue(target))
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// CommandLine is the default Kingpin parser.
|
||||||
|
CommandLine = New(filepath.Base(os.Args[0]), "")
|
||||||
|
// Global help flag. Exposed for user customisation.
|
||||||
|
HelpFlag = CommandLine.HelpFlag
|
||||||
|
// Top-level help command. Exposed for user customisation. May be nil.
|
||||||
|
HelpCommand = CommandLine.HelpCommand
|
||||||
|
// Global version flag. Exposed for user customisation. May be nil.
|
||||||
|
VersionFlag = CommandLine.VersionFlag
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command adds a new command to the default parser.
|
||||||
|
func Command(name, help string) *CmdClause {
|
||||||
|
return CommandLine.Command(name, help)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag adds a new flag to the default parser.
|
||||||
|
func Flag(name, help string) *FlagClause {
|
||||||
|
return CommandLine.Flag(name, help)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg adds a new argument to the top-level of the default parser.
|
||||||
|
func Arg(name, help string) *ArgClause {
|
||||||
|
return CommandLine.Arg(name, help)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and return the selected command. Will call the termination handler if
|
||||||
|
// an error is encountered.
|
||||||
|
func Parse() string {
|
||||||
|
selected := MustParse(CommandLine.Parse(os.Args[1:]))
|
||||||
|
if selected == "" && CommandLine.cmdGroup.have() {
|
||||||
|
Usage()
|
||||||
|
CommandLine.terminate(0)
|
||||||
|
}
|
||||||
|
return selected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf prints an error message to stderr.
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
CommandLine.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf prints an error message to stderr and exits.
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
CommandLine.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||||
|
// with the given prefix.
|
||||||
|
func FatalIfError(err error, format string, args ...interface{}) {
|
||||||
|
CommandLine.FatalIfError(err, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalUsage prints an error message followed by usage information, then
|
||||||
|
// exits with a non-zero status.
|
||||||
|
func FatalUsage(format string, args ...interface{}) {
|
||||||
|
CommandLine.FatalUsage(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalUsageContext writes a printf formatted error message to stderr, then
|
||||||
|
// usage information for the given ParseContext, before exiting.
|
||||||
|
func FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||||
|
CommandLine.FatalUsageContext(context, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage prints usage to stderr.
|
||||||
|
func Usage() {
|
||||||
|
CommandLine.Usage(os.Args[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set global usage template to use (defaults to DefaultUsageTemplate).
|
||||||
|
func UsageTemplate(template string) *Application {
|
||||||
|
return CommandLine.UsageTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParse can be used with app.Parse(args) to exit with an error if parsing fails.
|
||||||
|
func MustParse(command string, err error) string {
|
||||||
|
if err != nil {
|
||||||
|
Fatalf("%s, try --help", err)
|
||||||
|
}
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version adds a flag for displaying the application version number.
|
||||||
|
func Version(version string) *Application {
|
||||||
|
return CommandLine.Version(version)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||||
|
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func guessWidth(w io.Writer) int {
|
||||||
|
return 80
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
||||||
|
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func guessWidth(w io.Writer) int {
|
||||||
|
// check if COLUMNS env is set to comply with
|
||||||
|
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
|
||||||
|
colsStr := os.Getenv("COLUMNS")
|
||||||
|
if colsStr != "" {
|
||||||
|
if cols, err := strconv.Atoi(colsStr); err == nil {
|
||||||
|
return cols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := w.(*os.File); ok {
|
||||||
|
fd := t.Fd()
|
||||||
|
var dimensions [4]uint16
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall6(
|
||||||
|
syscall.SYS_IOCTL,
|
||||||
|
uintptr(fd),
|
||||||
|
uintptr(syscall.TIOCGWINSZ),
|
||||||
|
uintptr(unsafe.Pointer(&dimensions)),
|
||||||
|
0, 0, 0,
|
||||||
|
); err == 0 {
|
||||||
|
return int(dimensions[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 80
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data model for Kingpin command-line structure.
|
||||||
|
|
||||||
|
type FlagGroupModel struct {
|
||||||
|
Flags []*FlagModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagGroupModel) FlagSummary() string {
|
||||||
|
out := []string{}
|
||||||
|
count := 0
|
||||||
|
for _, flag := range f.Flags {
|
||||||
|
if flag.Name != "help" {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if flag.Required {
|
||||||
|
if flag.IsBoolFlag() {
|
||||||
|
out = append(out, fmt.Sprintf("--[no-]%s", flag.Name))
|
||||||
|
} else {
|
||||||
|
out = append(out, fmt.Sprintf("--%s=%s", flag.Name, flag.FormatPlaceHolder()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count != len(out) {
|
||||||
|
out = append(out, "[<flags>]")
|
||||||
|
}
|
||||||
|
return strings.Join(out, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagModel struct {
|
||||||
|
Name string
|
||||||
|
Help string
|
||||||
|
Short rune
|
||||||
|
Default []string
|
||||||
|
Envar string
|
||||||
|
PlaceHolder string
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagModel) String() string {
|
||||||
|
return f.Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagModel) IsBoolFlag() bool {
|
||||||
|
if fl, ok := f.Value.(boolFlag); ok {
|
||||||
|
return fl.IsBoolFlag()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagModel) FormatPlaceHolder() string {
|
||||||
|
if f.PlaceHolder != "" {
|
||||||
|
return f.PlaceHolder
|
||||||
|
}
|
||||||
|
if len(f.Default) > 0 {
|
||||||
|
ellipsis := ""
|
||||||
|
if len(f.Default) > 1 {
|
||||||
|
ellipsis = "..."
|
||||||
|
}
|
||||||
|
if _, ok := f.Value.(*stringValue); ok {
|
||||||
|
return strconv.Quote(f.Default[0]) + ellipsis
|
||||||
|
}
|
||||||
|
return f.Default[0] + ellipsis
|
||||||
|
}
|
||||||
|
return strings.ToUpper(f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArgGroupModel struct {
|
||||||
|
Args []*ArgModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgGroupModel) ArgSummary() string {
|
||||||
|
depth := 0
|
||||||
|
out := []string{}
|
||||||
|
for _, arg := range a.Args {
|
||||||
|
h := "<" + arg.Name + ">"
|
||||||
|
if !arg.Required {
|
||||||
|
h = "[" + h
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
out = append(out, h)
|
||||||
|
}
|
||||||
|
out[len(out)-1] = out[len(out)-1] + strings.Repeat("]", depth)
|
||||||
|
return strings.Join(out, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArgModel struct {
|
||||||
|
Name string
|
||||||
|
Help string
|
||||||
|
Default []string
|
||||||
|
Envar string
|
||||||
|
Required bool
|
||||||
|
Value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgModel) String() string {
|
||||||
|
return a.Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmdGroupModel struct {
|
||||||
|
Commands []*CmdModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
|
||||||
|
for _, cmd := range c.Commands {
|
||||||
|
if len(cmd.Commands) == 0 {
|
||||||
|
out = append(out, cmd)
|
||||||
|
}
|
||||||
|
out = append(out, cmd.FlattenedCommands()...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmdModel struct {
|
||||||
|
Name string
|
||||||
|
Aliases []string
|
||||||
|
Help string
|
||||||
|
FullCommand string
|
||||||
|
Depth int
|
||||||
|
Hidden bool
|
||||||
|
Default bool
|
||||||
|
*FlagGroupModel
|
||||||
|
*ArgGroupModel
|
||||||
|
*CmdGroupModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdModel) String() string {
|
||||||
|
return c.FullCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplicationModel struct {
|
||||||
|
Name string
|
||||||
|
Help string
|
||||||
|
Version string
|
||||||
|
Author string
|
||||||
|
*ArgGroupModel
|
||||||
|
*CmdGroupModel
|
||||||
|
*FlagGroupModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) Model() *ApplicationModel {
|
||||||
|
return &ApplicationModel{
|
||||||
|
Name: a.Name,
|
||||||
|
Help: a.Help,
|
||||||
|
Version: a.version,
|
||||||
|
Author: a.author,
|
||||||
|
FlagGroupModel: a.flagGroup.Model(),
|
||||||
|
ArgGroupModel: a.argGroup.Model(),
|
||||||
|
CmdGroupModel: a.cmdGroup.Model(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *argGroup) Model() *ArgGroupModel {
|
||||||
|
m := &ArgGroupModel{}
|
||||||
|
for _, arg := range a.args {
|
||||||
|
m.Args = append(m.Args, arg.Model())
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArgClause) Model() *ArgModel {
|
||||||
|
return &ArgModel{
|
||||||
|
Name: a.name,
|
||||||
|
Help: a.help,
|
||||||
|
Default: a.defaultValues,
|
||||||
|
Envar: a.envar,
|
||||||
|
Required: a.required,
|
||||||
|
Value: a.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flagGroup) Model() *FlagGroupModel {
|
||||||
|
m := &FlagGroupModel{}
|
||||||
|
for _, fl := range f.flagOrder {
|
||||||
|
m.Flags = append(m.Flags, fl.Model())
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FlagClause) Model() *FlagModel {
|
||||||
|
return &FlagModel{
|
||||||
|
Name: f.name,
|
||||||
|
Help: f.help,
|
||||||
|
Short: rune(f.shorthand),
|
||||||
|
Default: f.defaultValues,
|
||||||
|
Envar: f.envar,
|
||||||
|
PlaceHolder: f.placeholder,
|
||||||
|
Required: f.required,
|
||||||
|
Hidden: f.hidden,
|
||||||
|
Value: f.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cmdGroup) Model() *CmdGroupModel {
|
||||||
|
m := &CmdGroupModel{}
|
||||||
|
for _, cm := range c.commandOrder {
|
||||||
|
m.Commands = append(m.Commands, cm.Model())
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CmdClause) Model() *CmdModel {
|
||||||
|
depth := 0
|
||||||
|
for i := c; i != nil; i = i.parent {
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
return &CmdModel{
|
||||||
|
Name: c.name,
|
||||||
|
Aliases: c.aliases,
|
||||||
|
Help: c.help,
|
||||||
|
Depth: depth,
|
||||||
|
Hidden: c.hidden,
|
||||||
|
Default: c.isDefault,
|
||||||
|
FullCommand: c.FullCommand(),
|
||||||
|
FlagGroupModel: c.flagGroup.Model(),
|
||||||
|
ArgGroupModel: c.argGroup.Model(),
|
||||||
|
CmdGroupModel: c.cmdGroup.Model(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,384 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenType int
|
||||||
|
|
||||||
|
// Token types.
|
||||||
|
const (
|
||||||
|
TokenShort TokenType = iota
|
||||||
|
TokenLong
|
||||||
|
TokenArg
|
||||||
|
TokenError
|
||||||
|
TokenEOL
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t TokenType) String() string {
|
||||||
|
switch t {
|
||||||
|
case TokenShort:
|
||||||
|
return "short flag"
|
||||||
|
case TokenLong:
|
||||||
|
return "long flag"
|
||||||
|
case TokenArg:
|
||||||
|
return "argument"
|
||||||
|
case TokenError:
|
||||||
|
return "error"
|
||||||
|
case TokenEOL:
|
||||||
|
return "<EOL>"
|
||||||
|
}
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
TokenEOLMarker = Token{-1, TokenEOL, ""}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Index int
|
||||||
|
Type TokenType
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) Equal(o *Token) bool {
|
||||||
|
return t.Index == o.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) IsFlag() bool {
|
||||||
|
return t.Type == TokenShort || t.Type == TokenLong
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) IsEOF() bool {
|
||||||
|
return t.Type == TokenEOL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string {
|
||||||
|
switch t.Type {
|
||||||
|
case TokenShort:
|
||||||
|
return "-" + t.Value
|
||||||
|
case TokenLong:
|
||||||
|
return "--" + t.Value
|
||||||
|
case TokenArg:
|
||||||
|
return t.Value
|
||||||
|
case TokenError:
|
||||||
|
return "error: " + t.Value
|
||||||
|
case TokenEOL:
|
||||||
|
return "<EOL>"
|
||||||
|
default:
|
||||||
|
panic("unhandled type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A union of possible elements in a parse stack.
|
||||||
|
type ParseElement struct {
|
||||||
|
// Clause is either *CmdClause, *ArgClause or *FlagClause.
|
||||||
|
Clause interface{}
|
||||||
|
// Value is corresponding value for an ArgClause or FlagClause (if any).
|
||||||
|
Value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseContext holds the current context of the parser. When passed to
|
||||||
|
// Action() callbacks Elements will be fully populated with *FlagClause,
|
||||||
|
// *ArgClause and *CmdClause values and their corresponding arguments (if
|
||||||
|
// any).
|
||||||
|
type ParseContext struct {
|
||||||
|
SelectedCommand *CmdClause
|
||||||
|
ignoreDefault bool
|
||||||
|
argsOnly bool
|
||||||
|
peek []*Token
|
||||||
|
argi int // Index of current command-line arg we're processing.
|
||||||
|
args []string
|
||||||
|
rawArgs []string
|
||||||
|
flags *flagGroup
|
||||||
|
arguments *argGroup
|
||||||
|
argumenti int // Cursor into arguments
|
||||||
|
// Flags, arguments and commands encountered and collected during parse.
|
||||||
|
Elements []*ParseElement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) nextArg() *ArgClause {
|
||||||
|
if p.argumenti >= len(p.arguments.args) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
arg := p.arguments.args[p.argumenti]
|
||||||
|
if !arg.consumesRemainder() {
|
||||||
|
p.argumenti++
|
||||||
|
}
|
||||||
|
return arg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) next() {
|
||||||
|
p.argi++
|
||||||
|
p.args = p.args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTrailingArgs returns true if there are unparsed command-line arguments.
|
||||||
|
// This can occur if the parser can not match remaining arguments.
|
||||||
|
func (p *ParseContext) HasTrailingArgs() bool {
|
||||||
|
return len(p.args) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenize(args []string, ignoreDefault bool) *ParseContext {
|
||||||
|
return &ParseContext{
|
||||||
|
ignoreDefault: ignoreDefault,
|
||||||
|
args: args,
|
||||||
|
rawArgs: args,
|
||||||
|
flags: newFlagGroup(),
|
||||||
|
arguments: newArgGroup(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) mergeFlags(flags *flagGroup) {
|
||||||
|
for _, flag := range flags.flagOrder {
|
||||||
|
if flag.shorthand != 0 {
|
||||||
|
p.flags.short[string(flag.shorthand)] = flag
|
||||||
|
}
|
||||||
|
p.flags.long[flag.name] = flag
|
||||||
|
p.flags.flagOrder = append(p.flags.flagOrder, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) mergeArgs(args *argGroup) {
|
||||||
|
for _, arg := range args.args {
|
||||||
|
p.arguments.args = append(p.arguments.args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) EOL() bool {
|
||||||
|
return p.Peek().Type == TokenEOL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next token in the parse context.
|
||||||
|
func (p *ParseContext) Next() *Token {
|
||||||
|
if len(p.peek) > 0 {
|
||||||
|
return p.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of tokens.
|
||||||
|
if len(p.args) == 0 {
|
||||||
|
return &Token{Index: p.argi, Type: TokenEOL}
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := p.args[0]
|
||||||
|
p.next()
|
||||||
|
|
||||||
|
if p.argsOnly {
|
||||||
|
return &Token{p.argi, TokenArg, arg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All remaining args are passed directly.
|
||||||
|
if arg == "--" {
|
||||||
|
p.argsOnly = true
|
||||||
|
return p.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(arg, "--") {
|
||||||
|
parts := strings.SplitN(arg[2:], "=", 2)
|
||||||
|
token := &Token{p.argi, TokenLong, parts[0]}
|
||||||
|
if len(parts) == 2 {
|
||||||
|
p.Push(&Token{p.argi, TokenArg, parts[1]})
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(arg, "-") {
|
||||||
|
if len(arg) == 1 {
|
||||||
|
return &Token{Index: p.argi, Type: TokenShort}
|
||||||
|
}
|
||||||
|
shortRune, size := utf8.DecodeRuneInString(arg[1:])
|
||||||
|
short := string(shortRune)
|
||||||
|
flag, ok := p.flags.short[short]
|
||||||
|
// Not a known short flag, we'll just return it anyway.
|
||||||
|
if !ok {
|
||||||
|
} else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() {
|
||||||
|
// Bool short flag.
|
||||||
|
} else {
|
||||||
|
// Short flag with combined argument: -fARG
|
||||||
|
token := &Token{p.argi, TokenShort, short}
|
||||||
|
if len(arg) > size+1 {
|
||||||
|
p.Push(&Token{p.argi, TokenArg, arg[size+1:]})
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(arg) > size+1 {
|
||||||
|
p.args = append([]string{"-" + arg[size+1:]}, p.args...)
|
||||||
|
}
|
||||||
|
return &Token{p.argi, TokenShort, short}
|
||||||
|
} else if strings.HasPrefix(arg, "@") {
|
||||||
|
expanded, err := ExpandArgsFromFile(arg[1:])
|
||||||
|
if err != nil {
|
||||||
|
return &Token{p.argi, TokenError, err.Error()}
|
||||||
|
}
|
||||||
|
if len(p.args) == 0 {
|
||||||
|
p.args = append(p.args, expanded...)
|
||||||
|
} else if p.argi >= len(p.args) {
|
||||||
|
p.args = append(p.args[:p.argi-1], expanded...)
|
||||||
|
} else {
|
||||||
|
p.args = append(p.args[:p.argi-1], append(expanded, p.args[p.argi+1:]...)...)
|
||||||
|
}
|
||||||
|
return p.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Token{p.argi, TokenArg, arg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) Peek() *Token {
|
||||||
|
if len(p.peek) == 0 {
|
||||||
|
return p.Push(p.Next())
|
||||||
|
}
|
||||||
|
return p.peek[len(p.peek)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) Push(token *Token) *Token {
|
||||||
|
p.peek = append(p.peek, token)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) pop() *Token {
|
||||||
|
end := len(p.peek) - 1
|
||||||
|
token := p.peek[end]
|
||||||
|
p.peek = p.peek[0:end]
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) String() string {
|
||||||
|
return p.SelectedCommand.FullCommand()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) matchedFlag(flag *FlagClause, value string) {
|
||||||
|
p.Elements = append(p.Elements, &ParseElement{Clause: flag, Value: &value})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) matchedArg(arg *ArgClause, value string) {
|
||||||
|
p.Elements = append(p.Elements, &ParseElement{Clause: arg, Value: &value})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseContext) matchedCmd(cmd *CmdClause) {
|
||||||
|
p.Elements = append(p.Elements, &ParseElement{Clause: cmd})
|
||||||
|
p.mergeFlags(cmd.flagGroup)
|
||||||
|
p.mergeArgs(cmd.argGroup)
|
||||||
|
p.SelectedCommand = cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand arguments from a file. Lines starting with # will be treated as comments.
|
||||||
|
func ExpandArgsFromFile(filename string) (out []string, err error) {
|
||||||
|
r, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, line)
|
||||||
|
}
|
||||||
|
err = scanner.Err()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(context *ParseContext, app *Application) (err error) {
|
||||||
|
context.mergeFlags(app.flagGroup)
|
||||||
|
context.mergeArgs(app.argGroup)
|
||||||
|
|
||||||
|
cmds := app.cmdGroup
|
||||||
|
ignoreDefault := context.ignoreDefault
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for !context.EOL() {
|
||||||
|
token := context.Peek()
|
||||||
|
|
||||||
|
switch token.Type {
|
||||||
|
case TokenLong, TokenShort:
|
||||||
|
if flag, err := context.flags.parse(context); err != nil {
|
||||||
|
if !ignoreDefault {
|
||||||
|
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||||
|
cmd.completionAlts = cmds.cmdNames()
|
||||||
|
context.matchedCmd(cmd)
|
||||||
|
cmds = cmd.cmdGroup
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if flag == HelpFlag {
|
||||||
|
ignoreDefault = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case TokenArg:
|
||||||
|
if cmds.have() {
|
||||||
|
selectedDefault := false
|
||||||
|
cmd, ok := cmds.commands[token.String()]
|
||||||
|
if !ok {
|
||||||
|
if !ignoreDefault {
|
||||||
|
if cmd = cmds.defaultSubcommand(); cmd != nil {
|
||||||
|
cmd.completionAlts = cmds.cmdNames()
|
||||||
|
selectedDefault = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cmd == nil {
|
||||||
|
return fmt.Errorf("expected command but got %q", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cmd == HelpCommand {
|
||||||
|
ignoreDefault = true
|
||||||
|
}
|
||||||
|
cmd.completionAlts = nil
|
||||||
|
context.matchedCmd(cmd)
|
||||||
|
cmds = cmd.cmdGroup
|
||||||
|
if !selectedDefault {
|
||||||
|
context.Next()
|
||||||
|
}
|
||||||
|
} else if context.arguments.have() {
|
||||||
|
if app.noInterspersed {
|
||||||
|
// no more flags
|
||||||
|
context.argsOnly = true
|
||||||
|
}
|
||||||
|
arg := context.nextArg()
|
||||||
|
if arg == nil {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
context.matchedArg(arg, token.String())
|
||||||
|
context.Next()
|
||||||
|
} else {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
|
case TokenEOL:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to innermost default command.
|
||||||
|
for !ignoreDefault {
|
||||||
|
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||||
|
cmd.completionAlts = cmds.cmdNames()
|
||||||
|
context.matchedCmd(cmd)
|
||||||
|
cmds = cmd.cmdGroup
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !context.EOL() {
|
||||||
|
return fmt.Errorf("unexpected %s", context.Peek())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults for all remaining args.
|
||||||
|
for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() {
|
||||||
|
for _, defaultValue := range arg.defaultValues {
|
||||||
|
if err := arg.value.Set(defaultValue); err != nil {
|
||||||
|
return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alecthomas/units"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings interface {
|
||||||
|
SetValue(value Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type parserMixin struct {
|
||||||
|
value Value
|
||||||
|
required bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) SetValue(value Value) {
|
||||||
|
p.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringMap provides key=value parsing into a map.
|
||||||
|
func (p *parserMixin) StringMap() (target *map[string]string) {
|
||||||
|
target = &(map[string]string{})
|
||||||
|
p.StringMapVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration sets the parser to a time.Duration parser.
|
||||||
|
func (p *parserMixin) Duration() (target *time.Duration) {
|
||||||
|
target = new(time.Duration)
|
||||||
|
p.DurationVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes parses numeric byte units. eg. 1.5KB
|
||||||
|
func (p *parserMixin) Bytes() (target *units.Base2Bytes) {
|
||||||
|
target = new(units.Base2Bytes)
|
||||||
|
p.BytesVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP sets the parser to a net.IP parser.
|
||||||
|
func (p *parserMixin) IP() (target *net.IP) {
|
||||||
|
target = new(net.IP)
|
||||||
|
p.IPVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCP (host:port) address.
|
||||||
|
func (p *parserMixin) TCP() (target **net.TCPAddr) {
|
||||||
|
target = new(*net.TCPAddr)
|
||||||
|
p.TCPVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPVar (host:port) address.
|
||||||
|
func (p *parserMixin) TCPVar(target **net.TCPAddr) {
|
||||||
|
p.SetValue(newTCPAddrValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||||
|
func (p *parserMixin) ExistingFile() (target *string) {
|
||||||
|
target = new(string)
|
||||||
|
p.ExistingFileVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||||
|
func (p *parserMixin) ExistingDir() (target *string) {
|
||||||
|
target = new(string)
|
||||||
|
p.ExistingDirVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingFileOrDir sets the parser to one that requires and returns an existing file OR directory.
|
||||||
|
func (p *parserMixin) ExistingFileOrDir() (target *string) {
|
||||||
|
target = new(string)
|
||||||
|
p.ExistingFileOrDirVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// File returns an os.File against an existing file.
|
||||||
|
func (p *parserMixin) File() (target **os.File) {
|
||||||
|
target = new(*os.File)
|
||||||
|
p.FileVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// File attempts to open a File with os.OpenFile(flag, perm).
|
||||||
|
func (p *parserMixin) OpenFile(flag int, perm os.FileMode) (target **os.File) {
|
||||||
|
target = new(*os.File)
|
||||||
|
p.OpenFileVar(target, flag, perm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL provides a valid, parsed url.URL.
|
||||||
|
func (p *parserMixin) URL() (target **url.URL) {
|
||||||
|
target = new(*url.URL)
|
||||||
|
p.URLVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringMap provides key=value parsing into a map.
|
||||||
|
func (p *parserMixin) StringMapVar(target *map[string]string) {
|
||||||
|
p.SetValue(newStringMapValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float sets the parser to a float64 parser.
|
||||||
|
func (p *parserMixin) Float() (target *float64) {
|
||||||
|
return p.Float64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float sets the parser to a float64 parser.
|
||||||
|
func (p *parserMixin) FloatVar(target *float64) {
|
||||||
|
p.Float64Var(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duration sets the parser to a time.Duration parser.
|
||||||
|
func (p *parserMixin) DurationVar(target *time.Duration) {
|
||||||
|
p.SetValue(newDurationValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesVar parses numeric byte units. eg. 1.5KB
|
||||||
|
func (p *parserMixin) BytesVar(target *units.Base2Bytes) {
|
||||||
|
p.SetValue(newBytesValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP sets the parser to a net.IP parser.
|
||||||
|
func (p *parserMixin) IPVar(target *net.IP) {
|
||||||
|
p.SetValue(newIPValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||||
|
func (p *parserMixin) ExistingFileVar(target *string) {
|
||||||
|
p.SetValue(newExistingFileValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||||
|
func (p *parserMixin) ExistingDirVar(target *string) {
|
||||||
|
p.SetValue(newExistingDirValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||||
|
func (p *parserMixin) ExistingFileOrDirVar(target *string) {
|
||||||
|
p.SetValue(newExistingFileOrDirValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileVar opens an existing file.
|
||||||
|
func (p *parserMixin) FileVar(target **os.File) {
|
||||||
|
p.SetValue(newFileValue(target, os.O_RDONLY, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFileVar calls os.OpenFile(flag, perm)
|
||||||
|
func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) {
|
||||||
|
p.SetValue(newFileValue(target, flag, perm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL provides a valid, parsed url.URL.
|
||||||
|
func (p *parserMixin) URLVar(target **url.URL) {
|
||||||
|
p.SetValue(newURLValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLList provides a parsed list of url.URL values.
|
||||||
|
func (p *parserMixin) URLList() (target *[]*url.URL) {
|
||||||
|
target = new([]*url.URL)
|
||||||
|
p.URLListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLListVar provides a parsed list of url.URL values.
|
||||||
|
func (p *parserMixin) URLListVar(target *[]*url.URL) {
|
||||||
|
p.SetValue(newURLListValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum allows a value from a set of options.
|
||||||
|
func (p *parserMixin) Enum(options ...string) (target *string) {
|
||||||
|
target = new(string)
|
||||||
|
p.EnumVar(target, options...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumVar allows a value from a set of options.
|
||||||
|
func (p *parserMixin) EnumVar(target *string, options ...string) {
|
||||||
|
p.SetValue(newEnumFlag(target, options...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enums allows a set of values from a set of options.
|
||||||
|
func (p *parserMixin) Enums(options ...string) (target *[]string) {
|
||||||
|
target = new([]string)
|
||||||
|
p.EnumsVar(target, options...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumVar allows a value from a set of options.
|
||||||
|
func (p *parserMixin) EnumsVar(target *[]string, options ...string) {
|
||||||
|
p.SetValue(newEnumsFlag(target, options...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Counter increments a number each time it is encountered.
|
||||||
|
func (p *parserMixin) Counter() (target *int) {
|
||||||
|
target = new(int)
|
||||||
|
p.CounterVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) CounterVar(target *int) {
|
||||||
|
p.SetValue(newCounterValue(target))
|
||||||
|
}
|
|
@ -0,0 +1,262 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
// Default usage template.
|
||||||
|
var DefaultUsageTemplate = `{{define "FormatCommand"}}\
|
||||||
|
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||||
|
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatCommands"}}\
|
||||||
|
{{range .FlattenedCommands}}\
|
||||||
|
{{if not .Hidden}}\
|
||||||
|
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||||
|
{{.Help|Wrap 4}}
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatUsage"}}\
|
||||||
|
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||||
|
{{if .Help}}
|
||||||
|
{{.Help|Wrap 0}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{if .Context.SelectedCommand}}\
|
||||||
|
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||||
|
{{else}}\
|
||||||
|
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Flags}}\
|
||||||
|
Flags:
|
||||||
|
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Args}}\
|
||||||
|
Args:
|
||||||
|
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.SelectedCommand}}\
|
||||||
|
{{if len .Context.SelectedCommand.Commands}}\
|
||||||
|
Subcommands:
|
||||||
|
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||||
|
{{end}}\
|
||||||
|
{{else if .App.Commands}}\
|
||||||
|
Commands:
|
||||||
|
{{template "FormatCommands" .App}}
|
||||||
|
{{end}}\
|
||||||
|
`
|
||||||
|
|
||||||
|
// Usage template where command's optional flags are listed separately
|
||||||
|
var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand"}}\
|
||||||
|
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||||
|
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatCommands"}}\
|
||||||
|
{{range .FlattenedCommands}}\
|
||||||
|
{{if not .Hidden}}\
|
||||||
|
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||||
|
{{.Help|Wrap 4}}
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatUsage"}}\
|
||||||
|
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||||
|
{{if .Help}}
|
||||||
|
{{.Help|Wrap 0}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.SelectedCommand}}\
|
||||||
|
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||||
|
{{else}}\
|
||||||
|
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{if .Context.Flags|RequiredFlags}}\
|
||||||
|
Required flags:
|
||||||
|
{{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Flags|OptionalFlags}}\
|
||||||
|
Optional flags:
|
||||||
|
{{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Args}}\
|
||||||
|
Args:
|
||||||
|
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.SelectedCommand}}\
|
||||||
|
Subcommands:
|
||||||
|
{{if .Context.SelectedCommand.Commands}}\
|
||||||
|
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||||
|
{{end}}\
|
||||||
|
{{else if .App.Commands}}\
|
||||||
|
Commands:
|
||||||
|
{{template "FormatCommands" .App}}
|
||||||
|
{{end}}\
|
||||||
|
`
|
||||||
|
|
||||||
|
// Usage template with compactly formatted commands.
|
||||||
|
var CompactUsageTemplate = `{{define "FormatCommand"}}\
|
||||||
|
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||||
|
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatCommandList"}}\
|
||||||
|
{{range .}}\
|
||||||
|
{{if not .Hidden}}\
|
||||||
|
{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||||
|
{{end}}\
|
||||||
|
{{template "FormatCommandList" .Commands}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatUsage"}}\
|
||||||
|
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||||
|
{{if .Help}}
|
||||||
|
{{.Help|Wrap 0}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{if .Context.SelectedCommand}}\
|
||||||
|
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||||
|
{{else}}\
|
||||||
|
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Flags}}\
|
||||||
|
Flags:
|
||||||
|
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Args}}\
|
||||||
|
Args:
|
||||||
|
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.SelectedCommand}}\
|
||||||
|
{{if .Context.SelectedCommand.Commands}}\
|
||||||
|
Commands:
|
||||||
|
{{.Context.SelectedCommand}}
|
||||||
|
{{template "FormatCommandList" .Context.SelectedCommand.Commands}}
|
||||||
|
{{end}}\
|
||||||
|
{{else if .App.Commands}}\
|
||||||
|
Commands:
|
||||||
|
{{template "FormatCommandList" .App.Commands}}
|
||||||
|
{{end}}\
|
||||||
|
`
|
||||||
|
|
||||||
|
var ManPageTemplate = `{{define "FormatFlags"}}\
|
||||||
|
{{range .Flags}}\
|
||||||
|
{{if not .Hidden}}\
|
||||||
|
.TP
|
||||||
|
\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end}}\\fR
|
||||||
|
{{.Help}}
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatCommand"}}\
|
||||||
|
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||||
|
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}{{if .Default}}*{{end}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatCommands"}}\
|
||||||
|
{{range .FlattenedCommands}}\
|
||||||
|
{{if not .Hidden}}\
|
||||||
|
.SS
|
||||||
|
\fB{{.FullCommand}}{{template "FormatCommand" .}}\\fR
|
||||||
|
.PP
|
||||||
|
{{.Help}}
|
||||||
|
{{template "FormatFlags" .}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatUsage"}}\
|
||||||
|
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}\\fR
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
.TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}"
|
||||||
|
.SH "NAME"
|
||||||
|
{{.App.Name}}
|
||||||
|
.SH "SYNOPSIS"
|
||||||
|
.TP
|
||||||
|
\fB{{.App.Name}}{{template "FormatUsage" .App}}
|
||||||
|
.SH "DESCRIPTION"
|
||||||
|
{{.App.Help}}
|
||||||
|
.SH "OPTIONS"
|
||||||
|
{{template "FormatFlags" .App}}\
|
||||||
|
{{if .App.Commands}}\
|
||||||
|
.SH "COMMANDS"
|
||||||
|
{{template "FormatCommands" .App}}\
|
||||||
|
{{end}}\
|
||||||
|
`
|
||||||
|
|
||||||
|
// Default usage template.
|
||||||
|
var LongHelpTemplate = `{{define "FormatCommand"}}\
|
||||||
|
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||||
|
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatCommands"}}\
|
||||||
|
{{range .FlattenedCommands}}\
|
||||||
|
{{if not .Hidden}}\
|
||||||
|
{{.FullCommand}}{{template "FormatCommand" .}}
|
||||||
|
{{.Help|Wrap 4}}
|
||||||
|
{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}}
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{define "FormatUsage"}}\
|
||||||
|
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||||
|
{{if .Help}}
|
||||||
|
{{.Help|Wrap 0}}\
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
{{end}}\
|
||||||
|
|
||||||
|
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||||
|
{{if .Context.Flags}}\
|
||||||
|
Flags:
|
||||||
|
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .Context.Args}}\
|
||||||
|
Args:
|
||||||
|
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end}}\
|
||||||
|
{{if .App.Commands}}\
|
||||||
|
Commands:
|
||||||
|
{{template "FormatCommands" .App}}
|
||||||
|
{{end}}\
|
||||||
|
`
|
||||||
|
|
||||||
|
var BashCompletionTemplate = `
|
||||||
|
_{{.App.Name}}_bash_autocomplete() {
|
||||||
|
local cur prev opts base
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
var ZshCompletionTemplate = `
|
||||||
|
#compdef {{.App.Name}}
|
||||||
|
autoload -U compinit && compinit
|
||||||
|
autoload -U bashcompinit && bashcompinit
|
||||||
|
|
||||||
|
_{{.App.Name}}_bash_autocomplete() {
|
||||||
|
local cur prev opts base
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||||
|
`
|
|
@ -0,0 +1,211 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/doc"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
preIndent = " "
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) {
|
||||||
|
// Find size of first column.
|
||||||
|
s := 0
|
||||||
|
for _, row := range rows {
|
||||||
|
if c := len(row[0]); c > s && c < 30 {
|
||||||
|
s = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indentStr := strings.Repeat(" ", indent)
|
||||||
|
offsetStr := strings.Repeat(" ", s+padding)
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
doc.ToText(buf, row[1], "", preIndent, width-s-padding-indent)
|
||||||
|
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
|
||||||
|
fmt.Fprintf(w, "%s%-*s%*s", indentStr, s, row[0], padding, "")
|
||||||
|
if len(row[0]) >= 30 {
|
||||||
|
fmt.Fprintf(w, "\n%s%s", indentStr, offsetStr)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s\n", lines[0])
|
||||||
|
for _, line := range lines[1:] {
|
||||||
|
fmt.Fprintf(w, "%s%s%s\n", indentStr, offsetStr, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage writes application usage to w. It parses args to determine
|
||||||
|
// appropriate help context, such as which command to show help for.
|
||||||
|
func (a *Application) Usage(args []string) {
|
||||||
|
context, err := a.parseContext(true, args)
|
||||||
|
a.FatalIfError(err, "")
|
||||||
|
if err := a.UsageForContextWithTemplate(context, 2, a.usageTemplate); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatAppUsage(app *ApplicationModel) string {
|
||||||
|
s := []string{app.Name}
|
||||||
|
if len(app.Flags) > 0 {
|
||||||
|
s = append(s, app.FlagSummary())
|
||||||
|
}
|
||||||
|
if len(app.Args) > 0 {
|
||||||
|
s = append(s, app.ArgSummary())
|
||||||
|
}
|
||||||
|
return strings.Join(s, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string {
|
||||||
|
s := []string{app.Name, cmd.String()}
|
||||||
|
if len(app.Flags) > 0 {
|
||||||
|
s = append(s, app.FlagSummary())
|
||||||
|
}
|
||||||
|
if len(app.Args) > 0 {
|
||||||
|
s = append(s, app.ArgSummary())
|
||||||
|
}
|
||||||
|
return strings.Join(s, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatFlag(haveShort bool, flag *FlagModel) string {
|
||||||
|
flagString := ""
|
||||||
|
if flag.Short != 0 {
|
||||||
|
flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name)
|
||||||
|
} else {
|
||||||
|
if haveShort {
|
||||||
|
flagString += fmt.Sprintf(" --%s", flag.Name)
|
||||||
|
} else {
|
||||||
|
flagString += fmt.Sprintf("--%s", flag.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !flag.IsBoolFlag() {
|
||||||
|
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
|
||||||
|
}
|
||||||
|
if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() {
|
||||||
|
flagString += " ..."
|
||||||
|
}
|
||||||
|
return flagString
|
||||||
|
}
|
||||||
|
|
||||||
|
type templateParseContext struct {
|
||||||
|
SelectedCommand *CmdModel
|
||||||
|
*FlagGroupModel
|
||||||
|
*ArgGroupModel
|
||||||
|
}
|
||||||
|
|
||||||
|
type templateContext struct {
|
||||||
|
App *ApplicationModel
|
||||||
|
Width int
|
||||||
|
Context *templateParseContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageForContext displays usage information from a ParseContext (obtained from
|
||||||
|
// Application.ParseContext() or Action(f) callbacks).
|
||||||
|
func (a *Application) UsageForContext(context *ParseContext) error {
|
||||||
|
return a.UsageForContextWithTemplate(context, 2, a.usageTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageForContextWithTemplate is the base usage function. You generally don't need to use this.
|
||||||
|
func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent int, tmpl string) error {
|
||||||
|
width := guessWidth(a.usageWriter)
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"Indent": func(level int) string {
|
||||||
|
return strings.Repeat(" ", level*indent)
|
||||||
|
},
|
||||||
|
"Wrap": func(indent int, s string) string {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
indentText := strings.Repeat(" ", indent)
|
||||||
|
doc.ToText(buf, s, indentText, " "+indentText, width-indent)
|
||||||
|
return buf.String()
|
||||||
|
},
|
||||||
|
"FormatFlag": formatFlag,
|
||||||
|
"FlagsToTwoColumns": func(f []*FlagModel) [][2]string {
|
||||||
|
rows := [][2]string{}
|
||||||
|
haveShort := false
|
||||||
|
for _, flag := range f {
|
||||||
|
if flag.Short != 0 {
|
||||||
|
haveShort = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, flag := range f {
|
||||||
|
if !flag.Hidden {
|
||||||
|
rows = append(rows, [2]string{formatFlag(haveShort, flag), flag.Help})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
},
|
||||||
|
"RequiredFlags": func(f []*FlagModel) []*FlagModel {
|
||||||
|
requiredFlags := []*FlagModel{}
|
||||||
|
for _, flag := range f {
|
||||||
|
if flag.Required {
|
||||||
|
requiredFlags = append(requiredFlags, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requiredFlags
|
||||||
|
},
|
||||||
|
"OptionalFlags": func(f []*FlagModel) []*FlagModel {
|
||||||
|
optionalFlags := []*FlagModel{}
|
||||||
|
for _, flag := range f {
|
||||||
|
if !flag.Required {
|
||||||
|
optionalFlags = append(optionalFlags, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return optionalFlags
|
||||||
|
},
|
||||||
|
"ArgsToTwoColumns": func(a []*ArgModel) [][2]string {
|
||||||
|
rows := [][2]string{}
|
||||||
|
for _, arg := range a {
|
||||||
|
s := "<" + arg.Name + ">"
|
||||||
|
if !arg.Required {
|
||||||
|
s = "[" + s + "]"
|
||||||
|
}
|
||||||
|
rows = append(rows, [2]string{s, arg.Help})
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
},
|
||||||
|
"FormatTwoColumns": func(rows [][2]string) string {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
formatTwoColumns(buf, indent, indent, width, rows)
|
||||||
|
return buf.String()
|
||||||
|
},
|
||||||
|
"FormatTwoColumnsWithIndent": func(rows [][2]string, indent, padding int) string {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
formatTwoColumns(buf, indent, padding, width, rows)
|
||||||
|
return buf.String()
|
||||||
|
},
|
||||||
|
"FormatAppUsage": formatAppUsage,
|
||||||
|
"FormatCommandUsage": formatCmdUsage,
|
||||||
|
"IsCumulative": func(value Value) bool {
|
||||||
|
r, ok := value.(remainderArg)
|
||||||
|
return ok && r.IsCumulative()
|
||||||
|
},
|
||||||
|
"Char": func(c rune) string {
|
||||||
|
return string(c)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t, err := template.New("usage").Funcs(funcs).Parse(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var selectedCommand *CmdModel
|
||||||
|
if context.SelectedCommand != nil {
|
||||||
|
selectedCommand = context.SelectedCommand.Model()
|
||||||
|
}
|
||||||
|
ctx := templateContext{
|
||||||
|
App: a.Model(),
|
||||||
|
Width: width,
|
||||||
|
Context: &templateParseContext{
|
||||||
|
SelectedCommand: selectedCommand,
|
||||||
|
FlagGroupModel: context.flags.Model(),
|
||||||
|
ArgGroupModel: context.arguments.Model(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return t.Execute(a.usageWriter, ctx)
|
||||||
|
}
|
|
@ -0,0 +1,470 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
//go:generate go run ./cmd/genvalues/main.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alecthomas/units"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: Most of the base type values were lifted from:
|
||||||
|
// http://golang.org/src/pkg/flag/flag.go?s=20146:20222
|
||||||
|
|
||||||
|
// Value is the interface to the dynamic value stored in a flag.
|
||||||
|
// (The default value is represented as a string.)
|
||||||
|
//
|
||||||
|
// If a Value has an IsBoolFlag() bool method returning true, the command-line
|
||||||
|
// parser makes --name equivalent to -name=true rather than using the next
|
||||||
|
// command-line argument, and adds a --no-name counterpart for negating the
|
||||||
|
// flag.
|
||||||
|
type Value interface {
|
||||||
|
String() string
|
||||||
|
Set(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter is an interface that allows the contents of a Value to be retrieved.
|
||||||
|
// It wraps the Value interface, rather than being part of it, because it
|
||||||
|
// appeared after Go 1 and its compatibility rules. All Value types provided
|
||||||
|
// by this package satisfy the Getter interface.
|
||||||
|
type Getter interface {
|
||||||
|
Value
|
||||||
|
Get() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional interface to indicate boolean flags that don't accept a value, and
|
||||||
|
// implicitly have a --no-<x> negation counterpart.
|
||||||
|
type boolFlag interface {
|
||||||
|
Value
|
||||||
|
IsBoolFlag() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional interface for arguments that cumulatively consume all remaining
|
||||||
|
// input.
|
||||||
|
type remainderArg interface {
|
||||||
|
Value
|
||||||
|
IsCumulative() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional interface for flags that can be repeated.
|
||||||
|
type repeatableFlag interface {
|
||||||
|
Value
|
||||||
|
IsCumulative() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type accumulator struct {
|
||||||
|
element func(value interface{}) Value
|
||||||
|
typ reflect.Type
|
||||||
|
slice reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use reflection to accumulate values into a slice.
|
||||||
|
//
|
||||||
|
// target := []string{}
|
||||||
|
// newAccumulator(&target, func (value interface{}) Value {
|
||||||
|
// return newStringValue(value.(*string))
|
||||||
|
// })
|
||||||
|
func newAccumulator(slice interface{}, element func(value interface{}) Value) *accumulator {
|
||||||
|
typ := reflect.TypeOf(slice)
|
||||||
|
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Slice {
|
||||||
|
panic("expected a pointer to a slice")
|
||||||
|
}
|
||||||
|
return &accumulator{
|
||||||
|
element: element,
|
||||||
|
typ: typ.Elem().Elem(),
|
||||||
|
slice: reflect.ValueOf(slice),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accumulator) String() string {
|
||||||
|
out := []string{}
|
||||||
|
s := a.slice.Elem()
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
out = append(out, a.element(s.Index(i).Addr().Interface()).String())
|
||||||
|
}
|
||||||
|
return strings.Join(out, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accumulator) Set(value string) error {
|
||||||
|
e := reflect.New(a.typ)
|
||||||
|
if err := a.element(e.Interface()).Set(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice := reflect.Append(a.slice.Elem(), e.Elem())
|
||||||
|
a.slice.Elem().Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accumulator) Get() interface{} {
|
||||||
|
return a.slice.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *accumulator) IsCumulative() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||||
|
|
||||||
|
// -- time.Duration Value
|
||||||
|
type durationValue time.Duration
|
||||||
|
|
||||||
|
func newDurationValue(p *time.Duration) *durationValue {
|
||||||
|
return (*durationValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *durationValue) Set(s string) error {
|
||||||
|
v, err := time.ParseDuration(s)
|
||||||
|
*d = durationValue(v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *durationValue) Get() interface{} { return time.Duration(*d) }
|
||||||
|
|
||||||
|
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||||
|
|
||||||
|
// -- map[string]string Value
|
||||||
|
type stringMapValue map[string]string
|
||||||
|
|
||||||
|
func newStringMapValue(p *map[string]string) *stringMapValue {
|
||||||
|
return (*stringMapValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringMapRegex = regexp.MustCompile("[:=]")
|
||||||
|
|
||||||
|
func (s *stringMapValue) Set(value string) error {
|
||||||
|
parts := stringMapRegex.Split(value, 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("expected KEY=VALUE got '%s'", value)
|
||||||
|
}
|
||||||
|
(*s)[parts[0]] = parts[1]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapValue) Get() interface{} {
|
||||||
|
return (map[string]string)(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapValue) String() string {
|
||||||
|
return fmt.Sprintf("%s", map[string]string(*s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringMapValue) IsCumulative() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- net.IP Value
|
||||||
|
type ipValue net.IP
|
||||||
|
|
||||||
|
func newIPValue(p *net.IP) *ipValue {
|
||||||
|
return (*ipValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipValue) Set(value string) error {
|
||||||
|
if ip := net.ParseIP(value); ip == nil {
|
||||||
|
return fmt.Errorf("'%s' is not an IP address", value)
|
||||||
|
} else {
|
||||||
|
*i = *(*ipValue)(&ip)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipValue) Get() interface{} {
|
||||||
|
return (net.IP)(*i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipValue) String() string {
|
||||||
|
return (*net.IP)(i).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- *net.TCPAddr Value
|
||||||
|
type tcpAddrValue struct {
|
||||||
|
addr **net.TCPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTCPAddrValue(p **net.TCPAddr) *tcpAddrValue {
|
||||||
|
return &tcpAddrValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *tcpAddrValue) Set(value string) error {
|
||||||
|
if addr, err := net.ResolveTCPAddr("tcp", value); err != nil {
|
||||||
|
return fmt.Errorf("'%s' is not a valid TCP address: %s", value, err)
|
||||||
|
} else {
|
||||||
|
*i.addr = addr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tcpAddrValue) Get() interface{} {
|
||||||
|
return (*net.TCPAddr)(*t.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *tcpAddrValue) String() string {
|
||||||
|
return (*i.addr).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- existingFile Value
|
||||||
|
|
||||||
|
type fileStatValue struct {
|
||||||
|
path *string
|
||||||
|
predicate func(os.FileInfo) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileStatValue(p *string, predicate func(os.FileInfo) error) *fileStatValue {
|
||||||
|
return &fileStatValue{
|
||||||
|
path: p,
|
||||||
|
predicate: predicate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fileStatValue) Set(value string) error {
|
||||||
|
if s, err := os.Stat(value); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("path '%s' does not exist", value)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
} else if err := e.predicate(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e.path = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileStatValue) Get() interface{} {
|
||||||
|
return (string)(*f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *fileStatValue) String() string {
|
||||||
|
return *e.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- os.File value
|
||||||
|
|
||||||
|
type fileValue struct {
|
||||||
|
f **os.File
|
||||||
|
flag int
|
||||||
|
perm os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFileValue(p **os.File, flag int, perm os.FileMode) *fileValue {
|
||||||
|
return &fileValue{p, flag, perm}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileValue) Set(value string) error {
|
||||||
|
if fd, err := os.OpenFile(value, f.flag, f.perm); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
*f.f = fd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileValue) Get() interface{} {
|
||||||
|
return (*os.File)(*f.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileValue) String() string {
|
||||||
|
if *f.f == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return (*f.f).Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- url.URL Value
|
||||||
|
type urlValue struct {
|
||||||
|
u **url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func newURLValue(p **url.URL) *urlValue {
|
||||||
|
return &urlValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) Set(value string) error {
|
||||||
|
if url, err := url.Parse(value); err != nil {
|
||||||
|
return fmt.Errorf("invalid URL: %s", err)
|
||||||
|
} else {
|
||||||
|
*u.u = url
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) Get() interface{} {
|
||||||
|
return (*url.URL)(*u.u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlValue) String() string {
|
||||||
|
if *u.u == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return (*u.u).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- []*url.URL Value
|
||||||
|
type urlListValue []*url.URL
|
||||||
|
|
||||||
|
func newURLListValue(p *[]*url.URL) *urlListValue {
|
||||||
|
return (*urlListValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlListValue) Set(value string) error {
|
||||||
|
if url, err := url.Parse(value); err != nil {
|
||||||
|
return fmt.Errorf("invalid URL: %s", err)
|
||||||
|
} else {
|
||||||
|
*u = append(*u, url)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlListValue) Get() interface{} {
|
||||||
|
return ([]*url.URL)(*u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlListValue) String() string {
|
||||||
|
out := []string{}
|
||||||
|
for _, url := range *u {
|
||||||
|
out = append(out, url.String())
|
||||||
|
}
|
||||||
|
return strings.Join(out, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *urlListValue) IsCumulative() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// A flag whose value must be in a set of options.
|
||||||
|
type enumValue struct {
|
||||||
|
value *string
|
||||||
|
options []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEnumFlag(target *string, options ...string) *enumValue {
|
||||||
|
return &enumValue{
|
||||||
|
value: target,
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *enumValue) String() string {
|
||||||
|
return *a.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *enumValue) Set(value string) error {
|
||||||
|
for _, v := range a.options {
|
||||||
|
if v == value {
|
||||||
|
*a.value = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(a.options, ","), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enumValue) Get() interface{} {
|
||||||
|
return (string)(*e.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- []string Enum Value
|
||||||
|
type enumsValue struct {
|
||||||
|
value *[]string
|
||||||
|
options []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEnumsFlag(target *[]string, options ...string) *enumsValue {
|
||||||
|
return &enumsValue{
|
||||||
|
value: target,
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *enumsValue) Set(value string) error {
|
||||||
|
for _, v := range s.options {
|
||||||
|
if v == value {
|
||||||
|
*s.value = append(*s.value, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(s.options, ","), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *enumsValue) Get() interface{} {
|
||||||
|
return ([]string)(*e.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *enumsValue) String() string {
|
||||||
|
return strings.Join(*s.value, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *enumsValue) IsCumulative() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- units.Base2Bytes Value
|
||||||
|
type bytesValue units.Base2Bytes
|
||||||
|
|
||||||
|
func newBytesValue(p *units.Base2Bytes) *bytesValue {
|
||||||
|
return (*bytesValue)(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *bytesValue) Set(s string) error {
|
||||||
|
v, err := units.ParseBase2Bytes(s)
|
||||||
|
*d = bytesValue(v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *bytesValue) Get() interface{} { return units.Base2Bytes(*d) }
|
||||||
|
|
||||||
|
func (d *bytesValue) String() string { return (*units.Base2Bytes)(d).String() }
|
||||||
|
|
||||||
|
func newExistingFileValue(target *string) *fileStatValue {
|
||||||
|
return newFileStatValue(target, func(s os.FileInfo) error {
|
||||||
|
if s.IsDir() {
|
||||||
|
return fmt.Errorf("'%s' is a directory", s.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExistingDirValue(target *string) *fileStatValue {
|
||||||
|
return newFileStatValue(target, func(s os.FileInfo) error {
|
||||||
|
if !s.IsDir() {
|
||||||
|
return fmt.Errorf("'%s' is a file", s.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExistingFileOrDirValue(target *string) *fileStatValue {
|
||||||
|
return newFileStatValue(target, func(s os.FileInfo) error { return nil })
|
||||||
|
}
|
||||||
|
|
||||||
|
type counterValue int
|
||||||
|
|
||||||
|
func newCounterValue(n *int) *counterValue {
|
||||||
|
return (*counterValue)(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counterValue) Set(s string) error {
|
||||||
|
*c++
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counterValue) Get() interface{} { return (int)(*c) }
|
||||||
|
func (c *counterValue) IsBoolFlag() bool { return true }
|
||||||
|
func (c *counterValue) String() string { return fmt.Sprintf("%d", *c) }
|
||||||
|
func (c *counterValue) IsCumulative() bool { return true }
|
||||||
|
|
||||||
|
func resolveHost(value string) (net.IP, error) {
|
||||||
|
if ip := net.ParseIP(value); ip != nil {
|
||||||
|
return ip, nil
|
||||||
|
} else {
|
||||||
|
if addr, err := net.ResolveIPAddr("ip", value); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return addr.IP, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,821 @@
|
||||||
|
package kingpin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file is autogenerated by "go generate .". Do not modify.
|
||||||
|
|
||||||
|
// -- bool Value
|
||||||
|
type boolValue struct{ v *bool }
|
||||||
|
|
||||||
|
func newBoolValue(p *bool) *boolValue {
|
||||||
|
return &boolValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *boolValue) Set(s string) error {
|
||||||
|
v, err := strconv.ParseBool(s)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (bool)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *boolValue) Get() interface{} { return (bool)(*f.v) }
|
||||||
|
|
||||||
|
func (f *boolValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Bool parses the next command-line value as bool.
|
||||||
|
func (p *parserMixin) Bool() (target *bool) {
|
||||||
|
target = new(bool)
|
||||||
|
p.BoolVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) BoolVar(target *bool) {
|
||||||
|
p.SetValue(newBoolValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolList accumulates bool values into a slice.
|
||||||
|
func (p *parserMixin) BoolList() (target *[]bool) {
|
||||||
|
target = new([]bool)
|
||||||
|
p.BoolListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) BoolListVar(target *[]bool) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newBoolValue(v.(*bool))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- string Value
|
||||||
|
type stringValue struct{ v *string }
|
||||||
|
|
||||||
|
func newStringValue(p *string) *stringValue {
|
||||||
|
return &stringValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stringValue) Set(s string) error {
|
||||||
|
v, err := s, error(nil)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (string)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stringValue) Get() interface{} { return (string)(*f.v) }
|
||||||
|
|
||||||
|
func (f *stringValue) String() string { return string(*f.v) }
|
||||||
|
|
||||||
|
// String parses the next command-line value as string.
|
||||||
|
func (p *parserMixin) String() (target *string) {
|
||||||
|
target = new(string)
|
||||||
|
p.StringVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) StringVar(target *string) {
|
||||||
|
p.SetValue(newStringValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings accumulates string values into a slice.
|
||||||
|
func (p *parserMixin) Strings() (target *[]string) {
|
||||||
|
target = new([]string)
|
||||||
|
p.StringsVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) StringsVar(target *[]string) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newStringValue(v.(*string))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- uint Value
|
||||||
|
type uintValue struct{ v *uint }
|
||||||
|
|
||||||
|
func newUintValue(p *uint) *uintValue {
|
||||||
|
return &uintValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uintValue) Set(s string) error {
|
||||||
|
v, err := strconv.ParseUint(s, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (uint)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uintValue) Get() interface{} { return (uint)(*f.v) }
|
||||||
|
|
||||||
|
func (f *uintValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Uint parses the next command-line value as uint.
|
||||||
|
func (p *parserMixin) Uint() (target *uint) {
|
||||||
|
target = new(uint)
|
||||||
|
p.UintVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) UintVar(target *uint) {
|
||||||
|
p.SetValue(newUintValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uints accumulates uint values into a slice.
|
||||||
|
func (p *parserMixin) Uints() (target *[]uint) {
|
||||||
|
target = new([]uint)
|
||||||
|
p.UintsVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) UintsVar(target *[]uint) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newUintValue(v.(*uint))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- uint8 Value
|
||||||
|
type uint8Value struct{ v *uint8 }
|
||||||
|
|
||||||
|
func newUint8Value(p *uint8) *uint8Value {
|
||||||
|
return &uint8Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint8Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseUint(s, 0, 8)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (uint8)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint8Value) Get() interface{} { return (uint8)(*f.v) }
|
||||||
|
|
||||||
|
func (f *uint8Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Uint8 parses the next command-line value as uint8.
|
||||||
|
func (p *parserMixin) Uint8() (target *uint8) {
|
||||||
|
target = new(uint8)
|
||||||
|
p.Uint8Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint8Var(target *uint8) {
|
||||||
|
p.SetValue(newUint8Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8List accumulates uint8 values into a slice.
|
||||||
|
func (p *parserMixin) Uint8List() (target *[]uint8) {
|
||||||
|
target = new([]uint8)
|
||||||
|
p.Uint8ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint8ListVar(target *[]uint8) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newUint8Value(v.(*uint8))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- uint16 Value
|
||||||
|
type uint16Value struct{ v *uint16 }
|
||||||
|
|
||||||
|
func newUint16Value(p *uint16) *uint16Value {
|
||||||
|
return &uint16Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint16Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseUint(s, 0, 16)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (uint16)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint16Value) Get() interface{} { return (uint16)(*f.v) }
|
||||||
|
|
||||||
|
func (f *uint16Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Uint16 parses the next command-line value as uint16.
|
||||||
|
func (p *parserMixin) Uint16() (target *uint16) {
|
||||||
|
target = new(uint16)
|
||||||
|
p.Uint16Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint16Var(target *uint16) {
|
||||||
|
p.SetValue(newUint16Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16List accumulates uint16 values into a slice.
|
||||||
|
func (p *parserMixin) Uint16List() (target *[]uint16) {
|
||||||
|
target = new([]uint16)
|
||||||
|
p.Uint16ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint16ListVar(target *[]uint16) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newUint16Value(v.(*uint16))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- uint32 Value
|
||||||
|
type uint32Value struct{ v *uint32 }
|
||||||
|
|
||||||
|
func newUint32Value(p *uint32) *uint32Value {
|
||||||
|
return &uint32Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint32Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseUint(s, 0, 32)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (uint32)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint32Value) Get() interface{} { return (uint32)(*f.v) }
|
||||||
|
|
||||||
|
func (f *uint32Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Uint32 parses the next command-line value as uint32.
|
||||||
|
func (p *parserMixin) Uint32() (target *uint32) {
|
||||||
|
target = new(uint32)
|
||||||
|
p.Uint32Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint32Var(target *uint32) {
|
||||||
|
p.SetValue(newUint32Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32List accumulates uint32 values into a slice.
|
||||||
|
func (p *parserMixin) Uint32List() (target *[]uint32) {
|
||||||
|
target = new([]uint32)
|
||||||
|
p.Uint32ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint32ListVar(target *[]uint32) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newUint32Value(v.(*uint32))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- uint64 Value
|
||||||
|
type uint64Value struct{ v *uint64 }
|
||||||
|
|
||||||
|
func newUint64Value(p *uint64) *uint64Value {
|
||||||
|
return &uint64Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint64Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseUint(s, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (uint64)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *uint64Value) Get() interface{} { return (uint64)(*f.v) }
|
||||||
|
|
||||||
|
func (f *uint64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Uint64 parses the next command-line value as uint64.
|
||||||
|
func (p *parserMixin) Uint64() (target *uint64) {
|
||||||
|
target = new(uint64)
|
||||||
|
p.Uint64Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint64Var(target *uint64) {
|
||||||
|
p.SetValue(newUint64Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64List accumulates uint64 values into a slice.
|
||||||
|
func (p *parserMixin) Uint64List() (target *[]uint64) {
|
||||||
|
target = new([]uint64)
|
||||||
|
p.Uint64ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Uint64ListVar(target *[]uint64) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newUint64Value(v.(*uint64))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- int Value
|
||||||
|
type intValue struct{ v *int }
|
||||||
|
|
||||||
|
func newIntValue(p *int) *intValue {
|
||||||
|
return &intValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *intValue) Set(s string) error {
|
||||||
|
v, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (int)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *intValue) Get() interface{} { return (int)(*f.v) }
|
||||||
|
|
||||||
|
func (f *intValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Int parses the next command-line value as int.
|
||||||
|
func (p *parserMixin) Int() (target *int) {
|
||||||
|
target = new(int)
|
||||||
|
p.IntVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) IntVar(target *int) {
|
||||||
|
p.SetValue(newIntValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints accumulates int values into a slice.
|
||||||
|
func (p *parserMixin) Ints() (target *[]int) {
|
||||||
|
target = new([]int)
|
||||||
|
p.IntsVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) IntsVar(target *[]int) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newIntValue(v.(*int))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- int8 Value
|
||||||
|
type int8Value struct{ v *int8 }
|
||||||
|
|
||||||
|
func newInt8Value(p *int8) *int8Value {
|
||||||
|
return &int8Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int8Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseInt(s, 0, 8)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (int8)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int8Value) Get() interface{} { return (int8)(*f.v) }
|
||||||
|
|
||||||
|
func (f *int8Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Int8 parses the next command-line value as int8.
|
||||||
|
func (p *parserMixin) Int8() (target *int8) {
|
||||||
|
target = new(int8)
|
||||||
|
p.Int8Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int8Var(target *int8) {
|
||||||
|
p.SetValue(newInt8Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8List accumulates int8 values into a slice.
|
||||||
|
func (p *parserMixin) Int8List() (target *[]int8) {
|
||||||
|
target = new([]int8)
|
||||||
|
p.Int8ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int8ListVar(target *[]int8) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newInt8Value(v.(*int8))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- int16 Value
|
||||||
|
type int16Value struct{ v *int16 }
|
||||||
|
|
||||||
|
func newInt16Value(p *int16) *int16Value {
|
||||||
|
return &int16Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int16Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseInt(s, 0, 16)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (int16)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int16Value) Get() interface{} { return (int16)(*f.v) }
|
||||||
|
|
||||||
|
func (f *int16Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Int16 parses the next command-line value as int16.
|
||||||
|
func (p *parserMixin) Int16() (target *int16) {
|
||||||
|
target = new(int16)
|
||||||
|
p.Int16Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int16Var(target *int16) {
|
||||||
|
p.SetValue(newInt16Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16List accumulates int16 values into a slice.
|
||||||
|
func (p *parserMixin) Int16List() (target *[]int16) {
|
||||||
|
target = new([]int16)
|
||||||
|
p.Int16ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int16ListVar(target *[]int16) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newInt16Value(v.(*int16))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- int32 Value
|
||||||
|
type int32Value struct{ v *int32 }
|
||||||
|
|
||||||
|
func newInt32Value(p *int32) *int32Value {
|
||||||
|
return &int32Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int32Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseInt(s, 0, 32)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (int32)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int32Value) Get() interface{} { return (int32)(*f.v) }
|
||||||
|
|
||||||
|
func (f *int32Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Int32 parses the next command-line value as int32.
|
||||||
|
func (p *parserMixin) Int32() (target *int32) {
|
||||||
|
target = new(int32)
|
||||||
|
p.Int32Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int32Var(target *int32) {
|
||||||
|
p.SetValue(newInt32Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32List accumulates int32 values into a slice.
|
||||||
|
func (p *parserMixin) Int32List() (target *[]int32) {
|
||||||
|
target = new([]int32)
|
||||||
|
p.Int32ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int32ListVar(target *[]int32) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newInt32Value(v.(*int32))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- int64 Value
|
||||||
|
type int64Value struct{ v *int64 }
|
||||||
|
|
||||||
|
func newInt64Value(p *int64) *int64Value {
|
||||||
|
return &int64Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int64Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseInt(s, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (int64)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *int64Value) Get() interface{} { return (int64)(*f.v) }
|
||||||
|
|
||||||
|
func (f *int64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Int64 parses the next command-line value as int64.
|
||||||
|
func (p *parserMixin) Int64() (target *int64) {
|
||||||
|
target = new(int64)
|
||||||
|
p.Int64Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int64Var(target *int64) {
|
||||||
|
p.SetValue(newInt64Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64List accumulates int64 values into a slice.
|
||||||
|
func (p *parserMixin) Int64List() (target *[]int64) {
|
||||||
|
target = new([]int64)
|
||||||
|
p.Int64ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Int64ListVar(target *[]int64) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newInt64Value(v.(*int64))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- float64 Value
|
||||||
|
type float64Value struct{ v *float64 }
|
||||||
|
|
||||||
|
func newFloat64Value(p *float64) *float64Value {
|
||||||
|
return &float64Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *float64Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (float64)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *float64Value) Get() interface{} { return (float64)(*f.v) }
|
||||||
|
|
||||||
|
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Float64 parses the next command-line value as float64.
|
||||||
|
func (p *parserMixin) Float64() (target *float64) {
|
||||||
|
target = new(float64)
|
||||||
|
p.Float64Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Float64Var(target *float64) {
|
||||||
|
p.SetValue(newFloat64Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64List accumulates float64 values into a slice.
|
||||||
|
func (p *parserMixin) Float64List() (target *[]float64) {
|
||||||
|
target = new([]float64)
|
||||||
|
p.Float64ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Float64ListVar(target *[]float64) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newFloat64Value(v.(*float64))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- float32 Value
|
||||||
|
type float32Value struct{ v *float32 }
|
||||||
|
|
||||||
|
func newFloat32Value(p *float32) *float32Value {
|
||||||
|
return &float32Value{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *float32Value) Set(s string) error {
|
||||||
|
v, err := strconv.ParseFloat(s, 32)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (float32)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *float32Value) Get() interface{} { return (float32)(*f.v) }
|
||||||
|
|
||||||
|
func (f *float32Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Float32 parses the next command-line value as float32.
|
||||||
|
func (p *parserMixin) Float32() (target *float32) {
|
||||||
|
target = new(float32)
|
||||||
|
p.Float32Var(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Float32Var(target *float32) {
|
||||||
|
p.SetValue(newFloat32Value(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32List accumulates float32 values into a slice.
|
||||||
|
func (p *parserMixin) Float32List() (target *[]float32) {
|
||||||
|
target = new([]float32)
|
||||||
|
p.Float32ListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) Float32ListVar(target *[]float32) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newFloat32Value(v.(*float32))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DurationList accumulates time.Duration values into a slice.
|
||||||
|
func (p *parserMixin) DurationList() (target *[]time.Duration) {
|
||||||
|
target = new([]time.Duration)
|
||||||
|
p.DurationListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) DurationListVar(target *[]time.Duration) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newDurationValue(v.(*time.Duration))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPList accumulates net.IP values into a slice.
|
||||||
|
func (p *parserMixin) IPList() (target *[]net.IP) {
|
||||||
|
target = new([]net.IP)
|
||||||
|
p.IPListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) IPListVar(target *[]net.IP) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newIPValue(v.(*net.IP))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TCPList accumulates *net.TCPAddr values into a slice.
|
||||||
|
func (p *parserMixin) TCPList() (target *[]*net.TCPAddr) {
|
||||||
|
target = new([]*net.TCPAddr)
|
||||||
|
p.TCPListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) TCPListVar(target *[]*net.TCPAddr) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newTCPAddrValue(v.(**net.TCPAddr))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingFiles accumulates string values into a slice.
|
||||||
|
func (p *parserMixin) ExistingFiles() (target *[]string) {
|
||||||
|
target = new([]string)
|
||||||
|
p.ExistingFilesVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) ExistingFilesVar(target *[]string) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newExistingFileValue(v.(*string))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingDirs accumulates string values into a slice.
|
||||||
|
func (p *parserMixin) ExistingDirs() (target *[]string) {
|
||||||
|
target = new([]string)
|
||||||
|
p.ExistingDirsVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) ExistingDirsVar(target *[]string) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newExistingDirValue(v.(*string))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistingFilesOrDirs accumulates string values into a slice.
|
||||||
|
func (p *parserMixin) ExistingFilesOrDirs() (target *[]string) {
|
||||||
|
target = new([]string)
|
||||||
|
p.ExistingFilesOrDirsVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) ExistingFilesOrDirsVar(target *[]string) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newExistingFileOrDirValue(v.(*string))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- *regexp.Regexp Value
|
||||||
|
type regexpValue struct{ v **regexp.Regexp }
|
||||||
|
|
||||||
|
func newRegexpValue(p **regexp.Regexp) *regexpValue {
|
||||||
|
return ®expValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *regexpValue) Set(s string) error {
|
||||||
|
v, err := regexp.Compile(s)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (*regexp.Regexp)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *regexpValue) Get() interface{} { return (*regexp.Regexp)(*f.v) }
|
||||||
|
|
||||||
|
func (f *regexpValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Regexp parses the next command-line value as *regexp.Regexp.
|
||||||
|
func (p *parserMixin) Regexp() (target **regexp.Regexp) {
|
||||||
|
target = new(*regexp.Regexp)
|
||||||
|
p.RegexpVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) RegexpVar(target **regexp.Regexp) {
|
||||||
|
p.SetValue(newRegexpValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegexpList accumulates *regexp.Regexp values into a slice.
|
||||||
|
func (p *parserMixin) RegexpList() (target *[]*regexp.Regexp) {
|
||||||
|
target = new([]*regexp.Regexp)
|
||||||
|
p.RegexpListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) RegexpListVar(target *[]*regexp.Regexp) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newRegexpValue(v.(**regexp.Regexp))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- net.IP Value
|
||||||
|
type resolvedIPValue struct{ v *net.IP }
|
||||||
|
|
||||||
|
func newResolvedIPValue(p *net.IP) *resolvedIPValue {
|
||||||
|
return &resolvedIPValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *resolvedIPValue) Set(s string) error {
|
||||||
|
v, err := resolveHost(s)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = (net.IP)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *resolvedIPValue) Get() interface{} { return (net.IP)(*f.v) }
|
||||||
|
|
||||||
|
func (f *resolvedIPValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Resolve a hostname or IP to an IP.
|
||||||
|
func (p *parserMixin) ResolvedIP() (target *net.IP) {
|
||||||
|
target = new(net.IP)
|
||||||
|
p.ResolvedIPVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) ResolvedIPVar(target *net.IP) {
|
||||||
|
p.SetValue(newResolvedIPValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvedIPList accumulates net.IP values into a slice.
|
||||||
|
func (p *parserMixin) ResolvedIPList() (target *[]net.IP) {
|
||||||
|
target = new([]net.IP)
|
||||||
|
p.ResolvedIPListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) ResolvedIPListVar(target *[]net.IP) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newResolvedIPValue(v.(*net.IP))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- []byte Value
|
||||||
|
type hexBytesValue struct{ v *[]byte }
|
||||||
|
|
||||||
|
func newHexBytesValue(p *[]byte) *hexBytesValue {
|
||||||
|
return &hexBytesValue{p}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *hexBytesValue) Set(s string) error {
|
||||||
|
v, err := hex.DecodeString(s)
|
||||||
|
if err == nil {
|
||||||
|
*f.v = ([]byte)(v)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *hexBytesValue) Get() interface{} { return ([]byte)(*f.v) }
|
||||||
|
|
||||||
|
func (f *hexBytesValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||||
|
|
||||||
|
// Bytes as a hex string.
|
||||||
|
func (p *parserMixin) HexBytes() (target *[]byte) {
|
||||||
|
target = new([]byte)
|
||||||
|
p.HexBytesVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) HexBytesVar(target *[]byte) {
|
||||||
|
p.SetValue(newHexBytesValue(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HexBytesList accumulates []byte values into a slice.
|
||||||
|
func (p *parserMixin) HexBytesList() (target *[][]byte) {
|
||||||
|
target = new([][]byte)
|
||||||
|
p.HexBytesListVar(target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parserMixin) HexBytesListVar(target *[][]byte) {
|
||||||
|
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||||
|
return newHexBytesValue(v.(*[]byte))
|
||||||
|
}))
|
||||||
|
}
|
Loading…
Reference in New Issue