Example #1
0
 def _clean_user_namespace(self, venv):
     """ clean ipython username to remove old project's code
         TODO: document and refactor
     """
     msg = "cleaning user namespace"
     smash_log.info(msg)
     self.report(msg)
     names = []
     pm = get_smash().project_manager
     pdir = pm._current_project and pm.project_map[pm._current_project]
     namespace = get_smash().shell.user_ns
     for name, obj in namespace.items():
         try:
             fname = inspect.getfile(obj)
         except TypeError:
             # happens for anything that's not a module, class
             # method, function, traceback, frame or code object
             fname = None
         if fname:
             test = fname.startswith(venv)
             if pdir is not None:
                 test = test or fname.startswith(pdir)
             if test and \
                     not name.startswith('_') and \
                     not name.startswith(SMASH_DIR):
                 names.append(name)
     for name in names:
         del self.smash.shell.user_ns[name]
     if names:
         self.report("wiped from namespace: {0}".format(names))
Example #2
0
    def _showtraceback(self, etype, evalue, string_tb_lines):
        """ before we display the traceback, give smash error
            handlers one more chance to do something smart
        """
        string_tb = '\n'.join(string_tb_lines)
        smash_log.info('dispatching last-ditch-effort error handling: {0}'.format(
            [etype, evalue, string_tb]))
        sooper = super(SmashTerminalInteractiveShell, self)
        if self.smash is not None:
            for handler in self.smash.error_handlers:
                handled = handler(
                    self._smash_last_input, etype, evalue)
                if handled:
                    return

        if etype == NameError and '<ipython-input' in string_tb:
            #len(self._smash_last_input.split('\n')) == 1 and \
            #string is hardcoded in smashlib/ipy3x/core/compilerop.py
            # inside function code_name(code, number=0)
            try:
                msg = 'smash: "{0}": input error'
                msg = msg.format(
                    self._smash_last_input.strip())
            except UnicodeEncodeError:
               msg=str(evalue)
            print msg
        else:
            return sooper._showtraceback(etype, evalue, string_tb_lines)
Example #3
0
 def _unload_alias_group(self, group_name):
     smash_log.info('unloading alias group: {0}'.format(group_name))
     aliases, macros = self._get_alias_group(group_name)
     for alias in aliases:
         name, cmd = alias
         try:
             get_smash().shell.alias_manager.undefine_alias(name)
         except ValueError:
             continue
Example #4
0
 def system(self, cmd, quiet=False, **kargs):
     # print 'wrapping system call',cmd
     result = super(SmashTerminalInteractiveShell, self).system(
         cmd, **kargs)
     # put exit code into os.environ?
     error = self.user_ns['_exit_code']
     smash_log.info("exit code: {0}".format(error))
     # if error:
     #    get_smash().publish(C_COMMAND_FAIL, cmd, error)
     if not quiet and result:
         print result
Example #5
0
    def _load_alias_group(self, group_name):
        smash_log.info('loading alias group: {0}'.format(group_name))
        aliases, macros = self._get_alias_group(group_name)
        for alias in aliases:
            name, cmd = alias
            get_smash().shell.alias_manager.define_alias(name, cmd)
            smash_log.info(' alias: {0}'.format(name))
        self.report("Loaded {0} aliases".format(len(aliases)))

        for m in macros:
            name, macro = m
            self._load_macro(macro, name=name)
