diff --git a/Coon_v1.2.0.alfredworkflow b/Conic_v1.3.0.alfredworkflow
similarity index 94%
rename from Coon_v1.2.0.alfredworkflow
rename to Conic_v1.3.0.alfredworkflow
index 09528fa..1beb16e 100644
Binary files a/Coon_v1.2.0.alfredworkflow and b/Conic_v1.3.0.alfredworkflow differ
diff --git a/README.md b/README.md
index 92bee29..d0d51b8 100644
--- a/README.md
+++ b/README.md
@@ -2,21 +2,15 @@
-# Coon
+# Coinc
-Coon is an Alfred workflow which do currency conversion by using live currency rates from Open Exchange Rates API.
+Coinc is an Alfred workflow which do currency conversion by using live currency rates from Open Exchange Rates API.
For list of support currencies, Installation, Setup and Usage, see [Wiki](../../wiki)
-# Name Change
+## Naming
-I name this workflow with an online business name generator, with no background acknowledge or concept of the naming.
-
-My thoughts are simple, "con-" for "conversion", a mistype of "coin", and a same pronunciation "cone🍦" with a food I'm in favor.
-
-However, after the terrible, horrifing tragedy happened recently, this name is abandoned, and will be updated recently.
-
-My sincerly apologize for any unconfort that might arouse. #BlackLivesMattters
+This project has been renamed since v1.3.0. Eveything should be patched during the auto update, but if any problem happened, see this [documentation](../../wiki/Naming-&-v1.3.0-Manual-Update-Guide) for more info.
## Credit & License
diff --git a/src/currency/__init__.py b/src/coinc/__init__.py
similarity index 97%
rename from src/currency/__init__.py
rename to src/coinc/__init__.py
index f63adb6..a545fbb 100644
--- a/src/currency/__init__.py
+++ b/src/coinc/__init__.py
@@ -2,7 +2,7 @@
"""Functions to be called by workflow"""
import os
from datetime import datetime
-from .exceptions import CoonError, ConfigError
+from .exceptions import CoincError, ConfigError
from .query import Query
from .utils import (init_workflow, load_currencies, refresh_rates,
refresh_currencies, generate_list_items)
@@ -57,8 +57,8 @@ def convert(workflow):
init_workflow(workflow)
query = Query(workflow.args[1:])
query.run_pattern(workflow)
- except CoonError as error:
- workflow.logger.info("Coon: {}".format(type(error).__name__))
+ except CoincError as error:
+ workflow.logger.info("Coinc: {}".format(type(error).__name__))
workflow.logger.info(error)
workflow.add_item(title=error.args[0],
subtitle=error.args[1],
@@ -158,10 +158,10 @@ def refresh(workflow):
try:
refresh_rates(workflow.config)
refresh_currencies()
- except CoonError as error:
+ except CoincError as error:
workflow.logger.info(error)
print("{},{}".format("❌Error occured during refresh",
- "Coon: {}".format(type(error).__name__)))
+ "Coinc: {}".format(type(error).__name__)))
except Exception as error:
workflow.logger.info(error)
print("{},{}".format("❌Error occured during refresh",
diff --git a/src/currency/config.py b/src/coinc/config.py
similarity index 100%
rename from src/currency/config.py
rename to src/coinc/config.py
diff --git a/src/currency/exceptions.py b/src/coinc/exceptions.py
similarity index 62%
rename from src/currency/exceptions.py
rename to src/coinc/exceptions.py
index 60fe372..f724c97 100644
--- a/src/currency/exceptions.py
+++ b/src/coinc/exceptions.py
@@ -2,8 +2,8 @@
"""Exceptions used in this module"""
-class CoonError(Exception):
- """Base Class used to declare other errors for Coon
+class CoincError(Exception):
+ """Base Class used to declare other errors for Coinc
Extends:
Exception
@@ -11,46 +11,46 @@ class CoonError(Exception):
pass
-class ConfigError(CoonError):
+class ConfigError(CoincError):
"""Raised when there are invalid value filled in Configuration Sheet
Extends:
- CoonError
+ CoincError
"""
pass
-class QueryError(CoonError):
+class QueryError(CoincError):
"""Raised when invalid query were given
Extends:
- CoonError
+ CoincError
"""
pass
-class AppIDError(CoonError):
+class AppIDError(CoincError):
"""Raised when App ID can not be used
Extends:
- CoonError
+ CoincError
"""
pass
-class ApiError(CoonError):
+class ApiError(CoincError):
"""Raised when API is unreachable or return bad response
Extends:
- CoonError
+ CoincError
"""
pass
-class UnknownPythonError(CoonError):
+class UnknownPythonError(CoincError):
"""Raised when Python runtime version can not be correctly detacted
Extends:
- CoonError
+ CoincError
"""
pass
diff --git a/src/currency/query.py b/src/coinc/query.py
similarity index 100%
rename from src/currency/query.py
rename to src/coinc/query.py
diff --git a/src/currency/utils.py b/src/coinc/utils.py
similarity index 80%
rename from src/currency/utils.py
rename to src/coinc/utils.py
index cd31f16..7fe59a0 100644
--- a/src/currency/utils.py
+++ b/src/coinc/utils.py
@@ -8,10 +8,61 @@
import unicodedata
from .exceptions import ApiError, AppIDError, UnknownPythonError
-RATE_ENDPOINT = ("https://openexchangerates.org/api/latest.json"
- "?show_alternative=1&app_id={}")
-CURRENCY_ENDPOINT = ("https://openexchangerates.org/api/currencies.json"
- "?show_alternative=1")
+INFO_PLIST_PATH = "info.plist"
+OLD_BUNDLE_ID = "tech.tomy.coon"
+NEW_BUNDLE_ID = "tech.tomy.coinc"
+WORKFLOW_DATA_PATH = "~/Library/Application Support/Alfred/Workflow Data"
+RATE_ENDPOINT = (
+ "https://openexchangerates.org/api/latest.json" "?show_alternative=1&app_id={}"
+)
+CURRENCY_ENDPOINT = (
+ "https://openexchangerates.org/api/currencies.json" "?show_alternative=1"
+)
+
+
+def manual_update_patch(workflow):
+ """manual update metadatas for v1.3.0 name change
+
+ Update include two section, change Bundle ID in info.plist to a new one,
+ and rename the old data directory into new one
+
+ Arguments:
+ workflow {workflow.Workflow3} -- The workflow object
+
+ Returns:
+ bool -- Whether any modification got invoked
+
+ Raises:
+ UnknownPythonError -- Raised when Python runtime version can not be
+ correctly detacted
+ """
+ updated = False
+ # Fix Bundle ID
+ if workflow.bundleid.encode("utf-8") == OLD_BUNDLE_ID:
+ import plistlib
+
+ if sys.version_info.major == 2:
+ info = plistlib.readPlist(INFO_PLIST_PATH)
+ info["bundleid"] = NEW_BUNDLE_ID
+ plistlib.writePlist(info, INFO_PLIST_PATH)
+ elif sys.version_info.major == 3:
+ with open(INFO_PLIST_PATH, "rw") as file:
+ info = plistlib.load(file)
+ info["bundleid"] = NEW_BUNDLE_ID
+ plistlib.dump(info, INFO_PLIST_PATH)
+ else:
+ raise UnknownPythonError("Unexpected Python Version", sys.version_info)
+ workflow.logger.info("Bundle ID modified")
+ updated = True
+
+ # Move Data Directory
+ old_path = os.path.expanduser(os.path.join(WORKFLOW_DATA_PATH, OLD_BUNDLE_ID))
+ if os.path.exists(old_path):
+ new_path = os.path.expanduser(os.path.join(WORKFLOW_DATA_PATH, NEW_BUNDLE_ID))
+ os.rename(old_path, new_path)
+ workflow.logger.info("Data Directory moved")
+ updated = True
+ return updated
def init_workflow(workflow):
@@ -26,6 +77,7 @@ def init_workflow(workflow):
workflow -- the passed in workflow object
"""
from .config import Config
+
workflow.config = Config()
return workflow
@@ -43,8 +95,7 @@ def _calculate(value, from_currency, to_currency, rates, precision):
Returns:
float -- The result of the conversion
"""
- return round(value * (rates[to_currency] / rates[from_currency]),
- precision)
+ return round(value * (rates[to_currency] / rates[from_currency]), precision)
def _byteify(loaded_dict):
@@ -58,8 +109,7 @@ def _byteify(loaded_dict):
"""
if isinstance(loaded_dict, dict):
return {
- _byteify(key): _byteify(value)
- for key, value in loaded_dict.iteritems()
+ _byteify(key): _byteify(value) for key, value in loaded_dict.iteritems()
}
if isinstance(loaded_dict, list):
return [_byteify(element) for element in loaded_dict]
@@ -152,8 +202,9 @@ def is_it_something_mixed(query):
return (value, currency)
# Type 3: {symbol}{number}
- match_result = re.match(r"^(.+?)([0-9,]+(\.\d+)?)$",
- query) # Use '+?' for non-progressive match
+ match_result = re.match(
+ r"^(.+?)([0-9,]+(\.\d+)?)$", query
+ ) # Use '+?' for non-progressive match
if match_result:
value = is_it_float(match_result.groups()[1])
currency_symbol = is_it_symbol(match_result.groups()[0])
@@ -189,8 +240,7 @@ def load_currencies(path="currencies.json"):
elif sys.version_info.major == 3:
currencies = json.load(file)
else:
- raise UnknownPythonError("Unexpected Python Version",
- sys.version_info)
+ raise UnknownPythonError("Unexpected Python Version", sys.version_info)
return currencies
@@ -211,6 +261,7 @@ def refresh_currencies(path="currencies.json"):
"""
if sys.version_info.major == 2:
import urllib2
+
try:
response = urllib2.urlopen(CURRENCY_ENDPOINT)
except urllib2.HTTPError as err:
@@ -219,6 +270,7 @@ def refresh_currencies(path="currencies.json"):
currencies = _byteify(json.load(response, "utf-8"))
elif sys.version_info.major == 3:
from urllib import request, error
+
try:
response = request.urlopen(CURRENCY_ENDPOINT)
except error.HTTPError as err:
@@ -277,13 +329,15 @@ def refresh_rates(config, path="rates.json"):
"""
if sys.version_info.major == 2:
import urllib2
+
try:
response = urllib2.urlopen(RATE_ENDPOINT.format(config.app_id))
except urllib2.HTTPError as err:
response = _byteify(json.load(err, "utf-8"))
if err.code == 401:
- raise AppIDError("Invalid App ID: {}".format(config.app_id),
- response["description"])
+ raise AppIDError(
+ "Invalid App ID: {}".format(config.app_id), response["description"]
+ )
elif err.code == 429:
raise AppIDError("Access Restricted", response["description"])
else:
@@ -291,13 +345,15 @@ def refresh_rates(config, path="rates.json"):
rates = _byteify(json.load(response, "utf-8"))
elif sys.version_info.major == 3:
from urllib import request, error
+
try:
response = request.urlopen(RATE_ENDPOINT.format(config.app_id))
except error.HTTPError as err:
response = json.load(err)
if err.code == 401:
- raise AppIDError("Invalid App ID: {}".format(config.app_id),
- response["description"])
+ raise AppIDError(
+ "Invalid App ID: {}".format(config.app_id), response["description"]
+ )
elif err.code == 429:
raise AppIDError("Access Restricted", response["description"])
else:
@@ -332,8 +388,7 @@ def load_alias(path="alias.json"):
elif sys.version_info.major == 3:
alias = json.load(file)
else:
- raise UnknownPythonError("Unexpected Python Version",
- sys.version_info)
+ raise UnknownPythonError("Unexpected Python Version", sys.version_info)
return alias
@@ -359,13 +414,11 @@ def load_symbols(path="symbols.json"):
elif sys.version_info.major == 3:
symbols = json.load(file)
else:
- raise UnknownPythonError("Unexpected Python Version",
- sys.version_info)
+ raise UnknownPythonError("Unexpected Python Version", sys.version_info)
return symbols
-def generate_result_item(workflow, value, from_currency, to_currency, rates,
- icon):
+def generate_result_item(workflow, value, from_currency, to_currency, rates, icon):
"""Calculate conversion result and append item to workflow
Arguments:
@@ -381,29 +434,28 @@ def generate_result_item(workflow, value, from_currency, to_currency, rates,
"""
symbols = load_symbols()
result = str(
- _calculate(value, from_currency, to_currency, rates,
- workflow.config.precision))
+ _calculate(value, from_currency, to_currency, rates, workflow.config.precision)
+ )
result_symboled = "{}{}".format(symbols[to_currency], result)
- item = workflow.add_item(title="{} {} = {} {}".format(
- value, from_currency, result, to_currency),
- subtitle="Copy '{}' to clipboard".format(result),
- icon="flags/{}.png".format(icon),
- valid=True,
- arg=result,
- copytext=result)
+ item = workflow.add_item(
+ title="{} {} = {} {}".format(value, from_currency, result, to_currency),
+ subtitle="Copy '{}' to clipboard".format(result),
+ icon="flags/{}.png".format(icon),
+ valid=True,
+ arg=result,
+ copytext=result,
+ )
item.add_modifier(
key="alt",
subtitle="Copy '{}' to clipboard".format(result_symboled),
icon="flags/{}.png".format(icon),
valid=True,
- arg=result_symboled)
+ arg=result_symboled,
+ )
return item
-def generate_list_items(query,
- currency_codes,
- favorite_filter=None,
- sort=False):
+def generate_list_items(query, currency_codes, favorite_filter=None, sort=False):
"""Generate items from currency codes that can be add to workflow
Arguments:
@@ -424,13 +476,15 @@ def generate_list_items(query,
items = []
for code in currency_codes:
if currencies_filter(query, code, currencies[code], favorite_filter):
- items.append({
- "title": currencies[code],
- "subtitle": code,
- "icon": "flags/{}.png".format(code),
- "valid": True,
- "arg": code
- })
+ items.append(
+ {
+ "title": currencies[code],
+ "subtitle": code,
+ "icon": "flags/{}.png".format(code),
+ "valid": True,
+ "arg": code,
+ }
+ )
if sort:
items = sorted(items, key=lambda item: item["subtitle"])
return items
diff --git a/src/currency/workflow/.alfredversionchecked b/src/currency/workflow/.alfredversionchecked
deleted file mode 100644
index e69de29..0000000
diff --git a/src/currency/workflow/version b/src/currency/workflow/version
deleted file mode 100644
index 673b6a6..0000000
--- a/src/currency/workflow/version
+++ /dev/null
@@ -1 +0,0 @@
-1.37.2
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index 22334fa..3a050d7 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,25 +1,29 @@
# -*- coding: utf-8 -*-
"""Script for Default keyword"""
import sys
-import currency
-from currency.workflow import Workflow3
+import coinc
+from workflow import Workflow3
+from workflow.util import reload_workflow
def main(workflow):
"""The main workflow entry function"""
method = str(workflow.args.pop(0))
- if method in currency.__all__:
- workflow.run(getattr(currency, method))
+ if coinc.utils.manual_update_patch(workflow):
+ reload_workflow()
+ workflow.logger.info("Workflow Reloaded")
+ if method in coinc.__all__:
+ workflow.run(getattr(coinc, method))
else:
- workflow.run(currency.help_me)
+ workflow.run(coinc.help_me)
if __name__ == "__main__":
WF = Workflow3(
default_settings={"favorites": ["EUR", "CNY", "JPY", "GBP"]},
update_settings={
- "github_slug": "tomy0000000/coon",
+ "github_slug": "tomy0000000/Coinc",
"frequency": 7
},
- help_url="https://git.io/fjD6M")
+ help_url="https://git.io/JfjXg")
sys.exit(WF.run(main))
diff --git a/src/workflow/LICENCE.txt b/src/workflow/LICENCE.txt
new file mode 100644
index 0000000..f55c710
--- /dev/null
+++ b/src/workflow/LICENCE.txt
@@ -0,0 +1,36 @@
+All Python source code is under the MIT Licence.
+
+The documentation, in particular the tutorials, are under the
+Creative Commons Attribution-NonCommercial (CC BY-NC) licence.
+
+---------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Dean Jackson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+---------------------------------------------------------------------
+
+Creative Commons Attribution-NonCommercial (CC BY-NC) licence
+
+https://creativecommons.org/licenses/by-nc/4.0/legalcode
+
+(This one's quite long.)
diff --git a/src/currency/workflow/Notify.tgz b/src/workflow/Notify.tgz
similarity index 100%
rename from src/currency/workflow/Notify.tgz
rename to src/workflow/Notify.tgz
diff --git a/src/currency/workflow/__init__.py b/src/workflow/__init__.py
similarity index 100%
rename from src/currency/workflow/__init__.py
rename to src/workflow/__init__.py
diff --git a/src/currency/workflow/background.py b/src/workflow/background.py
similarity index 98%
rename from src/currency/workflow/background.py
rename to src/workflow/background.py
index ba5c52a..c2bd735 100644
--- a/src/currency/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/currency/workflow/notify.py b/src/workflow/notify.py
similarity index 94%
rename from src/currency/workflow/notify.py
rename to src/workflow/notify.py
index a4b7f40..28ec0b9 100644
--- a/src/currency/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/currency/workflow/update.py b/src/workflow/update.py
similarity index 99%
rename from src/currency/workflow/update.py
rename to src/workflow/update.py
index 6affc94..c039f7a 100644
--- a/src/currency/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/currency/workflow/util.py b/src/workflow/util.py
similarity index 80%
rename from src/currency/workflow/util.py
rename to src/workflow/util.py
index 27209d8..ab5e954 100644
--- a/src/currency/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
new file mode 100644
index 0000000..9c235b4
--- /dev/null
+++ b/src/workflow/version
@@ -0,0 +1 @@
+1.39.0
\ No newline at end of file
diff --git a/src/currency/workflow/web.py b/src/workflow/web.py
similarity index 92%
rename from src/currency/workflow/web.py
rename to src/workflow/web.py
index 0781911..84a7062 100644
--- a/src/currency/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
@@ -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/currency/workflow/workflow.py b/src/workflow/workflow.py
similarity index 99%
rename from src/currency/workflow/workflow.py
rename to src/workflow/workflow.py
index 2a057b0..3935227 100644
--- a/src/currency/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/currency/workflow/workflow3.py b/src/workflow/workflow3.py
similarity index 99%
rename from src/currency/workflow/workflow3.py
rename to src/workflow/workflow3.py
index b92c4be..22dc5c6 100644
--- a/src/currency/workflow/workflow3.py
+++ b/src/workflow/workflow3.py
@@ -717,5 +717,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()