Skip to content
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

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,22 @@ alfy.alfred = {

alfy.input = process.argv[2];

alfy.output = arr => {
console.log(JSON.stringify({items: arr}, null, '\t'));
const isDefined = x => x !== null && x !== undefined;

const formatItemArg = item => {
if (isDefined(item) && isDefined(item.variables)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be expanded. It's also possible to provide variables for mods like this

alfy.output([
    {
        title: 'Unicorn',
        arg: '🦄',
        variables: {
            color: 'white'
        },
        mods: {
            alt: {
                title: 'Rainbow',
                arg: '🌈',
                variables: {
                    color: 'red'
                }
            }
        }
    }
]);

If you press the alt key, it will now show the Rainbow instead.

const alfredworkflow = {arg: item.arg, variables: item.variables};
item.arg = JSON.stringify({alfredworkflow});
delete item.variables;
}
};

alfy.output = items => {
if (isDefined(items) && items.forEach) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just check if items is an array, otherwise throw a TypeError

if (!Array.isArray(items)) {
    throw new TypeError(`Expected \`items\` to be of type \`Array\`, got \`${typeof items}\``);
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a breaking change to the output API, right?

Old implementation:

alfy.output('hi!'); // no problem

New implementation:

alfy.output('hi!'); // TypeError

I'm happy to implement it, just want to confirm.

P.S. - does Alfy follow SemVer?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I've just changed it to this:

  if (Array.isArray(items)) {
    items = items.map(format);
  }

Let me know if you'd prefer the breaking change.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was always documented as accepting an Array. Enforcing the documented behavior is not a breaking change.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I don't think Alfred accepts anything else except an Array of items. This means that we are now just more verbose to what it accepts.

items.forEach(formatItemArg);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use items.map

items = items.map(item => format(item));

And then return the item in the format function.

console.log(JSON.stringify({items}, null, '\t'));
};

alfy.matches = (input, list, item) => {
Expand Down
Binary file added media/screenshot-variable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 28 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ 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/). In addition, 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/) if the user selects the item.

Example:

Expand All @@ -179,6 +179,33 @@ alfy.output([{

<img src="media/screenshot-output.png" width="694">

Using the `variables` property:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should put this under it's own section so we can easily link it from the highlights and to make it more clear. Not sure where though, any ideas @sindresorhus? Maybe a new section Environment variables under Usage or Example? Or on the same level as those?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new top-level section called "Environment variables".


```js
alfy.output([{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the objects on newlines

alfy.output([
    {
        title: 'Unicorn',
        arg: '🦄',
        variables: {
            color: 'white'
        }
    },
    {
        //...
    }
]);

title: 'Unicorn',
arg: '🦄',
variables: {color: 'white'}
}, {
title: 'Rainbow',
arg: '🌈',
variables: {color: 'myriad'}
}]);
```

You can access Alfred Workflow Variables through `process.env`:

```js
// After a user selects "Unicorn" or "Rainbow"
process.env.color
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we alias this as alfy.env?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would be nice.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

//=> '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}`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alfred Workflow Variables are also available in the workflow editor using the form {var:varname}.

Can you add an example for the color case above? Would {myColor:color} work for instance?


<img src="media/screenshot-variable.png" width="694">

#### matches(input, list, [item])

Returns an `Array` of items in `list` that case-insensitively contains `input`.
Expand Down
76 changes: 76 additions & 0 deletions test/output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import test from 'ava';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be import {serial as test} from 'ava'; to make sure it runs all the tests serially. Otherwise the output could be mangled. It's weird that it works right now to be honest.

import hookStd from 'hook-std';
import {alfy} from './_utils';

const m = alfy();

test.cb('.output() properly wraps item.variables', t => {
const unhook = hookStd.stdout({silent: true}, output => {
unhook();
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: '🦄',
variables: {fabulous: true}
}]);
});

test.cb('.output() wraps item.variables even if item.arg is not defined', t => {
const unhook = hookStd.stdout({silent: true}, output => {
unhook();
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',
variables: {fabulous: true}
}]);
});

test.cb('.output() does not wrap item.arg if item.variables is not defined', t => {
const unhook = hookStd.stdout({silent: true}, output => {
unhook();
const item = JSON.parse(output).items[0];
t.is(item.arg, '🦄');
t.is(item.variables, undefined);
t.end();
});
m.output([{
title: 'unicorn',
arg: '🦄'
}]);
});

test('.output() accepts null and undefined items', t => {
const unhook = hookStd.stdout({silent: true}, () => unhook());
t.notThrows(() => m.output([undefined, null]));
});

test('.output() accepts non-arrays', t => {
let done = false;
const unhook = hookStd.stdout({silent: true}, () => {
if (done) {
unhook();
}
});
t.notThrows(() => m.output({}));
t.notThrows(() => m.output('unicorn'));
t.notThrows(() => m.output(null));
done = true;
t.notThrows(() => m.output(undefined));
});