Example #6
0
    def _activate(self, path):
        absfpath = abspath(expanduser(path))
        self.publish(C_PRE_ACTIVATE, name=absfpath)
        if True:
            vbin = to_vbin(absfpath)
            vlib = to_vlib(absfpath)

            # compute e.g. <venv>/lib/python2.6.
            # we call bullshit if they have a more than one dir;
            # it might be a chroot but i dont think it's a venv
            python_dir = glob.glob(opj(vlib, 'python*/'))
            if len(python_dir) == 0:
                raise RuntimeError('no python dir in {0}'.format(vlib))
            if len(python_dir) > 1:
                err = "multiple python dirs matching in {0}".format(vlib)
                raise RuntimeError(err)
            python_dir = python_dir[0]

            # this bit might enable switching between two venv's
            # that are be "no-global-site" vs "use-global-site"
            # .. tabling it for now
            # site_file = opj(python_dir, 'site.py')
            # assert ope(site_file)
            # tmp = dict(__file__=site_file)
            # execfile(site_file, tmp)
            #  tmp['main']()

            # some environment variable manipulation that would
            # normally be done by 'source bin/activate', but is
            # not handled by activate_this.py
            #path = get_path().split(':')
            os.environ['VIRTUAL_ENV'] = absfpath

            sandbox = dict(__file__=opj(vbin, 'activate_this.py'))
            execfile(opj(vbin, 'activate_this.py'), sandbox)
            self.reset_path = sandbox['prev_sys_path']

            # libraries like 'datetime' can very occasionally fail on import
            # if this isnt done, and i'm not sure why activate_this.py doesnt
            # accomplish it.  it might have something to do with venv's using
            # mixed pythons (different versions) or mixed --no-site-packages
            # tabling it for now
            # dynload = opj(python_dir, 'lib-dynload')
            # sys.path.append(dynload)

            # NB: this rehash will update bins but iirc kills aliases!
            msg = '$PATH was adjusted to {0}'.format(os.environ['PATH'])
            smash_log.debug(msg)
            self.report('Adjusting $PATH')
            msg = 'rehashing aliases'
            smash_log.info(msg)
            self.shell.magic('rehashx')
            self.publish(C_POST_ACTIVATE, absfpath)
Example #7
0
def fabric_cmd_completer(self, event):
    completion_log.info("event [{0}]".format(event.__dict__))
    if event.symbol.startswith("-"):
        return []
    if ope("fabfile.py"):
        _fabfile = "fabfile.py"
    elif ope("Fabfile.py"):
        _fabfile = "Fabfile.py"
    else:
        smash_log.info("no fabfile was found")
        return []
    with open(_fabfile, "r") as fhandle:
        src = fhandle.read()
    node = ast.parse(src)
    return list([x.name for x in ast.walk(node) if isinstance(x, ast.FunctionDef)])
Example #8
0
    def deactivate(self):
        try:
            venv = get_venv()
        except KeyError:
            self.warning("no venv to deactivate")
            return False
        else:
            if not venv:
                return False
            self.report("venv_deactivate: " + venv)
            self.publish(C_PRE_DEACTIVATE, venv)

            if not ope(venv):
                self.warning('refusing to deactivate (relocated?) venv')
                return  # raise RuntimeError(err)

            del os.environ['VIRTUAL_ENV']
            path = get_path()
            path = path.split(':')

            # clean $PATH according to bash..
            # TODO: also rehash?
            vbin = to_vbin(venv)
            if vbin in path:
                msg = 'removing old venv bin from PATH: ' + \
                      summarize_fpath(str(vbin))
                self.report(msg)
                path.remove(vbin)
                os.environ['PATH'] = ':'.join(path)

            # clean sys.path according to python..
            # stupid, but this seems to work
            msg = 'clean sys.path'  # ,sys.path_changes)
            self.report(msg)
            smash_log.info(msg)
            reset_path = getattr(self, 'reset_path', None)
            if reset_path is not None:
                msg = str(set(sys.path) ^ set(reset_path))
                msg = 'sys.path difference: {0}'.format(msg)
                self.report(msg)
                smash_log.debug(msg)
                sys.path = reset_path
                self.reset_path = sys.path
            else:
                self.reset_path = sys.path
            self._clean_user_namespace(venv)
            self.publish(C_POST_DEACTIVATE, venv)
            return True
