Created
February 21, 2018 21:58
-
-
Save danbradham/962db0b4b0b8ade28fb096d01816431d to your computer and use it in GitHub Desktop.
Make Armin Ronacher's excellent click library even more excellent.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
''' | |
extraclick.py | |
============== | |
Make Armin Ronacher's excellent click library even more excellent. | |
- ListOption: click.Option subclass with a custom parser that eats all | |
arguments until the next option is found. This allows for the same sort | |
of behavior as argparses' nargs=*. | |
.. code-block:: python | |
>>> @click.option('--values', '-v', cls=ListOption) | |
- OBJECT: click.ParamType subclass that uses ast.literal_eval to convert | |
arguments to python objects in the same manner as Python Fire. | |
.. code-block:: python | |
>>> @click.argument('obj', type=OBJECT) | |
''' | |
from __future__ import absolute_import, division, print_function | |
import click | |
import ast | |
class NameToString(ast.NodeTransformer): | |
'''Visits Name nodes and transforms them to Str nodes''' | |
def visit_Name(self, node): | |
if node.id in ('True', 'False', 'None'): | |
return node | |
return ast.copy_location(ast.Str(node.id), node) | |
def safe_eval(string): | |
'''Safely evaluates "string", converting it to a valid python object. | |
This is the same technique used by Python Fire to evaluate arguments. This | |
method is intended to be used for cli argument conversion and therefore | |
converts all ast.Name nodes to ast.Str nodes to reduce the need for | |
nested quotes. Note in the *Conversions* section, "{a: 1.0}" and | |
"{'a': 1.0}" both evaluate to {'a': 1.0}. | |
Arguments: | |
string (str): String to convert to a python object | |
Conversions: | |
"1" => 1 | |
"1.0" => 1.0 | |
"(1,)" => (1,) | |
"string" => 'string' | |
"{a: 1.0}" => {'a': 1.0} | |
"{'a': 1.0}" => {'a': 1.0} | |
"[a, 1, 2.0]" => ['a', 1, 2.0] | |
''' | |
tree = NameToString().visit(ast.parse(string, mode='eval')) | |
value = ast.literal_eval(tree) | |
return value | |
class ObjType(click.ParamType): | |
'''ObjType converts a command line arguments to a python objects using | |
ast.literal_eval. For convenience use the global OBJECT instead of | |
ObjType() with click.argument or click.option. | |
See also: | |
safe_eval, click.ParamType | |
Examples: | |
>>> @click.command() | |
... @click.argument('obj', type=OBJECT) | |
... def show_obj(obj): | |
... print(type(obj), obj) | |
''' | |
name = 'object' | |
def convert(self, value, param, ctx): | |
try: | |
value = safe_eval(value) | |
except: | |
self.fail('%s is not a valid python object' % value, param, ctx) | |
else: | |
return value | |
# Use me with the type keyword argument of click.argument and click.option | |
OBJECT = ObjType() | |
class ListOption(click.Option): | |
'''An Option that gets all values up until the next cli option and passes | |
them as a list. ast.literal_eval is used to convert the options arguments | |
to python objects. | |
See also: | |
click.Option | |
Examples: | |
>>> @click.command() | |
... @click.option('--keys', '-k', cls=ListOption) | |
... @click.option('--other', '-o') | |
... def show_keys(keys, other): | |
... print(keys) | |
''' | |
def add_to_parser(self, parser, ctx): | |
result = super(ListOption, self).add_to_parser(parser, ctx) | |
name = self.opts[0] | |
self._parser = parser._long_opt.get(name, parser._short_opt.get(name)) | |
self._process = self._parser.process | |
def process(value, state): | |
value = [safe_eval(value)] | |
while state.rargs: | |
next_value = state.rargs[0] | |
for prefix in self._parser.prefixes: | |
if next_value.startswith(prefix): | |
break | |
else: | |
state.rargs.pop(0) | |
value.append(safe_eval(next_value)) | |
return self._process(value, state) | |
self._parser.process = process | |
return result |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment