-
-
Save deanishe/b16f018119ef3fe951af to your computer and use it in GitHub Desktop.
#!/usr/bin/python | |
# encoding: utf-8 | |
# | |
# Copyright (c) 2013 [email protected]. | |
# | |
# MIT Licence. See http://opensource.org/licenses/MIT | |
# | |
# Created on 2013-11-01 | |
# | |
"""workflow-build [options] <workflow-dir> | |
Build Alfred Workflows. | |
Compile contents of <workflow-dir> to a ZIP file (with extension | |
`.alfredworkflow`). | |
The name of the output file is generated from the workflow name, | |
which is extracted from the workflow's `info.plist`. If a `version` | |
file is contained within the workflow directory, it's contents | |
will be appended to the compiled workflow's filename. | |
Usage: | |
workflow-build [-v|-q|-d] [-f] [-o <outputdir>] <workflow-dir>... | |
workflow-build (-h|--version) | |
Options: | |
-o, --output=<outputdir> directory to save workflow(s) to | |
default is current working directory | |
-f, --force overwrite existing files | |
-h, --help show this message and exit | |
-V, --version show version number and exit | |
-q, --quiet only show errors and above | |
-v, --verbose show info messages and above | |
-d, --debug show debug messages | |
""" | |
from __future__ import print_function | |
from contextlib import contextmanager | |
from fnmatch import fnmatch | |
import logging | |
import os | |
import plistlib | |
import re | |
import shutil | |
import string | |
from subprocess import check_call, CalledProcessError | |
import sys | |
from tempfile import mkdtemp | |
from unicodedata import normalize | |
from docopt import docopt | |
__version__ = "0.6" | |
__author__ = "Dean Jackson <[email protected]>" | |
DEFAULT_LOG_LEVEL = logging.WARNING | |
# Characters permitted in workflow filenames | |
OK_CHARS = set(string.ascii_letters + string.digits + '-.') | |
EXCLUDE_PATTERNS = [ | |
'.*', | |
'*.pyc', | |
'*.log', | |
'*.acorn', | |
'*.swp', | |
'*.bak', | |
'*.sublime-project', | |
'*.sublime-workflow', | |
'*.git', | |
'*.dist-info', | |
'*.egg-info', | |
'__pycache__', | |
] | |
log = logging.getLogger('[%(levelname)s] %(message)s') | |
logging.basicConfig(format='', level=logging.DEBUG) | |
@contextmanager | |
def chdir(dirpath): | |
"""Context-manager to change working directory.""" | |
startdir = os.path.abspath(os.curdir) | |
os.chdir(dirpath) | |
log.debug('cwd=%s', dirpath) | |
yield | |
os.chdir(startdir) | |
log.debug('cwd=%s', startdir) | |
@contextmanager | |
def tempdir(): | |
"""Context-manager to create and cd to a temporary directory.""" | |
startdir = os.path.abspath(os.curdir) | |
dirpath = mkdtemp() | |
try: | |
yield dirpath | |
finally: | |
shutil.rmtree(dirpath) | |
def safename(name): | |
"""Make name filesystem and web-safe.""" | |
if isinstance(name, str): | |
name = unicode(name, 'utf-8') | |
# remove non-ASCII | |
s = normalize('NFKD', name) | |
b = s.encode('us-ascii', 'ignore') | |
clean = [] | |
for c in b: | |
if c in OK_CHARS: | |
clean.append(c) | |
else: | |
clean.append('-') | |
return re.sub(r'-+', '-', ''.join(clean)).strip('-') | |
def build_workflow(workflow_dir, outputdir, overwrite=False, verbose=False): | |
"""Create an .alfredworkflow file from the contents of `workflow_dir`.""" | |
with tempdir() as dirpath: | |
tmpdir = os.path.join(dirpath, 'workflow') | |
shutil.copytree(workflow_dir, tmpdir, | |
ignore=shutil.ignore_patterns(*EXCLUDE_PATTERNS)) | |
with chdir(tmpdir): | |
# ------------------------------------------------------------ | |
# Read workflow metadata from info.plist | |
info = plistlib.readPlist(u'info.plist') | |
version = info.get('version') | |
name = safename(info['name']) | |
zippath = os.path.join(outputdir, name) | |
if version: | |
zippath = '{}-{}'.format(zippath, version) | |
zippath += '.alfredworkflow' | |
# ------------------------------------------------------------ | |
# Remove unexported vars from info.plist | |
for k in info.get('variablesdontexport', {}): | |
info['variables'][k] = '' | |
plistlib.writePlist(info, 'info.plist') | |
# ------------------------------------------------------------ | |
# Build workflow | |
if os.path.exists(zippath): | |
if overwrite: | |
log.info('overwriting existing workflow') | |
os.unlink(zippath) | |
else: | |
log.error('File "%s" exists. Use -f to overwrite', zippath) | |
return False | |
# build workflow | |
command = ['zip', '-r'] | |
if not verbose: | |
command.append(u'-q') | |
command.extend([zippath, '.']) | |
log.debug('command=%r', command) | |
try: | |
check_call(command) | |
except CalledProcessError as err: | |
log.error('zip exited with %d', err.returncode) | |
return False | |
log.info('wrote %s', zippath) | |
return True | |
def main(args=None): | |
"""Run CLI.""" | |
# ------------------------------------------------------------ | |
# CLI flags | |
args = docopt(__doc__, version=__version__) | |
if args.get('--verbose'): | |
log.setLevel(logging.INFO) | |
elif args.get('--quiet'): | |
log.setLevel(logging.ERROR) | |
elif args.get('--debug'): | |
log.setLevel(logging.DEBUG) | |
else: | |
log.setLevel(DEFAULT_LOG_LEVEL) | |
log.debug('log level=%s', logging.getLevelName(log.level)) | |
log.debug('args=%r', args) | |
# Build options | |
force = args['--force'] | |
outputdir = os.path.abspath(args['--output'] or os.curdir) | |
workflow_dirs = [os.path.abspath(p) for p in args['<workflow-dir>']] | |
verbose = log.level == logging.DEBUG | |
log.debug(u'outputdir=%r, workflow_dirs=%r', outputdir, workflow_dirs) | |
# ------------------------------------------------------------ | |
# Build workflow(s) | |
errors = False | |
for path in workflow_dirs: | |
ok = build_workflow(path, outputdir, force, verbose) | |
if not ok: | |
errors = True | |
if errors: | |
return 1 | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(main(sys.argv[1:])) |
@chrisbro, that looks like the same problem I am having.
My .git
directory is getting matched by both the .*
and the .git
exclude patterns.
This removes 2 entries from the list of directories and causing one of my library directories to be missed.
@deanishe
Just need to add a break after line #209 the del dirnems[i]
and seems to work fine now
https://gist.github.com/duanemay/6663ae0a429bc0f78a0ab16b8065a47b
@deanishe this is a great script! I noticed it was using python 2 functions (unicode
, pslistlib.readPlist
, etc). I'm happy to update it, myself, but I figured I'd ask if you had a py3 version handy before I do that.
Thanks!
Actually, the changes were just a couple of lines:
diff --git a/bin/workflow-build b/bin/workflow-build
index 9c7b24c..dc76468 100755
--- a/bin/workflow-build
+++ b/bin/workflow-build
@@ -37,7 +37,6 @@ Options:
from __future__ import print_function
from contextlib import contextmanager
-from fnmatch import fnmatch
import logging
import os
import plistlib
@@ -104,8 +103,6 @@ def tempdir():
def safename(name):
"""Make name filesystem and web-safe."""
- if isinstance(name, str):
- name = unicode(name, "utf-8")
# remove non-ASCII
s = normalize("NFKD", name)
@@ -113,8 +110,9 @@ def safename(name):
clean = []
for c in b:
- if c in OK_CHARS:
- clean.append(c)
+ char = chr(c)
+ if char in OK_CHARS:
+ clean.append(char)
else:
clean.append("-")
@@ -132,7 +130,8 @@ def build_workflow(workflow_dir, outputdir, overwrite=False, verbose=False):
with chdir(tmpdir):
# ------------------------------------------------------------
# Read workflow metadata from info.plist
- info = plistlib.readPlist(u"info.plist")
+ with open("info.plist", "rb") as fp:
+ info = plistlib.load(fp)
version = info.get("version")
name = safename(info["name"])
zippath = os.path.join(outputdir, name)
@@ -147,7 +146,8 @@ def build_workflow(workflow_dir, outputdir, overwrite=False, verbose=False):
for k in info.get("variablesdontexport", {}):
info["variables"][k] = ""
- plistlib.writePlist(info, "info.plist")
+ with open("info.plist", "wb") as fp:
+ plistlib.dump(info, fp)
# ------------------------------------------------------------
# Build workflow
Updated for Python 3 with code from @xavdid :
https://gist.github.com/muyexi/3601a76d96bdcfa90eb9b8bf3910cd2a
Might be too tired to see what's up right now, but smacking into this which seems close to what you were just editing: