Skip to content

Commit

Permalink
feat: implemented the year (Y) directive
Browse files Browse the repository at this point in the history
The year directive can now also be used in addition to the system date to complete shorthand dates
in the cooked journal.

closes #13
  • Loading branch information
darylwright committed Apr 19, 2024
1 parent f68166c commit 1414277
Show file tree
Hide file tree
Showing 15 changed files with 760 additions and 48 deletions.
84 changes: 84 additions & 0 deletions diagram.html
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "NonTerminal",
"name": "yearDirective",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
Expand Down Expand Up @@ -1835,6 +1845,80 @@
]
}
]
},
{
"type": "Rule",
"name": "yearDirective",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "YearDirective",
"label": "YearDirective",
"idx": 0
},
{
"type": "Terminal",
"name": "YearDirectiveValue",
"label": "YearDirectiveValue",
"idx": 0,
"pattern": "\\d{4,5}(?=\\s+)"
},
{
"type": "Option",
"idx": 0,
"definition": [
{
"type": "NonTerminal",
"name": "inlineComment",
"idx": 0
}
]
},
{
"type": "Terminal",
"name": "NEWLINE",
"label": "NEWLINE",
"idx": 0,
"pattern": "(\\r\\n|\\r|\\n)"
},
{
"type": "Repetition",
"idx": 0,
"definition": [
{
"type": "NonTerminal",
"name": "yearDirectiveContentLine",
"idx": 1
}
]
}
]
},
{
"type": "Rule",
"name": "yearDirectiveContentLine",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "INDENT",
"label": "INDENT",
"idx": 0
},
{
"type": "NonTerminal",
"name": "inlineComment",
"idx": 0
},
{
"type": "Terminal",
"name": "NEWLINE",
"label": "NEWLINE",
"idx": 0,
"pattern": "(\\r\\n|\\r|\\n)"
}
]
}
];
</script>
Expand Down
132 changes: 132 additions & 0 deletions src/__tests__/cst_to_raw_visitor/year_directive.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import test from 'ava';

import { parseLedgerToCST } from '../../index';
import CstToRawVisitor from '../../lib/visitors/cst_to_raw';
import * as Raw from '../../lib/visitors/raw_types';
import { assertNoLexingOrParsingErrors } from '../utils';

test('returns a year directive object', (t) => {
const cstResults = [
parseLedgerToCST('Y2024\n'),
parseLedgerToCST('Y 2024\n'),
parseLedgerToCST('year2024\n'),
parseLedgerToCST('year 2024\n'),
parseLedgerToCST('apply year2024\n'),
parseLedgerToCST('apply year 2024\n')
];

for (const cstResult of cstResults) {
assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(result.length, 1, 'should modify a year directive');
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
t.deepEqual(
(result[0] as Raw.YearDirective).value,
{
year: '2024',
comments: undefined,
contentLines: []
},
'should correctly return a year directive object'
);
}
});

test('returns a year directive object with a comment', (t) => {
const cstResult = parseLedgerToCST('Y 2024 ; a comment\n');

assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(result.length, 1, 'should modify a year directive');
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
t.truthy(
(result[0] as Raw.YearDirective).value.comments,
'should contain a comment field'
);
t.is(
(result[0] as Raw.YearDirective).value.comments?.value.length,
1,
'should contain a single comment item'
);
});

test('returns a year directive a content line', (t) => {
const cstResult = parseLedgerToCST(`Y 2024
; content line
`);

assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(
result.length,
1,
'should modify a year directive with a sub-directive comment'
);
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
t.is(
(result[0] as Raw.YearDirective).value.contentLines.length,
1,
'should contain a year directive content line'
);
});

test('return a year directive with an inline comment and content line', (t) => {
const cstResult = parseLedgerToCST(`Y 2024 ; inline comment
; content line
`);

assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(
result.length,
1,
'should modify a year directive with a sub-directive comment'
);
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
t.truthy(
(result[0] as Raw.YearDirective).value.comments,
'should contain a comment field'
);
t.is(
(result[0] as Raw.YearDirective).value.comments?.value.length,
1,
'should contain a single comment item'
);
t.is(
(result[0] as Raw.YearDirective).value.contentLines.length,
1,
'should contain a year directive content line'
);
});

test('return a year directive with several content lines', (t) => {
const cstResult = parseLedgerToCST(`Y 2024 ; inline comment
; content line
; another line
; yet another line
`);

assertNoLexingOrParsingErrors(t, cstResult);

const result = CstToRawVisitor.journal(cstResult.cstJournal.children);

t.is(
result.length,
1,
'should modify a year directive with a sub-directive comment'
);
t.is(result[0].type, 'yearDirective', 'should be a yearDirective object');
t.is(
(result[0] as Raw.YearDirective).value.contentLines.length,
3,
'should contain several year directive content lines'
);
});
11 changes: 9 additions & 2 deletions src/__tests__/lexer/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import test, { TestFn } from 'ava';

import HLedgerLexer from '../../lib/lexer';
import * as utils from '../utils';

import type { TestFn } from 'ava';

export interface LexerTest {
pattern: string;
expected: unknown[];
Expand All @@ -13,6 +13,7 @@ function tokenize(pattern: string) {
return utils.simplifyLexResult(HLedgerLexer.tokenize(pattern));
}

// TODO: Remove this function when all lexer tests are using the macro function.
export function runLexerTests(avaTest: TestFn, tests: LexerTest[]) {
for (const { pattern, expected, title } of tests) {
avaTest(title, (t) => {
Expand All @@ -22,3 +23,9 @@ export function runLexerTests(avaTest: TestFn, tests: LexerTest[]) {
});
}
}

export const macro = test.macro<[string, unknown[]]>((t, pattern, expected) => {
const result = tokenize(pattern);

t.deepEqual(result, expected, pattern);
});
68 changes: 68 additions & 0 deletions src/__tests__/lexer/year_directives.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import test from 'ava';

import { macro } from './utils';

test('recognizes a year directive', macro, 'Y2024 ', [
'YearDirective',
'YearDirectiveValue'
]);

test('recognizes a year directive with an optional space', macro, 'Y 2024 ', [
'YearDirective',
'YearDirectiveValue'
]);

test(
'recognizes a year directive with a newline at the end',
macro,
'Y 2024\n',
['YearDirective', 'YearDirectiveValue', 'NEWLINE']
);

test(
'recognizes a year directive with a comment at the end',
macro,
'Y 2024 ; a comment\n',
[
'YearDirective',
'YearDirectiveValue',
'SemicolonComment',
'InlineCommentText',
'NEWLINE'
]
);

test('recognizes deprecated year directive form', macro, 'year 2024 ', [
'YearDirective',
'YearDirectiveValue'
]);

test(
'recognizes alternative deprecated year directive form',
macro,
'apply year 2024 ',
['YearDirective', 'YearDirectiveValue']
);

test(
'recognizes deprecated year directive form without optional space',
macro,
'year2024 ',
['YearDirective', 'YearDirectiveValue']
);

test(
'recognizes alternative deprecated year directive form without optional space',
macro,
'apply year2024 ',
['YearDirective', 'YearDirectiveValue']
);

test('does not recognize an invalid year directive value', macro, 'Y2024a ', [
'YearDirective'
]);

test('recognizes a five digit year in year directive', macro, 'Y12024 ', [
'YearDirective',
'YearDirectiveValue'
]);
Loading

0 comments on commit 1414277

Please sign in to comment.