├── .gitignore ├── LESSON1.md ├── LESSON2.md ├── PROJECT.md ├── README.md ├── lesson-1 ├── cat.js ├── grep.js ├── ls.js └── public │ ├── grep.scm │ ├── index.html │ └── solutions.js ├── lesson-2 ├── append.js ├── cat.js ├── piping.js ├── public │ ├── example.js │ └── index.html ├── wc.js └── write.js ├── project ├── functions.js └── test.js └── solutions ├── lesson-1 ├── cat.js ├── grep.js └── ls.js └── lesson-2 ├── append.js ├── cat.js ├── piping.js ├── wc.js └── write.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LESSON1.md: -------------------------------------------------------------------------------- 1 | # Lesson 1 2 | 3 | ## Introduction 4 | 5 | It's important to realise that, in giving you access to the file system and network, 6 | node can be used for more than just creating web servers. 7 | 8 | In this series of lessons we are going to use some of node's features to do some shell scripting! 9 | 10 | A shell script is a program designed to be run by the Unix shell. Typical operations performed by shell scripts include file manipulation, program execution, and printing text. 11 | 12 | You've likely come across some typical shell commands already. Have you used `cat`, `ls`, or `grep` in your 13 | terminal before? To start off today, we're going to implement our own versions of these commands using javascript. 14 | 15 | ### Getting started 16 | 17 | Clone this repo and go into the lesson-1 folder in your terminal. The relevant files for this first part of the workshop are there. 18 | 19 | From your terminal if you want to execute a javascript program you've written you can use the following command: 20 | 21 | ``` 22 | node path_to_program.js 23 | ``` 24 | 25 | Note the path you give to the program is relative to what directory you are in. 26 | 27 | Something you may not know is that you can actually pass arguments into your javascript program from 28 | the command line as well. For example: 29 | 30 | ``` 31 | node path_to_program.js node is awesome 32 | ``` 33 | 34 | All arguments (in this case `'node'`, `'is'`, and `'awesome'`, separated by spaces) will be passed into your program as strings and can be accessed inside it using an object called `process` that is in-built with node. 35 | 36 | `process` has a property called `argv`. It is an array. `process.argv[0]` is always the path to node on your machine, and `process.argv[1]` is always the path to the executed javascript file. 37 | 38 | In this case `process.argv` will be: 39 | 40 | ```javscript 41 | process.argv // = [path_to_node, path_to_current_file, 'node', 'is', 'awesome']; 42 | 43 | //so you can access the arguments you pass in like so: 44 | 45 | process.argv[2] // = 'node'; 46 | process.argv[3] // = 'is'; 47 | process.argv[4] // = 'awesome'; 48 | 49 | ``` 50 | 51 | This is useful as it means we can pass into our programs the names of, or paths to, files, to be manipulated within the node program. 52 | 53 | `process` has other useful methods. One in particular is `process.stdout.write()`. It is very similar to `console.log()` in that it will take what you input it and output it to the terminal. It has subtle differences, try experimenting with both in this exercise. 54 | 55 | In this case if we use `node path_to_file.js node is awesome` to call `path_to_file.js` and inside the program write: 56 | 57 | ``` 58 | process.stdout.write(process.argv[2] + process.argv[3] + process.argv[4]); 59 | ``` 60 | 61 | It will output `node is awesome` to the terminal. 62 | 63 | ### Exercise 1 - cat 64 | 65 | In unix, `cat` is a command that takes the path to a file as its argument and will output the contents of that file to the terminal. It is used like this: 66 | 67 | `cat path_to_file.extension` 68 | 69 | Try outputting the contents of `index.html` in the public folder of this repo to the terminal to see what it looks like. 70 | 71 | #### Task 72 | 73 | Inside `cat.js` write a program that when called like this from the terminal 74 | 75 | `node path_to_cat.js path_to_file.extension` 76 | 77 | will output the contents of that latter file to the terminal like `cat`. 78 | 79 | *Hint: You will need the `process` object and the `fs` module.* 80 | 81 | ### Exercise 2 - ls 82 | 83 | `ls` is a command that when called will output the names of all the files and directories in the directory it was called from to the terminal. It is called like this: 84 | 85 | `ls` 86 | 87 | Try using `ls` in one of the folders of this repo to see what it looks like. 88 | 89 | #### Task 90 | 91 | Inside `ls.js` write a program that when called like this from the terminal `node path_to_ls.js` will output the names of all the files and directories in the directory you called from to the terminal. 92 | 93 | Calling `node ls.js` from the lesson-1 folder of this repo should print: 94 | 95 | `cat.js grep.js ls.js public ` 96 | 97 | Calling `node ../ls.js` from the public folder should print: 98 | 99 | `grep.scm index.html solutions.js `, etc. 100 | 101 | Don't worry about being exact with the spacing, just print them on the same line with some spacing. 102 | 103 | *Hint: you're going to need the method `process.cwd()` to access the directory your node command was called from. (You can't say I'm cryptic!).* 104 | 105 | ### Exercise 3 - options 106 | 107 | Many unix commands have what are called options. Options are arguments you can pass to the execution of the command that modify its behaviour. They are typically in the format of a dash followed by a lowercase letter. 108 | 109 | `ls -a` for example will show all the directories/files starting with a dot (like `.gitignore`) that will otherwise be hidden when you call the `ls` command. 110 | 111 | #### Task 112 | 113 | Your task is to modify your existing `ls` command in `ls.js` so that it can accept an option argument. 114 | 115 | Specifically: `node path_to_ls.js -ex extension`. If `-ex` is passed as first argument, your `ls` command should only print the names of the files in the current directory that have the extension specified by the second argument. Your program should do its normal behaviour if no arguments are provided. 116 | 117 | `node ls.js -ex js` when called from the root directory should print: 118 | 119 | `cat.js grep.js ls.js ` 120 | 121 | and `node ../ls.js -ex scm` from public: 122 | 123 | `grep.scm `, etc. 124 | 125 | ### Exercise 4 - grep 126 | 127 | `grep` stands for 'global regular expression print.' It's essentially a Unix regular expression command that will print to the terminal all lines in a file that match a specified pattern. 128 | 129 | In our public directory calling `grep 'all' grep.scm` from the command-line will print: 130 | 131 | ``` 132 | ;;; And fill all fruit with ripeness to the core; 133 | ;;; Spares the next swath and all its twined flowers: 134 | ;;;Then in a wailful choir the small gnats mourn 135 | ;;; Among the river sallows, borne aloft 136 | ;;; And gathering swallows twitter in the skies. 137 | ``` 138 | 139 | Where `all` is the pattern it is looking for and `grep.scm` the specified file to search for it in. 140 | Note that it prints the whole line if it finds the pattern inside it. 141 | 142 | #### Task 143 | 144 | Inside `grep.js` write a program that when executed like this 145 | 146 | `node path_to_grep.js pattern file` 147 | 148 | will print every line in the file specified that matches the pattern specified. Each line should be printed on its own line. 149 | 150 | *Hint: there are multiple ways of solving this.* 151 | 152 | *A specific core module exists that allows you to access the whole line of a file as you're reading it. Look through the list of node core modules or use Stack Overflow if you get stuck. Please don't use third party modules for this exercise. You should default to using core modules while you're learning where it's possible to.* 153 | 154 | ### Making your new scripts executable 155 | 156 | So that was fun! But wouldn't it be nice if rather than having to type 157 | 158 | `node script.js arguments` 159 | 160 | adjusting the script's file path all the time relative to our current location we could just run `script` in our terminal anywhere in our directory tree and know it will work just like `cat`, `ls`, and `grep`? 161 | 162 | You can do that in node and it only takes a few simple steps! 163 | 164 | 1: In each of your cat.js, ls.js, and grep.js files add this to the top line of the file: 165 | 166 | ``` 167 | #!/usr/bin/env node 168 | ``` 169 | 170 | 2: Run `npm init` in the root folder of this repo and create a `package.json`. 171 | 172 | 3: Adjust your `package.json` as follows: 173 | 174 | * remove the main entry: this is only used for modules that will be used through the module system (e.g. var _ = require('underscore');). 175 | * add preferGlobal and set it to true, which means if someone installs this module through npm and doesn’t use the --global option, they will be warned that the module is designed to be installed globally. 176 | * add the bin object, which maps commands to files. This means when this module is installed, npm will set up the named executables to execute their assigned javascript files. Don't name them after existing commands like ls, grep, etc, it needs to be unique. 177 | 178 | Your `package.json` should now look like this: 179 | 180 | ```javascript 181 | { 182 | "name": "node-shell-workshop", 183 | "version": "1.0.0", 184 | "description": "learn how to shell script with node", 185 | "scripts": { 186 | "test": "echo \"Error: no test specified\" && exit 1" 187 | }, 188 | "author": "your lovely selves", 189 | "license": "ISC", 190 | "preferGlobal": true, 191 | "bin": { 192 | "your-name-here-cat": "lesson-1/cat.js", 193 | "your-name-here-ls": "lesson-1/ls.js", 194 | "your-name-here-grep": "lesson-1/grep.js" 195 | } 196 | } 197 | ``` 198 | 199 | 4: Now in the same repo you can run `npm link` to install the script on your system. This creates a symlink to your project so that you can run the project whilst working on it, with no need to keep reinstalling it over and over again. 200 | 201 | 5: Now... move into a different directory and try out... 202 | 203 | ``` 204 | your-name-here-cat file.extension 205 | your-name-here-ls 206 | your-name-here-ls -ex extension 207 | your-name-here-grep pattern file.extension 208 | ``` 209 | 210 | : - ) 211 | 212 | [Click here to move on to LESSON 2](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/LESSON2.md) 213 | -------------------------------------------------------------------------------- /LESSON2.md: -------------------------------------------------------------------------------- 1 | # Lesson 2 2 | 3 | ## Introduction 4 | 5 | To recap, in the last lesson we covered: 6 | 7 | Executing a javascript program from the command-line using: 8 | 9 | ``` 10 | node file.js 11 | ``` 12 | 13 | The ability to pass in arguments to your file from the command-line: 14 | 15 | ``` 16 | node file.js argument1 argument2 17 | ``` 18 | 19 | Accessing them (as strings) within your file using the `process` object: 20 | 21 | ```javascript 22 | process.argv // = [path_to_node, path_to_javascript_file, 'argument1', 'argument2']; 23 | process.argv[2] // = 'argument1'; 24 | process.argv[3] // = 'argument2'; 25 | ``` 26 | 27 | Some of the `process` object's other methods: 28 | 29 | ```javascript 30 | process.stdout.write(input) // takes input and outputs it to the terminal screen; 31 | process.cwd() // returns the path to the directory your node command was called from; 32 | ``` 33 | 34 | And how to make our node scripts executable from the command-line. 35 | 36 | ``` 37 | your-name-here-cat public/index.html 38 | ``` 39 | 40 | To proceed with this lesson please clone this repo and go into the lesson-2 folder. **(N.B. If you cloned the repo in lesson 1, there is no need to clone again!)** 41 | 42 | ### Read Streams 43 | 44 | In this lesson we are going to cover an alternative way of reading and writing to files 45 | using one of the core features of node: `streams`. 46 | 47 | Whenever you've watched a video online, have you noticed you can start watching it 48 | even though the whole video hasn't finished loading? That's because it is being 'streamed', bit by bit, so that as every chunk of its data becomes available it is immediately put to use. 49 | 50 | A `stream` in node is simply an interface for working with 'streaming data' like this. 51 | Streams can be `readable` (e.g. reading a file), `writable` (e.g. writing new content to a 52 | file), or both. 53 | 54 | Up until now, whenever you've needed to read a file using node's `fs` module you've likely 55 | done something like the following: 56 | 57 | ```javascript 58 | fs.readFile(file, function(err, file) { 59 | if (err) console.error(err); 60 | // do something with file 61 | }); 62 | ``` 63 | 64 | The problem with this is that `readFile` will wait until it has read the entirety of 65 | the file provided before it will fire the callback that does something with it, which could take time. 66 | 67 | With streams we can act on the contents of the file as it is being read which in certain scenarios is 68 | a more efficient solution. The following accomplishes the exact same effect as `fs.readFile` with a read stream, however. 69 | 70 | ```javascript 71 | var readStream = fs.createReadStream(file); 72 | //set up a read stream on a target file and store it in a variable 73 | 74 | var fileContent = ''; 75 | //create an empty string we will use to store the contents of the read file. 76 | 77 | readStream.on('data', function (chunk) { 78 | fileContent += chunk; 79 | }); 80 | //every time a new chunk of the read file becomes available we append it to our fileContent variable 81 | 82 | readStream.on('end', function() { 83 | // do something with fileContent 84 | }); 85 | //once the stream has finished fileContent will contain all the content of the read file and we can 86 | //do something with it. 87 | ``` 88 | 89 | #### Breaking this down 90 | 91 | `streams` have a method `stream.on('event', function () {})`. What it does is subscribes a function to the specified event, so that it will be executed every time the event occurs. 92 | 93 | ``` 94 | readStream.on('data', function (chunk) { 95 | fileContent += chunk; 96 | }); 97 | ``` 98 | 99 | Here `data` is the type of event. The target file of `readStream` will be read bit by bit. Every time a new chunk becomes available, the `data` event is triggered and the function is called. Its first argument is always 100 | the contents of the new available `chunk`. 101 | 102 | Here we just append each chunk of new content to the `fileContent` variable as soon 103 | as it becomes available. Finally, when the stream has finished reading the file the `end` event is triggered. 104 | At this point, the whole file has been read chunk by chunk, and the variable `fileContent` 105 | should contain all the content of the read file. 106 | 107 | #### fs.readFile under the hood 108 | 109 | Under the hood, this is something akin to the definition of `fs.readFile`, it basically does the same thing as the above read stream example!: 110 | 111 | ```javascript 112 | //function definition 113 | fs.readFile = function(file, cb) { 114 | 115 | var readStream = fs.createReadStream(file); 116 | var fileContent = ''; 117 | 118 | readStream.on('data', function(chunk) { 119 | fileContent += chunk; 120 | }); 121 | 122 | readStream.on('error', function(err) { 123 | cb(err, fileContent) 124 | }); 125 | 126 | readStream.on('end', function() { 127 | cb(null, fileContent); 128 | }); 129 | } 130 | 131 | //function execution 132 | fs.readFile(index.html, function (err, file) { 133 | if (err) throw err; 134 | console.log(file); 135 | }); 136 | ``` 137 | 138 | If you want to see a detailed break-down on this at any point there are notes on this example stored [here.](https://github.com/FAC9/notes/blob/master/week4%265-Node/fs-readFile.md) It's not necessary to go into that much depth to do these exercises, the syntax you need is provided, just in case it provides any clarity. 139 | 140 | #### Task 141 | 142 | Inside `cat.js` re-write your `cat` command to use a read stream instead of `fs.readFile`. 143 | 144 | To recap, your `cat` command should be executed like this: 145 | 146 | `node cat.js file.extension` 147 | 148 | It should output the contents of `file.extension` to the terminal. You can try using 149 | your command on some example files in the public folder. 150 | 151 | *Hint: If you see something like this get outputted to your terminal:* 152 | 153 | ``` 154 | 155 | ``` 156 | 157 | *This is called a 'buffer'. It's an encoded format that represents the file's raw 158 | binary data. Each part of the sequence `68`, `65`, `6c` etc, represent characters 159 | of the file that is being read. `10` for example is equivalent to `/n`. To convert 160 | the buffer into a string you can use the `toString()` method, or provide `'utf-8'` as the 161 | second argument of `fs.createReadStream`.* 162 | 163 | ### Write Streams 164 | 165 | If read streams let you read files, write streams let you write content to them! 166 | 167 | Create a new file now called `write-stream.js` and try out the following. Type it rather than copy and paste: 168 | 169 | ```javascript 170 | var fs = require("fs"); 171 | var data = 'Simply Easy Learning'; 172 | 173 | // Create a writable stream 174 | var writeStream = fs.createWriteStream('output.txt'); 175 | 176 | // Write the data to stream with encoding to be utf8 177 | writeStream.write(data,'UTF8'); 178 | 179 | // Mark the end of file 180 | writeStream.end(); 181 | 182 | // When the stream finishes log 'write completed' 183 | writeStream.on('finish', function() { 184 | console.log("Write completed."); 185 | }); 186 | ``` 187 | 188 | Now try running `node write-stream.js`. It should log `Write completed.` to the terminal and a new file called `output.txt` with the content `Simply Easy Learning` should have been created. 189 | 190 | Did you notice write streams use `.write()` and `.end()` like the `response` object of your servers? That's because the `response` object is a write stream and when you're responding to the client you're 'writing' content to it! 191 | 192 | The `request` object, likewise, is a read stream as when the client makes a request you're 'reading' the content that has been 'streamed' to the server! 193 | 194 | ### Redirection 195 | 196 | In Unix, it is possible to take the output of a command that would normally be printed 197 | to the terminal (standard output) and redirect it so that the contents of that output 198 | are written to a file instead. 199 | 200 | The command we use to accomplish this is `>` : 201 | 202 | ``` 203 | cat public/index.html > public/example.js 204 | ``` 205 | 206 | `cat public/index.html` will read the html file and output its contents, then `>` will take 207 | this output and redirect it so that it is written to `public/example.js` instead. 208 | 209 | Go into the public folder and try this: 210 | 211 | ``` 212 | node path_to_your_cat.js index.html > example.js 213 | ``` 214 | 215 | Can you see `example.js` now has been overwritten to contain the contents of `index.html`? 216 | 217 | *(note: this command will over-write the file's prior content so be careful using this on your 218 | solution scripts.)* 219 | 220 | #### Task 221 | 222 | Inside `write.js` modify your `cat` command from the first exercise so that you can 223 | give it the following arguments 224 | 225 | ``` 226 | node write.js path_to_read_file '>' path_to_write_file 227 | ``` 228 | 229 | If `'>'` is given as argument followed by another file as an argument it will, 230 | instead of outputting the contents of `path_to_read_file` to the terminal, write the contents 231 | of it to `path_to_write_file` instead. 232 | 233 | *Hint: To write content to `path_to_write_file` you will need to create a write stream like so:* 234 | 235 | ```javascript 236 | var writeStream = fs.createWriteStream(path_to_write_file) 237 | ``` 238 | 239 | *If you want to take the output of a read stream and make it become the input 240 | of a write stream, this is called 'piping.' Piping in node is done using `streams` 241 | `pipe()` method:* 242 | 243 | ```javascript 244 | var readStream = fs.createReadStream(path_to_read_file); 245 | var writeStream = fs.createWriteStream(path_to_write_file); 246 | 247 | readStream.pipe(writeStream); 248 | ``` 249 | *What this code snippet means is every time a new `chunk` of `path_to_read_file` gets read by 250 | `readStream` it will immediately be redirected to become the input of `writeStream`. This input 251 | will get written to `path_to_write_file`.* 252 | 253 | ### Appending files 254 | 255 | You may not always want to completely re-write the contents of a file. What if 256 | you want the content of a file to remain intact but simply append new content 257 | onto the end of it? 258 | 259 | In Unix you can do this using `>>` : 260 | 261 | ``` 262 | cat public/index.html >> public/example.js 263 | ``` 264 | 265 | `cat public/index.html` will read the html file and output its contents, then `>>` will take 266 | this output and redirect it so that it is appended to the contents of `public/example.js`. 267 | 268 | Go into the public folder and try this: 269 | 270 | ``` 271 | node path_to_your_cat.js index.html >> example.js 272 | ``` 273 | 274 | Can you see `example.js` now has the contents of `index.html` appended onto the end? 275 | 276 | #### Task 277 | 278 | Inside `append.js` modify your `cat` command from the first exercise so that you can 279 | give it the following arguments 280 | 281 | ``` 282 | node append.js path_to_read_file '>>' path_to_write_file 283 | ``` 284 | 285 | If `'>>'` is provided as an argument followed by a file as another argument it will, 286 | instead of outputting the contents of `path_to_read_file` to the terminal, append 287 | it to `path_to_write_file` instead. 288 | 289 | *Hint: There are multiple ways of solving this. `fs.createReadStream`, and `fs.createWriteStream` 290 | can be passed a flags object as a second argument. In particular:* 291 | 292 | ```javascript 293 | var writeStream = fs.createWriteStream(path_to_write_file, { 'flags': 'a' }) 294 | ``` 295 | 296 | *allows write streams to append instead of write content.* 297 | 298 | ### Piping 299 | 300 | Piping is an incredibly powerful feature of Unix I/O (input/ output) shell scripting. 301 | We've already seen a little bit of its power in the last few tasks in how it allows 302 | us to chain commands. 303 | 304 | Piping allows you to take the output of one command and make it the input of the next 305 | using the `|` syntax: 306 | 307 | ``` 308 | grep 'html' public/index.html | wc -l 309 | ``` 310 | 311 | will output `4` to the terminal. 312 | 313 | Why? `grep` prints to the console all lines in `index.html` in which it finds the word `html`. 314 | `|` then takes the output (these four lines) and makes it the input of `wc -l`. `wc -l`is a command 315 | that counts how many lines are in a file. As the input given to it is four lines long, it returns 316 | `4` as output. 317 | 318 | You could continue piping on indefinitely: `command1 | command2 | command3`, etc. 319 | Each command's output becomes the next command's input. 320 | 321 | #### Task 322 | 323 | Firstly, inside `wc.js` try implementing your own `wc -l` in the following way: 324 | 325 | ``` 326 | node wc.js file.extension 327 | ``` 328 | 329 | It should print to the console the number of lines in the file specified by the argument. 330 | `node wc.js index.html` should print `10` (unless you modified the file to have more lines.) 331 | Try to use `fs.createReadStream` rather than `fs.readFile` to practice. 332 | 333 | Once you've done this, adjust your `wc.js` so that you can pipe into it the output of your `cat.js` 334 | command like this: 335 | 336 | ``` 337 | node cat.js public/index.html | node wc.js 338 | ``` 339 | 340 | This should output `10` still. 341 | 342 | Do not try to merge your `cat.js` and `wc.js` into a single file, that's cheating, 343 | the output of `cat.js` should become the input of `wc.js` using unix's in-built `|` syntax. 344 | 345 | *Hint: In order to access the input that has been piped into your program from a separate 346 | file you will need to use `process.openStdin()` (it means, open standard input):* 347 | 348 | ```javascript 349 | var stdin = process.openStdin(); 350 | 351 | var data = ''; 352 | 353 | stdin.on('data', (chunk) => { 354 | data += chunk; 355 | }); 356 | 357 | stdin.on('end', () => { 358 | console.log(data); 359 | }); 360 | ``` 361 | 362 | *This is exactly the same as our read stream example at the top of this readme, as 363 | process.openStdin() is a read stream!* 364 | 365 | ### Project 366 | 367 | With what we have covered in these two lessons, we now have everything we need to build some command-line tools! Move onto 368 | the [PROJECT.md](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/PROJECT.md) when ready. 369 | -------------------------------------------------------------------------------- /PROJECT.md: -------------------------------------------------------------------------------- 1 | # Project 2 | 3 | ## Introduction 4 | 5 | Now we have learned the basics of shell scripting with node.js we can put it to 6 | practice and build our own command-line tools! 7 | 8 | Specifically in this lesson your task is to build a test output formatter. A test output formatter is a program that when you pipe the results of your tests into, will read the results 9 | of those tests and reformat them as output. We will use tape for our tests in this exercise. 10 | 11 | A simple example of an output formatter is `tap-nyan`. To install it, create a `package.json` 12 | using `npm init`, and run `npm install --save-dev tap-nyan`. If you call `tap-nyan` like so: 13 | 14 | ``` 15 | node test.js | tap-nyan 16 | ``` 17 | 18 | It will output something like this to the terminal: 19 | 20 | ``` 21 | 13 -_-_-_-_-_-_-_,------, 22 | 0 -_-_-_-_-_-_-_| /\_/\ 23 | 0 -_-_-_-_-_-_-^|__( ^ .^) 24 | -_-_-_-_-_-_- "" "" 25 | Pass! 26 | ``` 27 | 28 | If you get an error message saying something like `command tap-nyan cannot be found` try running it like this instead: 29 | 30 | ``` 31 | node test.js | node_modules/.bin/tnyan 32 | ``` 33 | 34 | ### What's happening here? 35 | 36 | Something like this can be implemented using what you've already learned. The 37 | command `node test.js` executes the test file specified and outputs the results of 38 | the tape tests to the terminal. They look like this: 39 | 40 | ``` 41 | TAP version 13 42 | # Description for your test 43 | ok 1 just testing everything works 44 | # Simple values, number, strings, booleans 45 | ok 2 should be equal 46 | ok 3 should be equal 47 | ok 4 should be equal 48 | # Complex values - objects and arrays 49 | ok 5 should be equivalent 50 | ok 6 should be equivalent 51 | # Truthy and falsy 52 | ok 7 should be truthy 53 | ok 8 should be falsy 54 | ok 9 should be falsy 55 | # Async testing 56 | ok 10 should be equal 57 | ok 11 should be equal 58 | # Error handling 59 | ok 12 should throw 60 | ok 13 should be equal 61 | 62 | 1..13 63 | # tests 13 64 | # pass 13 65 | 66 | # ok 67 | ``` 68 | 69 | Using the `|` command this output then becomes the input of `tap-nyan`. Remember we 70 | can access piped in input, as a read stream, in our node programs using the 71 | `process.openStdin()` method. 72 | 73 | Then all the program is doing is reading the outputs of these tests, tallying how many 74 | pass or fail, and logging `pass!` if they all pass along with a cute ascii image. 75 | 76 | Try making some tests fail and seeing what `tap-nyan` responds with instead: 77 | 78 | ``` 79 | 12 -_-_-_-_-_-_-_,------, 80 | 1 -_-_-_-_-_-_-_| /\_/\ 81 | 0 -_-_-_-_-_-_-^|__( x .x) 82 | -_-_-_-_-_-_- "" "" 83 | Failed Tests: There was 1 failure 84 | 85 | ✗ Async testing: should be equal 86 | ``` 87 | 88 | The image changes, it tallies the number of failing tests, and lets you know which 89 | tests failed. 90 | 91 | ### Task 92 | 93 | Make your own output formatter! You can make it simple, like `tap-nyan`, or as complex 94 | as you like (how about piping the results of multiple test files into it?). The 95 | primary aim is just to have fun and be creative. 96 | 97 | I would recommend just starting out printing two numbers to the terminal, one being how many of the 98 | tests passed, the other how many of the tests failed. You can build on this to print 99 | 'pass!' if all tests pass and 'fail!' if any test failed. Then add-in your own styles, and features. 100 | 101 | Clone this repo and go into the `project` folder to find an example tape test file 102 | you can use. You'll need to create a `package.json` and install tape. 103 | 104 | Your output formatter should be implemented like so: 105 | 106 | ``` 107 | node test.js | node output-formatter.js 108 | ``` 109 | 110 | or, if you make it executable (follow the instructions in [LESSON1.md](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/LESSON1.md) to do this): 111 | 112 | ``` 113 | node test.js | output-formatter 114 | ``` 115 | 116 | The only program you need to write is `output-formatter.js`, however feel free 117 | to modularise and write tests for the code if it becomes appropriate. 118 | 119 | ### Colours and styling 120 | 121 | If you want to add colours and stylings to what you output to the terminal, the 122 | manual way of doing it is to add escape sequences to your `console.log()` like so: 123 | 124 | ```javascript 125 | console.log('\x1b[36m]', 'sometext'); // 'sometext's colour will be modified 126 | ``` 127 | 128 | The easier way to do it is to use a third-party module like `colors` or `chalk` which 129 | abstracts this for you. Go to their github repo's to find instructions on how to use 130 | and install them. 131 | 132 | chalk: https://github.com/chalk/chalk 133 | 134 | colors: https://www.npmjs.com/package/colors 135 | 136 | ### Publishing to npm 137 | 138 | Now we have created our own command-line tool, we have in effect created our 139 | own node module we can publish to the npm registry that others can 140 | install and use! 141 | 142 | Please first use the instructions in [LESSON1.md](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/LESSON1.md) to make your script `node output-formatter.js` globally executable. 143 | 144 | #### Creating a user 145 | 146 | You can publish any directory to npm that has a `package.json`. 147 | 148 | To publish, you must have a user on the npm registry. If you don't have one, you 149 | can create one with the command `npm adduser`. If you created one on the npm site, 150 | use `npm login` to store the credentials on the client. 151 | 152 | You can use `npm config ls` to ensure that the credentials are stored on your client. 153 | Check that it has been added to the registry by going to https://npmjs.com/~. 154 | 155 | #### Publishing your package 156 | 157 | *Note: The following is here for educational purposes. It's advisable to not be too frivolous in publishing to npm. If you do, be sure to provide ample documentation for your project on your repository and on npm.* 158 | 159 | Use `npm publish` to publish the package! That's it! 160 | 161 | Note that everything in the directory will be included unless it is ignored by a local `.gitignore` or `.npmignore` file. 162 | 163 | Also make sure there isn't already a package with the same name, owned by somebody else. 164 | 165 | If you want to test this has worked go to https://npmjs.com/package/. You should see the information for your new package. 166 | 167 | If you then want someone to install your new module onto their machine, try out: 168 | 169 | ``` 170 | npm install -g name-of-your-package 171 | ``` 172 | 173 | : - ) 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Author**: [@bradreeder](https://github.com/bradreeder) 2 | 3 | **Maintainer**: [@bradreeder](https://github.com/bradreeder) 4 | 5 | # Node Shell Workshop 6 | 7 | A tutorial that takes you through the basics of doing shell scripting with node.js. It culminates in a project using the lessons learned to build a small command-line tool. 8 | 9 | See [LESSON1.md](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/LESSON1.md) for the first lesson, [LESSON2.md](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/LESSON2.md) for the second, and [PROJECT.md](https://github.com/bradreeder/Node-Shell-Workshop/blob/master/PROJECT.md) for the final project. 10 | 11 | ## Resources 12 | 13 | Some useful resources for the project: 14 | 15 | * On using colour & styles on the output of your terminal stdout's: [this](https://coderwall.com/p/yphywg/printing-colorful-text-in-terminal-when-run-node-js-script) and [this.](http://blog.soulserv.net/terminal-friendly-application-with-node-js/) 16 | * On creating a terminal-menu: [this](https://github.com/substack/terminal-menu) and [this.](https://github.com/Pomax/terminal-menu-program) 17 | * [On displaying images (ascii art) in the terminal.](http://askubuntu.com/questions/97542/how-do-i-make-my-terminal-display-graphical-pictures) 18 | -------------------------------------------------------------------------------- /lesson-1/cat.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-1/cat.js -------------------------------------------------------------------------------- /lesson-1/grep.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-1/grep.js -------------------------------------------------------------------------------- /lesson-1/ls.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-1/ls.js -------------------------------------------------------------------------------- /lesson-1/public/grep.scm: -------------------------------------------------------------------------------- 1 | ;;;Season of mists and mellow fruitfulness, 2 | ;;; Close bosom-friend of the maturing sun; 3 | ;;;Conspiring with him how to load and bless 4 | ;;; With fruit the vines that round the thatch-eves run; 5 | ;;;To bend with apples the moss'd cottage-trees, 6 | ;;; And fill all fruit with ripeness to the core; 7 | ;;; To swell the gourd, and plump the hazel shells 8 | ;;; With a sweet kernel; to set budding more, 9 | ;;;And still more, later flowers for the bees, 10 | ;;;Until they think warm days will never cease, 11 | ;;; For summer has o'er-brimm'd their clammy cells. 12 | 13 | ;;;Who hath not seen thee oft amid thy store? 14 | ;;; Sometimes whoever seeks abroad may find 15 | ;;;Thee sitting careless on a granary floor, 16 | ;;; Thy hair soft-lifted by the winnowing wind; 17 | ;;;Or on a half-reap'd furrow sound asleep, 18 | ;;; Drows'd with the fume of poppies, while thy hook 19 | ;;; Spares the next swath and all its twined flowers: 20 | ;;;And sometimes like a gleaner thou dost keep 21 | ;;; Steady thy laden head across a brook; 22 | ;;; Or by a cyder-press, with patient look, 23 | ;;; Thou watchest the last oozings hours by hours. 24 | 25 | ;;;Where are the songs of spring? Ay, Where are they? 26 | ;;; Think not of them, thou hast thy music too,— 27 | ;;;While barred clouds bloom the soft-dying day, 28 | ;;; And touch the stubble-plains with rosy hue; 29 | ;;;Then in a wailful choir the small gnats mourn 30 | ;;; Among the river sallows, borne aloft 31 | ;;; Or sinking as the light wind lives or dies; 32 | ;;;And full-grown lambs loud bleat from hilly bourn; 33 | ;;; Hedge-crickets sing; and now with treble soft 34 | ;;; The red-breast whistles from a garden-croft; 35 | ;;; And gathering swallows twitter in the skies. 36 | -------------------------------------------------------------------------------- /lesson-1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Node Shell Scripting - Lesson 1 6 | 7 | 8 | i can has cat? 9 | 10 | 11 | -------------------------------------------------------------------------------- /lesson-1/public/solutions.js: -------------------------------------------------------------------------------- 1 | https://www.youtube.com/watch?v=dQw4w9WgXcQ 2 | -------------------------------------------------------------------------------- /lesson-2/append.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-2/append.js -------------------------------------------------------------------------------- /lesson-2/cat.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-2/cat.js -------------------------------------------------------------------------------- /lesson-2/piping.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-2/piping.js -------------------------------------------------------------------------------- /lesson-2/public/example.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-2/public/example.js -------------------------------------------------------------------------------- /lesson-2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | This is example html. 9 | 10 | 11 | -------------------------------------------------------------------------------- /lesson-2/wc.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-2/wc.js -------------------------------------------------------------------------------- /lesson-2/write.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/lesson-2/write.js -------------------------------------------------------------------------------- /project/functions.js: -------------------------------------------------------------------------------- 1 | function asyncDouble (n, cb) { 2 | setTimeout(function () { 3 | if (typeof n !== 'number') { 4 | cb(new TypeError('Expected number')); 5 | } else { 6 | cb(null, n * 2); 7 | } 8 | }, 10); 9 | } 10 | 11 | function checkWin (score) { 12 | if (score < 20) { 13 | throw new Error('Too low'); 14 | } else { 15 | return 'You win!'; 16 | } 17 | } 18 | 19 | module.exports = { 20 | asyncDouble, 21 | checkWin, 22 | }; 23 | -------------------------------------------------------------------------------- /project/test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var { asyncDouble, checkWin } = require('./functions'); 3 | 4 | test('Description for your test', function(t) { 5 | t.pass('just testing everything works'); 6 | t.end(); 7 | }); 8 | 9 | test('Simple values, number, strings, booleans', function(t) { 10 | t.equal(1, 1); 11 | t.equal('string', 'string'); 12 | t.equal(true, true); 13 | t.end(); 14 | }); 15 | 16 | test('Complex values - objects and arrays', function(t) { 17 | t.deepEqual([1, 2, 3], [1, 2, 3]); 18 | t.deepEqual({}, {}); 19 | t.end(); 20 | }); 21 | 22 | test('Truthy and falsy', function(t) { 23 | t.ok(true); 24 | t.notOk(false); 25 | t.notOk(''); 26 | t.end(); 27 | }); 28 | 29 | test('Async testing', function(t) { 30 | asyncDouble(2, function (error, value) { 31 | t.equal(error, null); 32 | t.equal(value, 4); 33 | t.end(); 34 | }); 35 | }); 36 | 37 | test('Error handling', function(t) { 38 | t.throws(function () { return checkWin(19); }); 39 | t.equal(checkWin(20), 'You win!'); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /solutions/lesson-1/cat.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = process.argv[2]; 5 | 6 | fs.readFile(process.cwd() + '/' + path, function(err, data) { 7 | if (err) { 8 | process.stdout.write('file dosnt exist'); 9 | } else { 10 | process.stdout.write(data); 11 | } 12 | }); -------------------------------------------------------------------------------- /solutions/lesson-1/grep.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const pattern = process.argv[2]; // the word would you like to search 5 | const fileName = process.argv[3]; //the file name or path 6 | 7 | fs.readFile(process.cwd() + '/' + fileName, function(err, data) { 8 | if (err) { 9 | process.stdout.write('file dosnt exist'); 10 | } else { 11 | var arr = data.toString().split('\n'); 12 | //console.log(arr); 13 | arr.forEach(function(line) { 14 | if (line.includes(pattern)) 15 | process.stdout.write(line + '\n'); 16 | }); 17 | 18 | } 19 | }); -------------------------------------------------------------------------------- /solutions/lesson-1/ls.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = process.cwd(); 5 | 6 | if (process.argv[2] === '-ex') { 7 | writeFiles(path, function(data) { 8 | data.forEach(function(file) { 9 | var fileExten = file.split('.'); 10 | if (fileExten[1] === process.argv[3]) 11 | process.stdout.write(file + '\n'); 12 | }); 13 | }); 14 | } else { 15 | writeFiles(path, function(data) { 16 | data.forEach(function(file) { 17 | process.stdout.write(file + '\n'); 18 | }); 19 | }); 20 | } 21 | 22 | function writeFiles(pth, cb) { 23 | fs.readdir(pth, function(err, data) { 24 | if (err) { 25 | process.stdout.write('file dosn\'t exist'); 26 | } else { 27 | cb(data); 28 | } 29 | }); 30 | } -------------------------------------------------------------------------------- /solutions/lesson-2/append.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = process.argv[2]; 3 | 4 | if (process.argv[3] === '>>') { 5 | var readStream = fs.createReadStream(path); 6 | var writeStream = fs.createWriteStream(process.argv[4], { 'flags': 'a' }); 7 | 8 | readStream.pipe(writeStream); 9 | } else { 10 | 11 | fs.readFile(process.cwd() + '/' + path, function(err, file) { 12 | if (err) { 13 | process.stdout.write('file dosnt exist'); 14 | } else { 15 | process.stdout.write(file); 16 | } 17 | }); 18 | } 19 | 20 | 21 | fs.readFile = function(file, cb) { 22 | 23 | var readStream = fs.createReadStream(file); 24 | var fileContent = ''; 25 | 26 | readStream.on('data', function(chunk) { 27 | fileContent += chunk; 28 | }); 29 | 30 | readStream.on('error', function(err) { 31 | cb(err, fileContent) 32 | }); 33 | 34 | readStream.on('end', function() { 35 | cb(null, fileContent); 36 | }); 37 | } -------------------------------------------------------------------------------- /solutions/lesson-2/cat.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = process.argv[2]; 3 | 4 | fs.readFile = function(file, cb) { 5 | 6 | var readStream = fs.createReadStream(file); 7 | var fileContent = ''; 8 | 9 | readStream.on('data', function(chunk) { 10 | fileContent += chunk; 11 | }); 12 | 13 | readStream.on('error', function(err) { 14 | cb(err, fileContent) 15 | }); 16 | 17 | readStream.on('end', function() { 18 | cb(null, fileContent); 19 | }); 20 | } 21 | 22 | 23 | fs.readFile(process.cwd() + '/' + path, function(err, file) { 24 | if (err) { 25 | process.stdout.write('file dosnt exist'); 26 | } else { 27 | process.stdout.write(file); 28 | } 29 | }); -------------------------------------------------------------------------------- /solutions/lesson-2/piping.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundersandcoders/Node-Shell-Workshop/7beef8f75f035f69e75f0c20688257b15ff1946b/solutions/lesson-2/piping.js -------------------------------------------------------------------------------- /solutions/lesson-2/wc.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = process.argv[2]; 3 | 4 | 5 | if (!path) { 6 | var stdin = process.openStdin(); 7 | var data = ''; 8 | stdin.on('data', function(chunk) { 9 | data += chunk; 10 | }); 11 | stdin.on('end', () => { 12 | var arr = data.toString().split('\n'); 13 | process.stdout.write(arr.length + '\n'); 14 | }); 15 | } else { 16 | var readStream = fs.createReadStream(path); 17 | var fileContent = ''; 18 | 19 | readStream.on('data', function(chunk) { 20 | fileContent += chunk; 21 | }); 22 | 23 | readStream.on('end', function() { 24 | var arr = fileContent.toString().split('\n'); 25 | process.stdout.write(arr.length + '\n'); 26 | }); 27 | } -------------------------------------------------------------------------------- /solutions/lesson-2/write.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = process.argv[2]; 3 | 4 | if (process.argv[3] === '>') { 5 | var readStream = fs.createReadStream(path); 6 | var writeStream = fs.createWriteStream(process.argv[4]); 7 | 8 | readStream.pipe(writeStream); 9 | } else { 10 | 11 | fs.readFile(process.cwd() + '/' + path, function(err, file) { 12 | if (err) { 13 | process.stdout.write('file dosnt exist'); 14 | } else { 15 | process.stdout.write(file); 16 | } 17 | }); 18 | } 19 | 20 | 21 | fs.readFile = function(file, cb) { 22 | 23 | var readStream = fs.createReadStream(file); 24 | var fileContent = ''; 25 | 26 | readStream.on('data', function(chunk) { 27 | fileContent += chunk; 28 | }); 29 | 30 | readStream.on('error', function(err) { 31 | cb(err, fileContent) 32 | }); 33 | 34 | readStream.on('end', function() { 35 | cb(null, fileContent); 36 | }); 37 | } --------------------------------------------------------------------------------