Skip to content
Omelette is a simple, template based autocompletion tool for Node projects with super easy API. (For Bash, Zsh and Fish)
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
coffee Throw error when no `process.env.SHELL` was detected at all Sep 27, 2018
example
resources Add files via upload May 7, 2017
src
.travis.yml deprecate node 0.6 Apr 25, 2018
LICENSE
README.md add explanation Nov 19, 2018
build.sh omelette is in src now, build script added, and event listeners are b… Apr 26, 2013
package.json ignore big resource folders to shrink the size of package size (#36) Mar 5, 2019

README.md

Omelette is a simple template based autocompletion tool for Node projects with super easy API.

npm version Build Status

yarn add omelette
# or
npm install omelette

You just have to decide your program name and CLI fragments.

omelette`github ${['pull', 'push']} ${['origin', 'upstream']} ${['master', 'develop']}`.init()

...and you are almost done! The output will look like this:

Quick Start

For a step by step guide please follow this link

Implementing omelette is very easy:

import * as omelette from 'omelette';

const firstArgument = ({ reply }) => {
  reply([ 'beautiful', 'cruel', 'far' ])
}

const planet = ({ reply }) => {
  reply([ 'world', 'mars', 'pluto' ])
}

omelette`hello|hi ${firstArgument} ${planet}`.init()

Simple Event Based API ☕️

It's based on a simple CLI template.

Let's think we have a executable file with the name githubber, in a global path.

In our program, the code will be:

import * as omelette from 'omelette';

// Write your CLI template.
const completion = omelette(`githubber|gh <action> <user> <repo>`);

// Bind events for every template part.
completion.on('action', ({ reply }) => {
  reply([ 'clone', 'update', 'push' ])
})

completion.on('user', ({ reply }) => {
  reply(fs.readdirSync('/Users/'))
})

completion.on('repo', ({ before, reply }) => {
  reply([
    `http://github.com/${before}/helloworld`,
    `http://github.com/${before}/blabla`
  ])
})

// Initialize the omelette.
completion.init()

// If you want to have a setup feature, you can use `omeletteInstance.setupShellInitFile()` function.
if (~process.argv.indexOf('--setup')) {
  completion.setupShellInitFile()
}

// Similarly, if you want to tear down autocompletion, use `omeletteInstance.cleanupShellInitFile()`
if (~process.argv.indexOf('--cleanup')) {
  completion.cleanupShellInitFile()
}

// Rest is yours
console.log("Your program's default workflow.")
console.log(process.argv)

complete.reply is the completion replier. You must pass the options into that method.

ES6 Template Literal API 🚀

You can use Template Literals to define your completion with a simpler (super easy) API.

import * as omelette from 'omelette';

// Just pass a template literal to use super easy API.
omelette`hello ${[ 'cruel', 'nice' ]} ${[ 'world', 'mars' ]}`.init()

Let's make the example above with ES6 TL:

import * as omelette from 'omelette'

// Write your CLI template.
omelette`
  githubber|gh

  ${[ 'clone', 'update', 'push' ]}
  ${() => fs.readdirSync('/Users/')}
  ${({ before }) => [
    `http://github.com/${before}/helloworld`,
    `http://github.com/${before}/blabla`,
  ]}
