Files
google-apis-rs/src/mako/cli/lib/cli.py
Sebastian Thiel f83dff672b refactor(config): bring in all required field data
Previously we only knew the type as string, now we have enums and
additional type information, like whether or not it's a POD.

However, borrow-checker doesn't like the current code, will need more
work.
2015-05-12 12:17:36 +02:00

264 lines
8.9 KiB
Python

import util
import os
import re
import collections
from copy import deepcopy
from random import (randint, random, choice)
SPLIT_START = '>>>>>>>'
SPLIT_END = '<<<<<<<'
PARAM_FLAG = 'p'
STRUCT_FLAG = 'r'
UPLOAD_FLAG = 'u'
OUTPUT_FLAG = 'o'
VALUE_ARG = 'v'
KEY_VALUE_ARG = 'kv'
SCOPE_FLAG = 'scope'
CONFIG_DIR_FLAG = 'config-dir'
DEBUG_FLAG = 'debug'
DEBUG_AUTH_FLAG = 'debug-auth'
DEFAULT_MIME = 'application/octet-stream'
MODE_ARG = 'mode'
FILE_ARG = 'file'
FILE_FLAG = 'f'
MIME_ARG = 'mime'
MIME_FLAG = 'm'
OUT_ARG = 'out'
SCOPE_ARG = 'url'
CONFIG_DIR_ARG = 'folder'
FIELD_SEP = '.'
CONFIG_DIR = '~/.google-service-cli'
POD_TYPES = set(('boolean', 'integer', 'number', 'uint32', 'double', 'float', 'int32', 'int64', 'uint64', 'string'))
re_splitters = re.compile(r"%s ([\w\-\.]+)\n(.*?)\n%s" % (SPLIT_START, SPLIT_END), re.MULTILINE|re.DOTALL)
MethodContext = collections.namedtuple('MethodContext', ['m', 'response_schema', 'params', 'request_value',
'media_params' ,'required_props', 'optional_props',
'part_prop'])
CTYPE_POD = 'pod'
CTYPE_ARRAY = 'list'
CTYPE_MAP = 'map'
SchemaEntry = collections.namedtuple('SchemaEntry', ['container_type', 'actual_property', 'property'])
JSON_TYPE_RND_MAP = {'boolean': lambda: str(bool(randint(0, 1))).lower(),
'integer' : lambda: randint(0, 100),
'uint32' : lambda: randint(0, 100),
'uint64' : lambda: randint(0, 65556),
'float' : lambda: random(),
'double' : lambda: random(),
'number' : lambda: random(),
'int32' : lambda: randint(-101, -1),
'int64' : lambda: randint(-101, -1),
'string': lambda: '%s' % choice(util.words).lower()}
JSON_TYPE_TO_ENUM_MAP = {'boolean' : 'Boolean',
'integer' : 'Int',
'number' : 'Float',
'uint32' : 'Int',
'double' : 'Float',
'float' : 'Float',
'int32' : 'Int',
'any' : 'String', # TODO: Figure out how to handle it. It's 'interface' in Go ...
'int64' : 'Int',
'uint64' : 'Uint',
'string' : 'String'}
CTYPE_TO_ENUM_MAP = {CTYPE_POD: 'Pod',
CTYPE_ARRAY: 'Vec',
CTYPE_MAP: 'Map'}
JSON_TYPE_VALUE_MAP = {'boolean': 'false',
'integer' : '-0',
'uint32' : '0',
'uint64' : '0',
'float' : '0.0',
'double' : '0.0',
'number' : '0.0',
'int32' : '-0',
'int64' : '-0',
'string': ''}
assert len(set(JSON_TYPE_RND_MAP.keys()) ^ POD_TYPES) == 0
def new_method_context(resource, method, c):
m = c.fqan_map[util.to_fqan(c.rtc_map[resource], resource, method)]
response_schema = util.method_response(c, m)
params, request_value = util.build_all_params(c, m)
media_params = util.method_media_params(m)
required_props, optional_props, part_prop = util.organize_params(params, request_value)
return MethodContext(m, response_schema, params, request_value, media_params,
required_props, optional_props, part_prop)
def comma_sep_fields(fields):
return ', '.join('"%s"' % mangle_subcommand(f) for f in fields)
# Returns a string representing a string-vector of mangled names
# fields is an iterator
def field_vec(fields):
return "vec![%s]" % comma_sep_fields(fields)
def pretty(n):
return ' '.join(s.capitalize() for s in mangle_subcommand(n).split('-'))
def is_request_value_property(mc, p):
return mc.request_value and mc.request_value.id == p.get(util.TREF)
# transform name to be a suitable subcommand
def mangle_subcommand(name):
return util.camel_to_under(name).replace('_', '-').replace('.', '-')
def ident(name):
return mangle_subcommand(name).replace('-', '_')
# Return a required value in Rust, using unwrap()
def req_value(name):
return 'opt.value_of("' + mangle_subcommand(name) + '").unwrap()'
def opt_value(name, opt='opt', default=''):
return opt + '.value_of("' + mangle_subcommand(name) + ('").unwrap_or("%s")' % default)
def opt_values(name, opt='opt'):
return opt + '.values_of("' + mangle_subcommand(name) + '").unwrap_or(Vec::new()).iter()'
def application_secret_path(program_name):
return program_name + '-secret.json'
# Returns identifier for method dealing with options for the given resource-method pair
def call_method_ident(resource, method):
return '_%s_%s' % (ident(resource), ident(method))
# transform the resource name into a suitable filename to contain the markdown documentation for it
def subcommand_md_filename(resource, method):
return mangle_subcommand(resource) + '_' + mangle_subcommand(method) + '.md'
def docopt_mode(protocols):
mode = '|'.join(protocols)
if len(protocols) > 1:
mode = '(%s)' % mode
return mode
# Returns a possibly remapped type, based on its name.
# Useful to map strings to more suitable types, i.e. counts
def actual_json_type(name, type):
if type == 'string' and 'Count' in name:
return 'int64'
return type
# return a string representing property `p` suitable for docopt argument parsing
def to_docopt_arg(p):
return '<%s>%s' % (mangle_subcommand(p.name), p.get('repeated', False) and '...' or '')
# Return schema' with fields dict: { 'field1' : SchemaField(...), 'SubSchema': schema' }
def to_cli_schema(c, schema):
res = deepcopy(schema)
fd = dict()
res['fields'] = fd
# util.nested_type_name
properties = schema.get('properties', dict())
if not properties and 'variant' in schema and 'map' in schema.variant:
for e in schema.variant.map:
assert util.TREF in e
properties[e.type_value] = e
# end handle enumerations
for pn, p in properties.iteritems():
def set_nested_schema(ns):
if ns.fields:
fd[pn] = ns
# end utility
def dup_property():
pc = deepcopy(p)
if 'type' in pc and pc.type == 'string' and 'Count' in pn:
pc.type = 'int64'
return pc
# end
if util.TREF in p:
if p[util.TREF] != schema.id: # prevent recursion (in case of self-referential schemas)
set_nested_schema(to_cli_schema(c, c.schemas[p[util.TREF]]))
elif p.type == 'array' and 'items' in p and 'type' in p.get('items') and p.get('items').type in POD_TYPES:
pc = dup_property()
fd[pn] = SchemaEntry(CTYPE_ARRAY, pc.get('items'), pc)
elif p.type == 'object':
if util.is_map_prop(p):
if 'type' in p.additionalProperties and p.additionalProperties.type in POD_TYPES:
pc = dup_property()
fd[pn] = SchemaEntry(CTYPE_MAP, pc.additionalProperties, pc)
else:
set_nested_schema(to_cli_schema(c, c.schemas[util.nested_type_name(schema.id, pn)]))
elif p.type in POD_TYPES:
pc = dup_property()
fd[pn] = SchemaEntry(CTYPE_POD, pc, pc)
# end handle property type
# end
return res
# Convert the given cli-schema (result from to_cli_schema(schema)) to a yaml-like string. It's suitable for
# documentation only
def cli_schema_to_yaml(schema, prefix=''):
if not prefix:
o = '%s%s:\n' % (prefix, util.unique_type_name(schema.id))
else:
o = ''
prefix += ' '
for fn in sorted(schema.fields.keys()):
f = schema.fields[fn]
o += '%s%s:' % (prefix, mangle_subcommand(fn))
if not isinstance(f, SchemaEntry):
o += '\n' + cli_schema_to_yaml(f, prefix)
else:
t = f.actual_property.type
if f.container_type == CTYPE_ARRAY:
t = '[%s]' % t
elif f.container_type == CTYPE_MAP:
t = '{ string: %s }' % t
o += ' %s\n' % t
# end for each field
return o
# Return a value string suitable for the given field.
def field_to_value(f):
v = JSON_TYPE_RND_MAP[f.actual_property.type]()
if f.container_type == CTYPE_MAP:
v = 'key=%s' % v
return v
# split the result along split segments
def process_template_result(r, output_file):
found = False
dir = None
if output_file:
dir = os.path.dirname(output_file)
if not os.path.isdir(dir):
os.makedirs(dir)
# end handle output directory
for m in re_splitters.finditer(r):
found = True
fh = open(os.path.join(dir, m.group(1)), 'wb')
fh.write(m.group(2).encode('UTF-8'))
fh.close()
# end for each match
if found:
r = None
return r