urfave-cli/generate-flag-types
Jesse Szwedko d71794de19 Make ApplyWithError a public method on errorableFlag
Add to altsrc flags. Otherwise, flagSet() was bypassing altsrc's attempt
at shadowing.
2016-11-19 10:51:20 -08:00

256 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
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())