Source: lib/node-tee.js

/**
 * tee (GNU coreutils) 8.32
 * Copyright (C) 2020 Free Software Foundation, Inc.
 * License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
 * This is free software: you are free to change and redistribute it.
 * There is NO WARRANTY, to the extent permitted by law.
 *
 * Written by Mike Parker, Richard M. Stallman, and David MacKenzie.
 *
 * Implemented in node.js by Essam A. El-Sherif.
 *
 * @module  node-tee
 * @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.
 * @version 1.0.1
 * @author  Essam A. El-Sherif
 */

/* Import nodeJS core modules */
import process from 'node:process';
import fs from 'node:fs';

/**
 * @const errno {object} - Imported error symbols/messages
 * @see   module:node-tee-errno.default
 */
import errno from './node-tee-errno.js';

/** @const {string} CMD_NODE - The coreutils node command */
const CMD_NODE = 'node-tee';

/** @const {string} CMD_NODE_VER - The coreutils node command version */
const CMD_NODE_VER = 'v1.0.1';

/** @const {object} cmdOptions - Command line arguments */
const cmdOptions = {
	readOpts:          true,            //    --
	append:            false,           // -a --append
	ignoreInterrupts:  false,           // -i --ignore-interrupts
	outputError:      'warn-nopipe',    //    --output-error[=MODE]
};

/** @const {Set} fileList - Files given as command line arguments */
const fileList = new Set();

/**
 *     function main
 * function parseCmdLine()
 * function processCmd()
 * function getHelp()
 * function getError(n)
 *
 * @func Main
 * @desc The application entry point function
 */
(() => {

	parseCmdLine();
	processCmd();

})('Main Function');

/**
 * function main
 *     function parseCmdLine()
 * function processCmd()
 * function getHelp()
 * function getError(n)
 *
 * @func     parseCmdLine
 * @desc     Command line parser function
 * @requires module:node-tee-errno.default
 */
function parseCmdLine(){

	const args = process.argv;

	for(let i = 2; i < args.length; i++)
		if(cmdOptions.readOpts && args[i] === '--help'){
			process.stdout.write(`${getHelp()}\n`);
			process.exit(0);
		}
		else
		if(cmdOptions.readOpts && args[i] === '--version'){
			process.stdout.write(`${CMD_NODE_VER}\n`);
			process.exit(0);
		}
		else
		if(args[i] === '--')
			cmdOptions.readOpts = false;
		else
		if(cmdOptions.readOpts && args[i] === '--append')
			cmdOptions.append = true;
		else
		if(cmdOptions.readOpts && args[i] === '--ignore-interrupts')
			cmdOptions.ignoreInterrupts = true;
		else
		if(cmdOptions.readOpts && args[i].startsWith('--output-error=')){
			let str = args[i];
			str = str.replace('--output-error=', '');

			switch(str){
				case 'warn':
				case 'warn-nopipe':
				case 'exit':
				case 'exit-nopipe':
					cmdOptions.outputError = str;
					break;
				case '':
					process.stderr.write(`${getError(0)}\n`);
					process.exit(1);
					break;
				default:
					process.stderr.write(`${getError(1).replace('_', str)}\n`);
					process.exit(1);
					break;
			}
		}
		else
		if(cmdOptions.readOpts && args[i] === '--output-error')
			true;
		else
		if(cmdOptions.readOpts && args[i].startsWith('--')){
			process.stderr.write(`${getError(2).replace('_', args[i])}\n`);
			process.exit(1);
		}
		else
		if(cmdOptions.readOpts && args[i] === '-')
			fileList.add(args[i]);
		else
		if(cmdOptions.readOpts && args[i].startsWith('-')){
			let str = args[i];
			str = str.replace('-', '');

			for(let c of str){
				switch(c){
					case 'a':
						cmdOptions.append = true;
						break;
					case 'i':
						cmdOptions.ignoreInterrupts = true;
						break;
					case 'p':
						cmdOptions.outputError = 'warn-nopipe';
						break;
					default:
						process.stderr.write(`${getError(3).replace('_', c)}\n`);
						process.exit(1);
				}
			}
		}
		else
		if(args[i].startsWith('-') && !cmdOptions.readOpts)
			fileList.add(args[i]);
		else
			fileList.add(args[i]);
}

/**
 * function main
 * function parseCmdLine()
 *     function processCmd()
 * function getHelp()
 * function getError(n)
 *
 * @func processCmd
 * @desc Command processer function
 */
function processCmd(){

	if(cmdOptions.ignoreInterrupts)
		process.on('SIGINT', () => {});

	process.stdout.on('error', (err) => {
		if(cmdOptions.outputError === 'warn' || cmdOptions.outputError === 'exit')
			process.stderr.write(`${CMD_NODE}: 'standard output': ${errno[err.code]}\n`);
	});

	for(const file of fileList){
		fs.open(file, cmdOptions.append ? 'a' : 'w', (err, fd) => {
			if(err){
				process.stderr.write(`${CMD_NODE}: ${file}: ${errno[err.code]}\n`);

				if(cmdOptions.outputError === 'exit' || cmdOptions.outputError === 'exit-nopipe')
					process.exit(1);
				else
					process.exitCode = 1;
			}
			else{
				const ws = fs.createWriteStream(file, {
					flags: cmdOptions.append ? 'a' : 'w',
					fd: fd
				});
				process.stdin.pipe(ws);
			}
		});
	}

	process.stdin.pipe(process.stdout);
}

/**
 * function main
 * function parseCmdLine()
 * function processCmd()
 *     function getHelp()
 * function getError(n)
 *
 * @func   getHelp
 * @return {string}
 * @desc   Function to return help info
 */
function getHelp(){

	return `\
Usage: ${CMD_NODE} [OPTION]... [FILE]...
Copy standard input to each FILE, and also to standard output.

  -a, --append              append to the given FILEs, do not overwrite
  -i, --ignore-interrupts   ignore interrupt signals
  -p                        diagnose errors writing to non pipes
      --output-error[=MODE]   set behavior on write error.  See MODE below
      --help     display this help and exit
      --version  output version information and exit

MODE determines behavior with write errors on the outputs:
  'warn'         diagnose errors writing to any output
  'warn-nopipe'  diagnose errors writing to any output not a pipe
  'exit'         exit on error writing to any output
  'exit-nopipe'  exit on error writing to any output not a pipe
The default MODE for the -p option is 'warn-nopipe'.
The default operation when --output-error is not specified, is to
exit immediately on error writing to a pipe, and diagnose errors
writing to non pipe outputs.`;
}

/**
 * function main
 * function parseCmdLine()
 * function processCmd()
 * function getHelp()
 *     function getError(n)
 *
 * @func   getError
 * @param  {number} Error number
 * @return {string} Error message
 * @desc   Function to return error message
 */
function getError(n){

const error = [
`\
${CMD_NODE}: ambiguous argument ‘’ for ‘--output-error’
Valid arguments are:
  - ‘warn’
  - ‘warn-nopipe’
  - ‘exit’
  - ‘exit-nopipe’
Try '${CMD_NODE} --help' for more information.`,

`\
${CMD_NODE}: invalid argument ‘_’ for ‘--output-error’
Valid arguments are:
  - ‘warn’
  - ‘warn-nopipe’
  - ‘exit’
  - ‘exit-nopipe’
Try '${CMD_NODE} --help' for more information.`,

`\
${CMD_NODE}: unrecognized option '_'
Try '${CMD_NODE} --help' for more information.`,

`\
${CMD_NODE}: invalid option -- '_'
Try '${CMD_NODE} --help' for more information.`,

];
	return error[n];
}