示例#1
0
def run(session, text, *, log=True, downgrade_errors=False):
    """execute a textual command

    Parameters
    ----------
    text : string
        The text of the command to execute.
    log : bool
        Print the command text to the reply log.
    downgrade_errors : bool
        True if errors in the command should be logged as informational.
    """

    from chimerax.core.commands import Command
    from chimerax.core.errors import UserError
    command = Command(session)
    try:
        results = command.run(text, log=log)
    except UserError as err:
        if downgrade_errors:
            session.logger.info(str(err))
        else:
            raise
        results = []
    return results[0] if len(results) == 1 else results
示例#2
0
 def process_command(self, command_text):
     if self._command_runner is None:
         from chimerax.core.commands import Command
         from chimerax.core.commands.cli import RegisteredCommandInfo
         command_registry = RegisteredCommandInfo()
         self._register_commands(command_registry)
         self._command_runner = Command(self.alignment.session, registry=command_registry)
     self._command_runner.run(command_text, log=False)
示例#3
0
def run(session, sv, text):
    from chimerax.core.commands import Command
    cmd = Command(session, registry=registry)
    global _sv
    _sv = sv
    try:
        cmd.run(text, log=False)
    finally:
        _sv = None
示例#4
0
    def execute(self):
        from contextlib import contextmanager

        @contextmanager
        def processing_command(line_edit, cmd_text, command_worked,
                               select_failed):
            line_edit.blockSignals(True)
            self._processing_command = True
            # as per the docs for contextmanager, the yield needs
            # to be in a try/except if the exit code is to execute
            # after errors
            try:
                yield
            finally:
                line_edit.blockSignals(False)
                line_edit.setText(cmd_text)
                if command_worked[0] or select_failed:
                    line_edit.selectAll()
                self._processing_command = False

        session = self.session
        logger = session.logger
        text = self.text.lineEdit().text()
        logger.status("")
        from chimerax.core import errors
        from chimerax.core.commands import Command
        from html import escape
        for cmd_text in text.split("\n"):
            if not cmd_text:
                continue
            # don't select the text if the command failed, so that
            # an accidental keypress won't erase the command, which
            # probably needs to be edited to work
            command_worked = [False]
            with processing_command(self.text.lineEdit(), cmd_text,
                                    command_worked,
                                    self.settings.select_failed):
                try:
                    self._just_typed_command = cmd_text
                    cmd = Command(session)
                    cmd.run(cmd_text)
                    command_worked[0] = True
                except SystemExit:
                    # TODO: somehow quit application
                    raise
                except errors.UserError as err:
                    logger.status(str(err), color="crimson")
                    from chimerax.core.logger import error_text_format
                    logger.info(error_text_format % escape(str(err)),
                                is_html=True)
                except BaseException:
                    raise
        self.set_focus()
示例#5
0
def log_equivalent_command(session, command_text):
    """
    Log a command.  This is typically used to show the equivalent
    command for some action (button press, mouse drag, ...) done
    with the grahical user interface.
    """
    from chimerax.core.commands import Command
    from chimerax.core.errors import UserError
    command = Command(session)
    try:
        command.run(command_text, log_only=True)
    except UserError as err:
        session.logger.info(str(err))
示例#6
0
def process_command(session, name, structure, substring):
    # all the commands use the trick that the structure arg is temporarily made available in the
    # global namespace as '_structure'
    global _structure, command_registry
    _structure = structure
    try:
        if command_registry is None:
            # register commands to private registry
            from chimerax.core.commands.cli import RegisteredCommandInfo
            command_registry = RegisteredCommandInfo()

            from chimerax.core.commands import Float3Arg, StringArg, BoolArg, \
                Float2Arg, DynamicEnum

            # atom
            register("atom",
                CmdDesc(
                    keyword=[("position", Float3Arg), ("res_name", StringArg),
                        ("select", BoolArg)],
                    synopsis="place helium atom"
                ), shim_place_atom, registry=command_registry)

            # peptide
            from chimerax.core.commands import Annotation
            class RepeatableFloat2Arg(Annotation):
                allow_repeat = True
                parse = Float2Arg.parse
                unparse = Float2Arg.unparse

            register("peptide",
                CmdDesc(
                    required=[("sequence", StringArg), ("phi_psis", RepeatableFloat2Arg)],
                    keyword=[("position", Float3Arg), ("chain_id", StringArg),
                        ("rot_lib", DynamicEnum(session.rotamers.library_names))],
                    synopsis="construct peptide from sequence"
                ), shim_place_peptide, registry=command_registry)

        cmd = Command(session, registry=command_registry)
        return cmd.run(name + ' ' + substring, log=False)[0]
    finally:
        _structure = None