`.init()

Also you can still use lambda functions to make more complex template literals:

Advanced Template Literals

import * as omelette from 'omelette';

omelette`
  githubber|gh
      ${['pull', 'push', 'star'] /* Direct command list */}
      ${require('some/other/commands') /* Import from another file */}
      ${getFromRemote('http://api.example.com/commands') /* Remote call at the beginning */}
      ${({ reply }) => fetch('http://api.example.com/lazy-commands').then(reply) /* Fetch when argument <tab>bed */}
      ${() => fs.readdirSync("/Users/") /* Access filesystem via Node */}
      ${({ before }) => [ /* Use parameters like `before`, `line`, `fragment` or `reply` */
        `${before}/helloworld`,
        `${before}/blabla`
      ]}
  `.init()

// No extra configuration required.

console.log("Your program's default workflow.")
console.log(process.argv)

Async API

Omelette allows you to use async functions. You have to use onAsync and to pass Promise object to the reply function.

complete.onAsync('user', async ({ reply }) => {
  reply(new Promise((resolve) => {
    fs.readdir('/Users/', (err, users) => {
      resolve(users)
    })
  }))
})

⚠️ A note about async handlers

If you are using async handlers, you have to use complete.next method to continue running your main workflow.

// ...

complete.onAsync('user', async ({ reply }) => {
  reply(new Promise((resolve) => {
    fs.readdir('/Users/', (err, users) => {
      resolve(users)
    })
  }))
})

// Instead of running directly, you need to set an handler to run your main workflow.
complete.next(()=> {
  console.log("Your program's default workflow.")
  console.log(process.argv)
})

// .init must be called after defining .next
complete.init()
// ...

Using util.promisify will make your async handlers easier.

import promisify from 'util';

complete.onAsync('user', async ({ reply }) => {
  reply(await promisify(fs.readdir)('/Users'))
})

Tree API 🌲

You can use simple objects as autocompletion definitions:

omelette('hello').tree({
  cruel: ['world', 'moon'],
  beautiful: ['mars', 'pluto']
}).init();

Install

Automated Install

Installing and making your users install the autocompletion feature is very simple.

You can use simply use setupShellInitFile function.

try {
  // Pick shell init file automatically
  complete.setupShellInitFile()

  // Or use a manually defined init file
  complete.setupShellInitFile('~/.my_bash_profile')

} catch (err) {
  // setupShellInitFile() throws if the used shell is not supported
}

If you use Bash, it will create a file at ~/.<program-name>/completion.sh and append a loader code to ~/.bash_profile file.

If you use Zsh, it appends a loader code to ~/.zshrc file.

If you use Fish, it appends a loader code to ~/.config/fish/config.fish file.

TL;DR: It does the Manual Install part, basically.

Automated Uninstallation

Similarly to installation, you can use cleanupShellInitFile to undo changes done by setupShellInitFile.

complete.cleanupShellInitFile()

As with setupShellInitFile(), wrap this in a try/catch block to handle unsupported shells.

Manual Installation

(You should add these instructions to your project's README)

In zsh, you can write these:

echo '. <(./githubber --completion)' >> .zshrc

In bash, you should write:

./githubber --completion >> ~/githubber.completion.sh
echo 'source ~/githubber.completion.sh' >> .bash_profile

In fish, you can write:

echo 'githubber --completion-fish | source' >> ~/.config/fish/config.fish

That's all!

Now you have an autocompletion system for your CLI tool.

Additions

There are some useful additions to omelette.

Parameters

Callbacks have two parameters:

  • The fragment name (e.g.command of <command> template) (only in global event)
  • The meta data
    • fragment: The number of fragment.
    • before: The previous word.
    • line: The whole command line buffer allow you to parse and reply as you wish.
    • reply: This is the reply function to use this-less API.

Global Event

You can also listen to all fragments by "complete" event.

complete.on('complete', (fragment, { reply }) => reply(["hello", "world"]));

Numbered Arguments

You can also listen to events in order.

complete.on('$1', ({ reply }) => reply(["hello", "world"]))

Autocompletion Tree

You can create a completion tree to more complex autocompletions.

omelette('hello').tree({
  how: {
    much: {
      is: {
        this: ['car'],
        that: ['house'],
      }
    }
    are: ['you'],
    many: ['cars', 'houses'],
  },
  where: {
    are: {
      you: ['from'],
      the: ['houses', 'cars'],
    },
    is: {
      // You can also add some logic with defining functions:
      your() {
        return ['house', 'car'];
      },
    }
  },
}).init()

Now, you will be able to use your completion as tree.

Thanks @jblandry for the idea.

Advanced Tree Implementations

You can seperate your autocompletion by importing objects from another file:

omelette('hello').tree(require('./autocompletion-tree.js')).init();

Short Names

You can set a short name for an executable:

In this example, githubber is long and gh is short.

omelette('githubber|gh <module> <command> <suboption>');

Test

Now you can try it in your shell.

git clone https://github.com/f/omelette
cd omelette/example
alias githubber="./githubber" # The app should be global, completion will search it on global level.
./githubber --setup --debug # --setup is not provided by omelette, you should proxy it.
# (reload bash, or source ~/.bash_profile or ~/.config/fish/config.fish)
omelette-debug-githubber # See Debugging section
githubber<tab>
ghb<tab> # short alias
gh<tab> # short alias

Debugging

--debug option generates a function called omlette-debug-<programname>. (omlette-debug-githubber in this example).

When you run omlette-debug-<programname>, it will create aliases for your application. (githubber and gh in this example).

A long name:

$ githubber<tab>
clone update push

Or short name:

$ gh<tab>
clone update push

Then you can start easily.

$ ./githubber<tab>
clone update push
$ ./githubber cl<tab>
$ ./githubber clone<tab>
Guest fka
$ ./githubber clone fka<tab>
$ ./githubber clone fka http://github.com/fka/<tab>
http://github.com/fka/helloworld
http://github.com/fka/blabla

Users?

  • Windows Azure uses Omelette to support autocompletion in azure-cli.
  • Office 365 CLI uses Omelette to support autocompletion in office365-cli.
  • Visual Studio App Center CLI uses Omelette to support autocompletion in appcenter-cli.

Contribute

I need your contributions to make that work better!

License

This project licensed under MIT.

You can’t perform that action at this time.