def parse(text, session): from chimerax.core.commands import AnnotationError, next_token if not text: raise AnnotationError("Expected %s" % SeqArg.name) token, text, rest = next_token(text) if ':' not in token: align_id, seq_id = "", token else: align_id, seq_id = token.split(':', 1) if not align_id: aln_seq = None for aln in session.alignments.alignments: try: seq = get_alignment_sequence(aln, seq_id) except MissingSequence: pass else: if aln_seq is None: aln_seq = (aln, seq) else: raise AnnotationError("Multiple sequences match '%s'; please also specify the" " alignment by prepending 'alignment-ID:'" % token) if aln_seq: return aln_seq, text, rest raise AnnotationError("No sequences match '%s'" % token) alignment = get_alignment_by_id(session, align_id) seq = get_alignment_sequence(alignment, seq_id) return (alignment, seq), text, rest
def parse(text, session): if not text: raise AnnotationError("Expected %s" % WheelArg.name) token, text, rest = OpenFileNameArg.parse(text, session) import os if os.path.splitext(token)[1] != ".whl": raise AnnotationError("Expected %s" % WheelArg.name) return token, text, rest
def parse(cls, text, session): from chimerax.core.commands import IntArg try: token, text, rest = IntArg.parse(text, session) except AnnotationError: raise AnnotationError("Expected %s" % cls.name) if (token % 2) == 1: raise AnnotationError("Expected %s" % cls.name) return token, text, rest
def parse(text, session): from chimerax.core.commands import next_token, AnnotationError token, text, rest = next_token(text) fields = token.split(',') if fields[0] not in ('x', 'y', 'z'): raise AnnotationError( 'Planes argument first field must be x, y, or z, got "%s"' % fields[0]) try: values = [int(f) for f in fields[1:]] except Exception: raise AnnotationError( 'Planes arguments after axis must be integers') result = tuple([fields[0]] + values) return result, text, rest
def name(session, name, text=None, skip_check=False): if name == "all": raise UserError("\"all\" is reserved and cannot be shown or defined") if text is None: from chimerax.core.commands import get_selector_description try: desc = get_selector_description(name, session) except KeyError: raise UserError("\"%s\" is not defined" % name) else: if desc: session.logger.info('\t'.join([name, desc])) else: if _is_reserved(name): raise UserError("\"%s\" is reserved and cannot be redefined" % name) if not skip_check: try: ast, used, unused = AtomSpecArg.parse(text, session) if unused: raise AnnotationError("contains extra trailing text") except AnnotationError as e: raise UserError("\"%s\": %s" % (text, str(e))) def selector(session, models, results, spec=text): objects, used, unused = ObjectsArg.parse(spec, session) results.combine(objects) from chimerax.core.commands import register_selector register_selector(name, selector, session.logger, user=True, desc=text) session.basic_actions.define(name, text)
def parse(text, session): from chimerax.core.commands import next_token token, text, rest = next_token(text) nv = _named_views(session).views if token in nv: return nv[token], text, rest raise AnnotationError("Expected a view name")
def parse(text, session): from chimerax.core.commands import AnnotationError, next_token if not text: raise AnnotationError("Expected %s" % SeqArg.name) token, text, rest = next_token(text) alignment = get_alignment_by_id(session, token) return alignment, text, rest
def parse(text, session): from chimerax.core.commands import next_token, TopModelsArg, PlaceArg token, text, rest = next_token(text) fields = token.split(',') if len(fields) % 13: raise AnnotationError("Expected model id and 12 comma-separated numbers") mp = [] while fields: tm, mtext, mrest = TopModelsArg.parse(fields[0], session) if len(tm) == 0: raise AnnotationError('No models specified by "%s"' % fields[0]) p = PlaceArg.parse_place(fields[1:13]) for m in tm: mp.append((m,p)) fields = fields[13:] return mp, text, rest
def name(session, name, text=None): if name == "all": raise UserError("\"all\" is reserved and cannot be redefined") if text is None: from chimerax.core.commands import get_selector try: sel = get_selector(name) except KeyError: raise UserError("\"%s\" is not defined" % name) else: value = _get_name_desc(sel, True) session.logger.info('\t'.join([name, value])) else: try: ast, used, unused = AtomSpecArg.parse(text, session) if unused: raise AnnotationError("contains extra trailing text") except AnnotationError as e: raise UserError("\"%s\": %s" % (text, str(e))) def selector(session, models, results, spec=text): objects, used, unused = ObjectsArg.parse(spec, session) results.combine(objects) selector.name_text = text from chimerax.core.commands import register_selector register_selector(name, selector, session.logger)
def parse(cls, text, session): atoms, used, rest = super().parse(text, session) if len(atoms) != 1: from chimerax.core.commands import AnnotationError raise AnnotationError( "Must specify exactly one atom (specified %d)" % len(atoms)) return atoms[0], used, rest
def parse(text, session): import re token, text, rest = next_token(text, convert=True) canonical = re.sub("[^\w\d.]+", "_", token, re.UNICODE) simple = token.replace('-', '_') if simple != canonical: raise AnnotationError("Invalid bundle name") return token, text, rest
def parse(cls, text, session): element_name, used, rest = super().parse(text, session) from . import Element e = Element.get_element(element_name) if e.number == 0: from chimerax.core.commands import AnnotationError raise AnnotationError("'%s' is not an atomic symbol" % element_name) return e, used, rest
def parse(text, session): from chimerax.core.commands import FloatsArg aa, text, rest = FloatsArg.parse(text, session) if len(aa) != 4: raise AnnotationError('Axis-angle must be 4 comma-separated floats, got %d from %s' % (len(aa), text)) from chimerax.geometry import rotation v = rotation(aa[:3], aa[3]) return v, text, rest
def parse(cls, text, session): m, text, rest = super().parse(text, session) from . import Structure models = [s for s in m.all_models() if isinstance(s, Structure)] if len(models) != 1: from chimerax.core.commands import AnnotationError raise AnnotationError('Must specify 1 structure, got %d for "%s"' % (len(models), text)) return models[0], text, rest
def parse(text, session): from chimerax.core.commands import next_token, AnnotationError if not text: raise AnnotationError('Missing index range argument') token, text, rest = next_token(text) fields = token.split(',') if len(fields) > 3: raise AnnotationError( "Index range has at most 3 comma-separated value") try: ses = [(None if f in ('', '.') else int(f)) for f in fields] except ValueError: raise AnnotationError("Index range values must be integers") if len(ses) == 1: ses.extend((ses[0], None)) elif len(ses) == 2: ses.append(None) return ses, text, rest
def parse(text, session): from chimerax.core.commands import FloatsArg q, text, rest = FloatsArg.parse(text, session) if len(q) != 4: raise AnnotationError('Quaternion must be 4 comma-separated floats, got %d from %s' % (len(q), text)) from chimerax.geometry import quaternion_rotation v = quaternion_rotation(q) return v, text, rest
def get_alignment_by_id(session, align_id, *, multiple_okay=False): if not align_id: if not session.alignments.alignments: raise AnnotationError("No alignments open!") elif len(session.alignments.alignments) > 1: if multiple_okay: return list(session.alignments.alignments) raise AnnotationError("More than one sequence alignment open;" " need to specify an alignment ID") alignment = session.alignments.alignments[0] else: try: alignment = session.alignments.alignments_map[align_id] except KeyError: raise AnnotationError("No known alignment with ID: '%s'" % align_id) if multiple_okay: return [alignment] return alignment
def parse(text, session): from chimerax.core.commands import FloatsArg s, text, rest = FloatsArg.parse(text, session) if len(s) == 1: v = (-s[0]/2, s[0]/2) elif len(s) == 2: v = s else: raise AnnotationError('Slab value must be 1 or 2 floats, got %d from %s' % (len(s), text)) return v, text, rest
def parse(text, session): from chimerax.core.commands import next_token, AnnotationError if not text: raise AnnotationError("Expected %s" % HelixArg.name) token, text, rest = next_token(text) fields = token.split(',') optimize = (fields and fields[-1] == 'opt') if optimize: fields = fields[:-1] herr = 'Invalid helix option rise,angle[,n][,opt]' if len(fields) in (2, 3): try: rise, angle = [float(f) for f in fields[:2]] n = int(fields[2]) if len(fields) == 3 else None except ValueError: raise AnnotationError(herr) else: raise AnnotationError(herr) hparams = (rise, angle, n, optimize) return hparams, text, rest
def parse(text, session): from chimerax.core.commands import AnnotationError, next_token if not text: raise AnnotationError("Expected %s" % NamedLabelsArg.name) lm = session_labels(session) token, text, rest = next_token(text) if lm is None: raise AnnotationError("No label with name: '%s'" % token) if lm.named_label(token) is None: possible = [ name for name in lm.label_names() if name.startswith(token) ] if 'all'.startswith(token): possible.append('all') if not possible: raise AnnotationError("Unknown label identifier: '%s'" % token) possible.sort(key=len) token = possible[0] labels = lm.all_labels if token == 'all' else [lm.named_label(token)] return labels, token, rest
def parse(cls, text, session): aspec, text, rest = super().parse(text, session) models = aspec.evaluate(session).models from chimerax.atomic import AtomicStructure # Need to use explicit type comparison here rather than isinstance() since some # things we *don't* want (e.g. ISOLDE's rotamer preview) are AtomicStructure # subclasses. mols = [m for m in models if type(m) == AtomicStructure] if len(mols) != 1: from chimerax.core.commands import AnnotationError raise AnnotationError(f'Must specify exactly one atomic structure, got {len(mols)} for {text}.') return mols[0], text, rest
def get_alignment_sequence(alignment, seq_id): try: sn = int(seq_id) except ValueError: for seq in alignment.seqs: if seq.name == seq_id: break else: raise MissingSequence("No sequence named '%s' found in alignment" % seq_id) else: if sn == 0: raise AnnotationError("Sequence index must be positive or negative integer," " not zero") if abs(sn) > len(alignment.seqs): raise AnnotationError("Sequence index (%d) larger than number of sequences" " in alignment (%d)" % (sn, len(alignment.seqs))) if sn > 0: seq = alignment.seqs[sn-1] else: seq = alignment.seqs[sn] return seq
def parse(cls, text, session): aspec, text, rest = super().parse(text, session) from . import MarkerSet msets = [ m for m in aspec.evaluate(session).models if isinstance(m, MarkerSet) ] if len(msets) != 1: raise AnnotationError( 'Must specify 1 marker set, got %d for "%s"' % (len(msets), aspec)) return msets[0], text, rest
def parse(text, session): from chimerax.core.commands import StringArg token, text, rest = StringArg.parse(text, session) target_chars = { 'a': 'atoms', 'b': 'bonds', 'p': 'pseudobonds', 'c': 'cartoons', 'r': 'cartoons', 's': 'surfaces', 'm': 'models' } for c in token: if c not in target_chars: from chimerax.core.commands import AnnotationError raise AnnotationError( 'Target option can only include letters ' + ', '.join('%s = %s' % (ch, name) for ch, name in target_chars.items()) + ', got %s' % c) targets = set(target_chars[char] for char in token) return targets, text, rest
def defattr(session, file_name, *, log=False, restriction=None): """define attributes on objects Parameters ---------- file_name : string Input file in 'defattr' format log : bool Whether to log assignment info restriction : Structures Collection or None If not None, structures to restrict the assignments to (in addition to any restrictions in the defattr file) """ if restriction is None: from chimerax.atomic import all_structures restriction = all_structures(session) control_defaults = { 'match mode': "any", 'recipient': "atoms", 'none handling': "None" } from chimerax.atomic import Atom, Bond, Pseudobond, Residue, Chain, Structure recipient_info = { "atoms": (Atom, lambda objs: objs.atoms), "bonds": (Bond, lambda objs: objs.bonds), "pseudobonds": (Pseudobond, lambda objs: objs.pseudobonds), "residues": (Residue, lambda objs: objs.residues), "chains": (Chain, lambda objs: objs.chains), # since we always restrict to structures, can just use Objects.models() "molecules": (Structure, lambda objs: objs.models), "structures": (Structure, lambda objs: objs.models), } legal_control_values = { 'match mode': set(["any", "non-zero", "1-to-1"]), 'recipient': set(recipient_info.keys()), 'none handling': set(["None", "string", "delete"]) } all_info = [] def append_all_info(attr_info, data_info, line_num, *, ai=all_info, fn=file_name): if 'attribute' not in attr_info: raise SyntaxError( "No attribute name defined for data lines %d and earlier in %s" % (line_num, fn)) if not data_info: raise SyntaxError("No data lines for attribute '%s' in %s" % (attr_info['attribute'], fn)) ai.append((attr_info, data_info)) from chimerax.core.commands import AtomSpecArg, AttrNameArg, AnnotationError, NoneArg, ColorArg, commas from chimerax.core.commands import IntArg, FloatArg from chimerax.io import open_input with open_input(file_name, encoding="utf-8") as f: data = [] attrs = {} for lnum, raw_line in enumerate(f): # spaces in values could be significant, so instead of stripping just drop the '\n' # (which all line endings are translated to if newline=None [default] for open()) line = raw_line[:-1] if not line.strip() or line[0] == '#': continue if line[0] == '\t': # data line datum = line[1:].split('\t') if len(datum) != 2: raise SyntaxError( "Data line %d in %s not of the form: <tab> atomspec <tab> value" % (lnum + 1, file_name)) data.append((lnum + 1, *datum)) continue # control line try: name, value = line.split(": ") except ValueError: raise SyntaxError( "Line %d in %s is either not of the form 'name: value'" " or is missing initial tab" % (lnum + 1, file_name)) name = name.strip().lower() value = value.strip() if name in attrs: # presumably another set of control/data lines starting append_all_info(attrs, data, lnum + 1) attrs = {} data = [] if name == 'attribute': try: final_value, *args = AttrNameArg.parse(value, session) except AnnotationError as e: raise SyntaxError( "Bad attribute name ('%s') given on line %d of %s: %s" % (value, lnum + 1, file_name, str(e))) elif name not in legal_control_values: raise SyntaxError( "Unrecognized control type ('%s') given on line %d of %s" % (name, lnum + 1, file_name)) elif value not in legal_control_values[name]: raise SyntaxError( "Illegal control value ('%s') for %s given on line %d of %s; legal" " values are: %s" % (value, name, lnum + 1, file_name, commas(legal_control_values[name]))) else: final_value = value attrs[name] = final_value append_all_info(attrs, data, lnum + 1) for attr_info, data_info in all_info: attr_name = attr_info['attribute'] color_attr = attr_name.lower().endswith( 'color') or attr_name.lower().endswith('colour') match_mode = attr_info.get('match mode', control_defaults['match mode']) none_handling = attr_info.get('none handling', control_defaults['none handling']) none_okay = none_handling != 'string' none_seen = False eval_vals = ["true", "false"] if none_okay: eval_vals.append("none") recipient = attr_info.get('recipient', control_defaults['recipient']) recip_class, instance_fetch = recipient_info[recipient] seen_types = set() try: pre_existing_attr = getattr(recip_class, attr_name) except AttributeError: pass else: if callable(pre_existing_attr): raise ValueError( "%s is a method of the %s class and cannot be redefined" % (attr_name, recip_class.__name__)) if attr_name[0].isupper(): raise ValueError( "%s is a constant in the %s class and cannot be redefined" % (attr_name, recip_class.__name__)) for line_num, spec, value_string in data_info: try: atom_spec, *args = AtomSpecArg.parse(spec, session) except AnnotationError as e: raise SyntaxError("Bad atom specifier (%s) on line %d of %s" % (spec, line_num, file_name)) try: objects = atom_spec.evaluate(session, models=restriction) except Exception as e: raise SyntaxError( "Error evaluating atom specifier (%s) on line %d of %s: %s" % (spec, line_num, file_name, str(e))) matches = instance_fetch(objects) if not matches and match_mode != "any": raise SyntaxError( "Selector (%s) on line %d of %s matched nothing" % (spec, line_num, file_name)) if len(matches) > 1 and match_mode == "1-to-1": raise SyntaxError( "Selector (%s) on line %d of %s matched multiple %s" % (spec, line_num, file_name, recipient)) if log: session.logger.info( "Selector %s matched %s" % (spec, commas([str(x) for x in matches], conjunction="and"))) if not value_string: raise SyntaxError("No data value on line %d of %s" % (line_num, file_name)) # Can't just use normal argument parsers willy nilly since strings are allowed to have # leading/trailing whitespace, don't want to accept shorten ed forms of booleans, etc. if color_attr: try: value, text, rest = ColorArg.parse(value_string, session) if rest: raise AnnotationError("trailing text") seen_types.add("color") value = value.uint8x4() except AnnotationError: if none_okay: try: value, text, rest = NoneArg.parse( value_string, session) if rest: raise AnnotationError("trailing text") seen_types.add(None) except AnnotationError: raise SyntaxError( "Value (%s) on line %d of %s is not recognizable as either a" " color value or None" % (value_string, line_num, file_name)) else: raise SyntaxError( "Value (%s) on line %d of %s is not recognizable as a color value" % (value_string, line_num, file_name)) else: if value_string.strip() != value_string: value = value_string seen_types.add(str) elif value_string.startswith('"') and value_string.endswith( '"'): value = value_string[1:-1] seen_types.add(str) elif value_string.lower() in eval_vals: value = eval(value_string.capitalize()) if value is None: seen_types.add(None) else: seen_types.add(bool) else: try: value, text, rest = IntArg.parse(value_string, session) if rest: raise AnnotationError("trailing text") seen_types.add(int) except AnnotationError: try: value, text, rest = FloatArg.parse( value_string, session) if rest: raise AnnotationError("trailing text") seen_types.add(float) except AnnotationError: value = value_string seen_types.add(str) for match in matches: if value is not None or none_handling == "None": setattr(match, attr_name, value) elif hasattr(match, attr_name): if pre_existing_attr: raise RuntimeError( "Cannot remove builtin attribute %s from class %s" % (attr_name, recip_class.__name__)) else: delattr(match, attr_name) can_return_none = None in seen_types seen_types.discard(None) if len(seen_types) == 1: seen_type = seen_types.pop() attr_type = None if seen_type == "color" else seen_type elif seen_types == set([int, float]): attr_type = float else: attr_type = None recip_class.register_attr(session, attr_name, "defattr command", attr_type=attr_type, can_return_none=can_return_none)