-
-
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:])) |
Might be too tired to see what's up right now, but smacking into this which seems close to what you were just editing:
DEBUG - [.*] .idea
DEBUG - [.*] .git
DEBUG - [*.git] .git
Traceback (most recent call last):
File "./workflow-build.py", line 326, in <module>
sys.exit(main(sys.argv[1:]))
File "./workflow-build.py", line 315, in main
ok = build_workflow(path, outputdir, force, verbose, dry_run)
File "./workflow-build.py", line 241, in build_workflow
wffiles = get_workflow_files('.')
File "./workflow-build.py", line 197, in get_workflow_files
del dirnames[i]
IndexError: list assignment index out of range
@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 workflowUpdated for Python 3 with code from @xavdid :
https://gist.github.com/muyexi/3601a76d96bdcfa90eb9b8bf3910cd2a
I've changed the way excludes are handled. It should work much better now.