From 81e3d90700859534c25ff0945e5048500fdb6ca4 Mon Sep 17 00:00:00 2001 From: Jeffrey Gerard Date: Tue, 16 Jul 2024 09:17:22 -0500 Subject: [PATCH] Refactor for easier testing following example from https://github.com/tj/commander.js/issues/1565#issuecomment-879661279 --- bin/audiomoth-utils.js | 79 -------------------------------------- bin/cli.js | 25 ++++++++++++ lib/command.js | 86 ++++++++++++++++++++++++++++++++++++++++++ package.json | 8 ++-- test/command.test.js | 38 +++++++++++++++++++ 5 files changed, 154 insertions(+), 82 deletions(-) delete mode 100644 bin/audiomoth-utils.js create mode 100644 bin/cli.js create mode 100644 lib/command.js create mode 100644 test/command.test.js diff --git a/bin/audiomoth-utils.js b/bin/audiomoth-utils.js deleted file mode 100644 index 8b8c54a..0000000 --- a/bin/audiomoth-utils.js +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env node - -const { Command, Option } = require("commander"); -const audiomothUtils = require("audiomoth-utils"); - -const program = new Command(); - -program - .description( - "Expand an AudioMoth T.WAV recording (a recording with amplitude thresholding or frequency triggering applied)" - ) // TODO use addCommand to handle the subcommands - .arguments(" ") - .option( - "-d, --destination [outputPath]", - "output directory to write expansions. If omitted, dump expanded files in the same folder as inputFile." - ) - .option( - "--prefix [prefix]", - "optional string (not including '_') to prefix to expanded filenames that will be created." - ) - .addOption( - new Option( - "--max-file-duration [seconds]", - "max duration of expanded file to write, in seconds" - ) - .argParser(parseInt) - .default(5) - ) - .option("--generate-silent-files", "generate silent files", false) - .option("--align-to-second-transitions", "align to second transitions", false) - .action((subcommand, inputFile, options) => { - const { - destination, - prefix, - maxFileDuration, - generateSilentFiles, - alignToSecondTransitions, - } = options; - - // Determine expansionType based on command - let expansionType; - switch (subcommand) { - case "expand-duration": - expansionType = "DURATION"; - break; - case "expand-event": - expansionType = "EVENT"; - break; - default: - console.error("Invalid subcommand"); - process.exit(9); - } - - // Call the expand function with the provided arguments and options - try { - const result = audiomothUtils.expand( - inputFile, - destination, // optional - prefix, // optional - expansionType, - maxFileDuration, - generateSilentFiles, - alignToSecondTransitions - ); - - if (result?.success) { - console.log("Expansion completed successfully."); - process.exit(0); // Exit with code 0 if success is true - } else { - console.error("Expansion failed:", result?.error); - process.exit(1); // Exit with code 1 if success is false - } - } catch (err) { - console.error("Error:", err); - process.exit(7); // Exit with code 7 if an exception occurred - } - }); - -program.parse(process.argv); diff --git a/bin/cli.js b/bin/cli.js new file mode 100644 index 0000000..a4cdce5 --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +const command = require("../lib/command"); +const program = command.makeProgram(); + +function main() { + try { + program.parse(process.argv); + } catch (err) { + if (err instanceof Error) { + // if (program.opts().debug) { + // console.error(`${err.stack}`); + // } + // if (err.message !== util.suppressTerminateExceptionMessage) { + console.log(`caught exception with message ${err.message}`); + // } + } else { + throw err; + } + // Recommended practice for node is set exitcode not force exit + process.exitCode = 7; // Exit with code 7 if an exception occurred + } +} + +main().then(() => {}); diff --git a/lib/command.js b/lib/command.js new file mode 100644 index 0000000..e2c903e --- /dev/null +++ b/lib/command.js @@ -0,0 +1,86 @@ +const { Command, Option } = require("commander"); +const audiomothUtils = require("audiomoth-utils"); + +function makeProgram() { + const program = new Command(); + + program + .description( + "Expand an AudioMoth T.WAV recording (a recording with amplitude thresholding or frequency triggering applied)" + ) // TODO use addCommand to handle the subcommands + .arguments(" ") + .option( + "-d, --destination [outputPath]", + "output directory to write expansions. If omitted, dump expanded files in the same folder as inputFile." + ) + .option( + "--prefix [prefix]", + "optional string (not including '_') to prefix to expanded filenames that will be created." + ) + .addOption( + new Option( + "--max-file-duration [seconds]", + "max duration of expanded file to write, in seconds" + ) + .argParser(parseInt) + .default(5) + ) + .option("--generate-silent-files", "generate silent files", false) + .option( + "--align-to-second-transitions", + "align to second transitions", + false + ) + .action((subcommand, inputFile, options) => { + const { + destination, + prefix, + maxFileDuration, + generateSilentFiles, + alignToSecondTransitions, + } = options; + + // Determine expansionType based on command + let expansionType; + switch (subcommand) { + case "expand-duration": + expansionType = "DURATION"; + break; + case "expand-event": + expansionType = "EVENT"; + break; + default: + console.error("Invalid subcommand"); + process.exit(9); + } + + // Call the expand function with the provided arguments and options + const result = audiomothUtils.expand( + inputFile, + destination, // optional + prefix, // optional + expansionType, + maxFileDuration, + generateSilentFiles, + alignToSecondTransitions + ); + + if (result?.success) { + console.log("Expansion completed successfully."); + process.exit(0); // Exit with code 0 if success is true + } else { + console.error("Expansion failed:", result?.error); + process.exit(1); // Exit with code 1 if success is false + } + }); + return program; +} + +function fab(args) { + makeProgram().parse(args, { from: "user" }); +} + +module.exports = { + makeProgram: makeProgram, + fab: fab, +}; diff --git a/package.json b/package.json index daa1f93..93eda99 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,19 @@ "version": "1.0.0", "description": "A thin CLI wrapper over AudioMoth-Utils", "bin": { - "hello-world": "./bin/audiomoth-utils.js" + "audiomoth-utils": "./bin/cli.js" }, "scripts": { - "start": "node ./bin/audiomoth-utils.js", - "build": "pkg . --targets node14-linux-x64,node14-win-x64 --out-path dist" + "start": "node ./bin/cli.js", + "build": "pkg . --targets node14-linux-x64,node14-win-x64 --out-path dist", + "test": "jest" }, "dependencies": { "audiomoth-utils": "1.5.0", "commander": "^12.1.0" }, "devDependencies": { + "jest": "^29.7.0", "pkg": "^5.8.1" } } diff --git a/test/command.test.js b/test/command.test.js new file mode 100644 index 0000000..f7132f9 --- /dev/null +++ b/test/command.test.js @@ -0,0 +1,38 @@ +const command = require("../lib/command"); + +const audiomothUtils = require("audiomoth-utils"); + +jest.mock("audiomoth-utils", () => ({ + expand: jest.fn(), +})); + +describe("audiomoth-utils CLI", () => { + beforeEach(() => { + audiomothUtils.expand.mockClear(); + audiomothUtils.expand.mockReturnValue({ success: true }); + // Mock console.log and console.error + console.log = jest.fn(); + console.error = jest.fn(); + // Mock process.exit + process.exit = jest.fn(); + }); + + test("calling with no arguments uses default values", () => { + command.fab(["expand-duration", "input.wav"]); + + expect(audiomothUtils.expand).toHaveBeenCalledWith( + "input.wav", + undefined, + undefined, + "DURATION", + 5, + false, + false + ); + + expect(console.log).toHaveBeenCalledWith( + "Expansion completed successfully." + ); + expect(process.exit).toHaveBeenCalledWith(0); + }); +});