Building Command-Line Tools and Dashboards with JavaScript

Building Command-Line Tools and Dashboards with JavaScript

If you use the terminal a lot, the idea of having your own command-line tools and dashboards surely sounds enticing. JavaScript is a good friend of the terminal! We'll talk about how you can utilize various JavaScript modules to build fun, quirky tools & applications that live in your terminal.

2b6d7bdd43058e87f53866eb86538a59?s=128

Galuh Sahid

July 19, 2018
Tweet

Transcript

  1. 5.
  2. 7.
  3. 8.
  4. 10.

    quotey $ quotey -c "stoicism.json" | grep Seneca Timendi causa

    est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca
  5. 13.

    "The Heroku CLI is for humans before machines. The primary

    goal of anyone developing CLI plugins should always be usability." - Heroku's CLI Style Guide
  6. 15.

    stdout $ quotey -c "stoicism.json" | grep Seneca Timendi causa

    est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca
  7. 16.

    stdout console.log("Lorem ipsum"); $ quotey -c "stoicism.json" | grep Seneca

    Timendi causa est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca process.stdout.write("Lorem Ipsum");
  8. 18.

    exit status function quit() { return process.exit(0); } • 0

    - 255 • 0: success, otherwise: failure
  9. 23.

    input: options $ git help -a $ git help -g

    $ rm -r -f my-directory $ rm -rf my-directory
  10. 26.

    lyrical - genius lyrics in your terminal features: • searches

    songs by artist • retrieves song lyrics given a song • genius API (node-genius + lyricist)
  11. 27.

    lyrical - genius lyrics in your terminal features: • searches

    songs by artist • retrieves song lyrics given a song • genius API (node-genius + lyricist) API: • able to search songs given the artist name (e.g. "The National") but... • unable to search songs given the song name (e.g. "About Today"); must use song ID (e.g. 253271)
  12. 28.

    lyrical lyrical/index.js function getSongLyricsByID(songID) { ... console.log(song.lyrics); } function getSongsByArtist(artist)

    { ... console.log(song.title + " (" + song.id + ")"); } 19 [Booklet] (2452146) 19 [Tracklist + Album Art] (2401219) 2017 Grammy's Song of the Year Speech (2985039) 21 [Booklet] (2452149) 21 [Tracklist + Album Cover] (2404413) 25 [Booklet] (2452122) 25 Thank You Letter (2378169)
  13. 30.

    process.argv console.log(process.argv); $ node index.js one two=three four [ '/usr/local/bin/node',

    '/Users/galuh.sahid/scotlandjs2018/index.js', 'one', 'two=three', 'four' ]
  14. 32.

    process.argv lyrical/index.js function runCli() { var userArgs = process.argv.slice(2); var

    searchBy = userArgs[0]; var query = userArgs[1]; if (searchBy == "song") { getSongLyricsByID(query); } else if (searchBy == "artist") { getSongsByArtist(artist); } else { console.log("Wrong options"); } } runCli();
  15. 34.

    minimist lyrical/index.js function getSongLyricsByID(songID, isLowercase) { ... console.log(song.lyrics); } function

    getSongsByArtist(artist) { ... console.log(song.title + " (" + song.id + ")"); }
  16. 35.
  17. 36.

    minimist lyrical/index.js var argv = require('minimist')(process.argv.slice(2)); ... function runCli() {

    var songID = argv["song"]; var artist = argv["artist"]; var isLowercase = argv["l"]; if (songID != null) { getSongLyricsByID(songID, isLowercase); } if (artist != null) { getSongsByArtist(artist); } }
  18. 39.

    commander.js lyrical/index.js program .command('song <songId>') .option('-l, --lowercase', 'Return song lyrics

    in lowercase') .alias('s') .description('Find song lyrics by songId') .action( function (song, args) { getSongLyricsByID(song, args.lowercase) }); program .command('artist <name>') .alias('a') .description('Find list of songs by artist') .action( function (artist) { getSongsByArtist(artist) }); program.parse(process.argv);
  19. 45.

    inquirer.js lyrical/index.js var inquirer = require('inquirer'); ... var searchArtistPrompt =

    { type: 'input', name: 'artist', message: 'Search an artist:' } var chooseSongPrompt = { type: 'list', name: 'songs', message: 'Pick a song:', choices: [] }
  20. 46.

    inquirer.js lyrical/index.js function chooseArtist() { inquirer.prompt(searchArtistPrompt).then(answer => { var artist

    = answer["artist"]; getSongsByArtist(artist).then(chooseSong); }); } function chooseSong(allSongs) { songTitles = Object.keys(allSongs); chooseSongPrompt["choices"] = songTitles; inquirer.prompt(chooseSongPrompt).then(answer => { chosenSong = answer["songs"]; getSongLyricsByID(allSongs[chosenSong]); }); } function runCli() { chooseArtist(); } runCli();
  21. 48.
  22. 49.
  23. 50.

    blessed & blessed-contrib • blessed • blessed-contrib Blessed is over

    16,000 lines of code and terminal goodness. It's completely implemented in javascript, and its goal consists of two things: • Reimplement ncurses entirely by parsing and compiling terminfo and termcap, and exposing a Program object which can output escape sequences compatible with any terminal. • Implement a widget API which is heavily optimized for terminals.
  24. 51.

    ncurses? curses? Performing operations on the terminals--even as "simple" as

    moving a cursor to a new location, scrolling the screen, erasing areas--is super difficult
  25. 52.

    ncurses? curses? Performing operations on the terminals--even as "simple" as

    moving a cursor to a new location, scrolling the screen, erasing areas--is super difficult We need to send control codes that tell the terminals what to do curses, ncurses (and in JS, blessed) abstract everything away
  26. 53.

    blessed escape sequences Boxes • Box • Text • Line

    • BigText Lists • List • FileManager • ListTable • Listbar Forms • Form • Input (abstract) • Textarea • Textbox • Button • Checkbox • RadioSet • RadioButton Prompts • Prompt • Question • Message • Loading Data Display • ProgressBar • Log • Table • Special Elements Terminal • Image • ANSIImage • OverlayImage • Video • Layout
  27. 54.

    blessed escape sequences Events • Mouse • Keypress • Move

    • Resize • Show • Destroy • etc. Methods • hide() • show() • focus() • etc.
  28. 56.

    blessed var blessed = require('blessed'); ... // creates a box

    that is aligned in the middle var box = blessed.box({ top: 'center', left: 'center', width: '50%', height: '50%', content: 'Hello {bold}ScotlandJS{/bold}! Why don\'t you give this box a click?', tags: true, border: { type: 'line' } });
  29. 57.

    blessed var blessed = require('blessed'); ... // creates a box

    that is aligned in the middle var box = blessed.box({ top: 'center', left: 'center', width: '50%', height: '50%', content: 'Hello {bold}ScotlandJS{/bold}! Why don\'t you give this box a click?', tags: true, border: { type: 'line' } });
  30. 58.

    blessed var blessed = require('blessed'); ... // creates a box

    that is aligned in the middle var box = blessed.box({ top: 'center', left: 'center', width: '50%', height: '50%', content: 'Hello {bold}ScotlandJS{/bold}! Why don\'t you give this box a click?', tags: true, border: { type: 'line' } });
  31. 59.

    blessed var blessed = require('blessed'); ... // creates a box

    that is aligned in the middle var box = blessed.box({ top: 'center', left: 'center', width: '50%', height: '50%', content: 'Hello {bold}ScotlandJS{/bold}! Why don\'t you give this box a click?', tags: true, border: { type: 'line' } });
  32. 60.

    blessed var blessed = require('blessed'); ... // creates a box

    that is aligned in the middle var box = blessed.box({ top: 'center', left: 'center', width: '50%', height: '50%', content: 'Hello {bold}ScotlandJS{/bold}! Why don\'t you give this box a click?', tags: true, border: { type: 'line' } });
  33. 61.

    blessed // If our box is clicked, change the content

    box.on('click', function (data) { box.content = '{center}This is so {red-fg}cool{/red-fg} amirite?! How about pressing the enter button and see what happens?{/center}'; screen.render(); });
  34. 62.

    blessed // If our box is clicked, change the content

    box.on('click', function (data) { box.content = '{center}This is so {red-fg}cool{/red-fg} amirite?! How about pressing the enter button and see what happens?{/center}'; screen.render(); });
  35. 63.

    blessed // If our box is clicked, change the content

    box.on('click', function (data) { box.content = '{center}This is so {red-fg}cool{/red-fg} amirite?! How about pressing the enter button and see what happens?{/center}'; screen.render(); }); // Handle 'enter' box.key('enter', function (ch, key) { ... });
  36. 64.

    blessed screen.key(['escape', 'q', 'C-c'], function (ch, key) { return process.exit(0);

    }); screen.key(['r', 'C-r'], function (ch, key) { refresh(); });
  37. 65.

    blessed screen.key(['r', 'C-r'], function (ch, key) { refresh(); }); function

    refresh() { box.content = '{center}This is so {red-fg}cool{/red-fg} amirite?! How about pressing the enter button and see what happens?{/center}'; screen.render(); }
  38. 66.

    blessed-contrib escape sequences • makes it easier to create dashboards

    • provides even more widgets that work out of the box
  39. 68.

    blessed-contrib var blessed = require('blessed') , contrib = require('blessed-contrib') var

    screen = blessed.screen(); var grid = new contrib.grid({rows: 12, cols: 12, screen: screen})
  40. 69.

    blessed-contrib var blessed = require('blessed') , contrib = require('blessed-contrib') var

    screen = blessed.screen(); var grid = new contrib.grid({rows: 12, cols: 12, screen: screen})
  41. 70.

    blessed-contrib var blessed = require('blessed') , contrib = require('blessed-contrib') var

    screen = blessed.screen(); var grid = new contrib.grid({rows: 12, cols: 12, screen: screen})
  42. 71.

    blessed-contrib ... // grid.set(row, col, rowSpan, colSpan, obj, opts) var

    map = grid.set(0, 0, 4, 4, contrib.map, {label: 'Fancy World Map'}) var box = grid.set(4, 4, 4, 4, blessed.box, {label: 'Some Box', content: 'Hi ScotlandJS!'}) screen.render()
  43. 72.

    blessed-contrib ... // grid.set(row, col, rowSpan, colSpan, obj, opts) var

    map = grid.set(0, 0, 4, 4, contrib.map, {label: 'Fancy World Map'}) var box = grid.set(4, 4, 4, 4, blessed.box, {label: 'Some Box', content: 'Hi ScotlandJS!'}) screen.render()
  44. 73.

    blessed-contrib ... // grid.set(row, col, rowSpan, colSpan, obj, opts) var

    map = grid.set(0, 0, 4, 4, contrib.map, {label: 'Fancy World Map'}) var box = grid.set(4, 4, 4, 4, blessed.box, {label: 'Some Box', content: 'Hi ScotlandJS!'}) screen.render()
  45. 74.

    blessed-contrib ... // grid.set(row, col, rowSpan, colSpan, obj, opts) var

    map = grid.set(0, 0, 4, 4, contrib.map, {label: 'Fancy World Map'}) var box = grid.set(4, 4, 4, 4, blessed.box, {label: 'Some Box', content: 'Hi ScotlandJS!'}) screen.render()
  46. 75.

    what now? • utilize more widgets that come from blessed

    and blessed-contrib • create your own custom blessed widgets (https://recodes.co/blessed-custom-widgets/)
  47. 79.
  48. 86.

    takeaways • command line programs and dashboards can be a

    great alternative to/companion for GUI • ... and are easy to build with JavaScript! -> easy to get creative with it! • Which libraries to use? It depends! • Single-command CLI app -> minimist • CLI apps with more complicated parsing -> commander.js • CLI apps with certain user flow -> inquirer.js • Dashboards -> blessed, blessed-contrib • Get creative and combine > 1 libraries!
  49. 87.

    • command line programs and dashboards are still cool •

    ... and are easy to build with JavaScript! -> easy to get creative with it! • there are so many ways to build one--the question is not "which library is the best?" but "which libraries are best for my application?" thank you! galuh.me @galuhsahid