Example #9
0
def source_file_namespace(fname):
    """ returns a dictionary of { fxn_name : FunctionMagic() }
        for bash functions in `fname`
    """
    if not os.path.exists(fname):
        raise ValueError("{0} does not exist".format(fname))
    fname = os.path.abspath(fname)
    smash_log.info("attempting to source: {0}".format(fname))
    fxns = get_functions_from_file(fname)
    smash_log.info("found functions: {0}".format(fxns))
    out = dict()
    for fxn_name in fxns:
        cmd = FunctionMagic(fxn_name, source=fname)
        out[fxn_name] = cmd
        get_smash().shell.magics_manager.register_function(
            cmd, magic_name=fxn_name)
    return out
Example #10
0
 def doit(_fpath, _suffix, _opener, _rest):
     if ope(_fpath) and not isdir(_fpath):
         if _opener is not None:
             self.report(
                 'Using _opener "{0}" for "{1}"'.format(_opener, _suffix))
             return '{0} {1}'.format(_opener, _fpath + _rest)
         else:
             msg = "Legit file input, but no _suffix alias could be found for " + \
                 _suffix
             self.report(msg)
             if is_editable(_fpath):
                 self.report(
                     "File looks like ASCII text, assuming I should edit it")
                 return doit(_fpath, _suffix, 'ed', _rest)
     else:
         msg = 'Attempted file input, but path "{0}" does not exist'
         msg = msg.format(fpath)
         smash_log.info(msg)
Example #11
0
    def __init__(self, shell, **kargs):
        if self.plugin_instance:
            raise Exception,'singleton'
        super(SmashPlugin, self).__init__(config=shell.config, shell=shell)

        # plugins should use self.contribute_* commands, which update the installation
        # record automatically.  later the installation record will help with uninstalling
        # plugins cleanly and completely
        self.installation_record = defaultdict(list)

        self.shell.configurables.append(self)
        self.init_logger()
        smash_log.info("initializing {0}".format(self))
        self.init_eventful()
        self.init_bus()
        self.init_magics()
        self.init_scheduled_tasks()
        self.init()
        self.plugin_instance = self
Example #12
0
    def run_cell(self, raw_cell, store_history=False,
                 silent=False, shell_futures=True):
        #assert self.smash
        #import re
        #regex = r'`[^`]*`';
        #line ='foo`bar`baz`a`';
        #tick_groups = [x[1:-1] for x in re.findall(regex, line)];
        #if tick_groups:
        #    print tick_groups

        #avoid race on embedded shell
        publish = getattr(self.smash,'publish',None)
        if publish:
            publish(C_PRE_RUN_CELL, raw_cell)
        smash_log.info("[{0}]".format(raw_cell.encode('utf-8').strip()))
        # as long as the expressions are semicolon separated,
        # this section allows hybrid bash/python expressions
        bits = split_on_unquoted_semicolons(raw_cell)
        if len(bits) > 1:
            smash_log.info("detected chaining with this input")
            out = []
            for x in bits:
                out.append(
                    self.run_cell(
                        x, store_history=store_history,
                        silent=silent, shell_futures=shell_futures))
            return out

        sooper = super(SmashTerminalInteractiveShell, self)
        out = sooper.run_cell(
            raw_cell, store_history=store_history,
            silent=silent, shell_futures=shell_futures)
        if self.smash is not None:
            if self._smash_last_input:
                # translated-input
                this = self.user_ns['In'][-1].strip()
                # untranslated-input
                last_input = self._smash_last_input
                self.smash.publish(C_POST_RUN_CELL, this)
                self.smash.publish(C_POST_RUN_INPUT, last_input)
            self._smash_last_input = ""
        return [out]
Example #13
0
    def init_config_inheritance(self):
        if self.load_bash_aliases:
            for alias, cmd in bash.get_aliases():
                # this check is a hack, but users will frequently override
                # cd to pushd which is already done in smash anyway
                if alias not in 'ed cd'.split():
                    self.shell.magic("alias {0} {1}".format(alias, cmd))

        self.shell.magics_manager.register_function(
            bash.source, magic_name='source')

        if self.load_bash_functions:
            smash_log.info("Loading bash functions")
            fxns = bash.get_functions()
            for fxn_name in fxns:
                fxn_bridge = bash.FunctionMagic(fxn_name, source='__host_shell__')
                self.shell.magics_manager.register_function(
                    fxn_bridge, magic_name=fxn_name)
            msg = "registered magic for bash functions: " + str(fxns)
            smash_log.info(msg)
