Skip to content

Commit 1414277

Browse files
committed
feat: implemented the year (Y) directive
The year directive can now also be used in addition to the system date to complete shorthand dates in the cooked journal. closes #13
1 parent f68166c commit 1414277

File tree

15 files changed

+760
-48
lines changed

15 files changed

+760
-48
lines changed

diagram.html

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,16 @@
289289
}
290290
]
291291
},
292+
{
293+
"type": "Alternative",
294+
"definition": [
295+
{
296+
"type": "NonTerminal",
297+
"name": "yearDirective",
298+
"idx": 0
299+
}
300+
]
301+
},
292302
{
293303
"type": "Alternative",
294304
"definition": [
@@ -1835,6 +1845,80 @@
18351845
]
18361846
}
18371847
]
1848+
},
1849+
{
1850+
"type": "Rule",
1851+
"name": "yearDirective",
1852+
"orgText": "",
1853+
"definition": [
1854+
{
1855+
"type": "Terminal",
1856+
"name": "YearDirective",
1857+
"label": "YearDirective",
1858+
"idx": 0
1859+
},
1860+
{
1861+
"type": "Terminal",
1862+
"name": "YearDirectiveValue",
1863+
"label": "YearDirectiveValue",
1864+
"idx": 0,
1865+
"pattern": "\\d{4,5}(?=\\s+)"
1866+
},
1867+
{
1868+
"type": "Option",
1869+
"idx": 0,
1870+
"definition": [
1871+
{
1872+
"type": "NonTerminal",
1873+
"name": "inlineComment",
1874+
"idx": 0
1875+
}
1876+
]
1877+
},
1878+
{
1879+
"type": "Terminal",
1880+
"name": "NEWLINE",
1881+
"label": "NEWLINE",
1882+
"idx": 0,
1883+
"pattern": "(\\r\\n|\\r|\\n)"
1884+
},
1885+
{
1886+
"type": "Repetition",
1887+
"idx": 0,
1888+
"definition": [
1889+
{
1890+
"type": "NonTerminal",
1891+
"name": "yearDirectiveContentLine",
1892+
"idx": 1
1893+
}
1894+
]
1895+
}
1896+
]
1897+
},
1898+
{
1899+
"type": "Rule",
1900+
"name": "yearDirectiveContentLine",
1901+
"orgText": "",
1902+
"definition": [
1903+
{
1904+
"type": "Terminal",
1905+
"name": "INDENT",
1906+
"label": "INDENT",
1907+
"idx": 0
1908+
},
1909+
{
1910+
"type": "NonTerminal",
1911+
"name": "inlineComment",
1912+
"idx": 0
1913+
},
1914+
{
1915+
"type": "Terminal",
1916+
"name": "NEWLINE",
1917+
"label": "NEWLINE",
1918+
"idx": 0,
1919+
"pattern": "(\\r\\n|\\r|\\n)"
1920+
}
1921+
]
18381922
}
18391923
];
18401924
</script>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import test from 'ava';
2+
3+
import { parseLedgerToCST } from '../../index';
4+
import CstToRawVisitor from '../../lib/visitors/cst_to_raw';
5+
import * as Raw from '../../lib/visitors/raw_types';
6+
import { assertNoLexingOrParsingErrors } from '../utils';
7+
8+
test('returns a year directive object', (t) => {
9+
const cstResults = [
10+
parseLedgerToCST('Y2024\n'),
11+
parseLedgerToCST('Y 2024\n'),
12+
parseLedgerToCST('year2024\n'),
13+
parseLedgerToCST('year 2024\n'),
14+
parseLedgerToCST('apply year2024\n'),
15+
parseLedgerToCST('apply year 2024\n')
16+
];
17+
18+
for (const cstResult of cstResults) {
19+
assertNoLexingOrParsingErrors(t, cstResult);
20+
21+
const result = CstToRawVisitor.journal(cstResult.cstJournal.children);
22+
23+
t.is(result.length, 1, 'should modify a year directive');
24+
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
25+
t.deepEqual(
26+
(result[0] as Raw.YearDirective).value,
27+
{
28+
year: '2024',
29+
comments: undefined,
30+
contentLines: []
31+
},
32+
'should correctly return a year directive object'
33+
);
34+
}
35+
});
36+
37+
test('returns a year directive object with a comment', (t) => {
38+
const cstResult = parseLedgerToCST('Y 2024 ; a comment\n');
39+
40+
assertNoLexingOrParsingErrors(t, cstResult);
41+
42+
const result = CstToRawVisitor.journal(cstResult.cstJournal.children);
43+
44+
t.is(result.length, 1, 'should modify a year directive');
45+
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
46+
t.truthy(
47+
(result[0] as Raw.YearDirective).value.comments,
48+
'should contain a comment field'
49+
);
50+
t.is(
51+
(result[0] as Raw.YearDirective).value.comments?.value.length,
52+
1,
53+
'should contain a single comment item'
54+
);
55+
});
56+
57+
test('returns a year directive a content line', (t) => {
58+
const cstResult = parseLedgerToCST(`Y 2024
59+
; content line
60+
`);
61+
62+
assertNoLexingOrParsingErrors(t, cstResult);
63+
64+
const result = CstToRawVisitor.journal(cstResult.cstJournal.children);
65+
66+
t.is(
67+
result.length,
68+
1,
69+
'should modify a year directive with a sub-directive comment'
70+
);
71+
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
72+
t.is(
73+
(result[0] as Raw.YearDirective).value.contentLines.length,
74+
1,
75+
'should contain a year directive content line'
76+
);
77+
});
78+
79+
test('return a year directive with an inline comment and content line', (t) => {
80+
const cstResult = parseLedgerToCST(`Y 2024 ; inline comment
81+
; content line
82+
`);
83+
84+
assertNoLexingOrParsingErrors(t, cstResult);
85+
86+
const result = CstToRawVisitor.journal(cstResult.cstJournal.children);
87+
88+
t.is(
89+
result.length,
90+
1,
91+
'should modify a year directive with a sub-directive comment'
92+
);
93+
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
94+
t.truthy(
95+
(result[0] as Raw.YearDirective).value.comments,
96+
'should contain a comment field'
97+
);
98+
t.is(
99+
(result[0] as Raw.YearDirective).value.comments?.value.length,
100+
1,
101+
'should contain a single comment item'
102+
);
103+
t.is(
104+
(result[0] as Raw.YearDirective).value.contentLines.length,
105+
1,
106+
'should contain a year directive content line'
107+
);
108+
});
109+
110+
test('return a year directive with several content lines', (t) => {
111+
const cstResult = parseLedgerToCST(`Y 2024 ; inline comment
112+
; content line
113+
; another line
114+
; yet another line
115+
`);
116+
117+
assertNoLexingOrParsingErrors(t, cstResult);
118+
119+
const result = CstToRawVisitor.journal(cstResult.cstJournal.children);
120+
121+
t.is(
122+
result.length,
123+
1,
124+
'should modify a year directive with a sub-directive comment'
125+
);
126+
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
127+
t.is(
128+
(result[0] as Raw.YearDirective).value.contentLines.length,
129+
3,
130+
'should contain several year directive content lines'
131+
);
132+
});

