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. building command-line tools and dashboards with JavaScript galuh.me @galuhsahid

  2. hi ! i'm galuh

  3. hi ! i'm galuh "

  4. command-line interface

  5. None
  6. why build command- line applications?

  7. None
  8. quotey From your own collection (.json) $ quotey -c "stoicism.json"

    From WikiQuote $ quotey -w "Grace Hopper"
  9. quotey $ echo 'quotey -c "stoicism.json"' >> ~/.bash_profile

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

    est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca
  11. how do we build one?

  12. how do we build one? there are so many ways

    to do it
  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
  14. anatomy of a CLI application

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

    est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca
  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");
  17. stderr console.error("Lorem ipsum"); process.stderr.write("Lorem Ipsum");

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

    - 255 • 0: success, otherwise: failure
  19. input: stdin process.stdin.resume(); process.stdin.setEncoding('utf8'); process.stdin.on('data', function(data) { console.log("Hello " +

    data); }); $ echo "ScotlandJS" | node index.js $ Hello ScotlandJS
  20. input: commands $ git init $ git help

  21. input: commands $ git init $ git help $ rm

    -rf my-directory
  22. input: options $ git help -a $ git help -g

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

    $ rm -r -f my-directory $ rm -rf my-directory
  24. input: arguments $ node index.js

  25. ok, show me how to do it

  26. lyrical - genius lyrics in your terminal features: • searches

    songs by artist • retrieves song lyrics given a song • genius API (node-genius + lyricist)
  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)
  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)
  29. process.argv

  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' ]
  31. process.argv

  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();
  33. minimist "parse argument options"

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

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

  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); } }
  37. commander.js "The complete solution for node.js command-line interfaces, inspired by

    Ruby's commander."
  38. commander.js

  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);
  40. commander.js

  41. more • yargs • vorpal.js • meow

  42. terminal user interface

  43. inquirer.js "A collection of common interactive command line user interfaces."

  44. inquirer.js

  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: [] }
  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();
  47. blessed & blessed- contrib

  48. None
  49. None
  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.
  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
  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
  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
  54. blessed escape sequences Events • Mouse • Keypress • Move

    • Resize • Show • Destroy • etc. Methods • hide() • show() • focus() • etc.
  55. blessed when clicked

  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' } });
  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' } });
  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' } });
  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' } });
  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' } });
  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(); });
  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(); });
  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) { ... });
  64. blessed screen.key(['escape', 'q', 'C-c'], function (ch, key) { return process.exit(0);

    }); screen.key(['r', 'C-r'], function (ch, key) { refresh(); });
  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(); }
  66. blessed-contrib escape sequences • makes it easier to create dashboards

    • provides even more widgets that work out of the box
  67. blessed-contrib escape sequences

  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})
  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})
  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})
  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()
  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()
  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()
  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()
  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/)
  76. other fun stuff

  77. colors • colors • chalk

  78. colors lyrical/index.js var colors = require('colors'); ... function getSongLyricsByID(songID, isLowercase)

    { ... console.log(song.lyrics.underline.red); }
  79. colors

  80. progress bars • progress bars • node-progress • cli-progress

  81. ASCII art • ASCII art • ascii-art • figlet

  82. more examples medium-cli by dheeraj joshi

  83. more examples gitter-cli by rodrigo espinosa

  84. more examples tiny-care-terminal by monica dinculescu

  85. more examples slap-editor by christopher jeffrey and dan kaplun

  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!
  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