Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Command-Line Tools and Dashboards with...

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.

Galuh Sahid

July 19, 2018
Tweet

More Decks by Galuh Sahid

Other Decks in Programming

Transcript

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

    est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca
  2. "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
  3. stdout $ quotey -c "stoicism.json" | grep Seneca Timendi causa

    est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca
  4. 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");
  5. exit status function quit() { return process.exit(0); } • 0

    - 255 • 0: success, otherwise: failure
  6. input: options $ git help -a $ git help -g

    $ rm -r -f my-directory $ rm -rf my-directory
  7. lyrical - genius lyrics in your terminal features: • searches

    songs by artist • retrieves song lyrics given a song • genius API (node-genius + lyricist)
  8. 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)
  9. 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)
  10. 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' ]
  11. 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();
  12. minimist lyrical/index.js function getSongLyricsByID(songID, isLowercase) { ... console.log(song.lyrics); } function

    getSongsByArtist(artist) { ... console.log(song.title + " (" + song.id + ")"); }
  13. 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); } }
  14. 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);
  15. 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: [] }
  16. 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();
  17. 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.
  18. 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
  19. 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
  20. 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
  21. blessed escape sequences Events • Mouse • Keypress • Move

    • Resize • Show • Destroy • etc. Methods • hide() • show() • focus() • etc.
  22. 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' } });
  23. 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' } });
  24. 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' } });
  25. 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' } });
  26. 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' } });
  27. 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(); });
  28. 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(); });
  29. 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) { ... });
  30. blessed screen.key(['escape', 'q', 'C-c'], function (ch, key) { return process.exit(0);

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

    • provides even more widgets that work out of the box
  33. 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})
  34. 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})
  35. 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})
  36. 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()
  37. 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()
  38. 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()
  39. 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()
  40. what now? • utilize more widgets that come from blessed

    and blessed-contrib • create your own custom blessed widgets (https://recodes.co/blessed-custom-widgets/)
  41. 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!
  42. • 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