Slide 1

Slide 1 text

building command-line tools and dashboards with JavaScript galuh.me @galuhsahid

Slide 2

Slide 2 text

hi ! i'm galuh

Slide 3

Slide 3 text

hi ! i'm galuh "

Slide 4

Slide 4 text

command-line interface

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

why build command- line applications?

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

quotey From your own collection (.json) $ quotey -c "stoicism.json" From WikiQuote $ quotey -w "Grace Hopper"

Slide 9

Slide 9 text

quotey $ echo 'quotey -c "stoicism.json"' >> ~/.bash_profile

Slide 10

Slide 10 text

quotey $ quotey -c "stoicism.json" | grep Seneca Timendi causa est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca

Slide 11

Slide 11 text

how do we build one?

Slide 12

Slide 12 text

how do we build one? there are so many ways to do it

Slide 13

Slide 13 text

"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

Slide 14

Slide 14 text

anatomy of a CLI application

Slide 15

Slide 15 text

stdout $ quotey -c "stoicism.json" | grep Seneca Timendi causa est nescire - Ignorance is the cause of fear. -- Seneca > -- Seneca

Slide 16

Slide 16 text

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");

Slide 17

Slide 17 text

stderr console.error("Lorem ipsum"); process.stderr.write("Lorem Ipsum");

Slide 18

Slide 18 text

exit status function quit() { return process.exit(0); } • 0 - 255 • 0: success, otherwise: failure

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

input: commands $ git init $ git help

Slide 21

Slide 21 text

input: commands $ git init $ git help $ rm -rf my-directory

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

input: options $ git help -a $ git help -g $ rm -r -f my-directory $ rm -rf my-directory

Slide 24

Slide 24 text

input: arguments $ node index.js

Slide 25

Slide 25 text

ok, show me how to do it

Slide 26

Slide 26 text

lyrical - genius lyrics in your terminal features: • searches songs by artist • retrieves song lyrics given a song • genius API (node-genius + lyricist)

Slide 27

Slide 27 text

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)

Slide 28

Slide 28 text

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)

Slide 29

Slide 29 text

process.argv

Slide 30

Slide 30 text

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' ]

Slide 31

Slide 31 text

process.argv

Slide 32

Slide 32 text

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();

Slide 33

Slide 33 text

minimist "parse argument options"

Slide 34

Slide 34 text

minimist lyrical/index.js function getSongLyricsByID(songID, isLowercase) { ... console.log(song.lyrics); } function getSongsByArtist(artist) { ... console.log(song.title + " (" + song.id + ")"); }

Slide 35

Slide 35 text

minimist

Slide 36

Slide 36 text

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); } }

Slide 37

Slide 37 text

commander.js "The complete solution for node.js command-line interfaces, inspired by Ruby's commander."

Slide 38

Slide 38 text

commander.js

Slide 39

Slide 39 text

commander.js lyrical/index.js program .command('song ') .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 ') .alias('a') .description('Find list of songs by artist') .action( function (artist) { getSongsByArtist(artist) }); program.parse(process.argv);

Slide 40

Slide 40 text

commander.js

Slide 41

Slide 41 text

more • yargs • vorpal.js • meow

Slide 42

Slide 42 text

terminal user interface

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

inquirer.js

Slide 45

Slide 45 text

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: [] }

Slide 46

Slide 46 text

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();

Slide 47

Slide 47 text

blessed & blessed- contrib

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

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.

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

blessed escape sequences Events • Mouse • Keypress • Move • Resize • Show • Destroy • etc. Methods • hide() • show() • focus() • etc.

Slide 55

Slide 55 text

blessed when clicked

Slide 56

Slide 56 text

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' } });

Slide 57

Slide 57 text

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' } });

Slide 58

Slide 58 text

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' } });

Slide 59

Slide 59 text

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' } });

Slide 60

Slide 60 text

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' } });

Slide 61

Slide 61 text

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(); });

Slide 62

Slide 62 text

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(); });

Slide 63

Slide 63 text

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) { ... });

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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(); }

Slide 66

Slide 66 text

blessed-contrib escape sequences • makes it easier to create dashboards • provides even more widgets that work out of the box

Slide 67

Slide 67 text

blessed-contrib escape sequences

Slide 68

Slide 68 text

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})

Slide 69

Slide 69 text

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})

Slide 70

Slide 70 text

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})

Slide 71

Slide 71 text

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()

Slide 72

Slide 72 text

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()

Slide 73

Slide 73 text

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()

Slide 74

Slide 74 text

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()

Slide 75

Slide 75 text

what now? • utilize more widgets that come from blessed and blessed-contrib • create your own custom blessed widgets (https://recodes.co/blessed-custom-widgets/)

Slide 76

Slide 76 text

other fun stuff

Slide 77

Slide 77 text

colors • colors • chalk

Slide 78

Slide 78 text

colors lyrical/index.js var colors = require('colors'); ... function getSongLyricsByID(songID, isLowercase) { ... console.log(song.lyrics.underline.red); }

Slide 79

Slide 79 text

colors

Slide 80

Slide 80 text

progress bars • progress bars • node-progress • cli-progress

Slide 81

Slide 81 text

ASCII art • ASCII art • ascii-art • figlet

Slide 82

Slide 82 text

more examples medium-cli by dheeraj joshi

Slide 83

Slide 83 text

more examples gitter-cli by rodrigo espinosa

Slide 84

Slide 84 text

more examples tiny-care-terminal by monica dinculescu

Slide 85

Slide 85 text

more examples slap-editor by christopher jeffrey and dan kaplun

Slide 86

Slide 86 text

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!

Slide 87

Slide 87 text

• 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