diff --git a/.gitignore b/.gitignore
index 3bdfd76..d14f96d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
._*
*.py[cod]
*~
+pipe.alfredworkflow
diff --git a/LICENSE b/LICENSE
index da56212..3ca5e73 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2016 Robin Breathe
+Copyright (c) 2013-2017 Robin Breathe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..491d1d6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+all:
+ zip -j9 --filesync pipe.alfredworkflow *.{json,plist,png,py}
diff --git a/README.md b/README.md
index 0c1c617..f1d82d5 100644
--- a/README.md
+++ b/README.md
@@ -1,53 +1,48 @@
-# pipe workflow by isometry
+# pipe transformation workflow for Alfred
-A workflow for [Alfred](http://www.alfredapp.com/) to transform the currently selected text or the contents of the clipboard by passing it through an arbitrary shell one-liner.
+An [Alfred](http://www.alfredapp.com/) workflow enabling easy transformation of the current contents of the clipboard by piping through arbitrary shell one-liners.
## Requirements
- [Alfred](http://www.alfredapp.com/) (version 2.0+)
- The [Alfred Powerpack](http://www.alfredapp.com/powerpack/).
-- [pipe.alfredworkflow](https://raw.github.com/isometry/alfredworkflows/master/pipe.alfredworkflow)
## Usage
-(Optional) assign hotkeys for the two Hotkey handlers in the workflow. I recommend `Cmd+Shift+|` and `Cmd+Ctrl+\`, respectively.
+Trigger the workflow by hotkey or keyword (default=`|`, override with the `keyword` variable) followed by an arbitrarily simple or complex shell one-liner to transform the contents of the clipboard in-place; optionally use the `Cmd`-modifier to immediately paste the results into the foreground app.
-Two actions are available, both taking an arbitrarily complex shell pipe as their argument:
+### Examples
-1. triggered by the first hotkey or by the `|` or `pipe` keywords, will transform the clipboard in-place by passing its contents through the pipe given as argument.
-2. triggered by the second hotkey, will transform the currently selected text in-place by passing its contents through the pipe given as argument.
+- Transform to UPPERCASE: `| perl -nle 'print uc'` or `| tr a-z A-Z`
+- Base64 encode: `| base64`
+- Base64 decode: `| base64 --decode`
+- Top 10 unique lines with counts: `| sort | uniq -c | sort -rn | head -10`
-A number of built-in pipelines are [included](https://raw.github.com/isometry/alfredworkflows/net.isometry.alfred.pipe/builtins.json), and custom aliases can also be defined with the following syntax:
+### Built-ins
-`| alias NAME=PIPE | LINE@@`
+A number of example pipelines (including those above) are [built-in](https://github.com/isometry/alfred-pipe/raw/master/builtins.json).
-`| alias tac=sed '1!G;h;$!d'@@`
+Built-ins can be disabled en-mass by setting the `load_builtins` variable to any value other than `yes`.
-## Contributions & Thanks
+### Aliases
-- ctwise
+To save repetitive typing, custom aliases can be defined with the following syntax:
+
+`| alias NAME=PIPE | LINE @@@`
+
+The trailing `@@@` (override with the `alias_terminator` variable) terminates the alias definition and causes it to be saved.
-## License
+#### Examples
-(The MIT License)
+- `| alias tac=sed '1!G;h;$!d' @@@`
+- `| alias top10=sort | uniq -c | sort -rn | head -10 @@@`
-Copyright (c) 2013 Robin Breathe
+#### Alias removal
-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:
+Any custom alias can be removed with:
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+`| alias NAME=@@@`
-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.
\ No newline at end of file
+## Contributions & Thanks
+
+- ctwise
diff --git a/alfred.py b/alfred.py
index 6e71f13..fe484a8 100644
--- a/alfred.py
+++ b/alfred.py
@@ -64,8 +64,8 @@ def unescape(query, characters=None):
def work(volatile):
path = {
- True: os.getenv('alfred_workflow_cache'),
- False: os.getenv('alfred_workflow_data')
+ True: os.getenv('alfred_workflow_cache', os.getenv('TMPDIR', '/tmp')),
+ False: os.getenv('alfred_workflow_data', os.getenv('HOME', '/tmp'))
}[bool(volatile)]
return _create(os.path.expanduser(path))
diff --git a/builtins.json b/builtins.json
index 5567007..8a8481e 100644
--- a/builtins.json
+++ b/builtins.json
@@ -1,6 +1,6 @@
{
- "openssl base64 -d": "decode base64",
- "openssl base64 -e": "encode base64",
+ "base64": "encode base64",
+ "base64 --decode": "decode base64",
"openssl enc -d -base64 -aes256 -k X": "AES-256 decrypt with passphrase 'X'",
"openssl enc -e -base64 -aes256 -k X": "AES-256 encrypt with passphrase 'X'",
"openssl x509 -noout -fingerprint": "x509 fingerprint",
diff --git a/info.plist b/info.plist
index 8e09a81..02d78e8 100644
--- a/info.plist
+++ b/info.plist
@@ -4,31 +4,47 @@
bundleid
net.isometry.alfred.pipe
+ category
+ Tools
connections
- 64D17591-D71C-421B-B701-91E99A3C0318
+ 16039760-F173-4AB8-9C73-DA7401D5DE23
destinationuid
- D029EE08-8642-404B-83FE-BB5DFEE9C6B6
+ 1D296119-4F9D-4FDC-9777-1DA7CF655D19
modifiers
0
modifiersubtext
+ vitoclose
+
- 74681B4B-CBE4-403F-BDF7-39F4D2319ABD
+ 1D296119-4F9D-4FDC-9777-1DA7CF655D19
destinationuid
- 16039760-F173-4AB8-9C73-DA7401D5DE23
+ 9B8A5712-D806-47E3-9045-EC31AB0C6485
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
+
+
+ destinationuid
+ CB1F4AD1-CC6A-4CE9-9953-5BA92C4B2634
modifiers
0
modifiersubtext
+ vitoclose
+
- 880D9394-A59A-4BC1-BB46-01461CEFEE80
+ 74681B4B-CBE4-403F-BDF7-39F4D2319ABD
destinationuid
@@ -37,20 +53,34 @@
0
modifiersubtext
+ vitoclose
+
+
+
+ destinationuid
+ D4DF549F-4556-4AA6-BF1A-159154EB3051
+ modifiers
+ 1048576
+ modifiersubtext
+ Paste on completion
+ vitoclose
+
- CCEE8787-6024-46B4-B155-240F86561281
+ 9B8A5712-D806-47E3-9045-EC31AB0C6485
destinationuid
- E8AC122D-DD51-491F-A734-C89446EB1924
+ 8E5E1774-FB5B-47C7-A0E0-AE958708254F
modifiers
0
modifiersubtext
+ vitoclose
+
- D029EE08-8642-404B-83FE-BB5DFEE9C6B6
+ CB1F4AD1-CC6A-4CE9-9953-5BA92C4B2634
destinationuid
@@ -59,6 +89,21 @@
0
modifiersubtext
+ vitoclose
+
+
+
+ D4DF549F-4556-4AA6-BF1A-159154EB3051
+
+
+ destinationuid
+ 16039760-F173-4AB8-9C73-DA7401D5DE23
+ modifiers
+ 0
+ modifiersubtext
+
+ vitoclose
+
D637E280-521E-432F-85C1-3F58E064A66E
@@ -70,6 +115,8 @@
0
modifiersubtext
+ vitoclose
+
@@ -83,6 +130,42 @@
pipe
objects
+
+ config
+
+ autopaste
+
+ clipboardtext
+ {query}
+ transient
+
+
+ type
+ alfred.workflow.output.clipboard
+ uid
+ 8E5E1774-FB5B-47C7-A0E0-AE958708254F
+ version
+ 2
+
+
+ config
+
+ inputstring
+ {var:paste}
+ matchcasesensitive
+
+ matchmode
+ 1
+ matchstring
+ 1
+
+ type
+ alfred.workflow.utility.filter
+ uid
+ 9B8A5712-D806-47E3-9045-EC31AB0C6485
+ version
+ 1
+
config
@@ -90,38 +173,67 @@
0
argument
0
+ focusedappvariable
+
+ focusedappvariablename
+
hotkey
- 42
+ 11
hotmod
- 1179648
+ 1310720
hotstring
- |
+ X
leftcursor
modsmode
0
+ relatedAppsMode
+ 0
type
alfred.workflow.trigger.hotkey
uid
D637E280-521E-432F-85C1-3F58E064A66E
+ version
+ 2
config
+ alfredfiltersresults
+
+ alfredfiltersresultsmatchmode
+ 0
+ argumenttrimmode
+ 0
argumenttype
0
escaping
0
keyword
- |
+ {var:keyword}
+ queuedelaycustom
+ 1
+ queuedelayimmediatelyinitially
+
+ queuedelaymode
+ 0
+ queuemode
+ 1
+ runningsubtext
+ Processing…
script
- from pipe import complete
-print complete("""{query}""")
+
+ scriptargtype
+ 1
+ scriptfile
+ pipe.py
+ subtext
+ Enter one-liner or alias
title
Pipe clipboard through one-liner
type
- 3
+ 8
withspace
@@ -129,192 +241,212 @@ print complete("""{query}""")
alfred.workflow.input.scriptfilter
uid
74681B4B-CBE4-403F-BDF7-39F4D2319ABD
+ version
+ 2
config
- autopaste
-
- clipboardtext
- {query}
-
- type
- alfred.workflow.output.clipboard
- uid
- 0FCF109F-A181-4D53-A4DB-DB54DA7440B8
-
-
- config
-
+ concurrently
+
escaping
0
script
pbpaste | {query}
+ scriptargtype
+ 0
+ scriptfile
+
type
5
type
alfred.workflow.action.script
uid
- D029EE08-8642-404B-83FE-BB5DFEE9C6B6
+ 16039760-F173-4AB8-9C73-DA7401D5DE23
+ version
+ 2
config
-
- action
- 0
- argument
- 1
- hotkey
- 42
- hotmod
- 1310720
- hotstring
- \
- leftcursor
-
- modsmode
- 0
-
+
type
- alfred.workflow.trigger.hotkey
+ alfred.workflow.utility.junction
uid
- CCEE8787-6024-46B4-B155-240F86561281
+ 1D296119-4F9D-4FDC-9777-1DA7CF655D19
+ version
+ 1
config
- applescript
- on alfred_script(q)
- tell application "Alfred 2" to search "|| "
-end alfred_script
- cachescript
+ autopaste
+ clipboardtext
+ {query}
+ transient
+
type
- alfred.workflow.action.applescript
+ alfred.workflow.output.clipboard
uid
- E8AC122D-DD51-491F-A734-C89446EB1924
+ 0FCF109F-A181-4D53-A4DB-DB54DA7440B8
+ version
+ 2
config
- argumenttype
- 0
- escaping
- 0
- keyword
- ||
- script
- from pipe import complete
-print complete("""{query}""")
- title
- Pipe clipboard through one-liner and paste
- type
- 3
- withspace
-
+ argument
+ {query}
+ variables
+
+ paste
+ 1
+
type
- alfred.workflow.input.scriptfilter
+ alfred.workflow.utility.argument
uid
- 64D17591-D71C-421B-B701-91E99A3C0318
+ D4DF549F-4556-4AA6-BF1A-159154EB3051
+ version
+ 1
config
- argumenttype
- 0
- escaping
- 0
- keyword
- pipe
- script
- from pipe import complete
-print complete("""{query}""")
- title
- Pipe clipboard through one-liner
- type
- 3
- withspace
+ inputstring
+ {var:paste}
+ matchcasesensitive
-
- type
- alfred.workflow.input.scriptfilter
- uid
- 880D9394-A59A-4BC1-BB46-01461CEFEE80
-
-
- config
-
- escaping
+ matchmode
0
- script
- pbpaste | {query} | pbcopy
- type
- 5
+ matchstring
+ 1
type
- alfred.workflow.action.script
+ alfred.workflow.utility.filter
uid
- 16039760-F173-4AB8-9C73-DA7401D5DE23
+ CB1F4AD1-CC6A-4CE9-9953-5BA92C4B2634
+ version
+ 1
readme
- Usage:
+ Trigger the workflow by hotkey or keyword (default=`|`, override with the `keyword` variable) followed by an arbitrarily simple or complex shell one-liner to transform the contents of the clipboard in-place; optionally use the `Cmd`-modifier to immediately paste the results into the foreground app.
+
+Examples:
+
+Uppercase: `| perl -nle 'print uc'`
+base64 encode: `| openssl base64 -e`
+base64 decode: `| openssl base64 -d`
+
+A number of example pipelines (including those above) are [built-in](https://github.com/isometry/alfred-pipe/raw/master/builtins.json).
+
+To save repetitive typing, custom aliases can be defined with the following syntax:
-# Sort the contents of the clipboard, counting duplicates
-| sort | uniq -c
+`| alias NAME=PIPE | LINE@@@`
-| …your one-liner of choice.
+The trailing `@@@` (override with `alias_terminator` variable) terminates the alias definition and causes it to be saved.
+
+For example:
+
+`| alias tac=sed '1!G;h;$!d'@@@` (reverse line-by-line)
+
+Any custom aliases can be deleted with:
+
+`| alias NAME=@@@`
+
+Built-ins can be disabled en-mass by setting the `load_builtins` variable to any value other than `yes`.
uidata
0FCF109F-A181-4D53-A4DB-DB54DA7440B8
+ xpos
+ 740
ypos
- 420
+ 140
16039760-F173-4AB8-9C73-DA7401D5DE23
+ note
+ Run the one-liner
+ xpos
+ 430
ypos
- 60
+ 80
- 64D17591-D71C-421B-B701-91E99A3C0318
+ 1D296119-4F9D-4FDC-9777-1DA7CF655D19
+ xpos
+ 580
ypos
- 420
+ 110
74681B4B-CBE4-403F-BDF7-39F4D2319ABD
+ note
+ Prompt for one-liner or alias
+ xpos
+ 190
ypos
- 60
+ 80
- 880D9394-A59A-4BC1-BB46-01461CEFEE80
+ 8E5E1774-FB5B-47C7-A0E0-AE958708254F
+ xpos
+ 740
ypos
- 180
+ 20
- CCEE8787-6024-46B4-B155-240F86561281
+ 9B8A5712-D806-47E3-9045-EC31AB0C6485
+ note
+ paste!=1
+ xpos
+ 660
ypos
- 300
+ 50
- D029EE08-8642-404B-83FE-BB5DFEE9C6B6
+ CB1F4AD1-CC6A-4CE9-9953-5BA92C4B2634
+ note
+ paste==1
+ xpos
+ 660
ypos
- 420
+ 170
- D637E280-521E-432F-85C1-3F58E064A66E
+ D4DF549F-4556-4AA6-BF1A-159154EB3051
+ note
+ paste=1
+ xpos
+ 350
ypos
- 60
+ 160
- E8AC122D-DD51-491F-A734-C89446EB1924
+ D637E280-521E-432F-85C1-3F58E064A66E
+ xpos
+ 30
ypos
- 300
+ 80
+ variables
+
+ alias_terminator
+ @@@
+ keyword
+ |
+ load_builtins
+ yes
+ max_results
+ 9
+
+ version
+ 1.1
webaddress
https://github.com/isometry/alfredworkflows/tree/master/net.isometry.alfred.pipe
diff --git a/pipe.py b/pipe.py
index 7ae68c7..754eaec 100755
--- a/pipe.py
+++ b/pipe.py
@@ -1,111 +1,133 @@
+#!/usr/bin/env python2.7
#-*- coding: utf-8 -*-
-# pipe.alfredworkflow, v1.0
-# Robin Breathe, 2013
+# pipe.alfredworkflow, v1.1
+# Robin Breathe, 2013-2017
+
+from __future__ import unicode_literals
+from __future__ import print_function
import alfred
-import json
+import json, sys, os
from fnmatch import fnmatch
from os import path
from time import strftime
-_MAX_RESULTS=9
-_ALIASES_FILE=u'aliases.json'
-_BUILTINS_FILE=u'builtins.json'
-_TIMESTAMP=u'%Y-%m-%d @ %H:%M'
+DEFAULT_MAX_RESULTS=9
+DEFAULT_ALIAS_TERMINATOR="@@@"
+ALIASES_FILE="aliases.json"
+BUILTINS_FILE="builtins.json"
-def fetch_aliases(_path=_ALIASES_FILE):
+def fetch_aliases(_path):
file = path.join(alfred.work(volatile=False), _path)
if not path.isfile(file):
return {}
return json.load(open(file, 'r'))
-def write_aliases(_dict, _path=_ALIASES_FILE):
+def write_aliases(_dict, _path):
file = path.join(alfred.work(volatile=False), _path)
json.dump(_dict, open(file, 'w'), indent=4, separators=(',', ': '))
-def define_alias(_dict, definition):
- if u'=' in definition:
- (alias, pipe) = definition.split(u'=', 1)
+def define_alias(_dict, definition, alias_file):
+ if '=' in definition:
+ (alias, pipe) = definition.split('=', 1)
else:
- (alias, pipe) = (definition, u'')
+ (alias, pipe) = (definition, '')
+
+ terminator = os.getenv("alias_terminator", DEFAULT_ALIAS_TERMINATOR);
if not alias:
return alfred.xml([alfred.Item(
- attributes = {'uid': u'pipe:help', 'valid': u'no'},
- title = u"alias NAME=VALUE",
- subtitle = u'Terminate VALUE with @@ to save',
- icon = u'icon.png'
+ attributes = {'valid': 'no'},
+ title = "alias NAME=ARBITRARY-ONE-LINER",
+ subtitle = "Terminate ONE-LINER with '{0}' to save or 'NAME={0}' to delete alias".format(terminator),
+ icon = 'icon.png'
+ )])
+
+ if pipe and pipe == terminator:
+ _dict.pop(alias, None)
+ write_aliases(_dict, alias_file)
+ return alfred.xml([alfred.Item(
+ attributes = {'valid': 'no', 'autocomplete': ''},
+ title = "alias {0}={1}".format(alias, pipe),
+ subtitle = 'Alias deleted! TAB to continue',
+ icon = 'icon.png'
)])
- if pipe and pipe.endswith('@@'):
- pipe = pipe[:-2]
+
+ if pipe and pipe.endswith(terminator):
+ pipe = pipe[:-len(terminator)]
_dict[alias] = pipe
- write_aliases(_dict)
+ write_aliases(_dict, alias_file)
return alfred.xml([alfred.Item(
- attributes = {'uid': u'pipe:{}'.format(pipe) , 'valid': u'no', 'autocomplete': alias},
- title = u"alias {}={}".format(alias, pipe),
- subtitle = u'Alias saved! TAB to continue',
- icon = u'icon.png'
+ attributes = {'valid': 'no', 'autocomplete': alias},
+ title = "alias {0}={1}".format(alias, pipe),
+ subtitle = 'Alias saved! TAB to continue',
+ icon = 'icon.png'
)])
return alfred.xml([alfred.Item(
- attributes = {'uid': u'pipe:{}'.format(pipe) , 'valid': u'no'},
- title = u"alias {}={}".format(alias, pipe or 'VALUE'),
- subtitle = u'Terminate with @@ to save',
- icon = u'icon.png'
+ attributes = {'valid': 'no'},
+ title = "alias {0}={1}".format(alias, pipe),
+ subtitle = 'Terminate with {0} to save'.format(terminator),
+ icon = 'icon.png'
)])
def exact_alias(_dict, query):
pipe = _dict[query]
return alfred.xml([alfred.Item(
- attributes = {'uid': u'pipe:{}'.format(pipe), 'arg': pipe},
+ attributes = {'uid': 'pipe:{}'.format(pipe), 'arg': pipe},
title = pipe,
- subtitle = u'(expanded alias)',
- icon = u'icon.png'
+ subtitle = '(expanded alias)',
+ icon = 'icon.png'
)])
def match_aliases(_dict, query):
results = []
for (alias, pipe) in _dict.iteritems():
- if (pipe != query) and fnmatch(alias, u'{}*'.format(query)):
+ if (pipe != query) and fnmatch(alias, '{}*'.format(query)):
results.append(alfred.Item(
- attributes = {'uid': u'pipe:{}'.format(pipe) , 'arg': pipe, 'autocomplete': pipe},
+ attributes = {'uid': 'pipe:{}'.format(pipe) , 'arg': pipe, 'autocomplete': pipe},
title = pipe,
- subtitle = u'(alias: {})'.format(alias),
- icon = u'icon.png'
+ subtitle = '(alias: {})'.format(alias),
+ icon = 'icon.png'
))
return results
-def fetch_builtins(_path=_BUILTINS_FILE):
+def fetch_builtins(_path):
return json.load(open(_path, 'r'))
def match_builtins(_dict, query):
results = []
for (pipe, desc) in _dict.iteritems():
- if fnmatch(pipe, u'*{}*'.format(query)) or fnmatch(desc, u'*{}*'.format(query)):
+ if fnmatch(pipe, '*{}*'.format(query)) or fnmatch(desc, '*{}*'.format(query)):
results.append(alfred.Item(
- attributes = {'uid': u'pipe:{}'.format(pipe) , 'arg': pipe, 'autocomplete': pipe},
+ attributes = {'uid': 'pipe:{}'.format(pipe) , 'arg': pipe, 'autocomplete': pipe},
title = pipe,
- subtitle = u'(builtin: {})'.format(desc),
- icon = u'icon.png'
+ subtitle = '(builtin: {})'.format(desc),
+ icon = 'icon.png'
))
return results
def verbatim(query):
return alfred.Item(
- attributes = {'uid': u'pipe:{}'.format(query), 'arg': query},
+ attributes = {'uid': 'pipe:{}'.format(query), 'arg': query},
title = query,
subtitle = None,
- icon = u'icon.png'
+ icon = 'icon.png'
)
-def complete(query, maxresults=_MAX_RESULTS):
- aliases = fetch_aliases()
- builtins = fetch_builtins()
+def complete():
+ query = sys.argv[1]
+
+ max_results = int(os.getenv('max_results', DEFAULT_MAX_RESULTS))
+ load_builtins = bool(os.getenv('builtins_file', "yes") == "yes")
+
+ aliases = fetch_aliases(ALIASES_FILE)
+ builtins = load_builtins and fetch_builtins(BUILTINS_FILE) or {}
if query.startswith('alias '):
- return define_alias(aliases, query[6:])
+ return define_alias(aliases, query[6:], ALIASES_FILE)
results = []
@@ -118,4 +140,7 @@ def complete(query, maxresults=_MAX_RESULTS):
):
results.extend(matches)
- return alfred.xml(results, maxresults=maxresults)
+ return alfred.xml(results, maxresults=max_results)
+
+if __name__ == '__main__':
+ print(complete())