Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: option to control when ROMs get grouped by their game name #692

Merged
merged 6 commits into from
Sep 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/modules/argumentsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Constants from '../constants.js';
import ConsolePoly from '../polyfill/consolePoly.js';
import ROMHeader from '../types/files/romHeader.js';
import Internationalization from '../types/internationalization.js';
import Options, { MergeMode } from '../types/options.js';
import Options, { GameSubdirMode, MergeMode } from '../types/options.js';
import PatchFactory from '../types/patches/patchFactory.js';

/**
Expand Down Expand Up @@ -55,7 +55,7 @@ export default class ArgumentsParser {

const groupInput = 'Input options (supports globbing):';
const groupDatInput = 'DAT input options:';
const groupRomOutput = 'ROM output options:';
const groupRomOutput = 'ROM output options (processed in order):';
const groupRomZip = 'ROM zip command options:';
const groupRomSymlink = 'ROM symlink command options:';
const groupRomHeader = 'ROM header options:';
Expand Down Expand Up @@ -290,6 +290,16 @@ export default class ArgumentsParser {
requiresArg: true,
implies: 'dir-letter',
})
.option('dir-game-subdir', {
group: groupRomOutput,
description: 'Append the name of the game as an output directory depending on its ROMs',
choices: Object.keys(GameSubdirMode)
.filter((mode) => Number.isNaN(Number(mode)))
.map((mode) => mode.toLowerCase()),
coerce: ArgumentsParser.getLastValue, // don't allow string[] values
requiresArg: true,
default: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
})
.option('overwrite', {
group: groupRomOutput,
alias: 'O',
Expand Down Expand Up @@ -385,7 +395,6 @@ export default class ArgumentsParser {
.option('merge-roms', {
group: groupRomMergeSplit,
description: 'ROM merge/split mode',
// type: 'string',
choices: Object.keys(MergeMode)
.filter((mode) => Number.isNaN(Number(mode)))
.map((mode) => mode.toLowerCase()),
Expand Down
22 changes: 22 additions & 0 deletions src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export enum MergeMode {
MERGED,
}

export enum GameSubdirMode {
// Never add the Game name as a subdirectory
NEVER = 1,
// Add the Game name as a subdirectory if it has multiple output files
MULTIPLE,
// Always add the Game name as a subdirectory
ALWAYS,
}

export interface OptionsProps {
readonly commands?: string[],

Expand All @@ -58,6 +67,7 @@ export interface OptionsProps {
readonly dirDatDescription?: boolean,
readonly dirLetter?: boolean,
readonly dirLetterLimit?: number,
readonly dirGameSubdir?: string,
readonly overwrite?: boolean,
readonly overwriteInvalid?: boolean,
readonly cleanExclude?: string[],
Expand Down Expand Up @@ -171,6 +181,8 @@ export default class Options implements OptionsProps {

readonly dirLetterLimit: number;

readonly dirGameSubdir?: string;

readonly overwrite: boolean;

readonly overwriteInvalid: boolean;
Expand Down Expand Up @@ -316,6 +328,7 @@ export default class Options implements OptionsProps {
this.dirDatDescription = options?.dirDatDescription ?? false;
this.dirLetter = options?.dirLetter ?? false;
this.dirLetterLimit = options?.dirLetterLimit ?? 0;
this.dirGameSubdir = options?.dirGameSubdir;
this.overwrite = options?.overwrite ?? false;
this.overwriteInvalid = options?.overwriteInvalid ?? false;
this.cleanExclude = options?.cleanExclude ?? [];
Expand Down Expand Up @@ -729,6 +742,15 @@ export default class Options implements OptionsProps {
return this.dirLetterLimit;
}

getDirGameSubdir(): GameSubdirMode | undefined {
const subdirMode = Object.keys(GameSubdirMode)
.find((mode) => mode.toLowerCase() === this.dirGameSubdir?.toLowerCase());
if (!subdirMode) {
return undefined;
}
return GameSubdirMode[subdirMode as keyof typeof GameSubdirMode];
}

getOverwrite(): boolean {
return this.overwrite;
}
Expand Down
12 changes: 7 additions & 5 deletions src/types/outputFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ArchiveEntry from './files/archives/archiveEntry.js';
import File from './files/file.js';
import FileFactory from './files/fileFactory.js';
import GameConsole from './gameConsole.js';
import Options from './options.js';
import Options, { GameSubdirMode } from './options.js';

/**
* A {@link ParsedPath} that carries {@link ArchiveEntry} path information.
Expand Down Expand Up @@ -362,10 +362,12 @@ export default class OutputFactory {
output = path.join(dir, output);
}

if (game
&& game.getRoms().length > 1
&& !FileFactory.isArchive(ext)
) {
if (game && (
(options.getDirGameSubdir() === GameSubdirMode.MULTIPLE
&& game.getRoms().length > 1
&& !FileFactory.isArchive(ext))
|| options.getDirGameSubdir() === GameSubdirMode.ALWAYS
)) {
output = path.join(game.getName(), output);
}

Expand Down
9 changes: 8 additions & 1 deletion test/igir.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Igir from '../src/igir.js';
import ArrayPoly from '../src/polyfill/arrayPoly.js';
import fsPoly from '../src/polyfill/fsPoly.js';
import FileFactory from '../src/types/files/fileFactory.js';
import Options, { OptionsProps } from '../src/types/options.js';
import Options, { GameSubdirMode, OptionsProps } from '../src/types/options.js';

interface TestOutput {
outputFilesAndCrcs: string[][],
Expand Down Expand Up @@ -155,6 +155,7 @@ describe('with explicit DATs', () => {
input: [path.join(inputTemp, 'roms')],
output: outputTemp,
dirDatName: true,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

expect(result.outputFilesAndCrcs).toEqual([
Expand Down Expand Up @@ -202,6 +203,7 @@ describe('with explicit DATs', () => {
dat: [path.join(inputTemp, 'dats', 'one.dat')],
input: [path.join(inputTemp, 'roms')],
output: outputTemp,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
single: true,
preferParent: true,
});
Expand Down Expand Up @@ -244,6 +246,7 @@ describe('with explicit DATs', () => {
input: [path.join(inputTemp, 'roms')],
output: path.join(outputTemp, '{outputExt}'),
dirDatName: true,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

expect(result.outputFilesAndCrcs).toEqual([
Expand Down Expand Up @@ -297,6 +300,7 @@ describe('with explicit DATs', () => {
input: [path.join(inputTemp, 'roms')],
output: outputTemp,
dirDatName: true,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

expect(result.outputFilesAndCrcs).toEqual([
Expand Down Expand Up @@ -450,6 +454,7 @@ describe('with explicit DATs', () => {
input: [path.join(inputTemp, 'roms')],
output: outputTemp,
dirDatName: true,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

expect(result.outputFilesAndCrcs).toEqual([
Expand Down Expand Up @@ -499,6 +504,7 @@ describe('with explicit DATs', () => {
patch: [path.join(inputTemp, 'patches')],
output: outputTemp,
dirDatName: true,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
removeHeaders: [''], // all
});

Expand Down Expand Up @@ -706,6 +712,7 @@ describe('with inferred DATs', () => {
commands: ['move', 'extract', 'test'],
input: [path.join(inputTemp, 'roms')],
output: outputTemp,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

expect(result.outputFilesAndCrcs).toEqual([
Expand Down
13 changes: 12 additions & 1 deletion test/modules/argumentsParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Constants from '../../src/constants.js';
import ArgumentsParser from '../../src/modules/argumentsParser.js';
import Header from '../../src/types/dats/logiqx/header.js';
import LogiqxDAT from '../../src/types/dats/logiqx/logiqxDat.js';
import { MergeMode } from '../../src/types/options.js';
import { GameSubdirMode, MergeMode } from '../../src/types/options.js';

const dummyRequiredArgs = ['--input', os.devNull, '--output', os.devNull];
const dummyCommandAndRequiredArgs = ['copy', ...dummyRequiredArgs];
Expand Down Expand Up @@ -108,6 +108,7 @@ describe('options', () => {
expect(options.getDirDatDescription()).toEqual(false);
expect(options.getDirLetter()).toEqual(false);
expect(options.getDirLetterLimit()).toEqual(0);
expect(options.getDirGameSubdir()).toEqual(GameSubdirMode.MULTIPLE);
expect(options.getOverwrite()).toEqual(false);
expect(options.getOverwriteInvalid()).toEqual(false);

Expand Down Expand Up @@ -350,6 +351,16 @@ describe('options', () => {
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dir-letter', '--dir-letter-limit', '5', '--dir-letter-limit', '10']).getDirLetterLimit()).toEqual(10);
});

it('should parse "dir-game-subdir"', () => {
expect(argumentsParser.parse(dummyCommandAndRequiredArgs).getDirGameSubdir())
.toEqual(GameSubdirMode.MULTIPLE);
expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dir-game-subdir', 'foobar']).getDirGameSubdir()).toThrow(/invalid values/i);
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dir-game-subdir', 'never']).getDirGameSubdir()).toEqual(GameSubdirMode.NEVER);
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dir-game-subdir', 'multiple']).getDirGameSubdir()).toEqual(GameSubdirMode.MULTIPLE);
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dir-game-subdir', 'always']).getDirGameSubdir()).toEqual(GameSubdirMode.ALWAYS);
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dir-game-subdir', 'always', '--dir-game-subdir', 'never']).getDirGameSubdir()).toEqual(GameSubdirMode.NEVER);
});

it('should parse "single"', () => {
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dat', os.devNull, '-s']).getSingle()).toEqual(true);
expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--dat', os.devNull, '--single']).getSingle()).toEqual(true);
Expand Down
3 changes: 2 additions & 1 deletion test/modules/candidateGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Tar from '../../src/types/files/archives/tar.js';
import Zip from '../../src/types/files/archives/zip.js';
import File from '../../src/types/files/file.js';
import ROMHeader from '../../src/types/files/romHeader.js';
import Options from '../../src/types/options.js';
import Options, { GameSubdirMode } from '../../src/types/options.js';
import ReleaseCandidate from '../../src/types/releaseCandidate.js';
import ProgressBarFake from '../console/progressBarFake.js';

Expand Down Expand Up @@ -257,6 +257,7 @@ describe('with ROMs with headers', () => {
const options = new Options({
commands: ['copy', 'extract'],
removeHeaders: [''], // all
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

// When
Expand Down
4 changes: 3 additions & 1 deletion test/modules/candidatePostProcessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Header from '../../src/types/dats/logiqx/header.js';
import LogiqxDAT from '../../src/types/dats/logiqx/logiqxDat.js';
import Parent from '../../src/types/dats/parent.js';
import ROM from '../../src/types/dats/rom.js';
import Options from '../../src/types/options.js';
import Options, { GameSubdirMode } from '../../src/types/options.js';
import ReleaseCandidate from '../../src/types/releaseCandidate.js';
import ROMWithFiles from '../../src/types/romWithFiles.js';
import ProgressBarFake from '../console/progressBarFake.js';
Expand Down Expand Up @@ -70,6 +70,7 @@ it('should do nothing with no options', async () => {
const options = new Options({
commands: ['copy'],
output: 'Output',
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

const parentsToCandidates = await runCandidatePostProcessor(options);
Expand Down Expand Up @@ -169,6 +170,7 @@ describe('dirLetterLimit', () => {
output: 'Output',
dirLetter: true,
dirLetterLimit: limit,
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});

const parentsToCandidates = await runCandidatePostProcessor(options);
Expand Down
12 changes: 9 additions & 3 deletions test/modules/candidateWriter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Archive from '../../src/types/files/archives/archive.js';
import ArchiveEntry from '../../src/types/files/archives/archiveEntry.js';
import File from '../../src/types/files/file.js';
import FileFactory from '../../src/types/files/fileFactory.js';
import Options, { OptionsProps } from '../../src/types/options.js';
import Options, { GameSubdirMode, OptionsProps } from '../../src/types/options.js';
import ProgressBarFake from '../console/progressBarFake.js';

async function copyFixturesToTemp(
Expand Down Expand Up @@ -841,7 +841,10 @@ describe('extract', () => {
])('should copy, extract, and test: %s', async (inputGlob, expectedOutputPaths) => {
await copyFixturesToTemp(async (inputTemp, outputTemp) => {
// Given
const options = new Options({ commands: ['copy', 'extract', 'test'] });
const options = new Options({
commands: ['copy', 'extract', 'test'],
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});
const inputFilesBefore = await walkAndStat(inputTemp);
await expect(walkAndStat(outputTemp)).resolves.toHaveLength(0);

Expand Down Expand Up @@ -891,7 +894,10 @@ describe('extract', () => {
])('should move, extract, and test: %s', async (inputGlob, expectedOutputPaths, expectedDeletedInputPaths) => {
await copyFixturesToTemp(async (inputTemp, outputTemp) => {
// Given
const options = new Options({ commands: ['move', 'extract', 'test'] });
const options = new Options({
commands: ['move', 'extract', 'test'],
dirGameSubdir: GameSubdirMode[GameSubdirMode.MULTIPLE].toLowerCase(),
});
const romFilesBefore = await walkAndStat(path.join(inputTemp, 'roms'));
await expect(walkAndStat(outputTemp)).resolves.toHaveLength(0);

Expand Down
Loading