示例#7
0
def cmd_save(session, file_name, rest_of_line, *, log=True):
    tokens = []
    remainder = rest_of_line
    while remainder:
        token, token_log, remainder = next_token(remainder)
        remainder = remainder.lstrip()
        tokens.append(token)
    format_name = None
    for i in range(len(tokens)-2, -1, -2):
        test_token = tokens[i].lower()
        if "format".startswith(test_token):
            format_name = tokens[i+1]
    provider_cmd_text = "save " + " ".join([FileNameArg.unparse(file_name)]
        + [StringArg.unparse(token) for token in tokens])

    try:
        from .manager import NoSaverError
        mgr = session.save_command
        data_format= file_format(session, file_name, format_name)
        try:
            provider_args = mgr.save_args(data_format)
        except NoSaverError as e:
            raise LimitationError(str(e))

        # register a private 'save' command that handles the provider's keywords
        registry = RegisteredCommandInfo()
        keywords = {
            'format': DynamicEnum(lambda ses=session: format_names(ses)),
        }
        for keyword, annotation in provider_args.items():
            if keyword in keywords:
                raise ValueError("Save-provider keyword '%s' conflicts with builtin arg"
                    " of same name" % keyword)
            keywords[keyword] = annotation
        # for convenience, allow 'models' to be a second positional argument instead of a keyword
        if 'models' in keywords:
            optional = [('models', keywords['models'])]
            del keywords['models']
        else:
            optional = []
        desc = CmdDesc(required=[('file_name', SaveFileNameArg)], optional=optional,
            keyword=keywords.items(), hidden=mgr.hidden_args(data_format), synopsis="unnecessary")
        register("save", desc, provider_save, registry=registry)
    except BaseException as e:
        # want to log command even for keyboard interrupts
        log_command(session, "save", provider_cmd_text, url=_main_save_CmdDesc.url)
        raise
    Command(session, registry=registry).run(provider_cmd_text, log=log)
示例#8
0
def run_expectfail(session, command):
    from chimerax.core.errors import NotABug, UserError
    from chimerax.core.commands import run, Command

    # first confirm that command is an actual command
    cmd = Command(session)
    cmd.current_text = command
    cmd._find_command_name(no_aliases=True)
    if not cmd._ci:
        raise UserError("Unknown commmand")
    try:
        cmd.run(command)
    except NotABug:
        session.logger.info("Failed as expected")
    else:
        raise UserError("Command failed to fail")
