diff --git a/.github/workflows/run-tests.yml b/.github/workflows/continuous-integration.yml similarity index 98% rename from .github/workflows/run-tests.yml rename to .github/workflows/continuous-integration.yml index 65b05b6a..52f7e7e8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,4 +1,4 @@ -name: "build" +name: "CI" on: push: diff --git a/.travis.yml b/.travis.yml index 9eae351b..bdee27eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ install: - pip install -r requirements-ci.txt script: + - ./run-tests.sh -l - ./run-tests.sh after_success: diff --git a/alfred-workflow-1.38.0.zip b/alfred-workflow-1.39.0.zip similarity index 93% rename from alfred-workflow-1.38.0.zip rename to alfred-workflow-1.39.0.zip index 2b764110..e8fff215 100644 Binary files a/alfred-workflow-1.38.0.zip and b/alfred-workflow-1.39.0.zip differ diff --git a/docs/Alfred-Workflow.docset.zip b/docs/Alfred-Workflow.docset.zip index 58db2b96..e876dc60 100644 Binary files a/docs/Alfred-Workflow.docset.zip and b/docs/Alfred-Workflow.docset.zip differ diff --git a/docs/api/notify.rst.inc b/docs/api/notify.rst.inc index 43bdb1b1..7c9f66c9 100644 --- a/docs/api/notify.rst.inc +++ b/docs/api/notify.rst.inc @@ -26,14 +26,3 @@ icon and then calls the application to post notifications. .. autofunction:: notify - -Helper functions -^^^^^^^^^^^^^^^^ - -These internal helper functions may also be useful in your workflow. - -.. autofunction:: convert_image - -.. autofunction:: png_to_icns - -.. autofunction:: validate_sound diff --git a/docs/api/util.rst.inc b/docs/api/util.rst.inc index d1ac43ef..f4c33998 100644 --- a/docs/api/util.rst.inc +++ b/docs/api/util.rst.inc @@ -1,8 +1,8 @@ .. _api-util: -Utilities & helpers -------------------- +Utility & helper functions +-------------------------- .. currentmodule:: workflow.util @@ -11,9 +11,10 @@ A collection of functions and classes for common workflow-related tasks, such as running AppleScript or JXA code, or calling an External Trigger. -.. autofunction:: utf8ify +Scripting +^^^^^^^^^ -.. autofunction:: applescriptify +Functions to simplify running scripts, programs and applications. .. autofunction:: run_command @@ -28,23 +29,67 @@ as running AppleScript or JXA code, or calling an External Trigger. .. autofunction:: appinfo -Configuration -^^^^^^^^^^^^^ +Text +^^^^ + +Text encoding and formatting. + +.. autofunction:: unicodify + +.. autofunction:: utf8ify + +.. autofunction:: applescriptify -Functions for manipulating the values of workflow variables in the -`workflow configuration sheet`_/``info.plist``. + +Alfred's API +^^^^^^^^^^^^ + +Alfred-Workflow provides functions that enable you to call Alfred's AppleScript API directly from Python. + + +Workflow stuff +"""""""""""""" + +Manipulate the values of workflow variables in the `workflow configuration sheet`_/``info.plist``. .. autofunction:: set_config .. autofunction:: unset_config +Tell Alfred to reload a workflow from disk if it has changed. Normally, Alfred will notice when a workflow changes, but it won't if the workflow's directory is a symlink. + +.. autofunction:: reload_workflow + + +Alfred stuff +"""""""""""" -Other helpers +You can open Alfred in normal or file navigation mode: + +.. autofunction:: search_in_alfred + +.. autofunction:: browse_in_alfred + +Or tell Alfred to action one or more files/directories: + +.. autofunction:: action_in_alfred + +Finally, you can tell Alfred to use a specific theme: + +.. autofunction:: set_theme + + +Miscellaneous ^^^^^^^^^^^^^ These utility classes and functions are used internally by Alfred-Workflow, but may also be useful in your workflow. + +Writing files +""""""""""""" + + .. autoclass:: LockFile :members: @@ -54,6 +99,16 @@ but may also be useful in your workflow. .. autofunction:: atomic_writer +Images & sounds +""""""""""""""" + +.. autofunction:: workflow.notify.convert_image + +.. autofunction:: workflow.notify.png_to_icns + +.. autofunction:: workflow.notify.validate_sound + + .. _api-util-exceptions: diff --git a/docs/conf.py b/docs/conf.py index 3d991a7d..2b400d8b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -153,7 +153,8 @@ 'logo_name': True, 'logo_text_align': 'center', 'description': "A helper library for creating workflows for Alfred 2+.", - # 'font_family': "Georgia, 'goudy old style', 'minion pro', 'bell mt', 'Hiragino Mincho Pro', serif", + # 'font_family': ("Georgia, 'goudy old style', 'minion pro'," + # "'bell mt', 'Hiragino Mincho Pro', serif"), } # The name for this set of Sphinx documents. If None, it defaults to diff --git a/tests/test_util.py b/tests/test_util.py index 83f7e941..f0cc290d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -2,14 +2,12 @@ # encoding: utf-8 # # Copyright (c) 2017 Dean Jackson -# # MIT Licence. See http://opensource.org/licenses/MIT # # Created on 2017-12-17 # -""" -""" +"""Unit tests for workflow/util.py.""" from __future__ import print_function, absolute_import @@ -22,14 +20,19 @@ from .conftest import env from workflow.util import ( + action_in_alfred, appinfo, applescriptify, + browse_in_alfred, jxa_app_name, + reload_workflow, run_applescript, run_command, run_jxa, run_trigger, + search_in_alfred, set_config, + set_theme, unicodify, unset_config, utf8ify, @@ -145,6 +148,7 @@ def test_run_jxa(testfile): return "1" } """ + # Run script passed as text out = run_jxa(script) assert out.strip() == '1' @@ -188,14 +192,14 @@ def test_app_name(): def test_run_trigger(alfred4): """Call External Trigger""" name = 'test' - bundleid = 'net.deanishe.alfred-workflow' + bundleid = 'com.example.workflow' arg = 'test arg' # With bundle ID script = ( 'Application("com.runningwithcrayons.Alfred")' '.runTrigger("test", ' - '{"inWorkflow": "net.deanishe.alfred-workflow"});' + '{"inWorkflow": "com.example.workflow"});' ) cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] with MockCall() as m: @@ -206,7 +210,7 @@ def test_run_trigger(alfred4): script = ( 'Application("com.runningwithcrayons.Alfred")' '.runTrigger("test", ' - '{"inWorkflow": "net.deanishe.alfred-workflow", ' + '{"inWorkflow": "com.example.workflow", ' '"withArgument": "test arg"});' ) cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] @@ -220,34 +224,26 @@ def test_run_trigger(alfred4): '.runTrigger("test", ' '{"inWorkflow": "net.deanishe.alfred-workflow"});' ) - os.environ['alfred_workflow_bundleid'] = bundleid - try: - cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] - with MockCall() as m: - run_trigger(name) - assert m.cmd == cmd - finally: - del os.environ['alfred_workflow_bundleid'] + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + run_trigger(name) + assert m.cmd == cmd def test_set_config(alfred4): """Set Configuration.""" name = 'test' - bundleid = 'net.deanishe.alfred-workflow' + bundleid = 'com.example.workflow' value = 'test' - # argclause = 'with argument "test arg"' # With bundle ID script = ( 'Application("com.runningwithcrayons.Alfred")' '.setConfiguration("test", ' '{"exportable": false, ' - '"inWorkflow": "net.deanishe.alfred-workflow", ' + '"inWorkflow": "com.example.workflow", ' '"toValue": "test"});' ) - # script = AS_CONFIG_SET.format(name=name, value=value, - # bundleid=bundleid, - # export='exportable false') cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] with MockCall() as m: @@ -259,12 +255,9 @@ def test_set_config(alfred4): 'Application("com.runningwithcrayons.Alfred")' '.setConfiguration("test", ' '{"exportable": true, ' - '"inWorkflow": "net.deanishe.alfred-workflow", ' + '"inWorkflow": "com.example.workflow", ' '"toValue": "test"});' ) - # script = AS_CONFIG_SET.format(name=name, value=value, - # bundleid=bundleid, - # export='exportable true') cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] with MockCall() as m: @@ -272,7 +265,6 @@ def test_set_config(alfred4): assert m.cmd == cmd # With bundle ID from env - os.environ['alfred_workflow_bundleid'] = bundleid script = ( 'Application("com.runningwithcrayons.Alfred")' '.setConfiguration("test", ' @@ -280,30 +272,22 @@ def test_set_config(alfred4): '"inWorkflow": "net.deanishe.alfred-workflow", ' '"toValue": "test"});' ) - try: - # script = AS_CONFIG_SET.format(name=name, value=value, - # bundleid=bundleid, - # export='exportable false') - - cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] - with MockCall() as m: - set_config(name, value) - assert m.cmd == cmd - finally: - del os.environ['alfred_workflow_bundleid'] + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + set_config(name, value) + assert m.cmd == cmd def test_unset_config(alfred4): """Unset Configuration.""" name = 'test' - bundleid = 'net.deanishe.alfred-workflow' - # argclause = 'with argument "test arg"' + bundleid = 'com.example.workflow' # With bundle ID script = ( 'Application("com.runningwithcrayons.Alfred")' '.removeConfiguration("test", ' - '{"inWorkflow": "net.deanishe.alfred-workflow"});' + '{"inWorkflow": "com.example.workflow"});' ) cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] with MockCall() as m: @@ -321,6 +305,94 @@ def test_unset_config(alfred4): del os.environ['alfred_workflow_bundleid'] +def test_search_in_alfred(alfred4): + """Search.""" + query = 'badger, badger, badger' + + # With query + script = ( + 'Application("com.runningwithcrayons.Alfred")' + '.search("badger, badger, badger");' + ) + + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + search_in_alfred(query) + assert m.cmd == cmd + + # Without query (just opens Alfred) + script = 'Application("com.runningwithcrayons.Alfred").search("");' + + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + search_in_alfred() + assert m.cmd == cmd + + +def test_action_in_alfred(alfred4): + """Action.""" + paths = ['~/Documents', '~/Desktop'] + + script = ( + 'Application("com.runningwithcrayons.Alfred")' + '.action(["~/Documents", "~/Desktop"]);' + ) + + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + action_in_alfred(paths) + assert m.cmd == cmd + + +def test_browse_in_alfred(alfred4): + """Browse.""" + path = '~/Documents' + script = 'Application("com.runningwithcrayons.Alfred").browse("~/Documents");' + + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + browse_in_alfred(path) + assert m.cmd == cmd + + +def test_reload_workflow(alfred4): + """Reload workflow.""" + bundleid = 'com.example.workflow' + script = ( + 'Application("com.runningwithcrayons.Alfred")' + '.reloadWorkflow("com.example.workflow");' + ) + + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + reload_workflow(bundleid) + assert m.cmd == cmd + + # With bundle ID from env + script = ( + 'Application("com.runningwithcrayons.Alfred")' + '.reloadWorkflow("net.deanishe.alfred-workflow");' + ) + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + reload_workflow() + assert m.cmd == cmd + + +def test_set_theme(alfred4): + """Set Alfred theme.""" + theme = 'Alfred Dark' + script = ( + 'Application("com.runningwithcrayons.Alfred")' + '.setTheme("Alfred Dark");' + ) + + cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script] + with MockCall() as m: + set_theme(theme) + assert m.cmd == cmd + + def test_appinfo(): """App info for Safari.""" for name, bundleid, path in [ diff --git a/tests/test_workflow_xml.py b/tests/test_workflow_xml.py index 7c3b0749..38955bf2 100644 --- a/tests/test_workflow_xml.py +++ b/tests/test_workflow_xml.py @@ -8,8 +8,7 @@ # Created on 2017-05-06 # -""" -""" +"""Unit tests for Workflow's XML feedback generation.""" from __future__ import print_function diff --git a/tests/util.py b/tests/util.py index a1e0c238..71ba4dba 100644 --- a/tests/util.py +++ b/tests/util.py @@ -59,10 +59,12 @@ def _check_output(self, cmd, **kwargs): self.cmd = cmd def __enter__(self): + """Monkey-patch subprocess.check_output.""" self._set_up() return self def __exit__(self, *args): + """Restore subprocess.check_output.""" self._tear_down() @@ -108,6 +110,7 @@ def _call(self, cmd, *args, **kwargs): self.kwargs = kwargs def __enter__(self): + """Monkey-patch "system" functions called by Workflow.""" if self.override_call: self.call_orig = subprocess.call subprocess.call = self._call @@ -127,6 +130,7 @@ def __enter__(self): return self def __exit__(self, *args): + """Restore monkey-patched functions.""" if self.call_orig: subprocess.call = self.call_orig @@ -151,12 +155,14 @@ def __init__(self, version, path=None): self.path = path or VERSION_PATH def __enter__(self): + """Create version file.""" with open(self.path, 'wb') as fp: fp.write(self.version) print('version {0} in {1}'.format(self.version, self.path), file=sys.stderr) def __exit__(self, *args): + """Remove version file.""" if os.path.exists(self.path): os.unlink(self.path) @@ -174,6 +180,7 @@ def __init__(self, *names, **names2codes): self.programs.update(names2codes) def __enter__(self): + """Inject program(s) into PATH.""" self.tempdir = tempfile.mkdtemp() for name, retcode in self.programs.items(): path = os.path.join(self.tempdir, name) @@ -186,6 +193,7 @@ def __enter__(self): os.environ['PATH'] = self.tempdir + ':' + self.orig_path def __exit__(self, *args): + """Remove program(s) from PATH.""" os.environ['PATH'] = self.orig_path try: shutil.rmtree(self.tempdir) @@ -211,7 +219,7 @@ def __enter__(self): delete_info_plist(self.dest_path) def __exit__(self, *args): - """Create or delete ``info.plist``.""" + """Delete ``info.plist``.""" if self.present: delete_info_plist(self.dest_path) diff --git a/tox.ini b/tox.ini index 97a2975d..b5897f08 100644 --- a/tox.ini +++ b/tox.ini @@ -1,47 +1,3 @@ -[flake8] -ignore = - D100, - ; Missing docstring in magic method - D105, - D200, - D201, - D203, - D204, - D400, - D401, - ; Missing blank line after last section - D413, - ; missing whitespace around arithmetic operator - E226, - E402, - ; flake8-future-import. Ignore everything to make all - ; from __future__ imports optional - FI10, - FI11, - FI12, - FI13, - FI14, - FI15, - FI16, - FI17, - FI50, - FI51, - FI52, - FI53, - FI54, - FI55, - FI56, - FI57, - FI90, - ; line break before binary operator - W503, - ; line break after binary operator - W504, - -[pydocstyle] -add_ignore = D105,D203,D266,D400,D401,D413 - - [pytest] ; addopts = --cov-report term-missing --cov workflow --capture=fd --doctest-modules ; addopts = --cov-report term-missing --capture=fd --doctest-modules @@ -79,3 +35,351 @@ deps = ; [testenv:py26] ; deps = ; {[testenv]deps} + +; [pydocstyle] +; add_ignore = D105,D203,D266,D400,D401,D413 + +[flake8] +builtins = unicode +ignore = + ; Missing docstring in public module + ; D100, + ; Missing docstring in public class + ; D101, + ; Missing docstring in public method + ; D102, + ; Missing docstring in public function + ; D103, + ; Missing docstring in public package + ; D104, + ; Missing docstring in magic method + ; D105, + ; Missing docstring in public nested class + ; D106, + ; Missing docstring in __init__ + D107, + ; One-line docstring should fit on one line with quotes + ; D200, + ; No blank lines allowed before function docstring + ; D201, + ; No blank lines allowed after function docstring + ; D202, + ; 1 blank line required before class docstring + ; D203, + ; 1 blank line required after class docstring + ; D204, + ; 1 blank line required between summary line and description + ; D205, + ; Docstring should be indented with spaces, not tabs + ; D206, + ; Docstring is under-indented + ; D207, + ; Docstring is over-indented + ; D208, + ; Multi-line docstring closing quotes should be on a separate line + ; D209, + ; No whitespaces allowed surrounding docstring text + ; D210, + ; No blank lines allowed before class docstring + ; D211, + ; Multi-line docstring summary should start at the first line + ; D212, + ; Multi-line docstring summary should start at the second line + ; D213, + ; Section is over-indented + ; D214, + ; Section underline is over-indented + ; D215, + ; Use """triple double quotes""" + ; D300, + ; Use r""" if any backslashes in a docstring + ; D301, + ; Use u""" for Unicode docstrings + ; D302, + ; First line should end with a period + D400, + ; First line should be in imperative mood; try rephrasing + D401, + ; First line should not be the function’s “signature” + ; D402, + ; First word of the first line should be properly capitalized + ; D403, + ; First word of the docstring should not be This + ; D404, + ; Section name should be properly capitalized + ; D405, + ; Section name should end with a newline + ; D406, + ; Missing dashed underline after section + ; D407, + ; Section underline should be in the line following the section’s name + ; D408, + ; Section underline should match the length of its name + ; D409, + ; Missing blank line after section + ; D410, + ; Missing blank line before section + ; D411, + ; No blank lines allowed between a section header and its content + ; D412, + ; Missing blank line after last section + ; D413, + ; Section has no content + ; D414, + ; First line should end with a period, question mark, or exclamation point + ; D415, + ; Section name should end with a colon + ; D416, + ; Missing argument descriptions in the docstring + ; D417, + + ; indentation contains mixed spaces and tabs + ; E101, + ; indentation is not a multiple of four + ; E111, + ; expected an indented block + ; E112, + ; unexpected indentation + ; E113, + ; indentation is not a multiple of four (comment) + ; E114, + ; expected an indented block (comment) + ; E115, + ; unexpected indentation (comment) + ; E116, + ; over-indented + ; E117, + ; continuation line under-indented for hanging indent + E121, + ; continuation line missing indentation or outdented + ; E122, + ; closing bracket does not match indentation of opening bracket’s line + E123, + ; closing bracket does not match visual indentation + ; E124, + ; continuation line with same indent as next logical line + ; E125, + ; continuation line over-indented for hanging indent + E126, + ; continuation line over-indented for visual indent + ; E127, + ; continuation line under-indented for visual indent + ; E128, + ; visually indented line with same indent as next logical line + ; E129, + ; continuation line unaligned for hanging indent + ; E131, + ; closing bracket is missing indentation + E133, + ; whitespace after ‘(‘ + ; E201, + ; whitespace before ‘)’ + ; E202, + ; whitespace before ‘:’ + ; E203, + ; whitespace before ‘(‘ + ; E211, + ; multiple spaces before operator + ; E221, + ; multiple spaces after operator + ; E222, + ; tab before operator + ; E223, + ; tab after operator + ; E224, + ; missing whitespace around operator + ; E225, + ; missing whitespace around arithmetic operator + E226, + ; missing whitespace around bitwise or shift operator + ; E227, + ; missing whitespace around modulo operator + ; E228, + ; missing whitespace after ‘,’, ‘;’, or ‘:’ + ; E231, + ; multiple spaces after ‘,’ + ; E241, + ; tab after ‘,’ + ; E242, + ; unexpected spaces around keyword / parameter equals + ; E251, + ; at least two spaces before inline comment + ; E261, + ; inline comment should start with ‘# ‘ + ; E262, + ; block comment should start with ‘# ‘ + ; E265, + ; too many leading ‘#’ for block comment + ; E266, + ; multiple spaces after keyword + ; E271, + ; multiple spaces before keyword + ; E272, + ; tab after keyword + ; E273, + ; tab before keyword + ; E274, + ; missing whitespace after keyword + ; E275, + ; expected 1 blank line, found 0 + ; E301, + ; expected 2 blank lines, found 0 + ; E302, + ; too many blank lines (3) + ; E303, + ; blank lines found after function decorator + ; E304, + ; expected 2 blank lines after end of function or class + ; E305, + ; expected 1 blank line before a nested definition + ; E306, + ; multiple imports on one line + ; E401, + ; module level import not at top of file + E402, + ; line too long (82 > 79 characters) + ; E501, + ; the backslash is redundant between brackets + ; E502, + ; multiple statements on one line (colon) + ; E701, + ; multiple statements on one line (semicolon) + ; E702, + ; statement ends with a semicolon + ; E703, + ; multiple statements on one line (def) + E704, + ; comparison to None should be ‘if cond is None:’ + ; E711, + ; comparison to True should be ‘if cond is True:’ or ‘if cond:’ + ; E712, + ; test for membership should be ‘not in’ + ; E713, + ; test for object identity should be ‘is not’ + ; E714, + ; do not compare types, use ‘isinstance()’ + ; E721, + ; do not use bare except, specify exception instead + ; E722, + ; do not assign a lambda expression, use a def + ; E731, + ; do not use variables named ‘l’, ‘O’, or ‘I’ + ; E741, + ; do not define classes named ‘l’, ‘O’, or ‘I’ + ; E742, + ; do not define functions named ‘l’, ‘O’, or ‘I’ + ; E743, + ; SyntaxError or IndentationError + ; E901, + ; IOError + ; E902, + ; indentation contains tabs + + ; W191, + ; trailing whitespace + ; W291, + ; no newline at end of file + ; W292, + ; blank line contains whitespace + ; W293, + ; blank line at end of file + ; W391, + ; line break before binary operator + W503, + ; line break after binary operator + W504, + ; doc line too long (82 > 79 characters) + ; W505, + ; .has_key() is deprecated, use ‘in’ + ; W601, + ; deprecated form of raising exception + ; W602, + ; ‘<>’ is deprecated, use ‘!=’ + ; W603, + ; backticks are deprecated, use ‘repr()’ + ; W604, + ; invalid escape sequence ‘x’ + ; W605, + ; ‘async’ and ‘await’ are reserved keywords starting with Python 3.7 + ; W606, + ; module imported but unused + ; F401, + ; import module from line N shadowed by loop variable + ; F402, + ; ‘from module import *’ used; unable to detect undefined names + ; F403, + ; future import(s) name after other statements + ; F404, + ; name may be undefined, or defined from star imports: module + ; F405, + ; redefinition of unused name from line N + ; F811, + ; list comprehension redefines name from line N + ; F812, + ; undefined name name + ; F821, + ; undefined name name in __all__ + ; F822, + ; local variable name ... referenced before assignment + ; F823, + ; duplicate argument name in function definition + ; F831, + ; local variable name is assigned to but never used + ; F841, + + ; flake8-future-import. Ignore everything to make all + ; from __future__ imports optional + FI10, + FI11, + FI12, + FI13, + FI14, + FI15, + FI16, + FI17, + FI50, + FI51, + FI52, + FI53, + FI54, + FI55, + FI56, + FI57, + FI90, + + ; class names should use CapWords convention + ; N801, + ; function name should be lowercase + ; N802, + ; argument name should be lowercase + ; N803, + ; first argument of a classmethod should be named 'cls' + ; N804, + ; first argument of a method should be named 'self' + ; N805, + ; variable in function should be lowercase + ; N806, + ; function name should not start and end with '__' + ; N807, + ; constant imported as non constant + ; N811, + ; lowercase imported as non lowercase + ; N812, + ; camelcase imported as lowercase + ; N813, + ; camelcase imported as constant + ; N814, + ; mixedCase variable in class scope + ; N815, + ; mixedCase variable in global scope + ; N816, + ; camelcase imported as acronym + ; N817 + +exclude = + .git, + __pycache__, + build, + dist, + +max-line-length = 90 diff --git a/workflow/util.py b/workflow/util.py index 97684aeb..ab5e9548 100644 --- a/workflow/util.py +++ b/workflow/util.py @@ -31,19 +31,21 @@ # "com.runningwithcrayons.Alfred" depending on version. # # Open Alfred in search (regular) mode -JXA_SEARCH = "Application({app}).search({arg});" +JXA_SEARCH = 'Application({app}).search({arg});' # Open Alfred's File Actions on an argument -JXA_ACTION = "Application({app}).action({arg});" +JXA_ACTION = 'Application({app}).action({arg});' # Open Alfred's navigation mode at path -JXA_BROWSE = "Application({app}).browse({arg});" +JXA_BROWSE = 'Application({app}).browse({arg});' # Set the specified theme -JXA_SET_THEME = "Application({app}).setTheme({arg});" +JXA_SET_THEME = 'Application({app}).setTheme({arg});' # Call an External Trigger -JXA_TRIGGER = "Application({app}).runTrigger({arg}, {opts});" +JXA_TRIGGER = 'Application({app}).runTrigger({arg}, {opts});' # Save a variable to the workflow configuration sheet/info.plist -JXA_SET_CONFIG = "Application({app}).setConfiguration({arg}, {opts});" +JXA_SET_CONFIG = 'Application({app}).setConfiguration({arg}, {opts});' # Delete a variable from the workflow configuration sheet/info.plist -JXA_UNSET_CONFIG = "Application({app}).removeConfiguration({arg}, {opts});" +JXA_UNSET_CONFIG = 'Application({app}).removeConfiguration({arg}, {opts});' +# Tell Alfred to reload a workflow from disk +JXA_RELOAD_WORKFLOW = 'Application({app}).reloadWorkflow({arg});' class AcquisitionError(Exception): @@ -148,17 +150,16 @@ def applescriptify(s): .. versionadded:: 1.31 Replaces ``"`` with `"& quote &"`. Use this function if you want - to insert a string into an AppleScript script: - >>> query = 'g "python" test' - >>> applescriptify(query) + + >>> applescriptify('g "python" test') 'g " & quote & "python" & quote & "test' Args: s (unicode): Unicode string to escape. Returns: - unicode: Escaped string + unicode: Escaped string. """ return s.replace(u'"', u'" & quote & "') @@ -173,11 +174,11 @@ def run_command(cmd, **kwargs): all arguments are encoded to UTF-8 first. Args: - cmd (list): Command arguments to pass to ``check_output``. - **kwargs: Keyword arguments to pass to ``check_output``. + cmd (list): Command arguments to pass to :func:`~subprocess.check_output`. + **kwargs: Keyword arguments to pass to :func:`~subprocess.check_output`. Returns: - str: Output returned by ``check_output``. + str: Output returned by :func:`~subprocess.check_output`. """ cmd = [utf8ify(s) for s in cmd] @@ -197,6 +198,7 @@ def run_applescript(script, *args, **kwargs): script (str, optional): Filepath of script or code to run. *args: Optional command-line arguments to pass to the script. **kwargs: Pass ``lang`` to run a language other than AppleScript. + Any other keyword arguments are passed to :func:`run_command`. Returns: str: Output of run command. @@ -242,8 +244,8 @@ def run_trigger(name, bundleid=None, arg=None): .. versionadded:: 1.31 - If ``bundleid`` is not specified, reads the bundle ID of the current - workflow from Alfred's environment variables. + If ``bundleid`` is not specified, the bundle ID of the calling + workflow is used. Args: name (str): Name of External Trigger to call. @@ -264,11 +266,29 @@ def run_trigger(name, bundleid=None, arg=None): run_applescript(script, lang='JavaScript') +def set_theme(theme_name): + """Change Alfred's theme. + + .. versionadded:: 1.39.0 + + Args: + theme_name (unicode): Name of theme Alfred should use. + + """ + appname = jxa_app_name() + script = JXA_SET_THEME.format(app=json.dumps(appname), + arg=json.dumps(theme_name)) + run_applescript(script, lang='JavaScript') + + def set_config(name, value, bundleid=None, exportable=False): """Set a workflow variable in ``info.plist``. .. versionadded:: 1.33 + If ``bundleid`` is not specified, the bundle ID of the calling + workflow is used. + Args: name (str): Name of variable to set. value (str): Value to set variable to. @@ -297,6 +317,9 @@ def unset_config(name, bundleid=None): .. versionadded:: 1.33 + If ``bundleid`` is not specified, the bundle ID of the calling + workflow is used. + Args: name (str): Name of variable to delete. bundleid (str, optional): Bundle ID of workflow variable belongs to. @@ -313,6 +336,71 @@ def unset_config(name, bundleid=None): run_applescript(script, lang='JavaScript') +def search_in_alfred(query=None): + """Open Alfred with given search query. + + .. versionadded:: 1.39.0 + + Omit ``query`` to simply open Alfred's main window. + + Args: + query (unicode, optional): Search query. + + """ + query = query or u'' + appname = jxa_app_name() + script = JXA_SEARCH.format(app=json.dumps(appname), arg=json.dumps(query)) + run_applescript(script, lang='JavaScript') + + +def browse_in_alfred(path): + """Open Alfred's filesystem navigation mode at ``path``. + + .. versionadded:: 1.39.0 + + Args: + path (unicode): File or directory path. + + """ + appname = jxa_app_name() + script = JXA_BROWSE.format(app=json.dumps(appname), arg=json.dumps(path)) + run_applescript(script, lang='JavaScript') + + +def action_in_alfred(paths): + """Action the give filepaths in Alfred. + + .. versionadded:: 1.39.0 + + Args: + paths (list): Unicode paths to files/directories to action. + + """ + appname = jxa_app_name() + script = JXA_ACTION.format(app=json.dumps(appname), arg=json.dumps(paths)) + run_applescript(script, lang='JavaScript') + + +def reload_workflow(bundleid=None): + """Tell Alfred to reload a workflow from disk. + + .. versionadded:: 1.39.0 + + If ``bundleid`` is not specified, the bundle ID of the calling + workflow is used. + + Args: + bundleid (unicode, optional): Bundle ID of workflow to reload. + + """ + bundleid = bundleid or os.getenv('alfred_workflow_bundleid') + appname = jxa_app_name() + script = JXA_RELOAD_WORKFLOW.format(app=json.dumps(appname), + arg=json.dumps(bundleid)) + + run_applescript(script, lang='JavaScript') + + def appinfo(name): """Get information about an installed application. diff --git a/workflow/version b/workflow/version index 4afb1d2b..9c235b49 100644 --- a/workflow/version +++ b/workflow/version @@ -1 +1 @@ -1.38.0 \ No newline at end of file +1.39.0 \ No newline at end of file