-
-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support setting Alfred variables with alfy.output (#43) #44
Changes from all commits
979febc
1084938
76de28b
7f4ac51
fecf997
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ const cleanStack = require('clean-stack'); | |
const dotProp = require('dot-prop'); | ||
const CacheConf = require('cache-conf'); | ||
const updateNotification = require('./lib/update-notification'); | ||
const {format} = require('./lib/utils'); | ||
|
||
const alfy = module.exports; | ||
|
||
|
@@ -38,8 +39,13 @@ alfy.alfred = { | |
|
||
alfy.input = process.argv[2]; | ||
|
||
alfy.output = arr => { | ||
console.log(JSON.stringify({items: arr}, null, '\t')); | ||
alfy.output = items => { | ||
if (!Array.isArray(items)) { | ||
throw new TypeError(`Expected \`items\` to be an Array, got \`${typeof items}\``); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use items = items.map(item => format(item)); And then return the item in the |
||
items = items.map(item => format(item)); | ||
console.log(JSON.stringify({items}, null, '\t')); | ||
}; | ||
|
||
alfy.matches = (input, list, item) => { | ||
|
@@ -159,6 +165,8 @@ alfy.icon = { | |
delete: getIcon('ToolbarDeleteIcon') | ||
}; | ||
|
||
alfy.env = process.env; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sindresorhus Should we put this in |
||
|
||
loudRejection(alfy.error); | ||
process.on('uncaughtException', alfy.error); | ||
hookStd.stderr(alfy.error); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
const isPlainObj = require('is-plain-obj'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
const wrapArg = item => { | ||
const alfredworkflow = {arg: item.arg, variables: item.env}; | ||
const arg = JSON.stringify({alfredworkflow}); | ||
const newItem = Object.assign({}, item, {arg}); | ||
delete newItem.env; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we overwrite |
||
return newItem; | ||
}; | ||
|
||
const formatMods = item => { | ||
const copy = Object.assign({}, item); | ||
|
||
for (const mod of Object.keys(item.mods)) { | ||
copy.mods[mod] = wrapArg(item.mods[mod]); | ||
} | ||
|
||
return copy; | ||
}; | ||
|
||
// See https://www.alfredforum.com/topic/9070-how-to-workflowenvironment-variables/ for documentation on setting environment variables from Alfred workflows. | ||
module.exports.format = item => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (!isPlainObj(item)) { | ||
throw new TypeError(`Expected \`item\` to be a plain object, got \`${typeof item}\`.`); | ||
} | ||
|
||
if (item.env) { | ||
item = wrapArg(item); | ||
} | ||
|
||
if (item.mods) { | ||
item = formatMods(item); | ||
} | ||
|
||
return item; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,14 +48,15 @@ | |
], | ||
"dependencies": { | ||
"alfred-link": "^0.2.0", | ||
"alfred-notifier": "^0.2.0", | ||
"alfred-notifier": "^0.2.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert, semver takes care of this. |
||
"cache-conf": "^0.3.0", | ||
"clean-stack": "^1.0.0", | ||
"clean-stack": "^1.3.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert |
||
"conf": "^0.11.0", | ||
"dot-prop": "^4.0.0", | ||
"dot-prop": "^4.1.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert |
||
"execa": "^0.5.0", | ||
"got": "^6.3.0", | ||
"hook-std": "^0.2.0", | ||
"is-plain-obj": "^1.1.0", | ||
"loud-rejection": "^1.6.0", | ||
"npm-run-path": "^2.0.2", | ||
"read-pkg-up": "^1.0.1" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
- [Finds the `node` binary.](run-node.sh) | ||
- Presents uncaught exceptions and unhandled Promise rejections to the user.<br> | ||
*No need to manually `.catch()` top-level promises.* | ||
- Easily set [environment variables](#environment-variables). | ||
|
||
|
||
## Prerequisites | ||
|
@@ -165,7 +166,12 @@ Return output to Alfred. | |
|
||
Type: `Array` | ||
|
||
List of `Object` with any of the [supported properties](https://www.alfredapp.com/help/workflows/inputs/script-filter/json/). | ||
List of `Object` with any of the [supported properties](https://www.alfredapp.com/help/workflows/inputs/script-filter/json/). If a list item has a `variables` property, it will be used to set Alfred's [Workflow Environment Variables](https://www.alfredapp.com/help/workflows/advanced/variables/) when the user selects the item. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
See also: | ||
|
||
- [Environment variables in Alfy](#environment-variables) | ||
- [How to set environment variables in Alfred Workflows](https://www.alfredforum.com/topic/9070-how-to-workflowenvironment-variables/) | ||
|
||
Example: | ||
|
||
|
@@ -490,6 +496,42 @@ Example: `'adbd4f66bc3ae8493832af61a41ee609b20d8705'` | |
|
||
Non-synced local preferences are stored within `Alfred.alfredpreferences` under `…/preferences/local/${preferencesLocalHash}/`. | ||
|
||
## Environment Variables | ||
|
||
Alfy makes it easy to set environment variables when a user selects an item: | ||
|
||
```js | ||
alfy.output([ | ||
{ | ||
title: 'Unicorn', | ||
arg: '🦄', | ||
env: { | ||
color: 'white' | ||
} | ||
}, | ||
{ | ||
title: 'Rainbow', | ||
arg: '🌈', | ||
env: { | ||
color: 'myriad' | ||
} | ||
} | ||
]); | ||
``` | ||
|
||
You can access Alfred Workflow Variables through `process.env` or `alfy.env`: | ||
|
||
```js | ||
// After a user selects "Unicorn" or "Rainbow" | ||
process.env.color | ||
alfy.env.color | ||
//=> 'white' if they selected Unicorn | ||
//=> 'myriad' if they selected Rainbow | ||
``` | ||
|
||
Alfred Workflow Variables are also available in the workflow editor using the form `{var:varname}`. For example, `{var:color}` | ||
|
||
<img src="media/screenshot-variable.png" width="694"> | ||
|
||
## Users | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import {serial as test} from 'ava'; | ||
import hookStd from 'hook-std'; | ||
import {alfy} from './_utils'; | ||
|
||
const itemWithMod = { | ||
title: 'unicorn', | ||
arg: '🦄', | ||
env: {fabulous: true}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put it on a new line |
||
mods: { | ||
alt: { | ||
title: 'Rainbow', | ||
arg: '🌈', | ||
env: { | ||
color: 'myriad' | ||
} | ||
} | ||
} | ||
}; | ||
|
||
const m = alfy(); | ||
const hook = cb => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return a promise instead of a callback, this way you can just use |
||
const unhook = hookStd.stdout({silent: true}, output => { | ||
unhook(); | ||
if (cb) { | ||
cb(output); | ||
} | ||
}); | ||
}; | ||
|
||
test.cb('.output() properly wraps item.env', t => { | ||
hook(output => { | ||
const item = JSON.parse(output).items[0]; | ||
const arg = JSON.parse(item.arg); | ||
t.deepEqual(arg, { | ||
alfredworkflow: { | ||
arg: '🦄', | ||
variables: {fabulous: true} | ||
} | ||
}); | ||
t.end(); | ||
}); | ||
m.output([{ | ||
title: 'unicorn', | ||
arg: '🦄', | ||
env: {fabulous: true} | ||
}]); | ||
}); | ||
|
||
test.cb('.output() wraps item.env even if item.arg is not defined', t => { | ||
hook(output => { | ||
const item = JSON.parse(output).items[0]; | ||
const arg = JSON.parse(item.arg); | ||
t.deepEqual(arg, { | ||
alfredworkflow: { | ||
variables: {fabulous: true} | ||
} | ||
}); | ||
t.end(); | ||
}); | ||
m.output([{ | ||
title: 'unicorn', | ||
env: {fabulous: true} | ||
}]); | ||
}); | ||
|
||
test('.output() throws if it doesn\'t receive an array of plain objects', t => { | ||
hook(); | ||
const outputNulls = () => m.output([null, null]); | ||
t.throws(outputNulls, TypeError); | ||
}); | ||
|
||
test.cb('.output() does not wrap item.arg if item.env is not defined', t => { | ||
hook(output => { | ||
const item = JSON.parse(output).items[0]; | ||
t.is(item.arg, '🦄'); | ||
t.is(item.env, undefined); | ||
t.end(); | ||
}); | ||
m.output([{ | ||
title: 'unicorn', | ||
arg: '🦄' | ||
}]); | ||
}); | ||
|
||
test('.output() throws a TypeError if its argument isn\'t an array', t => { | ||
let done = false; | ||
const unhook = hookStd.stdout({silent: true}, () => { | ||
if (done) { | ||
unhook(); | ||
} | ||
}); | ||
t.throws(() => m.output({}), TypeError); | ||
t.throws(() => m.output('unicorn'), TypeError); | ||
t.throws(() => m.output(null), TypeError); | ||
done = true; | ||
t.throws(() => m.output(undefined), TypeError); | ||
}); | ||
|
||
test.cb('.output() wraps mod items', t => { | ||
hook(output => { | ||
const item = JSON.parse(output).items[0]; | ||
const altArg = JSON.parse(item.mods.alt.arg); | ||
t.deepEqual(altArg, { | ||
alfredworkflow: { | ||
arg: '🌈', | ||
variables: { | ||
color: 'myriad' | ||
} | ||
} | ||
}); | ||
t.end(); | ||
}); | ||
m.output([itemWithMod]); | ||
}); | ||
|
||
test.cb('.output() doesn\'t change original item when mod items are present', t => { | ||
hook(output => { | ||
const item = JSON.parse(output).items[0]; | ||
const arg = JSON.parse(item.arg); | ||
t.deepEqual(arg, { | ||
alfredworkflow: { | ||
arg: '🦄', | ||
variables: {fabulous: true} | ||
} | ||
}); | ||
t.end(); | ||
}); | ||
m.output([itemWithMod]); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Destructuring is not yet supported in Node 4. So just import it as
utils
and useutils.format
below.