"Fossies" - the Fresh Open Source Software Archive

Member "http-prompt-2.1.0/http_prompt/execution.py" (5 Mar 2021, 20137 Bytes) of package /linux/www/http-prompt-2.1.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "execution.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.0.0_vs_2.1.0.

    1 import io
    2 import json
    3 import re
    4 import os
    5 import sys
    6 from urllib.parse import urlparse, urljoin
    7 
    8 import click
    9 
   10 from subprocess import CalledProcessError, Popen, PIPE
   11 
   12 from httpie.context import Environment
   13 from httpie.core import main as httpie_main
   14 from parsimonious.exceptions import ParseError, VisitationError
   15 from parsimonious.grammar import Grammar
   16 from parsimonious.nodes import NodeVisitor
   17 from parsimonious.nodes import Node
   18 from pygments.token import String, Name
   19 
   20 from .completion import ROOT_COMMANDS, ACTIONS, OPTION_NAMES, HEADER_NAMES
   21 from .context import Context
   22 from .context.transform import (
   23     extract_args_for_httpie_main,
   24     format_to_curl,
   25     format_to_httpie,
   26     format_to_http_prompt)
   27 from .output import Printer, TextWriter
   28 from .utils import unescape, unquote, colformat
   29 
   30 
   31 HTTPIE_PROGRAM_NAME = 'http'
   32 
   33 
   34 grammar = r"""
   35     command = mutation / immutation
   36 
   37     mutation = concat_mut+ / nonconcat_mut
   38     immutation = preview / action / ls / env / help / exit / exec / source / clear / _
   39 
   40     concat_mut = option_mut / full_quoted_mut / value_quoted_mut / unquoted_mut
   41     nonconcat_mut = cd / rm
   42 
   43     preview = _ tool _ (method _)? (urlpath _)? concat_mut* redir_out? _
   44     action = _ method _ (urlpath _)? concat_mut* redir_out? _
   45     urlpath = (~r"https?://" unquoted_string) /
   46               (!concat_mut !redir_out string)
   47 
   48     clear = _ "clear" _
   49     help = _ "help" _
   50     exit = _ "exit" _
   51     ls = _ "ls" _ (urlpath _)? (redir_out)?
   52     env  = _ "env" _ (redir_out)?
   53     source = _ "source" _ filepath _
   54     exec = _ "exec" _ filepath _
   55 
   56     redir_out = redir_append / redir_write / pipe
   57     redir_append = _ ">>" _ filepath _
   58     redir_write = _ ">" _ filepath _
   59     pipe = _ "|" _ (shell_subs / shell_code) _
   60 
   61     unquoted_mut = _ unquoted_mutkey mutop unquoted_mutval _
   62     full_quoted_mut = full_squoted_mut / full_dquoted_mut
   63     value_quoted_mut = value_squoted_mut / value_dquoted_mut
   64     full_squoted_mut = _ "'" squoted_mutkey mutop squoted_mutval "'" _
   65     full_dquoted_mut = _ '"' dquoted_mutkey mutop dquoted_mutval '"' _
   66     value_squoted_mut = _ unquoted_mutkey mutop "'" squoted_mutval "'" _
   67     value_dquoted_mut = _ unquoted_mutkey mutop '"' dquoted_mutval '"' _
   68     mutop = ":=" / ":" / "==" / "="
   69     unquoted_mutkey = unquoted_mutkey_item+
   70     unquoted_mutval = unquoted_stringitem*
   71     unquoted_mutkey_item = shell_subs / unquoted_mutkey_char / escapeseq
   72     unquoted_mutkey_char = ~r"[^\s'\"\\=:>]"
   73     squoted_mutkey = squoted_mutkey_item+
   74     squoted_mutval = squoted_stringitem*
   75     squoted_mutkey_item = shell_subs / squoted_mutkey_char / escapeseq
   76     squoted_mutkey_char = ~r"[^\r\n'\\=:]"
   77     dquoted_mutkey = dquoted_mutkey_item+
   78     dquoted_mutval = dquoted_stringitem*
   79     dquoted_mutkey_item = shell_subs / dquoted_mutkey_char / escapeseq
   80     dquoted_mutkey_char = ~r'[^\r\n"\\=:]'
   81 
   82     option_mut = flag_option_mut / value_option_mut
   83     flag_option_mut = _ flag_optname _
   84     flag_optname = "--json" / "-j" / "--form" / "-f" / "--verbose" / "-v" /
   85                    "--headers" / "-h" / "--body" / "-b" / "--stream" / "-S" /
   86                    "--download" / "-d" / "--continue" / "-c" / "--follow" /
   87                    "--check-status" / "--ignore-stdin" / "--help" /
   88                    "--version" / "--traceback" / "--debug"
   89     value_option_mut = _ value_optname ~r"(\s+|=)" string _
   90     value_optname = "--pretty" / "--style" / "-s" / "--print" / "-p" /
   91                     "--output" / "-o" / "--session-read-only" / "--session" /
   92                     "--auth-type" / "--auth" / "-a" / "--proxy" / "--verify" /
   93                     "--cert" / "--cert-key" / "--timeout"
   94 
   95     cd = _ "cd" _ string? _
   96     rm = (_ "rm" _ "*" _) / (_ "rm" _ ~r"\-(h|q|b|o)" _ mutkey _)
   97     tool = "httpie" / "curl"
   98     method = ~r"get"i / ~r"head"i / ~r"post"i / ~r"put"i / ~r"delete"i /
   99              ~r"patch"i / ~r"options"i / ~r"connect"i
  100     mutkey = unquoted_mutkey / ("'" squoted_mutkey "'") /
  101              ('"' dquoted_mutkey '"') / flag_optname / value_optname
  102 
  103     string = quoted_string / unquoted_string
  104     quoted_string = ('"' dquoted_stringitem* '"') /
  105                     ("'" squoted_stringitem* "'")
  106     unquoted_string = unquoted_stringitem+
  107     dquoted_stringitem = shell_subs / dquoted_stringchar / escapeseq
  108     squoted_stringitem = shell_subs / squoted_stringchar / escapeseq
  109     unquoted_stringitem = shell_subs / unquoted_stringchar / escapeseq
  110     dquoted_stringchar = ~r'[^\r\n"\\]'
  111     squoted_stringchar = ~r"[^\r\n'\\]"
  112     unquoted_stringchar = ~r"[^\s'\\]"
  113     escapeseq = ~r"\\."
  114     _ = ~r"\s*"
  115 
  116     shell_subs = "`" shell_code "`"
  117     shell_code = ~r"[^`]*"
  118 """
  119 
  120 if sys.platform == 'win32':
  121     # XXX: Windows use backslashes as separators in its filesystem path, so we
  122     # have to avoid using backslashes to escape chars here.
  123     grammar += r"""
  124         filepath = quoted_filepath / unquoted_filepath
  125         quoted_filepath = ('"' dquoted_filepath_char+ '"') /
  126                           ("'" squoted_filepath_char+ "'")
  127         dquoted_filepath_char = ~r'[^\r\n"]'
  128         squoted_filepath_char = ~r"[^\r\n']"
  129         unquoted_filepath = unquoted_filepath_char+
  130         unquoted_filepath_char = ~r"[^\s\"]"
  131     """
  132 else:
  133     grammar += r"""
  134         filepath = string
  135     """
  136 
  137 grammar = Grammar(grammar)
  138 
  139 
  140 if Environment.colors == 256:
  141     from pygments.formatters.terminal256 import (
  142         Terminal256Formatter as TerminalFormatter)
  143 else:
  144     from pygments.formatters.terminal import TerminalFormatter
  145 
  146 
  147 def urljoin2(base, path, **kwargs):
  148     if not base.endswith('/'):
  149         base += '/'
  150     url = urljoin(base, path, **kwargs)
  151     if url.endswith('/') and not path.endswith('/'):
  152         url = url[:-1]
  153     return url
  154 
  155 
  156 def generate_help_text():
  157     """Return a formatted string listing commands, HTTPie options, and HTTP
  158     actions.
  159     """
  160     def generate_cmds_with_explanations(summary, cmds):
  161         text = '{0}:\n'.format(summary)
  162         for cmd, explanation in cmds:
  163             text += '\t{0:<10}\t{1:<20}\n'.format(cmd, explanation)
  164         return text + '\n'
  165 
  166     text = generate_cmds_with_explanations('Commands', ROOT_COMMANDS.items())
  167     text += generate_cmds_with_explanations('Options', OPTION_NAMES.items())
  168     text += generate_cmds_with_explanations('Actions', ACTIONS.items())
  169     text += generate_cmds_with_explanations('Headers', HEADER_NAMES.items())
  170     return text
  171 
  172 
  173 if sys.platform == 'win32':  # nocover
  174     def normalize_filepath(path):
  175         return unquote(path)
  176 else:
  177     def normalize_filepath(path):
  178         return unescape(unquote(path))
  179 
  180 
  181 class DummyExecutionListener(object):
  182 
  183     def context_changed(self, context):
  184         pass
  185 
  186     def response_returned(self, context, response):
  187         pass
  188 
  189 
  190 class ExecutionVisitor(NodeVisitor):
  191 
  192     unwrapped_exceptions = (CalledProcessError,)
  193 
  194     def __init__(self, context, listener=None, style=None):
  195         super(ExecutionVisitor, self).__init__()
  196         self.context = context
  197 
  198         self.context_override = Context(context.url)
  199         self.method = None
  200         self.tool = None
  201         self._output = Printer()
  202 
  203         # If there's a pipe, as in "httpie post | sed s/POST/GET/", this
  204         # variable points to the "sed" Popen object. The variable is necessary
  205         # because the we need to redirect Popen.stdout to Printer, which does
  206         # output pagination.
  207         self.pipe_proc = None
  208 
  209         self.listener = listener or DummyExecutionListener()
  210 
  211         # Last response object returned by HTTPie
  212         self.last_response = None
  213 
  214         # Pygments formatter, used to render output with colors in some cases
  215         if style:
  216             self.formatter = TerminalFormatter(style=style)
  217         else:
  218             self.formatter = None
  219 
  220     @property
  221     def output(self):
  222         return self._output
  223 
  224     @output.setter
  225     def output(self, new_output):
  226         if self._output:
  227             self._output.close()
  228         self._output = new_output
  229 
  230     def visit_method(self, node, children):
  231         self.method = node.text
  232         return node
  233 
  234     def visit_urlpath(self, node, children):
  235         path = node.text
  236         self.context_override.url = urljoin2(self.context_override.url, path)
  237         return node
  238 
  239     def visit_cd(self, node, children):
  240         _, _, _, path, _ = children
  241 
  242         if isinstance(path, Node):
  243             seg = urlparse(self.context_override.url)
  244             self.context_override.url = seg.scheme + "://" + seg.netloc
  245         else:
  246             self.context_override.url = urljoin2(
  247                 self.context_override.url, path)
  248 
  249         return node
  250 
  251     def visit_rm(self, node, children):
  252         children = children[0]
  253         kind = children[3].text
  254 
  255         if kind == '*':
  256             # Clear context
  257             for target in [self.context.headers,
  258                            self.context.querystring_params,
  259                            self.context.body_params,
  260                            self.context.body_json_params,
  261                            self.context.options]:
  262                 target.clear()
  263             return node
  264 
  265         name = children[5]
  266         if kind == '-h':
  267             target = self.context.headers
  268         elif kind == '-q':
  269             target = self.context.querystring_params
  270         elif kind == '-o':
  271             target = self.context.options
  272         else:
  273             assert kind == '-b'
  274             # TODO: This is kind of ugly, will fix it
  275             if name == '*':
  276                 self.context.body_params.clear()
  277                 self.context.body_json_params.clear()
  278             else:
  279                 try:
  280                     del self.context.body_params[name]
  281                 except KeyError:
  282                     del self.context.body_json_params[name]
  283             return node
  284 
  285         if name == '*':
  286             target.clear()
  287         else:
  288             del target[name]
  289 
  290         return node
  291 
  292     def visit_help(self, node, children):
  293         self.output.write(generate_help_text())
  294         return node
  295 
  296     def _redirect_output(self, filepath, mode):
  297         filepath = normalize_filepath(filepath)
  298         self.output = TextWriter(open(os.path.expandvars(filepath), mode))
  299 
  300     def visit_redir_append(self, node, children):
  301         self._redirect_output(children[3], 'ab')
  302         return node
  303 
  304     def visit_redir_write(self, node, children):
  305         self._redirect_output(children[3], 'wb')
  306         return node
  307 
  308     def visit_pipe(self, node, children):
  309         cmd = children[3]
  310         self.pipe_proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE)
  311         self.output = TextWriter(self.pipe_proc.stdin)
  312         return node
  313 
  314     def visit_exec(self, node, children):
  315         path = normalize_filepath(children[3])
  316         with io.open(path, encoding='utf-8') as f:
  317             # Wipe out context first
  318             execute('rm *', self.context, self.listener)
  319             for line in f:
  320                 execute(line, self.context, self.listener)
  321         return node
  322 
  323     def visit_source(self, node, children):
  324         path = normalize_filepath(children[3])
  325         with io.open(path, encoding='utf-8') as f:
  326             for line in f:
  327                 execute(line, self.context, self.listener)
  328         return node
  329 
  330     def _colorize(self, text, token_type):
  331         if not self.formatter:
  332             return text
  333 
  334         out = io.StringIO()
  335         self.formatter.format([(token_type, text)], out)
  336         return out.getvalue()
  337 
  338     def visit_ls(self, node, children):
  339         path = urlparse(self.context_override.url).path
  340         path = filter(None, path.split('/'))
  341         nodes = self.context.root.ls(*path)
  342         if self.output.isatty():
  343             names = []
  344             for node in nodes:
  345                 token_type = String if node.data.get('type') == 'dir' else Name
  346                 name = self._colorize(node.name, token_type)
  347                 names.append(name)
  348             lines = list(colformat(list(names)))
  349         else:
  350             lines = [n.name for n in nodes]
  351         if lines:
  352             self.output.write('\n'.join(lines))
  353         return node
  354 
  355     def visit_env(self, node, children):
  356         text = format_to_http_prompt(self.context)
  357         self.output.write(text)
  358         return node
  359 
  360     def visit_exit(self, node, children):
  361         self.context.should_exit = True
  362         return node
  363 
  364     def visit_clear(self, node, children):
  365         self.output.clear()
  366         return node
  367 
  368     def visit_mutkey(self, node, children):
  369         if isinstance(children[0], list):
  370             return children[0][1]
  371         return children[0]
  372 
  373     def _mutate(self, node, key, op, val):
  374         if op == ':=':
  375             self.context_override.body_json_params[key] = json.loads(val)
  376         elif op == ':':
  377             self.context_override.headers[key] = val
  378         elif op == '=':
  379             self.context_override.body_params[key] = val
  380         elif op == '==':
  381             # You can have multiple querystring params with the same name,
  382             # so we use a list to store multiple values (#20)
  383             params = self.context_override.querystring_params
  384             if key not in params:
  385                 params[key] = [val]
  386             else:
  387                 params[key].append(val)
  388         return node
  389 
  390     def visit_unquoted_mut(self, node, children):
  391         _, key, op, val, _ = children
  392         return self._mutate(node, key, op, val)
  393 
  394     def visit_full_squoted_mut(self, node, children):
  395         _, _, key, op, val, _, _ = children
  396         return self._mutate(node, key, op, val)
  397 
  398     def visit_full_dquoted_mut(self, node, children):
  399         _, _, key, op, val, _, _ = children
  400         return self._mutate(node, key, op, val)
  401 
  402     def visit_value_squoted_mut(self, node, children):
  403         _, key, op, _, val, _, _ = children
  404         return self._mutate(node, key, op, val)
  405 
  406     def visit_value_dquoted_mut(self, node, children):
  407         _, key, op, _, val, _, _ = children
  408         return self._mutate(node, key, op, val)
  409 
  410     def _visit_mut_key_or_val(self, node, children):
  411         return unescape(''.join([c for c in children]), exclude=':=')
  412 
  413     visit_unquoted_mutkey = _visit_mut_key_or_val
  414     visit_unquoted_mutval = _visit_mut_key_or_val
  415     visit_squoted_mutkey = _visit_mut_key_or_val
  416     visit_squoted_mutval = _visit_mut_key_or_val
  417     visit_dquoted_mutkey = _visit_mut_key_or_val
  418     visit_dquoted_mutval = _visit_mut_key_or_val
  419 
  420     def visit_mutop(self, node, children):
  421         return node.text
  422 
  423     def visit_flag_option_mut(self, node, children):
  424         _, key, _ = children
  425         self.context_override.options[key] = None
  426         return node
  427 
  428     def visit_flag_optname(self, node, children):
  429         return node.text
  430 
  431     def visit_value_option_mut(self, node, children):
  432         _, key, _, val, _ = children
  433         self.context_override.options[key] = val
  434         return node
  435 
  436     def visit_value_optname(self, node, children):
  437         return node.text
  438 
  439     def visit_filepath(self, node, children):
  440         return children[0]
  441 
  442     def visit_string(self, node, children):
  443         return children[0]
  444 
  445     def visit_quoted_filepath(self, node, children):
  446         return node.text[1:-1]
  447 
  448     def visit_unquoted_filepath(self, node, children):
  449         return node.text
  450 
  451     def visit_unquoted_string(self, node, children):
  452         return unescape(''.join(children))
  453 
  454     def visit_quoted_string(self, node, children):
  455         return self._visit_mut_key_or_val(node, children[0][1])
  456 
  457     def _visit_stringitem(self, node, children):
  458         child = children[0]
  459         if hasattr(child, 'text'):
  460             return child.text
  461         return child
  462 
  463     visit_unquoted_mutkey_item = _visit_stringitem
  464     visit_unquoted_stringitem = _visit_stringitem
  465     visit_squoted_mutkey_item = _visit_stringitem
  466     visit_squoted_stringitem = _visit_stringitem
  467     visit_dquoted_mutkey_item = _visit_stringitem
  468     visit_dquoted_stringitem = _visit_stringitem
  469 
  470     def visit_tool(self, node, children):
  471         self.tool = node.text
  472         return node
  473 
  474     def visit_mutation(self, node, children):
  475         self.context.update(self.context_override)
  476         self.listener.context_changed(self.context)
  477         return node
  478 
  479     def _final_context(self):
  480         context = self.context.copy()
  481         context.update(self.context_override)
  482         return context
  483 
  484     def _trace_get_response(self, frame, event, arg):
  485         func_name = frame.f_code.co_name
  486         if func_name == 'get_response':
  487             if event == 'call':
  488                 return self._trace_get_response
  489             elif event == 'return':
  490                 self.last_response = arg
  491 
  492     def _call_httpie_main(self):
  493         context = self._final_context()
  494         args = extract_args_for_httpie_main(context, self.method)
  495         env = Environment(stdout=self.output, stdin=sys.stdin,
  496                           is_windows=False)
  497         env.stdout_isatty = self.output.isatty()
  498         env.stdin_isatty = sys.stdin.isatty()
  499 
  500         # XXX: httpie_main() doesn't provide an API for us to get the
  501         # HTTP response object, so we use this super dirty hack -
  502         # sys.settrace() to intercept get_response() that is called in
  503         # httpie_main() internally. The HTTP response intercepted is
  504         # assigned to self.last_response, which self.listener may be
  505         # interested in.
  506         sys.settrace(self._trace_get_response)
  507         try:
  508             httpie_main([HTTPIE_PROGRAM_NAME, *args], env=env)
  509         finally:
  510             sys.settrace(None)
  511 
  512     def visit_immutation(self, node, children):
  513         self.output.close()
  514         if self.pipe_proc:
  515             Printer().write(self.pipe_proc.stdout.read())
  516         return node
  517 
  518     def visit_preview(self, node, children):
  519         context = self._final_context()
  520         if self.tool == 'httpie':
  521             command = format_to_httpie(context, self.method)
  522         else:
  523             assert self.tool == 'curl'
  524             command = format_to_curl(context, self.method)
  525         self.output.write(command)
  526         return node
  527 
  528     def visit_action(self, node, children):
  529         self._call_httpie_main()
  530         if self.last_response:
  531             self.listener.response_returned(self.context, self.last_response)
  532         return node
  533 
  534     def visit_shell_subs(self, node, children):
  535         cmd = children[1]
  536         p = Popen(cmd, shell=True, stdout=PIPE)
  537         return p.stdout.read().decode('utf-8').rstrip()
  538 
  539     def visit_shell_code(self, node, children):
  540         return node.text
  541 
  542     def generic_visit(self, node, children):
  543         if not node.expr_name and node.children:
  544             if len(children) == 1:
  545                 return children[0]
  546             return children
  547         return node
  548 
  549 
  550 def execute(command, context, listener=None, style=None):
  551     try:
  552         root = grammar.parse(command)
  553     except ParseError as err:
  554         # TODO: Better error message
  555         part = command[err.pos:err.pos + 10]
  556         click.secho('Syntax error near "%s"' % part, err=True, fg='red')
  557     else:
  558         visitor = ExecutionVisitor(context, listener=listener, style=style)
  559         try:
  560             visitor.visit(root)
  561         except VisitationError as err:
  562             exc_class = err.original_class
  563             if exc_class is KeyError:
  564                 # XXX: Need to parse VisitationError error message to get the
  565                 # original error message as VisitationError doesn't hold the
  566                 # original exception object
  567                 key = re.search(r"KeyError: u?'(.*)'", str(err)).group(1)
  568                 click.secho("Key '%s' not found" % key, err=True,
  569                             fg='red')
  570             elif issubclass(exc_class, IOError):
  571                 msg = str(err).splitlines()[0]
  572 
  573                 # Remove the exception class name at the beginning
  574                 msg = msg[msg.find(':') + 2:]
  575                 click.secho(msg, err=True, fg='red')
  576             else:
  577                 # TODO: Better error message
  578                 click.secho(str(err), err=True, fg='red')
  579         except CalledProcessError as err:
  580             click.secho(err.output + ' (exit status %d)' % err.returncode,
  581                         fg='red')