Example #14
0
    def showsyntaxerror(self, filename=None):
        """ when a syntax error is encountered,
            consider just broadcasting a signal instead
            of showing a traceback.
        """
        smash_log.info(
            "last efforts to do something "
            "meaningful with input before "
            "syntax error")
        lastline = self._smash_last_input
        clean_line = lastline.strip()
        if not clean_line:
            # this is not input!
            # possibly there is actually an error in smash itself
            raise

        if is_path(clean_line):
            # NB: in this case a regex looks like a path or URL,
            # but it's not necessarily true that the endpoint
            # actuals exists
            smash_log.info("detected path input: {0}".format(clean_line))
            self.smash.publish(C_FILE_INPUT, clean_line)
        elif is_path(clean_line.split()[0]):
            self.system(clean_line)
        else:
            smash_log.info('nothing to do but call super()')
            sooper = super(SmashTerminalInteractiveShell, self)
            return sooper.showsyntaxerror(filename=filename)
Example #15
0
    def load_from_etc(self, fname, schema=None):
        """ if schema is given, validate it.  otherwise
            just load blindly """
        smash_log.info('loading and validating {0}'.format(fname))
        schema = schema or _find_schema(fname)
        absf = opj(DIR_SMASH_ETC, fname)
        try:
            with open(absf) as fhandle:
                data = demjson.decode(fhandle.read())
        except demjson.JSONDecodeError:
            err = "file is not json: {0}".format(absf)
            # boot_log.critical(err)
            raise ConfigError(err)
        except IOError:
            smash_log.info("{0} does not exist..".format(absf))
            if getattr(schema, 'default', None) is not None:
                smash_log.info("..but a default is defined.  writing file")
                default = schema.default
                if not isinstance(default, basestring):
                    default = demjson.encode(schema.default)

                with open(absf, 'w') as fhandle:
                    fhandle.write(default)
                return self.load_from_etc(fname, schema)
            else:
                err = ("{0} does not exist, and no default"
                       " is defined").format(absf)
                # boot_log.critical(err)
                self.log.critical(err)
                raise SystemExit(err)
        try:
            schema(data)
        except voluptuous.Invalid, e:
            raise SystemExit("error validating {0}\n\t{1}".format(absf, e))
Example #16
0
 def check(self, line_info):
     """ note: the way that line_info splitting happens means
         that for a command like "apt-get foo", first/rest will
         be apt/-get foo.  it's better to just use line_info.line
     """
     if line_info.continue_prompt or not line_info.line.strip():
         return None
     shandler = self.prefilter_manager.get_handler_by_name(HANDLER_NAME)
     line = line_info.line
     prefixed_assignments = initial_assignments.search(line)
     if prefixed_assignments:
         smash_log.info('detected prefixed assignments')
         only_assignments = prefixed_assignments.group()
         nonassignment_section = line[len(only_assignments):]
         maybe_command = nonassignment_section.split()
         maybe_command = maybe_command and maybe_command[0]
         if have_alias(maybe_command):
             smash_log.info('prefixed assignments are followed by command alias')
             return shandler
     split_line = line.split()
     if have_alias(split_line[0]):
         return shandler
Example #17
0
 def handle(self, line_info):
     """ """
     cmd = line_info.line.strip()
     smash_log.info("shellhandler: {0}".format(cmd))
     return 'get_ipython().system(%r)' % (cmd, )
Example #18
0
 def venv_activate(self, parameter_s=''):
     msg = "venv_activate: " + parameter_s
     smash_log.info(msg)
     self.report(msg)
     self.plugin_obj.activate(parameter_s)
Example #19
0
 def init(self):
     smash_log.info("initializing")
     self._load_prompt_config()
     self.update_prompt()
     self.contribute_hook('pre_prompt_hook', self.update_prompt)
