258 lines
8.0 KiB
Python
Executable File
258 lines
8.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""
|
|
The flag types that ship with the cli library have many things in common, and
|
|
so we can take advantage of the `go generate` command to create much of the
|
|
source code from a list of definitions. These definitions attempt to cover
|
|
the parts that vary between flag types, and should evolve as needed.
|
|
|
|
An example of the minimum definition needed is:
|
|
|
|
{
|
|
"name": "SomeType",
|
|
"type": "sometype",
|
|
"context_default": "nil"
|
|
}
|
|
|
|
In this example, the code generated for the `cli` package will include a type
|
|
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
|
|
Fetching values by name via `*cli.Context` will default to a value of `nil`.
|
|
|
|
A more complete, albeit somewhat redundant, example showing all available
|
|
definition keys is:
|
|
|
|
{
|
|
"name": "VeryMuchType",
|
|
"type": "*VeryMuchType",
|
|
"value": true,
|
|
"dest": false,
|
|
"doctail": " which really only wraps a []float64, oh well!",
|
|
"context_type": "[]float64",
|
|
"context_default": "nil",
|
|
"parser": "parseVeryMuchType(f.Value.String())",
|
|
"parser_cast": "[]float64(parsed)"
|
|
}
|
|
|
|
The meaning of each field is as follows:
|
|
|
|
name (string) - The type "name", which will be suffixed with
|
|
`Flag` when generating the type definition
|
|
for `cli` and the wrapper type for `altsrc`
|
|
type (string) - The type that the generated `Flag` type for `cli`
|
|
is expected to "contain" as its `.Value` member
|
|
value (bool) - Should the generated `cli` type have a `Value`
|
|
member?
|
|
dest (bool) - Should the generated `cli` type support a
|
|
destination pointer?
|
|
doctail (string) - Additional docs for the `cli` flag type comment
|
|
context_type (string) - The literal type used in the `*cli.Context`
|
|
reader func signature
|
|
context_default (string) - The literal value used as the default by the
|
|
`*cli.Context` reader funcs when no value is
|
|
present
|
|
parser (string) - Literal code used to parse the flag `f`,
|
|
expected to have a return signature of
|
|
(value, error)
|
|
parser_cast (string) - Literal code used to cast the `parsed` value
|
|
returned from the `parser` code
|
|
"""
|
|
|
|
from __future__ import print_function, unicode_literals
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import textwrap
|
|
|
|
|
|
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
|
|
argparse.RawDescriptionHelpFormatter):
|
|
pass
|
|
|
|
|
|
def main(sysargs=sys.argv[:]):
|
|
parser = argparse.ArgumentParser(
|
|
description='Generate flag type code!',
|
|
formatter_class=_FancyFormatter)
|
|
parser.add_argument(
|
|
'package',
|
|
type=str, default='cli', choices=_WRITEFUNCS.keys(),
|
|
help='Package for which flag types will be generated'
|
|
)
|
|
parser.add_argument(
|
|
'-i', '--in-json',
|
|
type=argparse.FileType('r'),
|
|
default=sys.stdin,
|
|
help='Input JSON file which defines each type to be generated'
|
|
)
|
|
parser.add_argument(
|
|
'-o', '--out-go',
|
|
type=argparse.FileType('w'),
|
|
default=sys.stdout,
|
|
help='Output file/stream to which generated source will be written'
|
|
)
|
|
parser.epilog = __doc__
|
|
|
|
args = parser.parse_args(sysargs[1:])
|
|
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
|
|
return 0
|
|
|
|
|
|
def _generate_flag_types(writefunc, output_go, input_json):
|
|
types = json.load(input_json)
|
|
|
|
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
|
|
writefunc(tmp, types)
|
|
tmp.close()
|
|
|
|
new_content = subprocess.check_output(
|
|
['goimports', tmp.name]
|
|
).decode('utf-8')
|
|
|
|
print(new_content, file=output_go, end='')
|
|
output_go.flush()
|
|
os.remove(tmp.name)
|
|
|
|
|
|
def _set_typedef_defaults(typedef):
|
|
typedef.setdefault('doctail', '')
|
|
typedef.setdefault('context_type', typedef['type'])
|
|
typedef.setdefault('dest', True)
|
|
typedef.setdefault('value', True)
|
|
typedef.setdefault('parser', 'f.Value, error(nil)')
|
|
typedef.setdefault('parser_cast', 'parsed')
|
|
|
|
|
|
def _write_cli_flag_types(outfile, types):
|
|
_fwrite(outfile, """\
|
|
package cli
|
|
|
|
// WARNING: This file is generated!
|
|
|
|
""")
|
|
|
|
for typedef in types:
|
|
_set_typedef_defaults(typedef)
|
|
|
|
_fwrite(outfile, """\
|
|
// {name}Flag is a flag with type {type}{doctail}
|
|
type {name}Flag struct {{
|
|
Name string
|
|
Usage string
|
|
EnvVar string
|
|
FilePath string
|
|
Required bool
|
|
Hidden bool
|
|
""".format(**typedef))
|
|
|
|
if typedef['value']:
|
|
_fwrite(outfile, """\
|
|
Value {type}
|
|
""".format(**typedef))
|
|
|
|
if typedef['dest']:
|
|
_fwrite(outfile, """\
|
|
Destination *{type}
|
|
""".format(**typedef))
|
|
|
|
_fwrite(outfile, "\n}\n\n")
|
|
|
|
_fwrite(outfile, """\
|
|
// String returns a readable representation of this value
|
|
// (for usage defaults)
|
|
func (f {name}Flag) String() string {{
|
|
return FlagStringer(f)
|
|
}}
|
|
|
|
// GetName returns the name of the flag
|
|
func (f {name}Flag) GetName() string {{
|
|
return f.Name
|
|
}}
|
|
|
|
// {name} looks up the value of a local {name}Flag, returns
|
|
// {context_default} if not found
|
|
func (c *Context) {name}(name string) {context_type} {{
|
|
return lookup{name}(name, c.flagSet)
|
|
}}
|
|
|
|
// Global{name} looks up the value of a global {name}Flag, returns
|
|
// {context_default} if not found
|
|
func (c *Context) Global{name}(name string) {context_type} {{
|
|
if fs := lookupGlobalFlagSet(name, c); fs != nil {{
|
|
return lookup{name}(name, fs)
|
|
}}
|
|
return {context_default}
|
|
}}
|
|
|
|
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
|
|
f := set.Lookup(name)
|
|
if f != nil {{
|
|
parsed, err := {parser}
|
|
if err != nil {{
|
|
return {context_default}
|
|
}}
|
|
return {parser_cast}
|
|
}}
|
|
return {context_default}
|
|
}}
|
|
""".format(**typedef))
|
|
|
|
|
|
def _write_altsrc_flag_types(outfile, types):
|
|
_fwrite(outfile, """\
|
|
package altsrc
|
|
|
|
import (
|
|
"gopkg.in/urfave/cli.v1"
|
|
)
|
|
|
|
// WARNING: This file is generated!
|
|
|
|
""")
|
|
|
|
for typedef in types:
|
|
_set_typedef_defaults(typedef)
|
|
|
|
_fwrite(outfile, """\
|
|
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
|
|
// for other values to be specified
|
|
type {name}Flag struct {{
|
|
cli.{name}Flag
|
|
set *flag.FlagSet
|
|
}}
|
|
|
|
// New{name}Flag creates a new {name}Flag
|
|
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
|
|
return &{name}Flag{{{name}Flag: fl, set: nil}}
|
|
}}
|
|
|
|
// Apply saves the flagSet for later usage calls, then calls the
|
|
// wrapped {name}Flag.Apply
|
|
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
|
|
f.set = set
|
|
f.{name}Flag.Apply(set)
|
|
}}
|
|
|
|
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
|
// wrapped {name}Flag.ApplyWithError
|
|
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
|
|
f.set = set
|
|
return f.{name}Flag.ApplyWithError(set)
|
|
}}
|
|
""".format(**typedef))
|
|
|
|
|
|
def _fwrite(outfile, text):
|
|
print(textwrap.dedent(text), end='', file=outfile)
|
|
|
|
|
|
_WRITEFUNCS = {
|
|
'cli': _write_cli_flag_types,
|
|
'altsrc': _write_altsrc_flag_types
|
|
}
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|