src/__tests__/lexer/utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import test, { TestFn } from 'ava';
2+
13
import HLedgerLexer from '../../lib/lexer';
24
import * as utils from '../utils';
35

4-
import type { TestFn } from 'ava';
5-
66
export interface LexerTest {
77
pattern: string;
88
expected: unknown[];
@@ -13,6 +13,7 @@ function tokenize(pattern: string) {
1313
return utils.simplifyLexResult(HLedgerLexer.tokenize(pattern));
1414
}
1515

16+
// TODO: Remove this function when all lexer tests are using the macro function.
1617
export function runLexerTests(avaTest: TestFn, tests: LexerTest[]) {
1718
for (const { pattern, expected, title } of tests) {
1819
avaTest(title, (t) => {
@@ -22,3 +23,9 @@ export function runLexerTests(avaTest: TestFn, tests: LexerTest[]) {
2223
});
2324
}
2425
}
26+
27+
export const macro = test.macro<[string, unknown[]]>((t, pattern, expected) => {
28+
const result = tokenize(pattern);
29+
30+
t.deepEqual(result, expected, pattern);
31+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import test from 'ava';
2+
3+
import { macro } from './utils';
4+
5+
test('recognizes a year directive', macro, 'Y2024 ', [
6+
'YearDirective',
7+
'YearDirectiveValue'
8+
]);
9+
10+
test('recognizes a year directive with an optional space', macro, 'Y 2024 ', [
11+
'YearDirective',
12+
'YearDirectiveValue'
13+
]);
14+
15+
test(
16+
'recognizes a year directive with a newline at the end',
17+
macro,
18+
'Y 2024\n',
19+
['YearDirective', 'YearDirectiveValue', 'NEWLINE']
20+
);
21+
22+
test(
23+
'recognizes a year directive with a comment at the end',
24+
macro,
25+
'Y 2024 ; a comment\n',
26+
[
27+
'YearDirective',
28+
'YearDirectiveValue',
29+
'SemicolonComment',
30+
'InlineCommentText',
31+
'NEWLINE'
32+
]
33+
);
34+
35+
test('recognizes deprecated year directive form', macro, 'year 2024 ', [
36+
'YearDirective',
37+
'YearDirectiveValue'
38+
]);
39+
40+
test(
41+
'recognizes alternative deprecated year directive form',
42+
macro,
43+
'apply year 2024 ',
44+
['YearDirective', 'YearDirectiveValue']
45+
);
46+
47+
test(
48+
'recognizes deprecated year directive form without optional space',
49+
macro,
50+
'year2024 ',
51+
['YearDirective', 'YearDirectiveValue']
52+
);
53+
54+
test(
55+
'recognizes alternative deprecated year directive form without optional space',
56+
macro,
57+
'apply year2024 ',
58+
['YearDirective', 'YearDirectiveValue']
59+
);
60+
61+
test('does not recognize an invalid year directive value', macro, 'Y2024a ', [
62+
'YearDirective'
63+
]);
64+
65+
test('recognizes a five digit year in year directive', macro, 'Y12024 ', [
66+
'YearDirective',
67+
'YearDirectiveValue'
68+
]);

0 commit comments

Comments
 (0)