├── screenshots ├── .gitkeep └── logo.jpeg ├── .gitignore ├── src ├── icon.png ├── 88F079B1-3500-4916-817F-3E54B2A4ACD1.png ├── LICENSE.txt ├── info.plist ├── workflow.py └── alfred.py ├── Boot2Docker.alfredworkflow ├── CHANGELOG.md ├── bundle.sh ├── LICENSE.txt └── README.md /screenshots/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | .#* 5 | *.pyc 6 | .idea 7 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moul/alfred-workflow-boot2docker/master/src/icon.png -------------------------------------------------------------------------------- /screenshots/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moul/alfred-workflow-boot2docker/master/screenshots/logo.jpeg -------------------------------------------------------------------------------- /Boot2Docker.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moul/alfred-workflow-boot2docker/master/Boot2Docker.alfredworkflow -------------------------------------------------------------------------------- /src/88F079B1-3500-4916-817F-3E54B2A4ACD1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moul/alfred-workflow-boot2docker/master/src/88F079B1-3500-4916-817F-3E54B2A4ACD1.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.0 (2014-03-27) 4 | - Added commands: suspend, restart and init 5 | - Handling more states: suspended, aborted, paused, notexists 6 | 7 | ## 1.1.0 (2014-03-27) 8 | - Initial version 9 | -------------------------------------------------------------------------------- /bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script will create the Alfred workflow file and optionally it will install it. 4 | # To install it, pass the argument "-i" or "--install", e.g. 5 | # bundle.sh --install 6 | 7 | echo "Creating Boot2Docker workflow file..." 8 | 9 | WORKFLOW_FILE=Boot2Docker.alfredworkflow 10 | if [ -f "$WORKFLOW_FILE" ]; then 11 | echo "Removing previous workflow..." 12 | rm "$WORKFLOW_FILE" 13 | fi 14 | 15 | echo "Cleaning it..." 16 | find src \( -name "*~" -or -name ".??*~" -or -name "*.pyc" -or -name "#*#" -or -name ".DS_Store" \) -delete 17 | 18 | echo "Bundling it..." 19 | cd src && zip -r "../$WORKFLOW_FILE" * && cd .. 20 | 21 | while test $# -gt 0 22 | do 23 | case "$1" in 24 | --install | -i) 25 | echo "Installing $WORKFLOW_FILE..." 26 | open "$WORKFLOW_FILE" 27 | ;; 28 | esac 29 | shift 30 | done 31 | 32 | echo "$WORKFLOW_FILE is ready!" 33 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Manfred Touron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Manfred Touron 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Alfred Workflow: Boot2docker 2 | =========================== 3 | 4 | [![authors](https://sourcegraph.com/api/repos/github.com/moul/alfred-workflow-boot2docker/badges/authors.png)](https://sourcegraph.com/github.com/moul/alfred-workflow-boot2docker) 5 | [![library users](https://sourcegraph.com/api/repos/github.com/moul/alfred-workflow-boot2docker/badges/library-users.png)](https://sourcegraph.com/github.com/moul/alfred-workflow-boot2docker) 6 | [![Total views](https://sourcegraph.com/api/repos/github.com/moul/alfred-workflow-boot2docker/counters/views.png)](https://sourcegraph.com/github.com/moul/alfred-workflow-boot2docker) 7 | [![Views in the last 24 hours](https://sourcegraph.com/api/repos/github.com/moul/alfred-workflow-boot2docker/counters/views-24h.png)](https://sourcegraph.com/github.com/moul/alfred-workflow-boot2docker) 8 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/moul/alfred-workflow-boot2docker/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 9 | 10 | Boot2Docker Workflow for Alfred2 11 | 12 | Control [Boot2docker](https://github.com/boot2docker/boot2docker) from Alfred. 13 | 14 | Installation 15 | ------------ 16 | 17 | [Download](https://github.com/moul/alfred-workflow-boot2docker/raw/master/Boot2Docker.alfredworkflow) and import to Alfred 18 | 19 | Commands 20 | -------- 21 | 22 | - `boot2docker start`: Start instance 23 | - `boot2docker stop`: Stop instance 24 | - `boot2docker restart`: Restart instance 25 | - `boot2docker suspend`: Suspend instance 26 | - `boot2docker init`: Initialize boot2docker 27 | 28 | Dependencies 29 | ------------ 30 | 31 | - Alfred 2 with PowerPack 32 | - Python >= 2.7 33 | - [boot2docker](https://github.com/boot2docker/boot2docker) 34 | 35 | Links 36 | ----- 37 | 38 | - [Packal: Boot2docker](http://www.packal.org/workflow/boot2docker) 39 | - [Source Code](https://github.com/moul/alfred-workflow-boot2docker/) 40 | - [Official Forum Post](http://www.alfredforum.com/topic/4159-boot2docker-control-a-boot2docker-instance/?p=24951) 41 | 42 | License 43 | ------- 44 | 45 | MIT 46 | -------------------------------------------------------------------------------- /src/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.alfredapp.moul.boot2docker 7 | connections 8 | 9 | 16396DE4-CCD7-46D1-8C7B-3980F61C17CA 10 | 11 | 12 | destinationuid 13 | A34D9C60-6317-49B2-908A-C703339160E6 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | 19 | 20 | 88F079B1-3500-4916-817F-3E54B2A4ACD1 21 | 22 | 23 | destinationuid 24 | 16396DE4-CCD7-46D1-8C7B-3980F61C17CA 25 | modifiers 26 | 0 27 | modifiersubtext 28 | 29 | 30 | 31 | destinationuid 32 | F04C6FD5-FF36-4080-AC00-BA8B1F71AC08 33 | modifiers 34 | 0 35 | modifiersubtext 36 | 37 | 38 | 39 | F04C6FD5-FF36-4080-AC00-BA8B1F71AC08 40 | 41 | 42 | destinationuid 43 | A34D9C60-6317-49B2-908A-C703339160E6 44 | modifiers 45 | 0 46 | modifiersubtext 47 | 48 | 49 | 50 | 51 | createdby 52 | Manfred Touron 53 | description 54 | Boot2docker Workflow 55 | disabled 56 | 57 | name 58 | Boot2docker 59 | objects 60 | 61 | 62 | config 63 | 64 | escaping 65 | 127 66 | script 67 | python workflow.py command_notify "{query}" 68 | type 69 | 0 70 | 71 | type 72 | alfred.workflow.action.script 73 | uid 74 | 16396DE4-CCD7-46D1-8C7B-3980F61C17CA 75 | version 76 | 0 77 | 78 | 79 | config 80 | 81 | lastpathcomponent 82 | 83 | onlyshowifquerypopulated 84 | 85 | output 86 | 0 87 | removeextension 88 | 89 | sticky 90 | 91 | text 92 | {query} 93 | title 94 | Boot2Docker 95 | 96 | type 97 | alfred.workflow.output.notification 98 | uid 99 | A34D9C60-6317-49B2-908A-C703339160E6 100 | version 101 | 0 102 | 103 | 104 | config 105 | 106 | argumenttype 107 | 1 108 | escaping 109 | 127 110 | keyword 111 | boot2docker 112 | runningsubtext 113 | Please Wait 114 | script 115 | python workflow.py command_autocomplete "{query}" 116 | subtext 117 | Control Boot2Docker 118 | title 119 | boot2docker 120 | type 121 | 0 122 | withspace 123 | 124 | 125 | type 126 | alfred.workflow.input.scriptfilter 127 | uid 128 | 88F079B1-3500-4916-817F-3E54B2A4ACD1 129 | version 130 | 0 131 | 132 | 133 | config 134 | 135 | escaping 136 | 127 137 | script 138 | python workflow.py command_run "{query}" 139 | type 140 | 0 141 | 142 | type 143 | alfred.workflow.action.script 144 | uid 145 | F04C6FD5-FF36-4080-AC00-BA8B1F71AC08 146 | version 147 | 0 148 | 149 | 150 | readme 151 | 152 | uidata 153 | 154 | 16396DE4-CCD7-46D1-8C7B-3980F61C17CA 155 | 156 | ypos 157 | 10 158 | 159 | 88F079B1-3500-4916-817F-3E54B2A4ACD1 160 | 161 | ypos 162 | 60 163 | 164 | A34D9C60-6317-49B2-908A-C703339160E6 165 | 166 | ypos 167 | 60 168 | 169 | F04C6FD5-FF36-4080-AC00-BA8B1F71AC08 170 | 171 | ypos 172 | 130 173 | 174 | 175 | webaddress 176 | https://github.com/moul/alfred-workflow-boot2docker 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/workflow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import ConfigParser 5 | import time 6 | import subprocess 7 | 8 | import alfred 9 | 10 | 11 | def b2d_exec(command, args=None, binary_path='/usr/local/bin/boot2docker'): 12 | if not args: 13 | args = [] 14 | cmdline = ' '.join([binary_path, command] + args) 15 | ret = subprocess.check_output('{}; exit 0'.format(cmdline), shell=True) 16 | return ret 17 | 18 | 19 | class Boot2dockerWorkflow(alfred.AlfredWorkflow): 20 | _reserved_words = [] 21 | 22 | def __init__(self, max_results=20): 23 | self.max_results = max_results 24 | 25 | def command_autocomplete_iter(self, query): 26 | status = self.status 27 | if status == 'unknown': 28 | yield self.item(title='Boot2Docker is in an unknown state', 29 | description='Try to run boot2docker in your ' 30 | 'terminal to investigate', ignore=True) 31 | elif status == 'notexists': 32 | yield self.item(title='init', description='Initialize Boot2Docker', 33 | autocomplete=True, arg='init', match=query) 34 | elif status == 'running': 35 | yield self.item(title='stop', description='Stop Boot2Docker', 36 | autocomplete=True, arg='stop', match=query) 37 | yield self.item(title='suspend', description='Suspend Boot2Docker', 38 | autocomplete=True, arg='suspend', match=query) 39 | yield self.item(title='restart', description='Restart Boot2Docker', 40 | autocomplete=True, arg='restart', match=query) 41 | elif status in ('stopped', 'aborted', 'paused', 'suspended'): 42 | yield self.item(title='start', description='Start Boot2Docker', 43 | autocomplete=True, arg='start', match=query) 44 | 45 | def do_command_autocomplete(self, query): 46 | self.write_items(self.command_autocomplete_iter(query)) 47 | 48 | def do_command_notify(self, query=None): 49 | if query == 'start': 50 | self.write_text('Starting...\nyou will get notified when done.') 51 | elif query == 'stop': 52 | self.write_text('Stopping...\nyou will get notified when done.') 53 | elif query == 'suspend': 54 | self.write_text('Suspending...\nyou will get notified when done.') 55 | elif query == 'restart': 56 | self.write_text('Restarting...\nyou will get notified when done.') 57 | elif query == 'init': 58 | self.write_text('Initializing...\nyou will get notified when done.') 59 | 60 | def do_command_run(self, query=None): 61 | command = query 62 | return self.route_action(command, '') 63 | 64 | def do_start(self, query=None): 65 | if self.status == 'running': 66 | return 67 | ret = b2d_exec('start') 68 | if ret.find('Started.') > -1: 69 | self.write_text('Boot2Docker is started.') 70 | else: 71 | self.write_text('An error has occured when starting.\n' 72 | 'please run boot2docker from command line') 73 | 74 | def do_restart(self, query=None): 75 | if self.status == 'restart': 76 | return 77 | ret = b2d_exec('restart') 78 | if ret.find('Started.') > -1: 79 | self.write_text('Boot2Docker is started.') 80 | else: 81 | self.write_text('An error has occured when restarting.\n' 82 | 'please run boot2docker from command line') 83 | 84 | def do_stop(self, query=None): 85 | if self.status != 'running': 86 | return 87 | ret = b2d_exec('stop') 88 | if ret.find('Shutting down') > -1: 89 | self.write('Boot2Docker is stopped') 90 | elif ret.find('is not running.') > -1: 91 | self.write('Boot2Docker was not running') 92 | else: 93 | self.write('Error while trying to stop instance') 94 | 95 | def do_suspend(self, query=None): 96 | if self.status != 'running': 97 | return 98 | ret = b2d_exec('suspend') 99 | if ret.find('100%') > -1: 100 | self.write('Boot2Docker is suspended') 101 | 102 | def do_init(self, query=None): 103 | if self.status != 'notexists': 104 | return 105 | ret = b2d_exec('init') 106 | if ret.find('You can now type boot2docker up') > -1: 107 | self.write('Boot2Docker is initialized') 108 | 109 | @property 110 | def status(self): 111 | ret = b2d_exec('status') 112 | if ret.find('running') > -1: 113 | return 'running' 114 | elif ret.find('stopped') > -1: 115 | return 'stopped' 116 | elif ret.find('aborted') > -1: 117 | return 'aborted' 118 | elif ret.find('suspended') > -1: 119 | return 'suspended' 120 | elif ret.find('paused') > -1: 121 | return 'paused' 122 | elif ret.find('does not exist') > -1: 123 | return 'notexists' 124 | else: 125 | return 'unknown' 126 | 127 | def do_status(self, query=None): 128 | status = self.status 129 | if status == 'running': 130 | self.write_text('Boot2Docker is running') 131 | elif status == 'stopped': 132 | self.write_text('Boot2Docker is stopped') 133 | elif status == 'aborted': 134 | self.write_text('Boot2Docker is aborted') 135 | elif status == 'suspended': 136 | self.write_text('Boot2Docker is suspended') 137 | elif status == 'paused': 138 | self.write_text('Boot2Docker is paused') 139 | elif status == 'notexists': 140 | self.write_text('Boot2Docker VM does not exist') 141 | else: 142 | self.write_text('Boot2Docker is in an unknown state') 143 | 144 | 145 | 146 | def main(action, query): 147 | boot2docker = Boot2dockerWorkflow() 148 | boot2docker.route_action(action, query) 149 | 150 | 151 | if __name__ == "__main__": 152 | main(action=alfred.args()[0], query=alfred.args()[1]) 153 | -------------------------------------------------------------------------------- /src/alfred.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import itertools 3 | import os 4 | import plistlib 5 | import unicodedata 6 | import sys 7 | 8 | from xml.etree.ElementTree import Element, SubElement, tostring 9 | 10 | """ 11 | You should run your script via /bin/bash with all escape options ticked. 12 | The command line should be 13 | 14 | python yourscript.py "{query}" arg2 arg3 ... 15 | """ 16 | 17 | 18 | UNESCAPE_CHARACTERS = u""" ;()""" 19 | 20 | _MAX_RESULTS_DEFAULT = 9 21 | 22 | preferences = plistlib.readPlist('info.plist') 23 | bundleid = preferences['bundleid'] 24 | 25 | 26 | class Item(object): 27 | @classmethod 28 | def unicode(cls, value): 29 | try: 30 | items = value.iteritems() 31 | except AttributeError: 32 | return unicode(value) 33 | else: 34 | return dict(map(unicode, item) for item in items) 35 | 36 | def __init__(self, attributes, title, subtitle, icon=None): 37 | self.attributes = attributes 38 | self.title = title 39 | self.subtitle = subtitle 40 | self.icon = icon 41 | 42 | def __str__(self): 43 | return tostring(self.xml(), encoding='utf-8') 44 | 45 | def xml(self): 46 | item = Element(u'item', self.unicode(self.attributes)) 47 | for attribute in (u'title', u'subtitle', u'icon'): 48 | value = getattr(self, attribute) 49 | if value is None: 50 | continue 51 | try: 52 | (value, attributes) = value 53 | except: 54 | attributes = {} 55 | elem = SubElement(item, attribute, self.unicode(attributes)) 56 | elem.text = unicode(value) 57 | return item 58 | 59 | 60 | def args(characters=None): 61 | return tuple(unescape(decode(arg), characters) for arg in sys.argv[1:]) 62 | 63 | 64 | def config(): 65 | return _create('config') 66 | 67 | 68 | def decode(s): 69 | return unicodedata.normalize('NFC', s.decode('utf-8')) 70 | 71 | 72 | def get_uid(uid): 73 | return u'-'.join(map(unicode, (bundleid, uid))) 74 | 75 | 76 | def unescape(query, characters=None): 77 | if not characters: 78 | characters = UNESCAPE_CHARACTERS 79 | for character in characters: 80 | query = query.replace('\\%s' % character, character) 81 | return query 82 | 83 | 84 | def write(text): 85 | sys.stdout.write(text) 86 | 87 | 88 | def xml(items, maxresults=_MAX_RESULTS_DEFAULT): 89 | root = Element('items') 90 | for item in itertools.islice(items, maxresults): 91 | root.append(item.xml()) 92 | return tostring(root, encoding='utf-8') 93 | 94 | 95 | def _create(path): 96 | if not os.path.isdir(path): 97 | os.mkdir(path) 98 | if not os.access(path, os.W_OK): 99 | raise IOError('No write access: %s' % path) 100 | return path 101 | 102 | 103 | def work(volatile): 104 | path = { 105 | True: '~/Library/Caches/com.runningwithcrayons.Alfred-2/Workflow Data', 106 | False: '~/Library/Application Support/Alfred 2/Workflow Data' 107 | }[bool(volatile)] 108 | return _create(os.path.join(os.path.expanduser(path), bundleid)) 109 | 110 | 111 | def config_set(key, value, volatile=True): 112 | filepath = os.path.join(work(volatile), 'config.plist') 113 | try: 114 | conf = plistlib.readPlist(filepath) 115 | except IOError: 116 | conf = {} 117 | conf[key] = value 118 | plistlib.writePlist(conf, filepath) 119 | 120 | 121 | def config_get(key, default=None, volatile=True): 122 | filepath = os.path.join(work(volatile), 'config.plist') 123 | try: 124 | conf = plistlib.readPlist(filepath) 125 | except IOError: 126 | conf = {} 127 | if key in conf: 128 | return conf[key] 129 | return default 130 | 131 | 132 | class AlfredWorkflow(object): 133 | _reserved_words = [] 134 | 135 | def write_text(self, text): 136 | print(text) 137 | 138 | def write_item(self, item): 139 | return self.write_items([item]) 140 | 141 | def write_items(self, items, delete_none=True): 142 | if delete_none: 143 | items = filter(None, items) 144 | return write(xml(items, maxresults=self.max_results)) 145 | 146 | def message_item(self, title, message, icon=None, uid=0): 147 | return Item({u'uid': get_uid(uid), u'arg': '', 148 | u'ignore': 'yes'}, title, message, icon) 149 | 150 | def warning_item(self, title, message, uid=0): 151 | return self.message_item(title=title, message=message, uid=uid, 152 | icon='warning.png') 153 | 154 | def error_item(self, title, message, uid=0): 155 | return self.message_item(title=title, message=message, uid=uid, 156 | icon='error.png') 157 | 158 | def exception_item(self, title, exception, uid=0): 159 | message = str(exception).replace('\n', ' ') 160 | return self.error_item(title=title, message=message, uid=uid) 161 | 162 | def route_action(self, action, query=None): 163 | method_name = 'do_{}'.format(action) 164 | if not hasattr(self, method_name): 165 | raise RuntimeError('Unknown action {}'.format(action)) 166 | 167 | method = getattr(self, method_name) 168 | return method(query) 169 | 170 | def is_command(self, query): 171 | try: 172 | command, rest = query.split(' ', 1) 173 | except ValueError: 174 | command = query 175 | command = command.strip() 176 | return command in self._reserved_words or \ 177 | hasattr(self, 'do_{}'.format(command)) 178 | 179 | def item(self, title, description=None, arg=None, ignore=False, icon=None, 180 | uid=None, autocomplete=True, match=None): 181 | if match and arg and len(match.strip()) \ 182 | and not match.lower() in str(arg).lower(): 183 | return None 184 | options = {} 185 | if not uid: 186 | uid = 0 187 | options[u'uid'] = get_uid(uid) 188 | if arg: 189 | options[u'arg'] = arg 190 | if autocomplete: 191 | options[u'autocomplete'] = arg 192 | if ignore: 193 | options[u'ignore'] = 'yes' 194 | return Item(options, title, description, icon) 195 | --------------------------------------------------------------------------------