示例#9
0
class HeaderSequence(list):
    # sort_val determines the default ordering of headers.
    # Built-in headers change their sort_val to a value in the range
    # [1.0, 2.0) so they normally appear before registered headers.
    # Identical sort_vals tie-break on sequence name.
    sort_val = 2.0
    numbering_start = None
    fast_update = True # can header be updated quickly if only a few columns are changed?
    single_column_updateable = True # can a single column be updated, or only the entire header?
    ident = None    # should be string, used to identify header in commands and as part of the
                    # generated residue attribute name, so the string should be "attribute friendly"
    value_type = float
    value_none_okay = True

    ATTR_PREFIX = "seq_"

    def __init__(self, alignment, name=None, *, eval_while_hidden=False, session_restore=False):
        if name is None:
            if not hasattr(self, 'name'):
                self.name = ""
        else:
            self.name = name
        if not session_restore and self.ident is None:
            raise AssertionError("%s header class failed to define 'ident' attribute"
                % self.__class__.__name__)
        from weakref import proxy
        self.alignment = proxy(alignment)
        self.alignment.add_observer(self)
        self._notifications_suppressed = 0
        self._shown = False
        self.eval_while_hidden = eval_while_hidden
        self._update_needed = True
        self._edit_bounds = None
        self._alignment_being_edited = False
        self._command_runner = None
        if not hasattr(self.__class__, 'settings'):
            self.__class__.settings = self.make_settings(alignment.session)
        if self.eval_while_hidden:
            self.reevaluate()

    def add_options(self, options_container, *, category=None, verbose_labels=True):
        pass

    def align_change(self, left, right):
        """alignment changed in positions from 'left' to 'right'"""
        if self._alignment_being_edited and not self.fast_update:
            if self._edit_bounds is None:
                self._edit_bounds = (left, right)
            else:
                self._edit_bounds = (min(left, self._edit_bounds[0]), max(right, self._edit_bounds[1]))
            return
        if single_column_updateable:
            self.reevaluate(left, right)
        else:
            self.reevaluate()

    def alignment_notification(self, note_name, note_data):
        if note_name == self.alignment.NOTE_EDIT_START:
            self._alignment_being_edited = True
        if note_name == self.alignment.NOTE_EDIT_END:
            self._alignment_being_edited = False

    @contextmanager
    def alignment_notifications_suppressed(self):
        self._notifications_suppressed += 1
        try:
            yield
        finally:
            self._notifications_suppressed -= 1

    def destroy(self):
        if not self.alignment.being_destroyed:
            self.alignment.remove_observer(self)

    def evaluate(self, pos):
        raise NotImplementedError("evaluate() method must be"
            " implemented by %s subclass" % self.__class__.__name__)

    def position_color(self, position):
        return 'black'

    def get_state(self):
        state = {
            'name': self.name,
            'shown': self._shown,
            'eval_while_hidden': self.eval_while_hidden,
            'contents': self[:]
        }
        return state

    def __hash__(self):
        return id(self)

    def hist_infinity(self, position):
        """Convenience function to map arbitrary number to 0-1 range

           Used as the 'depiction_val' method for some kinds of data
        """
        raw = self[position]
        if raw is None:
            return 0.0
        from math import exp
        if raw >= 0:
            return 1.0 - 0.5 * exp(-raw)
        return 0.5 * exp(raw)

    def __lt__(self, other):
        return self.sort_val < other.sort_val

    def make_settings(self, session):
        """For derived classes with their own settings, the settings_info()
           method must be overridden (which see)"""
        settings_name, settings_info = self.settings_info()
        settings_defaults = {}
        self.__class__._setting_cmd_annotations = cmd_annotations = {}
        for attr_name, info in settings_info.items():
            annotation, default_value = info
            settings_defaults[attr_name] = default_value
            cmd_annotations[attr_name] = annotation
        from chimerax.core.settings import Settings
        class HeaderSettings(Settings):
            EXPLICIT_SAVE = settings_defaults
        return HeaderSettings(session, settings_name)

    def notify_alignment(self, note_name, *args):
        if not self._notifications_suppressed:
            if args:
                note_data = (self, *args)
            else:
                note_data = self
            self.alignment.notify(note_name, note_data)

    def num_options(self):
        return 0

    def option_data(self):
        from chimerax.ui.options import BooleanOption
        return [
            ("show initially", 'initially_shown', BooleanOption, {},
                "Show this header when sequence/alignment initially shown")
        ]

    def option_sorting(self, option):
        for base_label, attr_name, opt_class, opt_kw, balloon in HeaderSequence.option_data(self):
            if option.attr_name == attr_name:
                return (0, option.name.casefold())
        return (1, option.name.casefold())

    def positive_hist_infinity(self, position):
        """Convenience function to map arbitrary positive number to 0-1 range

           Used as the 'depiction_val' method for some kinds of data
        """
        raw = self[position]
        if raw is None:
            return 0.0
        from math import exp
        return 1.0 - exp(-raw)

    def process_command(self, command_text):
        if self._command_runner is None:
            from chimerax.core.commands import Command
            from chimerax.core.commands.cli import RegisteredCommandInfo
            command_registry = RegisteredCommandInfo()
            self._register_commands(command_registry)
            self._command_runner = Command(self.alignment.session, registry=command_registry)
        self._command_runner.run(command_text, log=False)

    def reason_requires_update(self, reason):
        return False

    def reevaluate(self, pos1=0, pos2=None, *, evaluation_func=None):
        """sequences changed, possibly including length"""
        if not self._shown and not self.eval_while_hidden:
            self._update_needed = True
            return
        prev_vals = self[:]
        if pos2 is None:
            pos2 = len(self.alignment.seqs[0]) - 1
        if evaluation_func is None:
            self[:] = []
            for pos in range(pos1, pos2+1):
                self.append(self.evaluate(pos))
        else:
            evaluation_func(pos1, pos2)
        self._update_needed = False
        self._edit_bounds = None
        if self._shown and not self._notifications_suppressed:
            cur_vals = self[:]
            if len(prev_vals) != len(cur_vals):
                bounds = None
            elif prev_vals == cur_vals:
                return
            elif prev_vals[0] != cur_vals[0] and prev_vals[-1] != cur_vals[-1]:
                bounds = None
            else:
                first_mismatch = last_mismatch = None
                for i, val in enumerate(prev_vals):
                    if val != cur_vals[i]:
                        last_mismatch = i
                        if first_mismatch is None:
                            first_mismatch = i
                bounds = (first_mismatch, last_mismatch)
            self.notify_alignment(self.alignment.NOTE_HDR_VALUES, bounds)

    @property
    def relevant(self):
        return True

    @property
    def residue_attr_name(self):
        return self.ATTR_PREFIX + self.ident

    @classmethod
    def session_restore(cls, session, alignment, state):
        inst = cls(alignment, session_restore=True)
        inst.set_state(state)
        return inst

    def set_state(self, state):
        self.name = state['name']
        self._shown = state.get('shown', state.get('visible', None))
        self.eval_while_hidden = state['eval_while_hidden']
        self[:] = state['contents']

    def settings_info(self):
        """This method needs to return a (name, dict) tuple where 'name' is used to distingush
           this group of settings from settings of other headers or tools (e.g. "consensus sequence header"),
           and 'dict' is a dictionary of (attr_name: (Annotation subclass, default_value)) key/value pairs.
           Annotation subclass will be used by the "seq header header_name setting" command to parse the
           text for the value into the proper value type.

           The dictionary must include the base class settings, so super().settings_info() must be
           called and the returned dictionary updated with the derived class's settings"""
        # the code relies on the fact that the returned settings dict is a different object every
        # time (it gets update()d), so don't make it a class variable!
        from chimerax.core.commands import BoolArg
        return "base header sequence", { 'initially_shown': (BoolArg, False) }

    @property
    def shown(self):
        return self._shown

    @shown.setter
    def shown(self, show):
        if show == self._shown:
            return
        self._shown = show
        # suppress the alignment notification
        with self.alignment_notifications_suppressed():
            if show:
                if self._edit_bounds:
                    self.reevaluate(*self._edit_bounds, suppress_callback=True)
                elif self._update_needed:
                    self.reevaluate()
        self.notify_alignment(self.alignment.NOTE_HDR_SHOWN)

    def _add_options(self, options_container, category, verbose_labels, option_data):
        for base_label, attr_name, opt_class, opt_kw, balloon in option_data:
            option = opt_class(self._final_option_label(base_label, verbose_labels), None,
                self._setting_option_cb, balloon=balloon, attr_name=attr_name, settings=self.settings,
                auto_set_attr=False, **opt_kw)
            if category is not None:
                options_container.add_option(category, option)
            else:
                options_container.add_option(option)

    def _final_option_label(self, base_label, verbose_labels):
        if verbose_labels:
            return "%s: %s" % (getattr(self, "settings_name", self.name), base_label)
        return base_label[0].upper() + base_label[1:]

    def _process_setting_command(self, session, setting_arg_text):
        from chimerax.core.commands import EnumOf
        enum = EnumOf(list(self._setting_cmd_annotations.keys()))
        attr_name, arg_text, remainder = enum.parse(setting_arg_text, session)
        remainder = remainder.strip()
        if not remainder:
            from chimerax.core.errors import UserError
            raise UserError("No value provided for setting")
        val, val_text, remainder = self._setting_cmd_annotations[attr_name].parse(remainder, session)
        if remainder and not remainder.isspace():
            from chimerax.core.errors import UserError
            raise UserError("Extraneous text after command")
        setattr(self.settings, attr_name, val)

    def _register_commands(self, registry):
        from chimerax.core.commands import register, CmdDesc, RestOfLine, SaveFileNameArg
        register("show", CmdDesc(synopsis='Show %s header' % self.ident),
            lambda session, hdr=self: setattr(hdr, "shown", True), registry=registry)
        register("hide", CmdDesc(synopsis='Hide %s header' % self.ident),
            lambda session, hdr=self: setattr(hdr, "shown", False), registry=registry)
        register("setting", CmdDesc(required=[('setting_arg_text', RestOfLine)],
            synopsis="change header setting"), self._process_setting_command, registry=registry)
        register("save", CmdDesc(required=[('file_name', SaveFileNameArg)],
            synopsis="save header values to file"), self._save, registry=registry)

    def _setting_option_cb(self, opt):
        from chimerax.core.commands import run, StringArg
        session = self.alignment.session
        align_arg = "%s " % StringArg.unparse(str(self.alignment)) \
            if len(session.alignments.alignments) > 1 else ""
        run(session, "seq header %s%s setting %s %s"
            % (align_arg, self.ident, opt.attr_name, StringArg.unparse(str(opt.value))))

    def _save(self, session, file_name):
        from chimerax.io import open_output
        with open_output(file_name, encoding="utf-8") as f:
            if hasattr(self, 'save_file_preamble'):
                print(self.save_file_preamble, file=f)
            print("%s header for %s" % (self.name, self.alignment), file=f)
            for i, val in enumerate(self):
                print("%d:" % (i+1), val, file=f)
