From 51096a05eff1a31c06dcc1f7e75c5d038178f97e Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Wed, 12 Jul 2023 18:12:16 -0400 Subject: [PATCH] Feature: no devices filter option (#476) --- docs/rom-filtering.md | 8 +++++ src/modules/argumentsParser.ts | 5 +++ src/modules/candidateFilter.ts | 1 + src/types/logiqx/game.ts | 9 ++++++ src/types/options.ts | 8 +++++ test/fixtures/dats/one.dat | 44 +++++++++++++++++++++++++++ test/fixtures/dats/one.zip | Bin 1179 -> 0 bytes test/fixtures/dats/patchable.dat | 36 +++++++++++----------- test/fixtures/dats/smdb.txt | 6 ---- test/fixtures/dats/smdb.zip | Bin 0 -> 858 bytes test/modules/argumentsParser.test.ts | 10 ++++++ test/modules/candidateFilter.test.ts | 34 ++++++++++++++++++++- test/modules/datScanner.test.ts | 6 ++-- 13 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 test/fixtures/dats/one.dat delete mode 100644 test/fixtures/dats/one.zip delete mode 100644 test/fixtures/dats/smdb.txt create mode 100644 test/fixtures/dats/smdb.zip diff --git a/docs/rom-filtering.md b/docs/rom-filtering.md index 3160a6556..e63af37f2 100644 --- a/docs/rom-filtering.md +++ b/docs/rom-filtering.md @@ -99,6 +99,14 @@ This option is best used for collating all BIOS files across all consoles to one Opposite of the above. This will filter out files that are not games. +### No MAME devices + +```text +--no-device +``` + +Filters out [MAME devices](https://wiki.mamedev.org/index.php/MAME_Device_Basics). MAME devices typically represent physical devices, such as microcontrollers, video display controllers, sounds boards, and more. Many MAME devices don't have any associated ROM files. + ### No unlicensed ```text diff --git a/src/modules/argumentsParser.ts b/src/modules/argumentsParser.ts index 81930a7fd..89d4f6769 100644 --- a/src/modules/argumentsParser.ts +++ b/src/modules/argumentsParser.ts @@ -369,6 +369,11 @@ export default class ArgumentsParser { type: 'boolean', conflicts: ['only-bios'], }) + .option('no-device', { + group: groupRomFiltering, + description: 'Filter out MAME devices', + type: 'boolean', + }) .option('no-unlicensed', { group: groupRomFiltering, description: 'Filter out unlicensed ROMs', diff --git a/src/modules/candidateFilter.ts b/src/modules/candidateFilter.ts index 46c332b1f..60336fe17 100644 --- a/src/modules/candidateFilter.ts +++ b/src/modules/candidateFilter.ts @@ -98,6 +98,7 @@ export default class CandidateFilter extends Module { this.regionNotAllowed(releaseCandidate), this.options.getOnlyBios() && !game.isBios(), this.options.getNoBios() && game.isBios(), + this.options.getNoDevice() && game.isDevice(), this.options.getOnlyRetail() && !game.isRetail(), this.options.getNoUnlicensed() && game.isUnlicensed(), this.options.getNoDemo() && game.isDemo(), diff --git a/src/types/logiqx/game.ts b/src/types/logiqx/game.ts index 23bfee495..842177a2f 100644 --- a/src/types/logiqx/game.ts +++ b/src/types/logiqx/game.ts @@ -33,6 +33,7 @@ export interface GameProps { readonly description?: string, readonly sourceFile?: string, readonly bios?: 'yes' | 'no', + readonly device?: 'yes' | 'no', readonly cloneOf?: string, readonly romOf?: string, readonly sampleOf?: string, @@ -58,6 +59,9 @@ export default class Game implements GameProps { @Expose({ name: 'isbios' }) readonly bios: 'yes' | 'no' = 'no'; + @Expose({ name: 'isdevice' }) + readonly device: 'yes' | 'no' = 'no'; + @Expose({ name: 'cloneof' }) readonly cloneOf?: string; @@ -79,6 +83,7 @@ export default class Game implements GameProps { this.name = options?.name || ''; this.description = options?.description || ''; this.bios = options?.bios || this.bios; + this.device = options?.device || this.device; this.cloneOf = options?.cloneOf; this.romOf = options?.romOf; this.sampleOf = options?.sampleOf; @@ -110,6 +115,10 @@ export default class Game implements GameProps { return this.bios === 'yes' || this.name.match(/\[BIOS\]/i) !== null; } + isDevice(): boolean { + return this.device === 'yes'; + } + getReleases(): Release[] { if (Array.isArray(this.release)) { return this.release; diff --git a/src/types/options.ts b/src/types/options.ts index 106719afe..253b663d4 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -59,6 +59,7 @@ export interface OptionsProps { readonly regionFilter?: string[], readonly onlyBios?: boolean, readonly noBios?: boolean, + readonly noDevice?: boolean, readonly noUnlicensed?: boolean, readonly onlyRetail?: boolean, readonly noDemo?: boolean, @@ -147,6 +148,8 @@ export default class Options implements OptionsProps { readonly noBios: boolean; + readonly noDevice: boolean; + readonly noUnlicensed: boolean; readonly onlyRetail: boolean; @@ -234,6 +237,7 @@ export default class Options implements OptionsProps { this.regionFilter = options?.regionFilter || []; this.onlyBios = options?.onlyBios || false; this.noBios = options?.noBios || false; + this.noDevice = options?.noDevice || false; this.noUnlicensed = options?.noUnlicensed || false; this.onlyRetail = options?.onlyRetail || false; this.noDemo = options?.noDemo || false; @@ -804,6 +808,10 @@ export default class Options implements OptionsProps { return this.noBios; } + getNoDevice(): boolean { + return this.noDevice; + } + getNoUnlicensed(): boolean { return this.noUnlicensed; } diff --git a/test/fixtures/dats/one.dat b/test/fixtures/dats/one.dat new file mode 100644 index 000000000..bfe31cc98 --- /dev/null +++ b/test/fixtures/dats/one.dat @@ -0,0 +1,44 @@ + + + +
+ One + One + 20220705 + emmercm +
+ + Empty + + + + + Foobar + + + + + Fizzbuzz + + + + + Lorem Ipsum + + + + + One Three + + + + + Three Four Five + + + + + + Device + +
diff --git a/test/fixtures/dats/one.zip b/test/fixtures/dats/one.zip deleted file mode 100644 index b39d488209a7372053d3cdcc1d76e0ccf1c95c24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1179 zcmWIWW@Zs#-~dAJq%~m-P%w>?fq|VtfgwLHRWBv6Bs7GVf&Kr4xDwjB0|QVq1H;*A-o>}g1dg9SxjdnAeaH93%GQ^Uee*f!BI)~G%^nQi5Bl%hEqJ+G|84BonY*sm?6g?*)aARD%ujk7w-ug#n_`Ss*im!GE7{HI>H~*8ll-_RBPem5;ZZUuNA>QnPbG z=H}|#e2Qy}%=gD{ez1Jz#{<_E*q_}fTqwq?reCGVx)vLwc2KKro zose)ARMJe;U{g%unc{75O1Qp`d(vA6_X{r5VvIZ&gk4+aq#C83(!s>VW|y1t+jNcj z>czACZS{)}?W$CJo3tJALg3xy??^_o_@BRlF5EP8~Lgi1u&3f6U@pe}CrZ9~A-~_TQO3 z+Cskt_~qNlHeI^yE4D>y!x6deXLBam-IVk86X_8-=(&>B<)ur=#Vp~)JdvrU2^$wI zJsiQpDL(gLhS8#6fvtQ_i_`aXG`uX0I5%B(>(%z(SC`qkq?!D^UvMn@?ax1TUq3R; zpZLP=~o4;<^)>qlrU(SRAh3h>9Ss<`s1Zcwg!_t+Z!{SJ?IKvKJMdexGcv`Np2M`J?060R5&fF??-RiNRW8Yc6R` zFpt@AV&Zf`?*B!GVuvk_+XH+&-Sqe@U-NJt(0p5TA!F|CX%c=KA^LMaOJ`btO)kuf zKhtws?C=8?uO~@cI4?x_#jW!VnjKKnWqE_CT7ANIuLd)}C2Iv9>|#IFuCU`w1`3()9{j5^Ojt5$%0+8;4cT_yjx(w!QufWUxS-Lsru(;HZqK&~X1-zz zcPOT|nm9&3vf0vDa$?^GmHu;8o@bQbZI#pw5?ZyvNT20CZ_@02htyNOJ%pwlUGcGX z_518CKC{=?H~wP`@Mh;Iu$j2*HZubQs8k5>W@Hj!MwA%Ha-b3e2DUVUSVWc|0p6@^ PAmxle7z(5fSU@}gm>U{` diff --git a/test/fixtures/dats/patchable.dat b/test/fixtures/dats/patchable.dat index 9134b473b..9d4c0554f 100644 --- a/test/fixtures/dats/patchable.dat +++ b/test/fixtures/dats/patchable.dat @@ -9,63 +9,63 @@ - + 92C85C9 - + - + 3708F2C - + - + Before - + - + 65D1206 - + - + Best - + - + 612644F - + - + C01173E - + - + KDULVQN - + - + 0F09A40 - + diff --git a/test/fixtures/dats/smdb.txt b/test/fixtures/dats/smdb.txt deleted file mode 100644 index 51694090b..000000000 --- a/test/fixtures/dats/smdb.txt +++ /dev/null @@ -1,6 +0,0 @@ -aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f Hardware Target Game Database/Dummy/Foobar.lnx 988881adc9fc3655077dc2d4d757d480b5ea0e11 14758f1afd44c09b7992073ccf00b43d b22c9747 7 -6e809804766eaa4dd42a2607b789f3e4e5d32fc321ba8dd3ef39ddc1ea2888e9 Hardware Target Game Database/Dummy/Fizzbuzz.nes 5a316d9f0e06964d94cdd62a933803d7147ddadb cbe8410861130a91609295349918c2c2 370517b5 9 -9d0dc61fa60a12a9613cc32fa43fc85bea343ec3a25f27d10ed81a7f0b9ec278 Hardware Target Game Database/Dummy/Lorem Ipsum.rom 1d913738eb363a4056c19e158aa81189a1eb7a55 fffcb698d88fbc9425a636ba7e4712a3 70856527 11 -174596d6c3cb2c6c6c8b47ce82b233e1f2e755e68c8ad22c397d25d4f8c01344 Hardware Target Game Database\Patchable\3708F2C.rom 181e0713260060fa7d6c64292902023ca7c88a06 b191e2d913681475ef6a579d4b40bb1f 20891c9f 1025 -2a52fae9cdbb1d6f1da486e501fe5d16d76a665be28e94c8a8fce27a7742a6c0 Hardware Target Game Database\Patchable\65D1206.rom 31306e0f014d3ac2c24d2abede5f9a67c097c5e7 5ab0c664d1ed83757fbe49e99c509cb0 20323455 1025 -d8cc4fff6a1caa4d425b35ad06639cb52f07b6594c063d3e24be064de94032a9 Hardware Target Game Database\Patchable\C01173E.rom 2f185826482db2878663c0cd393d62d0c10814ae c9e35aafaec606fcb16763200ce35b0f dfaebe28 1025 diff --git a/test/fixtures/dats/smdb.zip b/test/fixtures/dats/smdb.zip new file mode 100644 index 0000000000000000000000000000000000000000..18fb7d13da1433457c165b5bf153ac81a5d9c5b7 GIT binary patch literal 858 zcmWIWW@Zs#-~hr;y9&Y>pkN;h15iYPp*S}sNw1`$Bs7GVf!*S+a0(2UR&X;gvV3D? zU;yf7VCbE)H&4}^$1VG(<{y^0e;!Lh)^GM~5YY_EbDkI?l#>3YZrzI9h`CW8w zcbmV#T}mbC@VaEVEy8=P*R0JJ)YvWGaM$vd+Nw#r%=S*4vq7yy)N`}k#giTm+s~C+ zRQDyU`pmKYEXTRS+J_8#SIvrf-~8k6(^UKYd+yYpJ#N+$m?Y~lLDe?wJBMQQ8DWbp ztNJ(GI9q}lg`RTh~A6}ZMlk#-t?AEo11&&X(Sa#E8 z&S`_~wPyiowv*n?ZeNULPwoD9Ut@m#Hue8!zfJ!W^LF<97mv-SbaHmZEM{*^j(YCB zJj+5rVE4-NnF_g&4=o8Vn7Mx5Q$guni{A59Kh9(+PSm_5ka1^1uauKse-w|U#I@y5 z7}i{Hsa%kxWGb&Uy-M)xW6!KrPZwJ3k#;*~D|AAdXS)8R0*2}hdskg*&Pjh_aoR;& z_?7b3kUI~8_>x{85M8`^^ZJ<6>^k=Y41DfnH{}}yHoi0UHvDpZ%@rAQer|z>zxH@& zn;Ug8w`gl!DYv}0_(Ycc@A+%~a6eO4(5nB==FI+lhDvQv&Md3f2JHNCF)KMgcyW}S zex|)zVs+l`hA9OaU+t9`)_<4w>yE0*{bD|&o*}@Sog?V<=I$9x3=E(&9pKH#B*KhH i@5pkX^bP|{8bK^#Q-6RrD;r2XBM^eJ&^w@Q3=9B2eQk08 literal 0 HcmV?d00001 diff --git a/test/modules/argumentsParser.test.ts b/test/modules/argumentsParser.test.ts index 6be4d5013..9eedcb067 100644 --- a/test/modules/argumentsParser.test.ts +++ b/test/modules/argumentsParser.test.ts @@ -110,6 +110,7 @@ describe('options', () => { expect(options.getRegionFilter()).toHaveLength(0); expect(options.getOnlyBios()).toEqual(false); expect(options.getNoBios()).toEqual(false); + expect(options.getNoDevice()).toEqual(false); expect(options.getNoUnlicensed()).toEqual(false); expect(options.getOnlyRetail()).toEqual(false); expect(options.getNoDemo()).toEqual(false); @@ -499,6 +500,15 @@ describe('options', () => { expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-bios', '--only-bios'])).toThrow(/mutually exclusive/i); }); + it('should parse "no-device"', () => { + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-device']).getNoDevice()).toEqual(true); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-device', 'true']).getNoDevice()).toEqual(true); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-device', 'false']).getNoDevice()).toEqual(false); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-device', '--no-device']).getNoDevice()).toEqual(true); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-device', 'false', '--no-device', 'true']).getNoDevice()).toEqual(true); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-device', 'true', '--no-device', 'false']).getNoDevice()).toEqual(false); + }); + it('should parse "no-unlicensed"', () => { expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-unlicensed']).getNoUnlicensed()).toEqual(true); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--no-unlicensed', 'true']).getNoUnlicensed()).toEqual(true); diff --git a/test/modules/candidateFilter.test.ts b/test/modules/candidateFilter.test.ts index eff9461a9..0d1a17f23 100644 --- a/test/modules/candidateFilter.test.ts +++ b/test/modules/candidateFilter.test.ts @@ -439,7 +439,7 @@ describe('preFilter', () => { it('should return no candidates if none matching', async () => { await expectFilteredCandidates({ noBios: true }, [ await buildReleaseCandidatesWithRegionLanguage('one', 'USA', 'EN', { bios: 'yes' }), - await buildReleaseCandidatesWithRegionLanguage('two', 'USA', 'EN', { bios: 'yes' }), + await buildReleaseCandidatesWithRegionLanguage('two [BIOS]', 'USA', 'EN', { bios: 'no' }), ], 0); }); @@ -459,6 +459,38 @@ describe('preFilter', () => { }); }); + describe('no device', () => { + it('should return all candidates when option is false', async () => { + await expectFilteredCandidates({ noDevice: false }, [ + await buildReleaseCandidatesWithRegionLanguage('one', 'USA', 'EN', { device: 'no' }), + await buildReleaseCandidatesWithRegionLanguage('two', 'USA', 'EN', { device: 'yes' }), + await buildReleaseCandidatesWithRegionLanguage('three', 'USA', 'EN', { device: 'no' }), + ], 3); + }); + + it('should return no candidates if none matching', async () => { + await expectFilteredCandidates({ noDevice: true }, [ + await buildReleaseCandidatesWithRegionLanguage('one', 'USA', 'EN', { device: 'yes' }), + await buildReleaseCandidatesWithRegionLanguage('two', 'USA', 'EN', { device: 'yes' }), + ], 0); + }); + + it('should return some candidates if some matching', async () => { + await expectFilteredCandidates({ noDevice: true }, [ + await buildReleaseCandidatesWithRegionLanguage('one', 'USA', 'EN', { device: 'no' }), + await buildReleaseCandidatesWithRegionLanguage('two', 'USA', 'EN', { device: 'yes' }), + await buildReleaseCandidatesWithRegionLanguage('three', 'USA', 'EN', { device: 'no' }), + ], 2); + }); + + it('should return all candidates if all matching', async () => { + await expectFilteredCandidates({ noDevice: true }, [ + await buildReleaseCandidatesWithRegionLanguage('one', 'USA', 'EN', { device: 'no' }), + await buildReleaseCandidatesWithRegionLanguage('two', 'USA', 'EN', { device: 'no' }), + ], 2); + }); + }); + describe('no unlicensed', () => { it('should return all candidates when option is false', async () => { await expectFilteredCandidates({ noUnlicensed: false }, [ diff --git a/test/modules/datScanner.test.ts b/test/modules/datScanner.test.ts index 683e4b80a..9fd5e886a 100644 --- a/test/modules/datScanner.test.ts +++ b/test/modules/datScanner.test.ts @@ -63,7 +63,7 @@ describe('multiple files', () => { }); it('some files are path excluded', async () => { - await expect(createDatScanner(['test/fixtures/dats'], ['test/fixtures/**/*.dat']).scan()).resolves.toHaveLength(2); + await expect(createDatScanner(['test/fixtures/dats'], ['test/fixtures/**/*.dat']).scan()).resolves.toHaveLength(1); await expect(createDatScanner(['test/fixtures/dats'], ['test/fixtures/**/*.zip']).scan()).resolves.toHaveLength(4); }); @@ -91,6 +91,6 @@ describe('multiple files', () => { it('should scan single files', async () => { await expect(createDatScanner([path.join(path.resolve(), 'test', 'fixtures', 'dats', 'one.*')]).scan()).resolves.toHaveLength(1); await expect(createDatScanner(['test/fixtures/dats/one.*']).scan()).resolves.toHaveLength(1); - await expect(createDatScanner(['test/fixtures/*/one.zip']).scan()).resolves.toHaveLength(1); - await expect(createDatScanner(['test/fixtures/dats/one.zip']).scan()).resolves.toHaveLength(1); + await expect(createDatScanner(['test/fixtures/*/one.dat']).scan()).resolves.toHaveLength(1); + await expect(createDatScanner(['test/fixtures/dats/one.dat']).scan()).resolves.toHaveLength(1); });