You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

256 lines
8.0 KiB

  1. #!/usr/bin/env python
  2. """
  3. The flag types that ship with the cli library have many things in common, and
  4. so we can take advantage of the `go generate` command to create much of the
  5. source code from a list of definitions. These definitions attempt to cover
  6. the parts that vary between flag types, and should evolve as needed.
  7. An example of the minimum definition needed is:
  8. {
  9. "name": "SomeType",
  10. "type": "sometype",
  11. "context_default": "nil"
  12. }
  13. In this example, the code generated for the `cli` package will include a type
  14. named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
  15. Fetching values by name via `*cli.Context` will default to a value of `nil`.
  16. A more complete, albeit somewhat redundant, example showing all available
  17. definition keys is:
  18. {
  19. "name": "VeryMuchType",
  20. "type": "*VeryMuchType",
  21. "value": true,
  22. "dest": false,
  23. "doctail": " which really only wraps a []float64, oh well!",
  24. "context_type": "[]float64",
  25. "context_default": "nil",
  26. "parser": "parseVeryMuchType(f.Value.String())",
  27. "parser_cast": "[]float64(parsed)"
  28. }
  29. The meaning of each field is as follows:
  30. name (string) - The type "name", which will be suffixed with
  31. `Flag` when generating the type definition
  32. for `cli` and the wrapper type for `altsrc`
  33. type (string) - The type that the generated `Flag` type for `cli`
  34. is expected to "contain" as its `.Value` member
  35. value (bool) - Should the generated `cli` type have a `Value`
  36. member?
  37. dest (bool) - Should the generated `cli` type support a
  38. destination pointer?
  39. doctail (string) - Additional docs for the `cli` flag type comment
  40. context_type (string) - The literal type used in the `*cli.Context`
  41. reader func signature
  42. context_default (string) - The literal value used as the default by the
  43. `*cli.Context` reader funcs when no value is
  44. present
  45. parser (string) - Literal code used to parse the flag `f`,
  46. expected to have a return signature of
  47. (value, error)
  48. parser_cast (string) - Literal code used to cast the `parsed` value
  49. returned from the `parser` code
  50. """
  51. from __future__ import print_function, unicode_literals
  52. import argparse
  53. import json
  54. import os
  55. import subprocess
  56. import sys
  57. import tempfile
  58. import textwrap
  59. class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
  60. argparse.RawDescriptionHelpFormatter):
  61. pass
  62. def main(sysargs=sys.argv[:]):
  63. parser = argparse.ArgumentParser(
  64. description='Generate flag type code!',
  65. formatter_class=_FancyFormatter)
  66. parser.add_argument(
  67. 'package',
  68. type=str, default='cli', choices=_WRITEFUNCS.keys(),
  69. help='Package for which flag types will be generated'
  70. )
  71. parser.add_argument(
  72. '-i', '--in-json',
  73. type=argparse.FileType('r'),
  74. default=sys.stdin,
  75. help='Input JSON file which defines each type to be generated'
  76. )
  77. parser.add_argument(
  78. '-o', '--out-go',
  79. type=argparse.FileType('w'),
  80. default=sys.stdout,
  81. help='Output file/stream to which generated source will be written'
  82. )
  83. parser.epilog = __doc__
  84. args = parser.parse_args(sysargs[1:])
  85. _generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
  86. return 0
  87. def _generate_flag_types(writefunc, output_go, input_json):
  88. types = json.load(input_json)
  89. tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False)
  90. writefunc(tmp, types)
  91. tmp.close()
  92. new_content = subprocess.check_output(
  93. ['goimports', tmp.name]
  94. ).decode('utf-8')
  95. print(new_content, file=output_go, end='')
  96. output_go.flush()
  97. os.remove(tmp.name)
  98. def _set_typedef_defaults(typedef):
  99. typedef.setdefault('doctail', '')
  100. typedef.setdefault('context_type', typedef['type'])
  101. typedef.setdefault('dest', True)
  102. typedef.setdefault('value', True)
  103. typedef.setdefault('parser', 'f.Value, error(nil)')
  104. typedef.setdefault('parser_cast', 'parsed')
  105. def _write_cli_flag_types(outfile, types):
  106. _fwrite(outfile, """\
  107. package cli
  108. // WARNING: This file is generated!
  109. """)
  110. for typedef in types:
  111. _set_typedef_defaults(typedef)
  112. _fwrite(outfile, """\
  113. // {name}Flag is a flag with type {type}{doctail}
  114. type {name}Flag struct {{
  115. Name string
  116. Usage string
  117. EnvVar string
  118. Hidden bool
  119. """.format(**typedef))
  120. if typedef['value']:
  121. _fwrite(outfile, """\
  122. Value {type}
  123. """.format(**typedef))
  124. if typedef['dest']:
  125. _fwrite(outfile, """\
  126. Destination *{type}
  127. """.format(**typedef))
  128. _fwrite(outfile, "\n}\n\n")
  129. _fwrite(outfile, """\
  130. // String returns a readable representation of this value
  131. // (for usage defaults)
  132. func (f {name}Flag) String() string {{
  133. return FlagStringer(f)
  134. }}
  135. // GetName returns the name of the flag
  136. func (f {name}Flag) GetName() string {{
  137. return f.Name
  138. }}
  139. // {name} looks up the value of a local {name}Flag, returns
  140. // {context_default} if not found
  141. func (c *Context) {name}(name string) {context_type} {{
  142. return lookup{name}(name, c.flagSet)
  143. }}
  144. // Global{name} looks up the value of a global {name}Flag, returns
  145. // {context_default} if not found
  146. func (c *Context) Global{name}(name string) {context_type} {{
  147. if fs := lookupGlobalFlagSet(name, c); fs != nil {{
  148. return lookup{name}(name, fs)
  149. }}
  150. return {context_default}
  151. }}
  152. func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
  153. f := set.Lookup(name)
  154. if f != nil {{
  155. parsed, err := {parser}
  156. if err != nil {{
  157. return {context_default}
  158. }}
  159. return {parser_cast}
  160. }}
  161. return {context_default}
  162. }}
  163. """.format(**typedef))
  164. def _write_altsrc_flag_types(outfile, types):
  165. _fwrite(outfile, """\
  166. package altsrc
  167. import (
  168. "gopkg.in/urfave/cli.v1"
  169. )
  170. // WARNING: This file is generated!
  171. """)
  172. for typedef in types:
  173. _set_typedef_defaults(typedef)
  174. _fwrite(outfile, """\
  175. // {name}Flag is the flag type that wraps cli.{name}Flag to allow
  176. // for other values to be specified
  177. type {name}Flag struct {{
  178. cli.{name}Flag
  179. set *flag.FlagSet
  180. }}
  181. // New{name}Flag creates a new {name}Flag
  182. func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{
  183. return &{name}Flag{{{name}Flag: fl, set: nil}}
  184. }}
  185. // Apply saves the flagSet for later usage calls, then calls the
  186. // wrapped {name}Flag.Apply
  187. func (f *{name}Flag) Apply(set *flag.FlagSet) {{
  188. f.set = set
  189. f.{name}Flag.Apply(set)
  190. }}
  191. // ApplyWithError saves the flagSet for later usage calls, then calls the
  192. // wrapped {name}Flag.ApplyWithError
  193. func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
  194. f.set = set
  195. return f.{name}Flag.ApplyWithError(set)
  196. }}
  197. """.format(**typedef))
  198. def _fwrite(outfile, text):
  199. print(textwrap.dedent(text), end='', file=outfile)
  200. _WRITEFUNCS = {
  201. 'cli': _write_cli_flag_types,
  202. 'altsrc': _write_altsrc_flag_types
  203. }
  204. if __name__ == '__main__':
  205. sys.exit(main())