Example #20
0
 def input_finished_hook(self, raw_finished_input):
     for x in REHASH_IF:
         if x in raw_finished_input:
             msg = "detected possible $PATH changes (rehashing)"
             smash_log.info(msg)
             self.smash.shell.magic('rehashx')
Example #21
0
def complete(to_complete):
    """ wow! so this is stupid, but what can you do? to understand
        the command required to get completion information out of bash,
        start by executing "printf '/et\x09\x09' | bash -i".  What this
        command does is put bash into interactive mode, then simulate
        typing "/et<tab><tab>" inside bash.  The tab-completion information
        can be scraped out of the text, but several things complicate the final
        solution:

        1) the tab-completion info, apart from being post-processed, must be
           scraped from stderr, not from stdout.

        2) for post-processing, without knowledge of how the prompt will be
           rendered or if there is some kind of banner that will be printed,
           it's hard to know where exactly to start capturing tab-completion
           options.

        3) the method used to get the tab completion involves the bash builtins
           "printf", meaning we have to launch subprocess with "bash -c"

        4) completion output could be paginated by bash if there are lots of
           options.  have to account for that and still try to get all the
           options instead of just the first page

        5) sending EOF does not work unless it comes after a newline.  this
           means we have to take extra steps to avoid actually executing the
           command we want to complete (executing the command is not what the
           user expects and is possibly unsafe).  to avoid executing the line,
           after getting tab completion you have to simulate control-a (go to
           start of line) followed by '#'.  this comments the line and prevents
           it from executing.  note: you cannot send control-c to cancel the
           execution of the line because we are dealing with pipes, whereas
           control-c is processed only by proper tty's.
    """
    # smash_log.info("[{0}]".format(to_complete))
    if not to_complete:
        return []
    cmd = '''bash -c "printf 'PS1=\"\";echo MARKER\n{complete}\t\t\x01#\necho MARKER'|bash -i"'''.format(
        complete=to_complete)
    p1 = Popen(cmd, shell=True, stdout=PIPE, stdin=PIPE, stderr=PIPE)
    out, err = p1.communicate()
    lines = err.split('\n')
    # smash_log.info(err)
    first_marker = None
    last_marker = None
    for i in range(len(lines)):
        line = lines[i]
        if line.strip().endswith('echo MARKER'):
            if first_marker is None:
                first_marker = i
            else:
                last_marker = i

    # SPECIAL CASE: pagination
    if last_marker is None:
        # when this happens there are too many options,
        # ie bash is asking something like this:
        #   Display all 103 possibilities? (y or n)
        # Pagination indicators like '--More--'must be removed
        lines = [x for x in lines if not line.startswith('--More')]
        last_marker = len(lines) - 3
        first_marker += 1

    complete_lines = lines[first_marker + 2:last_marker - 1]

    # SPECIAL-CASE: no completion options or only one option
    if not complete_lines:
        # NOTE:
        #   if there is only one option, readline simply applies it,
        #   which affects the current line in place.  apparently this
        #   results in tons of control-characters being dumped onto
        #   the line, and we have to clean those up for the output
        try:
            the_line = lines[first_marker + 1:last_marker][0]
        except IndexError:
            smash_log.info(
                'error completing '+str([
                    lines, first_marker, last_marker]))
            return []
        the_line = remove_control_characters(the_line)
        tmp = the_line[the_line.rfind(to_complete) + len(to_complete):]
        result = to_complete.split()[-1] + tmp

        if '#' in result:
            # this seems to only happen for directories.  not sure why
            result = result[:result.find('#')]
        if result == to_complete.split()[-1]:
            # SPECIAL-CASE: no completion options at all.
            return []
        return [result]
    else:
        # there are multiple completion options
        completion_choices_by_row = [x.split() for x in complete_lines]
        completion_choices = reduce(
            lambda x, y: x + y, completion_choices_by_row)
        return completion_choices