urbit/urb
2016-07-06 17:31:33 -04:00

348 lines
12 KiB
Python
Executable File

#!/usr/bin/python
# TODO:
# - -h text
# - output to clay
import sys
import logging
import json
import requests
import argparse
import base64
PORT = 12322
logging.basicConfig(level=logging.WARNING,
format='%(levelname)s %(funcName)s %(lineno)s - %(message)s',
stream=sys.stderr)
logging.debug(['sys.argv', sys.argv])
def preprocess_args(old_args):
"""Split out []
We use [] to delimit tuples. The following syntaxes are all equivalent:
-- --open --open a b --close --open c d --close --close
-- [ [ a b ] [ c d ] ]
-- [ [a b] [c d] ]
-- [[a b] [c d]]
-- etc
We don't allow [[a b][c d]]. The rule is that we accept zero or more [ at
the beginning of a token and zero or more ] at the end of a token.
In this function, we convert all legal syntaxes to as if they were entered
as in the first example above. This allows them to be parsed by a
relatively sane argparse system.
"""
if old_args == []:
return []
if old_args[0][0] == '[':
if len(old_args[0]) > 1:
return ['--open'] + preprocess_args([old_args[0][1:]] + old_args[1:])
else:
return ['--open'] + preprocess_args(old_args[1:])
if old_args[0][-1] == ']':
if len(old_args[0]) > 1:
return preprocess_args([old_args[0][:-1]]) + \
['--close'] + preprocess_args(old_args[1:])
else:
return ['--close'] + preprocess_args(old_args[1:])
return [old_args[0]] + preprocess_args(old_args[1:])
args = preprocess_args(sys.argv[1:])
logging.debug(['preprocessed', args])
class sourceAction(argparse.Action):
"""Handle source flag.
This is all the 'primitive' source flags -- no nesting, no tuple stuff,
just one flag with one argument.
Besides the normal argparse.Action arguments, we require the following named
argument:
-- which='foo'. Since all source flags use res.source, this specifies the
key of the entry for this flag.
"""
def __init__(self, option_strings, dest, **kwargs):
self.which = kwargs['which']
del kwargs['which']
super(sourceAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, res, new_value, option_string):
logging.debug('%r %r' % (new_value, option_string))
logging.debug('source %s' % res.source)
logging.debug('level %s' % res.level)
if res.source is not None:
def help(source, level):
logging.debug('source %s' % source)
logging.debug('level %s' % level)
if not isinstance(source, list):
raise ValueError('Already specified one source')
elif level == 0:
raise ValueError('Already specified a source %r %s' % (source, level))
elif level == 1:
return source + [self.construct_value(new_value)]
else:
return source[:-1] + [help(source[-1], level - 1)]
res.source = help(res.source, res.level)
else:
res.source = \
self.construct_value(new_value)
logging.debug(res.source)
def construct_value(self, new_value):
if new_value == '-':
return self.construct_value(''.join(sys.stdin))
elif new_value[0:2] == '@@':
with open(new_value[2:]) as f:
content = f.readlines()
return self.construct_value(''.join(content))
else:
return {self.which: new_value}
class transformerAction(argparse.Action):
"""Handle transformer flag.
This is all the tranformer flags. Each flag takes one argument and
transforms the previous source.
Besides the normal argparse.Action arguments, we require the following named
arguments:
-- which='foo'. Since all source flags use res.source, this specifies the
key of the entry for this flag.
-- nesting='foo'. The key for the argument is 'foo'.
"""
def __init__(self, option_strings, dest, **kwargs):
self.which = kwargs['which']
self.nesting = kwargs['nesting']
del kwargs['which']
del kwargs['nesting']
super(transformerAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, res, new_value, option_string):
logging.debug('%r %r' % (new_value, option_string))
logging.debug('source %s' % res.source)
logging.debug('level %s' % res.level)
if res.source is None:
raise ValueError('Need source before transformer')
else:
def help(source, level):
logging.debug('source %s' % source)
logging.debug('level %s' % level)
if level == 0 or level is None:
return {self.which: {self.nesting: new_value, "next": source}}
elif not isinstance(source, list):
raise ValueError('Already specified one source')
else:
return source[:-1] + [help(source[-1], level - 1)]
res.source = help(res.source, res.level)
logging.debug(res.source)
class openAction(argparse.Action):
"""Handle open tuple.
Opens a source tuple. Can only exist in the same places as any other
source.
"""
def __init__(self, option_strings, dest, **kwargs):
super(openAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, res, new_value, option_string):
if res.level is None:
res.level = 0
logging.debug('source %s' % res.source)
logging.debug('level %s' % res.level)
if res.source is None:
res.source = []
res.level = 1
return
def help(source, level):
if not isinstance(source, list):
raise ValueError('Starting tuple after source is finished')
if level == 1:
return (source + [[]], level + 1)
elif level > 1:
rsource, rlevel = help(source[-1], level - 1)
return (source[:-1] + [rsource], rlevel + 1)
else:
raise ValueError('opening strange level %r %s' % (source, level))
res.source, res.level = help(res.source, res.level)
class closeAction(argparse.Action):
"""Handle close tuple.
Closes a source tuple. Can only exist when a tuple is already open.
"""
def __init__(self, option_strings, dest, **kwargs):
super(closeAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, res, new_value, option_string):
if res.level is None:
raise ValueError('Ending tuple before starting one')
logging.debug('level %s' % res.level)
if res.source is None:
raise ValueError('Ending tuple with empty source')
def help(source, level):
if not isinstance(source, list):
raise ValueError('Ending tuple that isn\'t a tuple')
if level == 1:
return level - 1
elif level > 1:
return help(source[-1], level - 1) + 1
else:
raise ValueError('closing strange level %r %s' % (source, level))
res.level = help(res.source, res.level)
logging.debug('level %s' % res.level)
class sinkAction(argparse.Action):
"""Handle sink flag.
We expect only one sinkAction to ever be executed. We recommend using
mutually_exclusive_group's.
Besides the normal action flags, we require the following named argument:
-- which='foo'. Since all sink flags use res.sink, this specifies the key
of the entry for this flag.
"""
def __init__(self, option_strings, dest, **kwargs):
self.which = kwargs['which']
del kwargs['which']
super(sinkAction, self).__init__(option_strings, dest, **kwargs)
def __call__(self, parser, res, new_value, option_string):
res.sink = self.construct_value(new_value)
logging.debug(res.sink)
def construct_value(self, new_value):
if self.which == 'output-file':
return {self.which: new_value[::-1].replace('.','/',1)[::-1]}
else:
return {self.which: new_value}
parser = argparse.ArgumentParser(description='headless urbit')
parser.add_argument('-d', '--dojo', which='dojo',
metavar='command-line',
help='run dojo command',
action=sourceAction, dest='source')
parser.add_argument('-D', '--data', which='data',
metavar='text',
help='literal text data',
action=sourceAction)
parser.add_argument('-c', '--clay', which='clay',
metavar='clay-path',
help='load data from clay',
action=sourceAction)
parser.add_argument('-u', '--url', which='url',
metavar='url',
help='pull data from url',
action=sourceAction)
parser.add_argument('-a', '--api', which='api',
metavar='command',
help='get data from api',
action=sourceAction)
parser.add_argument('-g', '--get-api', which='get-api',
metavar='api:endpoint',
help='get data from api endpoint',
action=sourceAction)
parser.add_argument('-l', '--listen-api', which='listen-api',
metavar='api:event',
help='listen to event from api',
action=sourceAction)
parser.add_argument('-m', '--mark', which='as',
metavar='mark',
help='transform a source to another mark',
nesting='mark',
action=transformerAction)
parser.add_argument('-H', '--hoon', which='hoon',
metavar='code',
help='transform a source by hoon code',
nesting='code',
action=transformerAction)
parser.add_argument('--open',
nargs=0,
help='start tuple',
action=openAction, dest='level')
parser.add_argument('--close',
nargs=0,
help='stop tuple',
action=closeAction)
sinks = parser.add_mutually_exclusive_group()
sinks.add_argument('-s', '--stdout', const={'stdout': None},
default={'stdout': None},
action='store_const', dest='sink')
sinks.add_argument('-f', '--output-file', which='output-file',
metavar='path',
action=sinkAction)
sinks.add_argument('-C', '--output-clay', which='output-clay',
metavar='clay-path',
action=sinkAction)
sinks.add_argument('-U', '--output-url', which='url',
metavar='url',
action=sinkAction)
sinks.add_argument('-t', '--to-api', which='to-api',
metavar='api-command',
action=sinkAction)
sinks.add_argument('-n', '--send-api', which='send-api',
metavar='api:endpoint',
action=sinkAction)
sinks.add_argument('-x', '--command', which='command',
metavar='command',
action=sinkAction)
sinks.add_argument('-p', '--app', which='app',
metavar='app',
action=sinkAction)
args = parser.parse_args(args)
if args.source is None:
args.source = {"data": ''.join(sys.stdin)}
payload = {"source": args.source, "sink": args.sink}
logging.debug(['payload', json.dumps(payload)])
url = "http://localhost:%s" % PORT
r = requests.post(url, data=json.dumps(payload))
if r.text[0] == '"':
print r.text[1:-1].decode('string_escape')
elif r.text[0] == '{':
# print r.text
json_data = json.loads(r.text)
logging.debug(json_data)
with open(json_data['file'][:0:-1].replace('/','.',1)[::-1], 'w') as f:
f.write(base64.b64decode(json_data['data']))
else:
logging.warn("unrecognized response")
print r.text