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);
});