Branch data Line data Source code
1 [ + ]: 36 : /** 2 : 36 : * tee (GNU coreutils) 8.32 3 : 36 : * Copyright (C) 2020 Free Software Foundation, Inc. 4 : 36 : * License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. 5 : 36 : * This is free software: you are free to change and redistribute it. 6 : 36 : * There is NO WARRANTY, to the extent permitted by law. 7 : 36 : * 8 : 36 : * Written by Mike Parker, Richard M. Stallman, and David MacKenzie. 9 : 36 : * 10 : 36 : * Implemented in node.js by Essam A. El-Sherif. 11 : 36 : * 12 : 36 : * @module node-tee 13 : 36 : * @desc A Node.js implementation of the {@link https://www.gnu.org/software/coreutils/ "GNU coreutils"} '{@link https://www.gnu.org/software/coreutils/manual/html_node/tee-invocation.html "tee"}' redirection command version 8.32. 14 : 36 : * @version 1.0.1 15 : 36 : * @author Essam A. El-Sherif 16 : 36 : */ 17 : 36 : 18 : 36 : /* Import nodeJS core modules */ 19 : 36 : import process from 'node:process'; 20 : 36 : import fs from 'node:fs'; 21 : 36 : 22 : 36 : /** 23 : 36 : * @const errno {object} - Imported error symbols/messages 24 : 36 : * @see module:node-tee-errno.default 25 : 36 : */ 26 : 36 : import errno from './node-tee-errno.js'; 27 : 36 : 28 : 36 : /** @const {string} CMD_NODE - The coreutils node command */ 29 : 36 : const CMD_NODE = 'node-tee'; 30 : 36 : 31 : 36 : /** @const {string} CMD_NODE_VER - The coreutils node command version */ 32 : 36 : const CMD_NODE_VER = 'v1.0.1'; 33 : 36 : 34 : 36 : /** @const {object} cmdOptions - Command line arguments */ 35 : 36 : const cmdOptions = { 36 : 36 : readOpts: true, // -- 37 : 36 : append: false, // -a --append 38 : 36 : ignoreInterrupts: false, // -i --ignore-interrupts 39 : 36 : outputError: 'warn-nopipe', // --output-error[=MODE] 40 : 36 : }; 41 : 36 : 42 : 36 : /** @const {Set} fileList - Files given as command line arguments */ 43 : 36 : const fileList = new Set(); 44 : 36 : 45 : 36 : /** 46 : 36 : * function main 47 : 36 : * function parseCmdLine() 48 : 36 : * function processCmd() 49 : 36 : * function getHelp() 50 : 36 : * function getError(n) 51 : 36 : * 52 : 36 : * @func Main 53 : 36 : * @desc The application entry point function 54 : 36 : */ 55 [ + ]: 36 : (() => { 56 : 36 : 57 : 36 : parseCmdLine(); 58 : 36 : processCmd(); 59 : 36 : 60 : 36 : })('Main Function'); 61 : 36 : 62 : 36 : /** 63 : 36 : * function main 64 : 36 : * function parseCmdLine() 65 : 36 : * function processCmd() 66 : 36 : * function getHelp() 67 : 36 : * function getError(n) 68 : 36 : * 69 : 36 : * @func parseCmdLine 70 : 36 : * @desc Command line parser function 71 : 36 : * @requires module:node-tee-errno.default 72 : 36 : */ 73 [ + ]: 36 : function parseCmdLine(){ 74 : 36 : 75 : 36 : const args = process.argv; 76 : 36 : 77 : 36 : for(let i = 2; i < args.length; i++) 78 [ + ]: 36 : if(cmdOptions.readOpts && args[i] === '--help'){ 79 : 48 : process.stdout.write(`${getHelp()}\n`); 80 : 48 : process.exit(0); 81 : 48 : } 82 : 48 : else 83 : 48 : if(cmdOptions.readOpts && args[i] === '--version'){ 84 : 48 : process.stdout.write(`${CMD_NODE_VER}\n`); 85 : 48 : process.exit(0); 86 : 48 : } 87 : 48 : else 88 : 48 : if(args[i] === '--') 89 : 48 : cmdOptions.readOpts = false; 90 : 48 : else 91 : 48 : if(cmdOptions.readOpts && args[i] === '--append') 92 : 48 : cmdOptions.append = true; 93 : 48 : else 94 : 48 : if(cmdOptions.readOpts && args[i] === '--ignore-interrupts') 95 : 48 : cmdOptions.ignoreInterrupts = true; 96 : 48 : else 97 [ + ]: 48 : if(cmdOptions.readOpts && args[i].startsWith('--output-error=')){ 98 : 12 : let str = args[i]; 99 : 12 : str = str.replace('--output-error=', ''); 100 : 12 : 101 : 12 : switch(str){ 102 : 12 : case 'warn': 103 : 12 : case 'warn-nopipe': 104 : 12 : case 'exit': 105 : 12 : case 'exit-nopipe': 106 : 12 : cmdOptions.outputError = str; 107 : 12 : break; 108 : 12 : case '': 109 : 12 : process.stderr.write(`${getError(0)}\n`); 110 : 12 : process.exit(1); 111 : 12 : break; 112 : 12 : default: 113 : 12 : process.stderr.write(`${getError(1).replace('_', str)}\n`); 114 : 12 : process.exit(1); 115 : 12 : break; 116 : 12 : } 117 [ + ]: 12 : } 118 : 28 : else 119 : 28 : if(cmdOptions.readOpts && args[i] === '--output-error') 120 : 28 : true; 121 : 28 : else 122 : 28 : if(cmdOptions.readOpts && args[i].startsWith('--')){ 123 : 28 : process.stderr.write(`${getError(2).replace('_', args[i])}\n`); 124 : 28 : process.exit(1); 125 : 28 : } 126 : 28 : else 127 : 28 : if(cmdOptions.readOpts && args[i] === '-') 128 : 28 : fileList.add(args[i]); 129 : 28 : else 130 [ + ]: 28 : if(cmdOptions.readOpts && args[i].startsWith('-')){ 131 : 1 : let str = args[i]; 132 : 1 : str = str.replace('-', ''); 133 : 1 : 134 : 1 : for(let c of str){ 135 : 1 : switch(c){ 136 : 1 : case 'a': 137 : 1 : cmdOptions.append = true; 138 : 1 : break; 139 : 1 : case 'i': 140 : 1 : cmdOptions.ignoreInterrupts = true; 141 : 1 : break; 142 : 1 : case 'p': 143 : 1 : cmdOptions.outputError = 'warn-nopipe'; 144 : 1 : break; 145 : 1 : default: 146 : 1 : process.stderr.write(`${getError(3).replace('_', c)}\n`); 147 : 1 : process.exit(1); 148 : 1 : } 149 : 1 : } 150 [ + ]: 1 : } 151 : 1 : else 152 : 1 : if(args[i].startsWith('-') && !cmdOptions.readOpts) 153 : 1 : fileList.add(args[i]); 154 : 1 : else 155 : 1 : fileList.add(args[i]); 156 : 36 : } 157 : 36 : 158 : 36 : /** 159 : 36 : * function main 160 : 36 : * function parseCmdLine() 161 : 36 : * function processCmd() 162 : 36 : * function getHelp() 163 : 36 : * function getError(n) 164 : 36 : * 165 : 36 : * @func processCmd 166 : 36 : * @desc Command processer function 167 : 36 : */ 168 [ + ]: 30 : function processCmd(){ 169 : 30 : 170 : 30 : if(cmdOptions.ignoreInterrupts) 171 : 30 : process.on('SIGINT', () => {}); 172 : 30 : 173 [ + ]: 30 : process.stdout.on('error', (err) => { 174 : 8 : if(cmdOptions.outputError === 'warn' || cmdOptions.outputError === 'exit') 175 : 8 : process.stderr.write(`${CMD_NODE}: 'standard output': ${errno[err.code]}\n`); 176 : 30 : }); 177 : 30 : 178 [ + ]: 30 : for(const file of fileList){ 179 [ + ]: 20 : fs.open(file, cmdOptions.append ? 'a' : 'w', (err, fd) => { 180 [ + ]: 27 : if(err){ 181 : 10 : process.stderr.write(`${CMD_NODE}: ${file}: ${errno[err.code]}\n`); 182 : 10 : 183 : 10 : if(cmdOptions.outputError === 'exit' || cmdOptions.outputError === 'exit-nopipe') 184 : 10 : process.exit(1); 185 : 10 : else 186 : 10 : process.exitCode = 1; 187 [ + ]: 10 : } 188 : 10 : else{ 189 : 10 : const ws = fs.createWriteStream(file, { 190 : 10 : flags: cmdOptions.append ? 'a' : 'w', 191 : 10 : fd: fd 192 : 10 : }); 193 : 10 : process.stdin.pipe(ws); 194 : 10 : } 195 : 20 : }); 196 : 20 : } 197 : 30 : 198 : 30 : process.stdin.pipe(process.stdout); 199 : 30 : } 200 : 36 : 201 : 36 : /** 202 : 36 : * function main 203 : 36 : * function parseCmdLine() 204 : 36 : * function processCmd() 205 : 36 : * function getHelp() 206 : 36 : * function getError(n) 207 : 36 : * 208 : 36 : * @func getHelp 209 : 36 : * @return {string} 210 : 36 : * @desc Function to return help info 211 : 36 : */ 212 [ + ]: 1 : function getHelp(){ 213 : 1 : 214 : 1 : return `\ 215 : 1 : Usage: ${CMD_NODE} [OPTION]... [FILE]... 216 : 1 : Copy standard input to each FILE, and also to standard output. 217 : 1 : 218 : 1 : -a, --append append to the given FILEs, do not overwrite 219 : 1 : -i, --ignore-interrupts ignore interrupt signals 220 : 1 : -p diagnose errors writing to non pipes 221 : 1 : --output-error[=MODE] set behavior on write error. See MODE below 222 : 1 : --help display this help and exit 223 : 1 : --version output version information and exit 224 : 1 : 225 : 1 : MODE determines behavior with write errors on the outputs: 226 : 1 : 'warn' diagnose errors writing to any output 227 : 1 : 'warn-nopipe' diagnose errors writing to any output not a pipe 228 : 1 : 'exit' exit on error writing to any output 229 : 1 : 'exit-nopipe' exit on error writing to any output not a pipe 230 : 1 : The default MODE for the -p option is 'warn-nopipe'. 231 : 1 : The default operation when --output-error is not specified, is to 232 : 1 : exit immediately on error writing to a pipe, and diagnose errors 233 : 1 : writing to non pipe outputs.`; 234 : 1 : } 235 : 36 : 236 : 36 : /** 237 : 36 : * function main 238 : 36 : * function parseCmdLine() 239 : 36 : * function processCmd() 240 : 36 : * function getHelp() 241 : 36 : * function getError(n) 242 : 36 : * 243 : 36 : * @func getError 244 : 36 : * @param {number} Error number 245 : 36 : * @return {string} Error message 246 : 36 : * @desc Function to return error message 247 : 36 : */ 248 [ + ]: 4 : function getError(n){ 249 : 4 : 250 : 4 : const error = [ 251 : 4 : `\ 252 : 4 : ${CMD_NODE}: ambiguous argument ‘’ for ‘--output-error’ 253 : 4 : Valid arguments are: 254 : 4 : - ‘warn’ 255 : 4 : - ‘warn-nopipe’ 256 : 4 : - ‘exit’ 257 : 4 : - ‘exit-nopipe’ 258 : 4 : Try '${CMD_NODE} --help' for more information.`, 259 : 4 : 260 : 4 : `\ 261 : 4 : ${CMD_NODE}: invalid argument ‘_’ for ‘--output-error’ 262 : 4 : Valid arguments are: 263 : 4 : - ‘warn’ 264 : 4 : - ‘warn-nopipe’ 265 : 4 : - ‘exit’ 266 : 4 : - ‘exit-nopipe’ 267 : 4 : Try '${CMD_NODE} --help' for more information.`, 268 : 4 : 269 : 4 : `\ 270 : 4 : ${CMD_NODE}: unrecognized option '_' 271 : 4 : Try '${CMD_NODE} --help' for more information.`, 272 : 4 : 273 : 4 : `\ 274 : 4 : ${CMD_NODE}: invalid option -- '_' 275 : 4 : Try '${CMD_NODE} --help' for more information.`, 276 : 4 : 277 : 4 : ]; 278 : 4 : return error[n]; 279 : 4 : }