示例#10
0
def run(session, cmd_text):
    from chimerax.core.commands import Command
    cmd = Command(session)
    cmd.run(cmd_text)
示例#11
0
def cmd_open(session, file_names, rest_of_line, *, log=True):
    tokens = []
    remainder = rest_of_line
    while remainder:
        token, token_log, remainder = next_token(remainder)
        remainder = remainder.lstrip()
        tokens.append(token)
    provider_cmd_text = "open " + " ".join([FileNameArg.unparse(fn)
        for fn in file_names] + [StringArg.unparse(token) for token in tokens])
    try:
        database_name = format_name = None
        for i in range(len(tokens)-2, -1, -2):
            test_token = tokens[i].lower()
            if "format".startswith(test_token):
                format_name = tokens[i+1]
            elif "fromdatabase".startswith(test_token):
                database_name = tokens[i+1]

        from .manager import NoOpenerError
        mgr = session.open_command
        fetches, files = fetches_vs_files(mgr, file_names, format_name, database_name)
        if fetches:
            try:
                provider_args = mgr.fetch_args(fetches[0][1], format_name=fetches[0][2])
            except NoOpenerError as e:
                raise LimitationError(str(e))
        else:
            data_format = file_format(session, files[0], format_name)
            if data_format is None:
                # let provider_open raise the error, which will show the command
                provider_args = {}
            else:
                try:
                    provider_args = mgr.open_args(data_format)
                except NoOpenerError as e:
                    raise LimitationError(str(e))

        # register a private 'open' command that handles the provider's keywords
        registry = RegisteredCommandInfo()

        def database_names(mgr=mgr):
            return mgr.database_names

        keywords = {
            'format': DynamicEnum(lambda ses=session:format_names(ses)),
            'from_database': DynamicEnum(database_names),
            'ignore_cache': BoolArg,
            'name': StringArg
        }
        for keyword, annotation in provider_args.items():
            if keyword in keywords:
                raise ValueError("Open-provider keyword '%s' conflicts with builtin arg of"
                    " same name" % keyword)
            keywords[keyword] = annotation
        desc = CmdDesc(required=[('names', OpenFileNamesArg)], keyword=keywords.items(),
            synopsis="read and display data")
        register("open", desc, provider_open, registry=registry)
    except BaseException as e:
        # want to log command even for keyboard interrupts
        log_command(session, "open", provider_cmd_text, url=_main_open_CmdDesc.url)
        raise
    return Command(session, registry=registry).run(provider_cmd_text, log=log)