#!/usr/bin/env python2 """ 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 Hidden bool Category string """.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 }} // GetHidden lets us know if the flag is hidden or not func (f {name}Flag) GetHidden() bool {{ return f.Hidden }} // GetCategory lets us access the flag category func (f {name}Flag) GetCategory() string {{ return f.Category }} // {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())