Skip to content

Commit de882c1

Browse files
authored
refactor: Simplify implementations of RecurringField and StatusField (#816)
* refactor: Introduce new class FilterInstruction * refactor: Introduce new class FilterInstructions * refactor: Reimplement RecurringField using FilterInstructions * refactor: Simplify calling FilterInstructions.push() * refactor: Rename FilterInstructions.push() to .add() * chore: Better JSDoc for FilterInstruction and FilterInstructions * chore: Remove a code sample that won't normally be called directly
1 parent de66473 commit de882c1

File tree

5 files changed

+114
-46
lines changed

5 files changed

+114
-46
lines changed

src/Query/Filter/Filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Task } from '../../Task';
44
* A filtering function, that takes a Task object and returns
55
* whether it matches a particular filtering instruction.
66
*/
7-
type Filter = (task: Task) => boolean;
7+
export type Filter = (task: Task) => boolean;
88

99
/**
1010
* A class which stores one of:

src/Query/Filter/FilterInstruction.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Filter, FilterOrErrorMessage } from './Filter';
2+
3+
/**
4+
* Implementation of a single instruction for filtering tasks, and its corresponding predicate.
5+
*
6+
* This is really a helper to simplify the implementation of individual filter
7+
* instructions, hiding away the details of parsing individual instruction lines.
8+
*
9+
* This will usually be accessed via {@link FilterInstructions.add}
10+
*
11+
* @see FilterInstructions
12+
*/
13+
export class FilterInstruction {
14+
private readonly _instruction: string;
15+
private readonly _filter: Filter;
16+
17+
/**
18+
* Constructor:
19+
* @param instruction - Full text of the instruction for the filter: must be matched exactly
20+
* @param filter
21+
*/
22+
constructor(instruction: string, filter: Filter) {
23+
this._instruction = instruction;
24+
this._filter = filter;
25+
}
26+
27+
public canCreateFilterForLine(line: string): boolean {
28+
return line == this._instruction;
29+
}
30+
31+
public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
32+
const result = new FilterOrErrorMessage();
33+
34+
if (line === this._instruction) {
35+
result.filter = this._filter;
36+
return result;
37+
}
38+
39+
result.error = `do not understand filter: ${line}`;
40+
return result;
41+
}
42+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { FilterInstruction } from './FilterInstruction';
2+
import { Filter, FilterOrErrorMessage } from './Filter';
3+
4+
/**
5+
* Implementation of a collection of instructions for filtering tasks.
6+
*
7+
* @example
8+
* private readonly _filters = new FilterInstructions();
9+
* this._filters.add('is recurring', (task) => task.recurrence !== null);
10+
*
11+
* @see FilterInstruction
12+
*/
13+
export class FilterInstructions {
14+
private readonly _filters: FilterInstruction[] = [];
15+
16+
public add(instruction: string, filter: Filter) {
17+
this._filters.push(new FilterInstruction(instruction, filter));
18+
}
19+
20+
public canCreateFilterForLine(line: string): boolean {
21+
for (const filter of this._filters) {
22+
if (filter.canCreateFilterForLine(line)) {
23+
return true;
24+
}
25+
}
26+
return false;
27+
}
28+
29+
public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
30+
for (const filter of this._filters) {
31+
const x = filter.createFilterOrErrorMessage(line);
32+
if (x.error === undefined) {
33+
return x;
34+
}
35+
}
36+
37+
const result = new FilterOrErrorMessage();
38+
result.error = `do not understand filter: ${line}`;
39+
return result;
40+
}
41+
}

src/Query/Filter/RecurringField.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
11
import { Field } from './Field';
2-
import { FilterOrErrorMessage } from './Filter';
2+
import type { FilterOrErrorMessage } from './Filter';
3+
import { FilterInstructions } from './FilterInstructions';
34

45
export class RecurringField extends Field {
5-
private readonly instructionForFieldPresence = 'is recurring';
6-
private readonly instructionForFieldAbsence = 'is not recurring';
6+
private readonly _filters = new FilterInstructions();
7+
8+
constructor() {
9+
super();
10+
this._filters.add('is recurring', (task) => task.recurrence !== null);
11+
this._filters.add(
12+
'is not recurring',
13+
(task) => task.recurrence === null,
14+
);
15+
}
716

817
public canCreateFilterForLine(line: string): boolean {
9-
return (
10-
line === this.instructionForFieldAbsence ||
11-
line === this.instructionForFieldPresence
12-
);
18+
return this._filters.canCreateFilterForLine(line);
1319
}
1420

1521
public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
16-
const result = new FilterOrErrorMessage();
17-
18-
if (line === this.instructionForFieldPresence) {
19-
result.filter = (task) => task.recurrence !== null;
20-
return result;
21-
}
22-
23-
if (line === this.instructionForFieldAbsence) {
24-
result.filter = (task) => task.recurrence === null;
25-
return result;
26-
}
27-
28-
result.error = `do not understand filter: ${line}`;
29-
30-
return result;
22+
return this._filters.createFilterOrErrorMessage(line);
3123
}
3224

3325
protected fieldName(): string {

src/Query/Filter/StatusField.ts

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
1-
import { Status } from '../../Task';
1+
import { Status, Task } from '../../Task';
22
import { Field } from './Field';
3-
import { FilterOrErrorMessage } from './Filter';
3+
import type { FilterOrErrorMessage } from './Filter';
4+
import { FilterInstructions } from './FilterInstructions';
45

56
export class StatusField extends Field {
6-
private readonly instructionForFieldPresence = 'done';
7-
private readonly instructionForFieldAbsence = 'not done';
7+
private readonly _filters = new FilterInstructions();
88

9-
public canCreateFilterForLine(line: string): boolean {
10-
return (
11-
line === this.instructionForFieldAbsence ||
12-
line === this.instructionForFieldPresence
9+
constructor() {
10+
super();
11+
12+
this._filters.add('done', (task: Task) => task.status === Status.Done);
13+
this._filters.add(
14+
'not done',
15+
(task: Task) => task.status !== Status.Done,
1316
);
1417
}
1518

16-
public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
17-
const result = new FilterOrErrorMessage();
18-
19-
if (line === this.instructionForFieldPresence) {
20-
result.filter = (task) => task.status === Status.Done;
21-
return result;
22-
}
23-
24-
if (line === this.instructionForFieldAbsence) {
25-
result.filter = (task) => task.status !== Status.Done;
26-
return result;
27-
}
28-
29-
result.error = `do not understand filter: ${line}`;
19+
public canCreateFilterForLine(line: string): boolean {
20+
return this._filters.canCreateFilterForLine(line);
21+
}
3022

31-
return result;
23+
public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
24+
return this._filters.createFilterOrErrorMessage(line);
3225
}
3326

3427
protected fieldName(): string {

0 commit comments

Comments
 (0)