diff --git a/src/cli/cli.ts b/src/cli/cli.ts index d3273cd..03bc409 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -13,7 +13,7 @@ program .version('1.2.0') .showHelpAfterError() .argument('', 'input file path') - .argument('', 'output file path') + .argument('', 'output file path. use "@" to use the input file name') .addOption( new Option('-i, --input-format [format]', 'input file format') .choices([ @@ -49,6 +49,10 @@ program new Option('--output-options-file [path]', 'output options json file') .default(null) ) + .addOption( + new Option('-d, --directory', 'whether the input and output paths are directories, in which case all files will be converted') + .default(false) + ) .action((inputFile, outputFile, opts) => { runCli({ inputFile, @@ -57,9 +61,10 @@ program outputFormat: opts.outputFormat, inputOptionsFile: opts.inputOptionsFile, outputOptionsFile: opts.outputOptionsFile, + directory: opts.directory, }) .then(() => console.log('Done.')) - .catch((error) => console.error(error)); - }); + .catch((error) => console.error(error?.toString())); + }) program.parse(); diff --git a/src/cli/index.ts b/src/cli/index.ts index 934b4ef..82e7881 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,7 +1,8 @@ import { createInput, getInputFormatAndOptions, InputFormat } from './input'; -import { createOutput, getOutputFormatAndOptions, OutputFormat } from './output'; +import { createOutput, getOutputFilename, getOutputFormatAndOptions, OutputFormat } from './output'; import * as fs from 'fs'; import * as fsPromises from 'fs/promises'; +import * as path from 'path'; export interface CliOptions { inputFile: string; @@ -10,11 +11,28 @@ export interface CliOptions { outputFormat: string; inputOptionsFile?: string; outputOptionsFile?: string; + directory?: boolean; +} + +interface ConversionOptions { + inputFile: string; + outputFile: string; + inputFormat: InputFormat; + outputFormat: OutputFormat; + inputOptions: object; + outputOptions: object; } export async function runCli(options: CliOptions): Promise { - console.log(`Converting "${options.inputFile}" to "${options.outputFile}"`); + const conversionOptions = await prepare(options); + + if (options.directory) + await convertDirectory(conversionOptions); + else + await convert(conversionOptions); +} +export async function prepare(options: CliOptions): Promise { const [inFormat, defaultInOpts] = getInputFormatAndOptions(options.inputFormat, options.inputFile); const [outFormat, defaultOutOpts] = getOutputFormatAndOptions(options.outputFormat, options.outputFile); @@ -26,20 +44,35 @@ export async function runCli(options: CliOptions): Promise { const inputOptions = await loadOptionsFile(options.inputOptionsFile); const outputOptions = await loadOptionsFile(options.outputOptionsFile); - const reader = createInput(inFormat, { ...defaultInOpts, ...inputOptions }); + return { + inputFile: options.inputFile, + outputFile: options.outputFile, + inputFormat: inFormat, + outputFormat: outFormat, + inputOptions: { ...defaultInOpts, ...inputOptions }, + outputOptions: { ...defaultOutOpts, ...outputOptions }, + }; +} + +export async function convert(options: ConversionOptions): Promise { + const reader = createInput(options.inputFormat, options.inputOptions); const inputFileStream = fs.createReadStream(options.inputFile); const readerStream = await reader.createStream(inputFileStream); const channels = readerStream.channels; - const writer = createOutput(outFormat, { ...defaultOutOpts, ...outputOptions, channels }); - const outputFileStream = fs.createWriteStream(options.outputFile); + const writer = createOutput(options.outputFormat, { ...options.outputOptions, channels }); + const outputFile = getOutputFilename(options.outputFile, options.inputFile, writer); + + await fsPromises.mkdir(path.dirname(outputFile)); + + const outputFileStream = fs.createWriteStream(outputFile); const writerStream = writer.createStream(readerStream.stream); writerStream.pipe(outputFileStream); - console.log('Converting...'); + console.log(`Converting "${options.inputFile}" to "${outputFile}"`); return new Promise((resolve, reject) => { outputFileStream.once('finish', () => resolve()); @@ -47,6 +80,28 @@ export async function runCli(options: CliOptions): Promise { }); } +export async function convertDirectory(options: ConversionOptions): Promise { + const inputDirents = await fsPromises.readdir(options.inputFile, { withFileTypes: true }); + const inputFiles = inputDirents.filter(dirent => dirent.isFile()); + + if (inputFiles.length === 0) + console.log('No files found in the directory'); + + for (const file of inputFiles) { + console.log(`File: "${file.name}"`); + + try { + await convert({ + ...options, + inputFile: path.join(options.inputFile, file.name), + outputFile: path.join(options.outputFile, '@'), + }); + } catch (error) { + console.error(error?.toString()); + } + } +} + async function loadOptionsFile(path: string | undefined): Promise { if (!path) return {}; diff --git a/src/cli/output.ts b/src/cli/output.ts index 5079979..348f729 100644 --- a/src/cli/output.ts +++ b/src/cli/output.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import { BaseWriter } from '../interfaces/base.writer'; import { CsvWriter, @@ -12,7 +13,6 @@ import { RacePakWriter, WinDarabWriter, } from '../index'; -import { DataChannel } from '../interfaces/data-channel'; export enum OutputFormat { AUTO = 'auto', @@ -55,6 +55,21 @@ export function getOutputFormatAndOptions(format: string, filename: string): [Ou throw new Error('Could not find an output format based on the file extension.'); } +export function getOutputFilename(outputFile: string, inputFile: string, writer: BaseWriter): string { + if (!outputFile.includes('@')) + return outputFile; + + const suffix = outputFile.endsWith(writer.extension) ? '' : writer.extension; + const basename = path.basename(inputFile); + const outputName = outputFile.replace('@', basename + suffix); + + // In case the output file is the same as the input, we'll prefix it with an underline + if (outputName === inputFile) + return outputFile.replace('@', '_' + basename + suffix); + + return outputName; +} + export function createOutput(format: OutputFormat, options: any): BaseWriter { if (format === OutputFormat.CSV) return new CsvWriter(options);