From dfb15b74ba9e7d34b7682a503311a81da8fd12bd Mon Sep 17 00:00:00 2001 From: ye11owSub Date: Sun, 5 Jan 2025 18:02:46 +0000 Subject: [PATCH] fixes after review --- .github/workflows/build.yml | 3 - modules/pymol/completing.py | 13 +-- modules/pymol/shortcut.py | 42 ++++++++-- testing/tests/api/shortcut.py | 80 ------------------- .../tests/api}/test_shortcut.py | 0 5 files changed, 45 insertions(+), 93 deletions(-) delete mode 100644 testing/tests/api/shortcut.py rename {tests/pymol => testing/tests/api}/test_shortcut.py (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8c4f43e0c..2abbf922b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,6 @@ jobs: - name: Test run: | pymol -ckqy testing/testing.py --run all - python -m pytest tests -vv build-Windows: @@ -100,7 +99,6 @@ jobs: run: | CALL %CONDA_ROOT%\\Scripts\\activate.bat pymol -ckqy testing\\testing.py --run all - python -m pytest tests -vv build-MacOS: @@ -137,4 +135,3 @@ jobs: run: |- export PATH="$CONDA_ROOT/bin:$PATH" pymol -ckqy testing/testing.py --run all - python -m pytest tests -vv diff --git a/modules/pymol/completing.py b/modules/pymol/completing.py index a9d525220..169006032 100644 --- a/modules/pymol/completing.py +++ b/modules/pymol/completing.py @@ -1,3 +1,5 @@ +from typing import Optional + from pymol.shortcut import Shortcut cmd = __import__("sys").modules["pymol.cmd"] @@ -7,14 +9,15 @@ class ExprShortcut(Shortcut): Expression shortcut for iterate/alter/label with "s." prefix setting autocompletion. ''' - def interpret(self, keyword, mode=False): + def interpret(self, keyword: str, mode: bool = False): if not keyword.startswith('s.'): return super().interpret(keyword, mode) - v = cmd.setting.setting_sc.interpret(keyword[2:]) - if isinstance(v, str): - return 's.' + v + v: Optional[int | str | list[str]] = cmd.setting.setting_sc.interpret(keyword[2:]) + + if isinstance(v, str) or isinstance(v, int): + return f"s.{v}" if isinstance(v, list): - return ['s.' + v for v in v] + return [f"s.{v}" for v in v] return None expr_sc = ExprShortcut([ diff --git a/modules/pymol/shortcut.py b/modules/pymol/shortcut.py index 1f172a85d..9c5f59cfc 100644 --- a/modules/pymol/shortcut.py +++ b/modules/pymol/shortcut.py @@ -48,20 +48,30 @@ def __delitem__(self, keyword: str) -> None: self.keywords.remove(keyword) self.rebuild() - def make_abbreviation(self, s: str, groups_length: int) -> str: + def _make_abbreviation(self, s: str, groups_length: int) -> str: """ + Creates an abbreviation for a string by shortening its components. + The abbreviation takes the first `groups_length` + characters of each part before the last component. + Example 1: Input: s:'abc_def_ghig', groups_length: 1 Output: 'a_d_ghig' + Example 2: Input: s:'abc_def', groups_length: 2 - Output: 'a_def' + Output: 'ab_def' """ groups = s.split("_") groups[:-1] = [c[0:groups_length] for c in groups[:-1]] return "_".join(groups) def optimize_symbols(self, keyword: str) -> None: + """ + Optimizes the given keyword by adding abbreviations and shortening + components. This method also builds a shortcut dictionary + for the keyword and its abbreviated forms. + """ for i in range(1, len(keyword)): substr = keyword[0:i] self.shortcut[substr] = 0 if substr in self.shortcut else keyword @@ -70,7 +80,7 @@ def optimize_symbols(self, keyword: str) -> None: return for n in (1, 2): - abbreviation = self.make_abbreviation(keyword, n) + abbreviation = self._make_abbreviation(keyword, n) if keyword == abbreviation: continue @@ -81,7 +91,13 @@ def optimize_symbols(self, keyword: str) -> None: sub = abbreviation[0:i] self.shortcut[sub] = 0 if sub in self.shortcut else keyword - def rebuild(self, keywords: Optional[Iterable] = None) -> None: + def rebuild(self, keywords: Optional[Iterable[str]] = None) -> None: + """ + Rebuilds the shortcuts and abbreviation dictionaries + based on the provided list of keywords. + This method clears the existing shortcuts and optimizes symbols + for the new list of keywords. + """ keywords = list(keywords) if keywords is not None else [] self.keywords = ( [keyword for keyword in keywords if keyword[:1] != "_"] @@ -97,6 +113,13 @@ def rebuild(self, keywords: Optional[Iterable] = None) -> None: self._rebuild_finalize() def _rebuild_finalize(self) -> None: + """ + Finalizes the rebuild process + by setting shortcuts for abbreviations and keywords. + + This method ensures that each abbreviation points to a single keyword and that + each keyword has a shortcut. + """ for abbreviation, keywords in self.abbreviation_dict.items(): if len(keywords) == 1: self.shortcut[abbreviation] = keywords[0] @@ -140,7 +163,9 @@ def interpret( else list(unique_keywords) ) - def append(self, keyword) -> None: + def append(self, keyword: str) -> None: + """Adds a new keyword to the list and rebuilds the shortcuts.""" + self.keywords.append(keyword) self.optimize_symbols(keyword) self._rebuild_finalize() @@ -148,6 +173,13 @@ def append(self, keyword) -> None: def auto_err( self, keyword: str, descrip: Optional[str] = None ) -> Optional[int | str | list[str]]: + """ + Automatically raises an error if a keyword is unknown or ambiguous. + + This method checks if a keyword is valid, and if not, + raises a descriptive error with suggestions for possible matches. + """ + if keyword == "": return diff --git a/testing/tests/api/shortcut.py b/testing/tests/api/shortcut.py deleted file mode 100644 index a1830b501..000000000 --- a/testing/tests/api/shortcut.py +++ /dev/null @@ -1,80 +0,0 @@ -import pymol -from pymol import cmd, testing, stored - -foos = ['foo'] -ba_s = ['bar', 'baz'] -coms = ['com', 'com_bla', 'com_xxx'] -words = foos + ba_s + coms - -class TestShortcut(testing.PyMOLTestCase): - - def testShortcut(self): - # build shortcut - sc = cmd.Shortcut(words) - - # get all keywords - self.assertItemsEqual(words, sc.interpret('')) - - # full/prefix hits - self.assertEqual('foo', sc.interpret('f')) - self.assertEqual('foo', sc.interpret('fo')) - self.assertEqual('foo', sc.interpret('foo')) - - self.assertItemsEqual(ba_s, sc.interpret('b')) - self.assertItemsEqual(ba_s, sc.interpret('ba')) - self.assertEqual('bar', sc.interpret('bar')) - - self.assertItemsEqual(coms, sc.interpret('c')) - self.assertItemsEqual(coms, sc.interpret('co')) - self.assertEqual('com', sc.interpret('com')) - - # add one - sc.append('foo_new') - self.assertItemsEqual(['foo', 'foo_new'], sc.interpret('f')) - self.assertEqual('foo', sc.interpret('foo')) - self.assertEqual('foo_new', sc.interpret('foo_')) - - self.assertEqual(False, '' in sc) - - - # abbreviations - self.assertEqual('foo_new', sc.interpret('f_')) - self.assertEqual('foo_new', sc.interpret('f_new')) - self.assertEqual('foo_new', sc.interpret('fo_')) - self.assertEqual('com_xxx', sc.interpret('c_x')) - self.assertEqual('com_xxx', sc.interpret('c_xxx')) - self.assertEqual('com_xxx', sc.interpret('co_x')) - - # missing key - self.assertEqual(None, sc.interpret('missing_key')) - - # auto error - self.assertEqual(None, sc.auto_err('')) - self.assertEqual(None, sc.auto_err('missing_key')) - self.assertItemsEqual(coms, sc.auto_err('co')) - self.assertEqual('com', sc.auto_err('com')) - - def testShortcutMode1(self): - # build shortcut - sc = cmd.Shortcut(words) - - # full/prefix hits - self.assertEqual('foo', sc.interpret('f', 1)) - self.assertItemsEqual(coms, sc.interpret('com', 1)) - - # add one - sc.append('foo_new') - self.assertItemsEqual(['foo', 'foo_new'], sc.interpret('foo', 1)) - - def testShortcutRebuild(self): - sc = cmd.Shortcut(words) - sc.rebuild(coms) - - self.assertEqual(None, sc.interpret('f')) - self.assertEqual(None, sc.interpret('foo')) - - self.assertItemsEqual(coms, sc.interpret('c')) - self.assertItemsEqual(coms, sc.interpret('com', 1)) - self.assertEqual('com', sc.interpret('com')) - self.assertEqual('com_xxx', sc.interpret('c_x')) - diff --git a/tests/pymol/test_shortcut.py b/testing/tests/api/test_shortcut.py similarity index 100% rename from tests/pymol/test_shortcut.py rename to testing/tests/api/test_shortcut.py