diff --git a/bin/hooks/pre-commit b/.githooks/pre-commit
similarity index 100%
rename from bin/hooks/pre-commit
rename to .githooks/pre-commit
diff --git a/Convert-3.7.0.alfredworkflow b/Convert-3.7.1.alfredworkflow
similarity index 88%
rename from Convert-3.7.0.alfredworkflow
rename to Convert-3.7.1.alfredworkflow
index b21b1ef..a3497e9 100644
Binary files a/Convert-3.7.0.alfredworkflow and b/Convert-3.7.1.alfredworkflow differ
diff --git a/src/info.plist b/src/info.plist
index c88e6c0..40e45ce 100644
--- a/src/info.plist
+++ b/src/info.plist
@@ -231,7 +231,11 @@
runningsubtext
Convertifying…
script
- /usr/bin/python convert.py "$1"
+ echo . >&2
+echo "http_proxy=$http_proxy">&2
+echo "https_proxy=$https_proxy">&2
+
+/usr/bin/python convert.py "$1"
scriptargtype
1
scriptfile
@@ -476,6 +480,8 @@ variables={allvars}
argument
{var:query}
+ passthroughargument
+
variables
@@ -653,7 +659,7 @@ UPDATE_INTERVAL is the number of minutes between exchange rate updates.
APP_KEY
version
- 3.7.0
+ 3.7.1
webaddress
diff --git a/src/workflow/background.py b/src/workflow/background.py
index ba5c52a..c2bd735 100644
--- a/src/workflow/background.py
+++ b/src/workflow/background.py
@@ -102,10 +102,7 @@ def _job_pid(name):
if _process_exists(pid):
return pid
- try:
- os.unlink(pidfile)
- except Exception: # pragma: no cover
- pass
+ os.unlink(pidfile)
def is_running(name):
diff --git a/src/workflow/notify.py b/src/workflow/notify.py
index a4b7f40..28ec0b9 100644
--- a/src/workflow/notify.py
+++ b/src/workflow/notify.py
@@ -117,8 +117,8 @@ def install_notifier():
# z.extractall(destdir)
tgz = tarfile.open(archive, 'r:gz')
tgz.extractall(destdir)
- assert os.path.exists(n), \
- 'Notify.app could not be installed in %s' % destdir
+ if not os.path.exists(n): # pragma: nocover
+ raise RuntimeError('Notify.app could not be installed in ' + destdir)
# Replace applet icon
icon = notifier_icon_path()
@@ -253,8 +253,9 @@ def png_to_icns(png_path, icns_path):
try:
iconset = os.path.join(tempdir, 'Icon.iconset')
- assert not os.path.exists(iconset), \
- 'iconset already exists: ' + iconset
+ if os.path.exists(iconset): # pragma: nocover
+ raise RuntimeError('iconset already exists: ' + iconset)
+
os.makedirs(iconset)
# Copy source icon to icon set and generate all the other
@@ -283,8 +284,9 @@ def png_to_icns(png_path, icns_path):
if retcode != 0:
raise RuntimeError('iconset exited with %d' % retcode)
- assert os.path.exists(icns_path), \
- 'generated ICNS file not found: ' + repr(icns_path)
+ if not os.path.exists(icns_path): # pragma: nocover
+ raise ValueError(
+ 'generated ICNS file not found: ' + repr(icns_path))
finally:
try:
shutil.rmtree(tempdir)
@@ -332,8 +334,8 @@ def ustr(s):
print('converting {0!r} to {1!r} ...'.format(o.png, icns),
file=sys.stderr)
- assert not os.path.exists(icns), \
- 'destination file already exists: ' + icns
+ if os.path.exists(icns):
+ raise ValueError('destination file already exists: ' + icns)
png_to_icns(o.png, icns)
sys.exit(0)
diff --git a/src/workflow/update.py b/src/workflow/update.py
index 6affc94..c039f7a 100644
--- a/src/workflow/update.py
+++ b/src/workflow/update.py
@@ -519,7 +519,7 @@ def install_update():
path = retrieve_download(Download.from_dict(dl))
wf().logger.info('installing updated workflow ...')
- subprocess.call(['open', path])
+ subprocess.call(['open', path]) # nosec
wf().cache_data(key, no_update)
return True
diff --git a/src/workflow/util.py b/src/workflow/util.py
index 27209d8..ab5e954 100644
--- a/src/workflow/util.py
+++ b/src/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.
@@ -325,11 +413,15 @@ def appinfo(name):
AppInfo: :class:`AppInfo` tuple or ``None`` if app isn't found.
"""
- cmd = ['mdfind', '-onlyin', '/Applications',
- '-onlyin', os.path.expanduser('~/Applications'),
- '(kMDItemContentTypeTree == com.apple.application &&'
- '(kMDItemDisplayName == "{0}" || kMDItemFSName == "{0}.app"))'
- .format(name)]
+ cmd = [
+ 'mdfind',
+ '-onlyin', '/Applications',
+ '-onlyin', '/System/Applications',
+ '-onlyin', os.path.expanduser('~/Applications'),
+ '(kMDItemContentTypeTree == com.apple.application &&'
+ '(kMDItemDisplayName == "{0}" || kMDItemFSName == "{0}.app"))'
+ .format(name)
+ ]
output = run_command(cmd).strip()
if not output:
diff --git a/src/workflow/version b/src/workflow/version
index 673b6a6..ebc91b4 100644
--- a/src/workflow/version
+++ b/src/workflow/version
@@ -1 +1 @@
-1.37.2
\ No newline at end of file
+1.40.0
\ No newline at end of file
diff --git a/src/workflow/web.py b/src/workflow/web.py
index 0781911..83212a8 100644
--- a/src/workflow/web.py
+++ b/src/workflow/web.py
@@ -9,6 +9,8 @@
"""Lightweight HTTP library with a requests-like interface."""
+from __future__ import absolute_import, print_function
+
import codecs
import json
import mimetypes
@@ -23,8 +25,10 @@
import urlparse
import zlib
+__version__ = open(os.path.join(os.path.dirname(__file__), 'version')).read()
-USER_AGENT = u'Alfred-Workflow/1.36 (+http://www.deanishe.net/alfred-workflow)'
+USER_AGENT = (u'Alfred-Workflow/' + __version__ +
+ ' (+http://www.deanishe.net/alfred-workflow)')
# Valid characters for multipart form data boundaries
BOUNDARY_CHARS = string.digits + string.ascii_letters
@@ -178,6 +182,18 @@ def itervalues(self):
yield v['val']
+class Request(urllib2.Request):
+ """Subclass of :class:`urllib2.Request` that supports custom methods."""
+
+ def __init__(self, *args, **kwargs):
+ """Create a new :class:`Request`."""
+ self._method = kwargs.pop('method', None)
+ urllib2.Request.__init__(self, *args, **kwargs)
+
+ def get_method(self):
+ return self._method.upper()
+
+
class Response(object):
"""
Returned by :func:`request` / :func:`get` / :func:`post` functions.
@@ -200,7 +216,7 @@ class Response(object):
def __init__(self, request, stream=False):
"""Call `request` with :mod:`urllib2` and process results.
- :param request: :class:`urllib2.Request` instance
+ :param request: :class:`Request` instance
:param stream: Whether to stream response or retrieve it all at once
:type stream: bool
@@ -512,7 +528,7 @@ def request(method, url, params=None, data=None, headers=None, cookies=None,
socket.setdefaulttimeout(timeout)
# Default handlers
- openers = []
+ openers = [urllib2.ProxyHandler(urllib2.getproxies())]
if not allow_redirects:
openers.append(NoRedirectHandler())
@@ -544,10 +560,6 @@ def request(method, url, params=None, data=None, headers=None, cookies=None,
headers['accept-encoding'] = ', '.join(encodings)
- # Force POST by providing an empty data string
- if method == 'POST' and not data:
- data = ''
-
if files:
if not data:
data = {}
@@ -575,7 +587,7 @@ def request(method, url, params=None, data=None, headers=None, cookies=None,
query = urllib.urlencode(str_dict(params), doseq=True)
url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
- req = urllib2.Request(url, data, headers)
+ req = Request(url, data, headers, method=method)
return Response(req, stream)
@@ -591,6 +603,18 @@ def get(url, params=None, headers=None, cookies=None, auth=None,
stream=stream)
+def delete(url, params=None, data=None, headers=None, cookies=None, auth=None,
+ timeout=60, allow_redirects=True, stream=False):
+ """Initiate a DELETE request. Arguments as for :func:`request`.
+
+ :returns: :class:`Response` instance
+
+ """
+ return request('DELETE', url, params, data, headers=headers,
+ cookies=cookies, auth=auth, timeout=timeout,
+ allow_redirects=allow_redirects, stream=stream)
+
+
def post(url, params=None, data=None, headers=None, cookies=None, files=None,
auth=None, timeout=60, allow_redirects=False, stream=False):
"""Initiate a POST request. Arguments as for :func:`request`.
@@ -602,6 +626,17 @@ def post(url, params=None, data=None, headers=None, cookies=None, files=None,
timeout, allow_redirects, stream)
+def put(url, params=None, data=None, headers=None, cookies=None, files=None,
+ auth=None, timeout=60, allow_redirects=False, stream=False):
+ """Initiate a PUT request. Arguments as for :func:`request`.
+
+ :returns: :class:`Response` instance
+
+ """
+ return request('PUT', url, params, data, headers, cookies, files, auth,
+ timeout, allow_redirects, stream)
+
+
def encode_multipart_formdata(fields, files):
"""Encode form data (``fields``) and ``files`` for POST request.
diff --git a/src/workflow/workflow.py b/src/workflow/workflow.py
index 2a057b0..3935227 100644
--- a/src/workflow/workflow.py
+++ b/src/workflow/workflow.py
@@ -2639,28 +2639,27 @@ def reset(self):
def open_log(self):
"""Open :attr:`logfile` in default app (usually Console.app)."""
- subprocess.call(['open', self.logfile])
+ subprocess.call(['open', self.logfile]) # nosec
def open_cachedir(self):
"""Open the workflow's :attr:`cachedir` in Finder."""
- subprocess.call(['open', self.cachedir])
+ subprocess.call(['open', self.cachedir]) # nosec
def open_datadir(self):
"""Open the workflow's :attr:`datadir` in Finder."""
- subprocess.call(['open', self.datadir])
+ subprocess.call(['open', self.datadir]) # nosec
def open_workflowdir(self):
"""Open the workflow's :attr:`workflowdir` in Finder."""
- subprocess.call(['open', self.workflowdir])
+ subprocess.call(['open', self.workflowdir]) # nosec
def open_terminal(self):
"""Open a Terminal window at workflow's :attr:`workflowdir`."""
- subprocess.call(['open', '-a', 'Terminal',
- self.workflowdir])
+ subprocess.call(['open', '-a', 'Terminal', self.workflowdir]) # nosec
def open_help(self):
"""Open :attr:`help_url` in default browser."""
- subprocess.call(['open', self.help_url])
+ subprocess.call(['open', self.help_url]) # nosec
return 'Opening workflow help URL in browser'
diff --git a/src/workflow/workflow3.py b/src/workflow/workflow3.py
index b92c4be..23a7aae 100644
--- a/src/workflow/workflow3.py
+++ b/src/workflow/workflow3.py
@@ -50,12 +50,16 @@ class Variables(dict):
information.
Args:
- arg (unicode, optional): Main output/``{query}``.
+ arg (unicode or list, optional): Main output/``{query}``.
**variables: Workflow variables to set.
+ In Alfred 4.1+ and Alfred-Workflow 1.40+, ``arg`` may also be a
+ :class:`list` or :class:`tuple`.
Attributes:
- arg (unicode): Output value (``{query}``).
+ arg (unicode or list): Output value (``{query}``).
+ In Alfred 4.1+ and Alfred-Workflow 1.40+, ``arg`` may also be a
+ :class:`list` or :class:`tuple`.
config (dict): Configuration for downstream workflow element.
"""
@@ -68,7 +72,7 @@ def __init__(self, arg=None, **variables):
@property
def obj(self):
- """Return ``alfredworkflow`` `dict`."""
+ """``alfredworkflow`` :class:`dict`."""
o = {}
if self:
d2 = {}
@@ -92,10 +96,10 @@ def __unicode__(self):
"""
if not self and not self.config:
- if self.arg:
- return self.arg
- else:
+ if not self.arg:
return u''
+ if isinstance(self.arg, unicode):
+ return self.arg
return json.dumps(self.obj)
@@ -328,6 +332,9 @@ def add_modifier(self, key, subtitle=None, arg=None, valid=None, icon=None,
:meth:`Workflow.add_item() `
for valid values.
+ In Alfred 4.1+ and Alfred-Workflow 1.40+, ``arg`` may also be a
+ :class:`list` or :class:`tuple`.
+
Returns:
Modifier: Configured :class:`Modifier`.
@@ -568,6 +575,9 @@ def add_item(self, title, subtitle='', arg=None, autocomplete=None,
turned on for your Script Filter, Alfred (version 3.5 and
above) will filter against this field, not ``title``.
+ In Alfred 4.1+ and Alfred-Workflow 1.40+, ``arg`` may also be a
+ :class:`list` or :class:`tuple`.
+
See :meth:`Workflow.add_item() ` for
the main documentation and other parameters.
@@ -717,5 +727,8 @@ def warn_empty(self, title, subtitle=u'', icon=None):
def send_feedback(self):
"""Print stored items to console/Alfred as JSON."""
- json.dump(self.obj, sys.stdout)
+ if self.debugging:
+ json.dump(self.obj, sys.stdout, indent=2, separators=(',', ': '))
+ else:
+ json.dump(self.obj, sys.stdout)
sys.stdout.flush()