beancount 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -16
- package/build/src/benchmark.mjs +1 -1
- package/build/src/classes/DatedNode.d.mts +40 -0
- package/build/src/classes/DatedNode.mjs +61 -0
- package/build/src/classes/Node.d.mts +92 -0
- package/build/src/classes/Node.mjs +107 -0
- package/build/src/classes/ParseResult.d.mts +75 -75
- package/build/src/classes/ParseResult.mjs +96 -98
- package/build/src/classes/nodes/Balance.d.mts +32 -0
- package/build/src/classes/nodes/Balance.mjs +50 -0
- package/build/src/classes/nodes/Blankline.d.mts +23 -0
- package/build/src/classes/nodes/Blankline.mjs +37 -0
- package/build/src/classes/nodes/Close.d.mts +20 -0
- package/build/src/classes/nodes/Close.mjs +31 -0
- package/build/src/classes/nodes/Comment.d.mts +25 -0
- package/build/src/classes/nodes/Comment.mjs +42 -0
- package/build/src/classes/nodes/Commodity.d.mts +20 -0
- package/build/src/classes/nodes/Commodity.mjs +31 -0
- package/build/src/classes/nodes/Custom.d.mts +23 -0
- package/build/src/classes/nodes/Custom.mjs +38 -0
- package/build/src/classes/nodes/Document.d.mts +22 -0
- package/build/src/classes/nodes/Document.mjs +34 -0
- package/build/src/classes/nodes/Event.d.mts +23 -0
- package/build/src/classes/nodes/Event.mjs +34 -0
- package/build/src/classes/nodes/Include.d.mts +20 -0
- package/build/src/classes/nodes/Include.mjs +31 -0
- package/build/src/classes/nodes/Note.d.mts +22 -0
- package/build/src/classes/nodes/Note.mjs +34 -0
- package/build/src/classes/nodes/Open.d.mts +27 -0
- package/build/src/classes/nodes/Open.mjs +66 -0
- package/build/src/classes/nodes/Option.d.mts +23 -0
- package/build/src/classes/nodes/Option.mjs +32 -0
- package/build/src/classes/nodes/Pad.d.mts +22 -0
- package/build/src/classes/nodes/Pad.mjs +33 -0
- package/build/src/classes/nodes/Plugin.d.mts +22 -0
- package/build/src/classes/nodes/Plugin.mjs +36 -0
- package/build/src/classes/nodes/Poptag.d.mts +21 -0
- package/build/src/classes/nodes/Poptag.mjs +34 -0
- package/build/src/classes/nodes/Price.d.mts +32 -0
- package/build/src/classes/nodes/Price.mjs +57 -0
- package/build/src/classes/nodes/Pushtag.d.mts +21 -0
- package/build/src/classes/nodes/Pushtag.mjs +34 -0
- package/build/src/classes/nodes/Query.d.mts +22 -0
- package/build/src/classes/nodes/Query.mjs +34 -0
- package/build/src/classes/nodes/Transaction/Posting.d.mts +59 -0
- package/build/src/classes/nodes/Transaction/Posting.mjs +97 -0
- package/build/src/classes/nodes/Transaction/Tag.d.mts +28 -0
- package/build/src/classes/nodes/Transaction/Tag.mjs +28 -0
- package/build/src/classes/nodes/Transaction/index.d.mts +70 -0
- package/build/src/classes/nodes/Transaction/index.mjs +193 -0
- package/build/src/classes/nodes/index.d.mts +19 -0
- package/build/src/classes/nodes/index.mjs +19 -0
- package/build/src/cli.mjs +4 -4
- package/build/src/deserialize.d.mts +54 -54
- package/build/src/deserialize.mjs +89 -89
- package/build/src/directiveTypes.d.mts +10 -0
- package/build/src/directiveTypes.mjs +29 -0
- package/build/src/genericParse.d.mts +27 -20
- package/build/src/genericParse.mjs +30 -30
- package/build/src/main.d.mts +30 -31
- package/build/src/main.mjs +30 -30
- package/build/src/nodeTypeToClass.d.mts +81 -0
- package/build/src/nodeTypeToClass.mjs +37 -0
- package/build/src/parse.d.mts +16 -16
- package/build/src/parse.mjs +37 -37
- package/build/src/parseFile.d.mts +2 -2
- package/build/src/parseFile.mjs +11 -11
- package/build/src/utils/splitStringIntoSourceFragments.d.ts +14 -0
- package/build/src/utils/splitStringIntoSourceFragments.js +48 -0
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { GenericParseResultWithDate } from '../../genericParse.mjs';
|
|
2
|
+
import { DatedNode } from '../DatedNode.mjs';
|
|
3
|
+
import { FormatOptions } from '../ParseResult.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* Represents a Price node that records the price of a commodity.
|
|
6
|
+
* Price directives establish the exchange rate between a commodity and a currency.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Price extends DatedNode {
|
|
9
|
+
/** @inheritdoc */
|
|
10
|
+
type: "price";
|
|
11
|
+
/** The commodity being priced */
|
|
12
|
+
commodity: string;
|
|
13
|
+
/** The currency the price is expressed in */
|
|
14
|
+
currency: string;
|
|
15
|
+
/** The price amount */
|
|
16
|
+
amount: string;
|
|
17
|
+
/**
|
|
18
|
+
* Gets the formatted price string (amount + currency).
|
|
19
|
+
* @returns The formatted price string
|
|
20
|
+
*/
|
|
21
|
+
get price(): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a Price instance from a generic parse result.
|
|
24
|
+
* @param genericParseResult - The parsed price node data
|
|
25
|
+
* @returns A new Price instance
|
|
26
|
+
*/
|
|
27
|
+
static fromGenericParseResult(genericParseResult: GenericParseResultWithDate): Price;
|
|
28
|
+
/** @inheritdoc */
|
|
29
|
+
toString(): string;
|
|
30
|
+
/** @inheritdoc */
|
|
31
|
+
toFormattedString(formatOptions?: FormatOptions): string;
|
|
32
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { assertNodeConstructor } from '../Node.mjs';
|
|
2
|
+
import { DatedNode } from '../DatedNode.mjs';
|
|
3
|
+
import { simpleParseLine } from '../../utils/simpleParseLine.mjs';
|
|
4
|
+
import { formatPrice } from '../../utils/formatPrice.mjs';
|
|
5
|
+
import { defaultFormatOptions } from '../ParseResult.mjs';
|
|
6
|
+
/**
|
|
7
|
+
* Represents a Price node that records the price of a commodity.
|
|
8
|
+
* Price directives establish the exchange rate between a commodity and a currency.
|
|
9
|
+
*/
|
|
10
|
+
export class Price extends DatedNode {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(...arguments);
|
|
13
|
+
/** @inheritdoc */
|
|
14
|
+
this.type = 'price';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Gets the formatted price string (amount + currency).
|
|
18
|
+
* @returns The formatted price string
|
|
19
|
+
*/
|
|
20
|
+
get price() {
|
|
21
|
+
return formatPrice(this.amount, this.currency);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a Price instance from a generic parse result.
|
|
25
|
+
* @param genericParseResult - The parsed price node data
|
|
26
|
+
* @returns A new Price instance
|
|
27
|
+
*/
|
|
28
|
+
static fromGenericParseResult(genericParseResult) {
|
|
29
|
+
const [commodity, amount, currency] = simpleParseLine(genericParseResult.header);
|
|
30
|
+
return new Price({
|
|
31
|
+
...genericParseResult.props,
|
|
32
|
+
commodity,
|
|
33
|
+
currency,
|
|
34
|
+
amount,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** @inheritdoc */
|
|
38
|
+
toString() {
|
|
39
|
+
return this.toFormattedString({ currencyColumn: 0 });
|
|
40
|
+
}
|
|
41
|
+
/** @inheritdoc */
|
|
42
|
+
toFormattedString(formatOptions = defaultFormatOptions) {
|
|
43
|
+
const parts = [this.getDateTypePrefix(), this.commodity];
|
|
44
|
+
const paddingLength = formatOptions.currencyColumn -
|
|
45
|
+
parts.join(' ').length -
|
|
46
|
+
this.amount.length -
|
|
47
|
+
2 - // indent
|
|
48
|
+
2; // spacing
|
|
49
|
+
if (paddingLength > 0) {
|
|
50
|
+
parts.push(' '.repeat(paddingLength));
|
|
51
|
+
}
|
|
52
|
+
parts.push(this.amount, `${this.currency}${this.getMetaDataString()}`);
|
|
53
|
+
return parts.join(' ');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Ensure class conforms to NodeConstructor pattern
|
|
57
|
+
assertNodeConstructor(Price);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GenericParseResult } from '../../genericParse.mjs';
|
|
2
|
+
import { Node } from '../Node.mjs';
|
|
3
|
+
import { Tag } from './Transaction/Tag.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* Represents a Pushtag node that pushes a tag onto the tag stack.
|
|
6
|
+
* Subsequent transactions will automatically inherit this tag until it's popped.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Pushtag extends Node {
|
|
9
|
+
/** @inheritdoc */
|
|
10
|
+
type: "pushtag";
|
|
11
|
+
/** The tag being pushed onto the stack */
|
|
12
|
+
tag: Tag;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a Pushtag instance from a generic parse result.
|
|
15
|
+
* @param genericParseResult - The parsed pushtag node data
|
|
16
|
+
* @returns A new Pushtag instance
|
|
17
|
+
*/
|
|
18
|
+
static fromGenericParseResult(genericParseResult: GenericParseResult): Pushtag;
|
|
19
|
+
/** @inheritdoc */
|
|
20
|
+
toString(): string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { simpleParseLine } from '../../utils/simpleParseLine.mjs';
|
|
2
|
+
import { assertNodeConstructor, Node } from '../Node.mjs';
|
|
3
|
+
import { Tag } from './Transaction/Tag.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* Represents a Pushtag node that pushes a tag onto the tag stack.
|
|
6
|
+
* Subsequent transactions will automatically inherit this tag until it's popped.
|
|
7
|
+
*/
|
|
8
|
+
export class Pushtag extends Node {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
/** @inheritdoc */
|
|
12
|
+
this.type = 'pushtag';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a Pushtag instance from a generic parse result.
|
|
16
|
+
* @param genericParseResult - The parsed pushtag node data
|
|
17
|
+
* @returns A new Pushtag instance
|
|
18
|
+
*/
|
|
19
|
+
static fromGenericParseResult(genericParseResult) {
|
|
20
|
+
const [tagContent] = simpleParseLine(genericParseResult.header);
|
|
21
|
+
return new Pushtag({
|
|
22
|
+
tag: new Tag({
|
|
23
|
+
content: tagContent.trim().replace(/^#/, ''),
|
|
24
|
+
fromStack: true,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** @inheritdoc */
|
|
29
|
+
toString() {
|
|
30
|
+
return `${this.type} #${this.tag.content}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Ensure class conforms to NodeConstructor pattern
|
|
34
|
+
assertNodeConstructor(Pushtag);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GenericParseResultWithDate } from '../../genericParse.mjs';
|
|
2
|
+
import { DatedNode } from '../DatedNode.mjs';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a Query node that defines a named SQL query.
|
|
5
|
+
* Query directives allow defining reusable queries in the Beancount file.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Query extends DatedNode {
|
|
8
|
+
/** @inheritdoc */
|
|
9
|
+
type: "query";
|
|
10
|
+
/** The name of the query */
|
|
11
|
+
name: string;
|
|
12
|
+
/** The SQL query contents */
|
|
13
|
+
sqlContents: string;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a Query instance from a generic parse result.
|
|
16
|
+
* @param genericParseResult - The parsed query node data
|
|
17
|
+
* @returns A new Query instance
|
|
18
|
+
*/
|
|
19
|
+
static fromGenericParseResult(genericParseResult: GenericParseResultWithDate): Query;
|
|
20
|
+
/** @inheritdoc */
|
|
21
|
+
toString(): string;
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { assertNodeConstructor } from '../Node.mjs';
|
|
2
|
+
import { DatedNode } from '../DatedNode.mjs';
|
|
3
|
+
import { stringAwareParseLine } from '../../utils/stringAwareParseLine.mjs';
|
|
4
|
+
import { parseString } from '../Value.mjs';
|
|
5
|
+
/**
|
|
6
|
+
* Represents a Query node that defines a named SQL query.
|
|
7
|
+
* Query directives allow defining reusable queries in the Beancount file.
|
|
8
|
+
*/
|
|
9
|
+
export class Query extends DatedNode {
|
|
10
|
+
constructor() {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
/** @inheritdoc */
|
|
13
|
+
this.type = 'query';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Creates a Query instance from a generic parse result.
|
|
17
|
+
* @param genericParseResult - The parsed query node data
|
|
18
|
+
* @returns A new Query instance
|
|
19
|
+
*/
|
|
20
|
+
static fromGenericParseResult(genericParseResult) {
|
|
21
|
+
const [name, sqlContents] = stringAwareParseLine(genericParseResult.header);
|
|
22
|
+
return new Query({
|
|
23
|
+
...genericParseResult.props,
|
|
24
|
+
name: parseString(name),
|
|
25
|
+
sqlContents: parseString(sqlContents).trim(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** @inheritdoc */
|
|
29
|
+
toString() {
|
|
30
|
+
return `${this.getDateTypePrefix()} "${this.name}" "${this.sqlContents}"${this.getMetaDataString()}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Ensure class conforms to NodeConstructor pattern
|
|
34
|
+
assertNodeConstructor(Query);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { FormatOptions } from '../../ParseResult.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a single posting (account movement) within a transaction.
|
|
4
|
+
* Each posting records an amount moving to/from an account.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Posting {
|
|
7
|
+
/** Optional posting flag (e.g., '*' for cleared) */
|
|
8
|
+
flag?: string;
|
|
9
|
+
/** The account name for this posting */
|
|
10
|
+
account: string;
|
|
11
|
+
/** The amount as a string (may be omitted for auto-calculated postings) */
|
|
12
|
+
amount?: string;
|
|
13
|
+
/** The currency code for the amount */
|
|
14
|
+
currency?: string;
|
|
15
|
+
/** Optional cost specification (e.g., for currency conversions) */
|
|
16
|
+
cost?: string;
|
|
17
|
+
/** Currency for the price annotation */
|
|
18
|
+
priceCurrency?: string;
|
|
19
|
+
/** Amount for the price annotation */
|
|
20
|
+
priceAmount?: string;
|
|
21
|
+
/** Optional comment for this posting */
|
|
22
|
+
comment?: string;
|
|
23
|
+
/** When a cost is given, defines the amount of at signs (1 for unit price, 2 for total price) */
|
|
24
|
+
atSigns?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new Posting instance.
|
|
27
|
+
* @param obj - Object containing posting properties
|
|
28
|
+
*/
|
|
29
|
+
constructor(obj: Record<string, unknown>);
|
|
30
|
+
/**
|
|
31
|
+
* Parses a posting line string into a Posting instance.
|
|
32
|
+
* Expected format: [Flag] Account [Amount Currency] [{Cost}] [@ PriceAmount PriceCurrency] [; Comment]
|
|
33
|
+
*
|
|
34
|
+
* @param unparsedline - The posting line string to parse
|
|
35
|
+
* @returns A new Posting instance
|
|
36
|
+
* @throws {Error} If the posting line cannot be parsed
|
|
37
|
+
*/
|
|
38
|
+
static fromString(unparsedline: string): Posting;
|
|
39
|
+
/**
|
|
40
|
+
* Gets the formatted price string combining amount, currency, cost, and price annotation.
|
|
41
|
+
* @returns The formatted price string, or undefined if no price components exist
|
|
42
|
+
*/
|
|
43
|
+
get price(): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Converts this posting to a string representation.
|
|
46
|
+
* Delegates to toFormattedString with zero currency column alignment.
|
|
47
|
+
*
|
|
48
|
+
* @returns The string representation of this posting
|
|
49
|
+
*/
|
|
50
|
+
toString(): string;
|
|
51
|
+
/**
|
|
52
|
+
* Converts this posting to a formatted string with aligned currency column.
|
|
53
|
+
* Adds padding before the price to align at the specified column.
|
|
54
|
+
*
|
|
55
|
+
* @param formatOptions - Formatting options including currency column position
|
|
56
|
+
* @returns The formatted string representation of this posting
|
|
57
|
+
*/
|
|
58
|
+
toFormattedString(formatOptions?: FormatOptions): string;
|
|
59
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { currencyPattern } from '../../../patterns.mjs';
|
|
2
|
+
import { formatPrice } from '../../../utils/formatPrice.mjs';
|
|
3
|
+
import { defaultFormatOptions } from '../../ParseResult.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* Represents a single posting (account movement) within a transaction.
|
|
6
|
+
* Each posting records an amount moving to/from an account.
|
|
7
|
+
*/
|
|
8
|
+
export class Posting {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new Posting instance.
|
|
11
|
+
* @param obj - Object containing posting properties
|
|
12
|
+
*/
|
|
13
|
+
constructor(obj) {
|
|
14
|
+
Object.assign(this, obj);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parses a posting line string into a Posting instance.
|
|
18
|
+
* Expected format: [Flag] Account [Amount Currency] [{Cost}] [@ PriceAmount PriceCurrency] [; Comment]
|
|
19
|
+
*
|
|
20
|
+
* @param unparsedline - The posting line string to parse
|
|
21
|
+
* @returns A new Posting instance
|
|
22
|
+
* @throws {Error} If the posting line cannot be parsed
|
|
23
|
+
*/
|
|
24
|
+
static fromString(unparsedline) {
|
|
25
|
+
// [Flag] Account Amount [Currency] [{Cost}] [@ Price]
|
|
26
|
+
const flagPattern = `([^ ]) +`;
|
|
27
|
+
const accountPattern = `([^ ]*)`;
|
|
28
|
+
const amountPattern = `([^A-Z;]*)`;
|
|
29
|
+
const costPattern = `{(.*)}`;
|
|
30
|
+
const pricePattern = `+(@|@@) +(?:(\\d+\\.?\\d*) ${currencyPattern})`;
|
|
31
|
+
const amountCurrenyCostPattern = `${amountPattern}(?: +${currencyPattern})?(?: +${costPattern})?(?: ${pricePattern})?`;
|
|
32
|
+
const commentPattern = `( *;.*)?`;
|
|
33
|
+
const pattern = `^(?:${flagPattern})?${accountPattern}(?: +${amountCurrenyCostPattern})?${commentPattern}$`;
|
|
34
|
+
const matches = RegExp(pattern).exec(unparsedline);
|
|
35
|
+
if (!matches) {
|
|
36
|
+
throw new Error(`Could not parse posting: ${unparsedline}`);
|
|
37
|
+
}
|
|
38
|
+
const [, flag, account, amount, currency, cost, atSigns, priceAmount, priceCurrency, comment,] = matches;
|
|
39
|
+
return new Posting({
|
|
40
|
+
flag: flag,
|
|
41
|
+
account: account?.trim(),
|
|
42
|
+
amount: amount?.trim().length > 0 ? amount.trim() : undefined,
|
|
43
|
+
currency: currency?.trim(),
|
|
44
|
+
cost: cost?.trim(),
|
|
45
|
+
priceAmount: priceAmount?.trim(),
|
|
46
|
+
priceCurrency: priceCurrency?.trim(),
|
|
47
|
+
atSigns: atSigns ? atSigns.length : undefined,
|
|
48
|
+
comment: comment?.replace(/^ *;/, '').trim(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets the formatted price string combining amount, currency, cost, and price annotation.
|
|
53
|
+
* @returns The formatted price string, or undefined if no price components exist
|
|
54
|
+
*/
|
|
55
|
+
get price() {
|
|
56
|
+
return formatPrice(this.amount, this.currency, this.cost, this.priceAmount, this.priceCurrency, this.atSigns);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Converts this posting to a string representation.
|
|
60
|
+
* Delegates to toFormattedString with zero currency column alignment.
|
|
61
|
+
*
|
|
62
|
+
* @returns The string representation of this posting
|
|
63
|
+
*/
|
|
64
|
+
toString() {
|
|
65
|
+
return this.toFormattedString({ currencyColumn: 0 });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Converts this posting to a formatted string with aligned currency column.
|
|
69
|
+
* Adds padding before the price to align at the specified column.
|
|
70
|
+
*
|
|
71
|
+
* @param formatOptions - Formatting options including currency column position
|
|
72
|
+
* @returns The formatted string representation of this posting
|
|
73
|
+
*/
|
|
74
|
+
toFormattedString(formatOptions = defaultFormatOptions) {
|
|
75
|
+
const parts = [];
|
|
76
|
+
if (this.flag !== undefined) {
|
|
77
|
+
parts.push(this.flag);
|
|
78
|
+
}
|
|
79
|
+
parts.push(this.account);
|
|
80
|
+
if (this.price !== undefined) {
|
|
81
|
+
const paddingLength = formatOptions.currencyColumn -
|
|
82
|
+
parts.join(' ').length -
|
|
83
|
+
this.amount.length -
|
|
84
|
+
2 - // indent
|
|
85
|
+
2 - // spacing
|
|
86
|
+
2; // not sure what this is for
|
|
87
|
+
if (paddingLength > 0) {
|
|
88
|
+
parts.push(' '.repeat(paddingLength));
|
|
89
|
+
}
|
|
90
|
+
parts.push(this.price);
|
|
91
|
+
}
|
|
92
|
+
if (this.comment !== undefined) {
|
|
93
|
+
parts.push(';', this.comment);
|
|
94
|
+
}
|
|
95
|
+
return parts.join(' ');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a tag associated with a transaction.
|
|
3
|
+
* Tags can be specified inline in the transaction or inherited from a tag stack (pushtag/poptag).
|
|
4
|
+
*/
|
|
5
|
+
export declare class Tag {
|
|
6
|
+
/** The tag name/content (without the '#' prefix) */
|
|
7
|
+
content: string;
|
|
8
|
+
/** Whether this tag comes from the tag stack (pushtag) or is inline */
|
|
9
|
+
fromStack: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new Tag instance.
|
|
12
|
+
* @param obj - Object containing tag content and source information
|
|
13
|
+
* @param obj.content - The tag name/content
|
|
14
|
+
* @param obj.fromStack - Whether this tag is from the tag stack
|
|
15
|
+
*/
|
|
16
|
+
constructor(obj: {
|
|
17
|
+
content: string;
|
|
18
|
+
fromStack: boolean;
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Converts this tag to its string representation.
|
|
22
|
+
* Tags from the stack return an empty string (they're implicit),
|
|
23
|
+
* while inline tags return '#tagname' format.
|
|
24
|
+
*
|
|
25
|
+
* @returns The tag string with '#' prefix, or empty string if from stack
|
|
26
|
+
*/
|
|
27
|
+
toString(): string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a tag associated with a transaction.
|
|
3
|
+
* Tags can be specified inline in the transaction or inherited from a tag stack (pushtag/poptag).
|
|
4
|
+
*/
|
|
5
|
+
export class Tag {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a new Tag instance.
|
|
8
|
+
* @param obj - Object containing tag content and source information
|
|
9
|
+
* @param obj.content - The tag name/content
|
|
10
|
+
* @param obj.fromStack - Whether this tag is from the tag stack
|
|
11
|
+
*/
|
|
12
|
+
constructor(obj) {
|
|
13
|
+
Object.assign(this, obj);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Converts this tag to its string representation.
|
|
17
|
+
* Tags from the stack return an empty string (they're implicit),
|
|
18
|
+
* while inline tags return '#tagname' format.
|
|
19
|
+
*
|
|
20
|
+
* @returns The tag string with '#' prefix, or empty string if from stack
|
|
21
|
+
*/
|
|
22
|
+
toString() {
|
|
23
|
+
if (this.fromStack) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
return `#${this.content}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import type { GenericParseResultTransaction } from '../../../genericParse.mjs';
|
|
3
|
+
import { DatedNode } from '../../DatedNode.mjs';
|
|
4
|
+
import { Posting } from './Posting.mjs';
|
|
5
|
+
import { Tag } from './Tag.mjs';
|
|
6
|
+
import { FormatOptions } from '../../ParseResult.mjs';
|
|
7
|
+
export interface PostingComment {
|
|
8
|
+
order: number;
|
|
9
|
+
comment: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Represents a Beancount transaction node.
|
|
13
|
+
* Transactions record financial movements between accounts with postings.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Transaction extends DatedNode {
|
|
16
|
+
/** @inheritdoc */
|
|
17
|
+
type: "transaction";
|
|
18
|
+
/** The payee of the transaction */
|
|
19
|
+
payee: string;
|
|
20
|
+
/** Optional narration/description of the transaction */
|
|
21
|
+
narration?: string;
|
|
22
|
+
/** Optional transaction flag (e.g., '*' for cleared, '!' for pending) */
|
|
23
|
+
flag?: string;
|
|
24
|
+
/** Array of postings (account movements) in this transaction */
|
|
25
|
+
postings: Posting[];
|
|
26
|
+
/** Array of comments under this transaction (mixed in with the postings) */
|
|
27
|
+
postingComments: PostingComment[];
|
|
28
|
+
/** Set of link identifiers associated with this transaction */
|
|
29
|
+
links: Set<string>;
|
|
30
|
+
/** Array of tags associated with this transaction (from inline tags and tag stack) */
|
|
31
|
+
tags: Tag[];
|
|
32
|
+
/**
|
|
33
|
+
* @inheritdoc
|
|
34
|
+
*/
|
|
35
|
+
constructor(obj: {
|
|
36
|
+
date: string | Temporal.PlainDate;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Transaction instance from a generic parse result.
|
|
41
|
+
* Parses payee, narration, links, tags, postings, and metadata.
|
|
42
|
+
*
|
|
43
|
+
* @param genericParseResult - The parsed transaction data
|
|
44
|
+
* @returns A new Transaction instance
|
|
45
|
+
*/
|
|
46
|
+
static fromGenericParseResult(genericParseResult: GenericParseResultTransaction): Transaction;
|
|
47
|
+
/** @inheritdoc */
|
|
48
|
+
toString(): string;
|
|
49
|
+
/** @inheritdoc */
|
|
50
|
+
toFormattedString(formatOptions?: FormatOptions): string;
|
|
51
|
+
/**
|
|
52
|
+
* Converts this transaction to a JSON-serializable object.
|
|
53
|
+
* Ensures the links Set is properly serialized as an array.
|
|
54
|
+
*
|
|
55
|
+
* @returns A JSON-serializable representation of this transaction
|
|
56
|
+
*/
|
|
57
|
+
toJSON(): Record<string, unknown>;
|
|
58
|
+
/**
|
|
59
|
+
* Transforms JSON data before creating a Transaction instance.
|
|
60
|
+
* Deserializes transaction-specific properties including postings, tags, links, and metadata.
|
|
61
|
+
*
|
|
62
|
+
* @param json - The JSON data to transform
|
|
63
|
+
* @returns The transformed data with:
|
|
64
|
+
* - postings converted to Posting instances
|
|
65
|
+
* - tags converted to Tag instances
|
|
66
|
+
* - links converted from array to Set<string>
|
|
67
|
+
* - metadata values converted to Value instances
|
|
68
|
+
*/
|
|
69
|
+
protected parseJSONData(json: Record<string, unknown>): Record<string, unknown>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { assertNodeConstructor } from '../../Node.mjs';
|
|
2
|
+
import { DatedNode } from '../../DatedNode.mjs';
|
|
3
|
+
import { stringAwareParseLine } from '../../../utils/stringAwareParseLine.mjs';
|
|
4
|
+
import { parseString, Value } from '../../Value.mjs';
|
|
5
|
+
import { parseMetadata } from '../../../utils/parseMetadata.mjs';
|
|
6
|
+
import { Posting } from './Posting.mjs';
|
|
7
|
+
import { Tag } from './Tag.mjs';
|
|
8
|
+
import { defaultFormatOptions } from '../../ParseResult.mjs';
|
|
9
|
+
/**
|
|
10
|
+
* Valid Beancount account type prefixes.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
const AccountTypes = ['Assets', 'Liabilities', 'Equity', 'Income', 'Expenses'];
|
|
14
|
+
/**
|
|
15
|
+
* Parses a string to extract narration/payee, links, and tags.
|
|
16
|
+
* Links are prefixed with '^' and tags with '#'.
|
|
17
|
+
*
|
|
18
|
+
* @param input - The string to parse (may contain narration in quotes, links, and tags)
|
|
19
|
+
* @returns An object containing extracted links, tags, and the remaining string (narration/payee)
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const getStringLinksAndTags = (input) => {
|
|
23
|
+
let links = new Set();
|
|
24
|
+
let tags = [];
|
|
25
|
+
// default if no narration
|
|
26
|
+
let strRemaining = '';
|
|
27
|
+
let linksAndTags = input;
|
|
28
|
+
// check for narration
|
|
29
|
+
const match = /^(".*")(.*)/.exec(input);
|
|
30
|
+
if (match) {
|
|
31
|
+
// strRemaining = narration
|
|
32
|
+
strRemaining = match[1];
|
|
33
|
+
linksAndTags = match[2];
|
|
34
|
+
}
|
|
35
|
+
const linksMatch = linksAndTags.matchAll(/\^([\w-_.]*)/g);
|
|
36
|
+
if (linksMatch) {
|
|
37
|
+
links = new Set(linksMatch.map((m) => m[1]));
|
|
38
|
+
}
|
|
39
|
+
const tagsMatch = linksAndTags.matchAll(/#([\w-_.]*)/g);
|
|
40
|
+
if (tagsMatch) {
|
|
41
|
+
tags = tagsMatch
|
|
42
|
+
.map((m) => new Tag({ content: m[1], fromStack: false }))
|
|
43
|
+
.toArray();
|
|
44
|
+
}
|
|
45
|
+
return { links, tags, string: strRemaining };
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Represents a Beancount transaction node.
|
|
49
|
+
* Transactions record financial movements between accounts with postings.
|
|
50
|
+
*/
|
|
51
|
+
export class Transaction extends DatedNode {
|
|
52
|
+
/**
|
|
53
|
+
* @inheritdoc
|
|
54
|
+
*/
|
|
55
|
+
constructor(obj) {
|
|
56
|
+
/*
|
|
57
|
+
* This constructor exists to provide a default value for some values.
|
|
58
|
+
* Class field initializers (e.g., `postingComments = []`) run after
|
|
59
|
+
* `super()` returns, which would overwrite values set by `Object.assign`
|
|
60
|
+
* in the parent constructor.
|
|
61
|
+
*/
|
|
62
|
+
super(obj);
|
|
63
|
+
/** @inheritdoc */
|
|
64
|
+
this.type = 'transaction';
|
|
65
|
+
this.postingComments ??= [];
|
|
66
|
+
this.postings ??= [];
|
|
67
|
+
this.links ??= new Set();
|
|
68
|
+
this.tags ??= [];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a Transaction instance from a generic parse result.
|
|
72
|
+
* Parses payee, narration, links, tags, postings, and metadata.
|
|
73
|
+
*
|
|
74
|
+
* @param genericParseResult - The parsed transaction data
|
|
75
|
+
* @returns A new Transaction instance
|
|
76
|
+
*/
|
|
77
|
+
static fromGenericParseResult(genericParseResult) {
|
|
78
|
+
// eslint-disable-next-line prefer-const
|
|
79
|
+
let [payee, ...rest] = stringAwareParseLine(genericParseResult.header);
|
|
80
|
+
let links = new Set();
|
|
81
|
+
let tags = [];
|
|
82
|
+
let narration;
|
|
83
|
+
if (rest.length === 0) {
|
|
84
|
+
// no narration
|
|
85
|
+
const payeeParsed = getStringLinksAndTags(payee);
|
|
86
|
+
payee = payeeParsed.string;
|
|
87
|
+
links = payeeParsed.links;
|
|
88
|
+
tags = payeeParsed.tags;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const payeeParsed = getStringLinksAndTags(rest.join(' '));
|
|
92
|
+
narration = payeeParsed.string;
|
|
93
|
+
links = payeeParsed.links;
|
|
94
|
+
tags = payeeParsed.tags;
|
|
95
|
+
}
|
|
96
|
+
const unparsedPostings = [];
|
|
97
|
+
const unparsedMetadata = [];
|
|
98
|
+
const postingComments = [];
|
|
99
|
+
genericParseResult.body.forEach((line) => {
|
|
100
|
+
// V remove flag, V get account type
|
|
101
|
+
const accountType = line.replace(/^[^ ] /, '').split(':')[0];
|
|
102
|
+
if (AccountTypes.includes(accountType)) {
|
|
103
|
+
unparsedPostings.push(line);
|
|
104
|
+
}
|
|
105
|
+
else if (line.startsWith(';')) {
|
|
106
|
+
postingComments.push({
|
|
107
|
+
comment: line,
|
|
108
|
+
order: unparsedPostings.length + postingComments.length,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
unparsedMetadata.push(line);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
const postings = unparsedPostings.map((p) => Posting.fromString(p));
|
|
116
|
+
const metadata = parseMetadata(unparsedMetadata);
|
|
117
|
+
return new Transaction({
|
|
118
|
+
...genericParseResult.props,
|
|
119
|
+
payee: parseString(payee),
|
|
120
|
+
narration: narration ? parseString(narration) : undefined,
|
|
121
|
+
postings,
|
|
122
|
+
postingComments,
|
|
123
|
+
metadata,
|
|
124
|
+
links,
|
|
125
|
+
tags,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** @inheritdoc */
|
|
129
|
+
toString() {
|
|
130
|
+
return this.toFormattedString({ currencyColumn: 0 });
|
|
131
|
+
}
|
|
132
|
+
/** @inheritdoc */
|
|
133
|
+
toFormattedString(formatOptions = defaultFormatOptions) {
|
|
134
|
+
const firstLine = [
|
|
135
|
+
this.date.toJSON(),
|
|
136
|
+
this.flag ?? 'txn',
|
|
137
|
+
`"${this.payee}"`,
|
|
138
|
+
];
|
|
139
|
+
if (this.narration !== undefined) {
|
|
140
|
+
firstLine.push(`"${this.narration}"`);
|
|
141
|
+
}
|
|
142
|
+
firstLine.push(...this.links.values().map((l) => `^${l}`));
|
|
143
|
+
firstLine.push(...this.tags.map((t) => t.toString()));
|
|
144
|
+
const lines = [firstLine.join(' ') + this.getMetaDataString()];
|
|
145
|
+
const postingLines = this.postings.map((p) => ` ${p.toFormattedString(formatOptions)}`);
|
|
146
|
+
this.postingComments.forEach((comment) => {
|
|
147
|
+
postingLines.splice(comment.order, 0, ` ${comment.comment}`);
|
|
148
|
+
});
|
|
149
|
+
lines.push(...postingLines);
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Converts this transaction to a JSON-serializable object.
|
|
154
|
+
* Ensures the links Set is properly serialized as an array.
|
|
155
|
+
*
|
|
156
|
+
* @returns A JSON-serializable representation of this transaction
|
|
157
|
+
*/
|
|
158
|
+
toJSON() {
|
|
159
|
+
return {
|
|
160
|
+
...this,
|
|
161
|
+
links: Array.from(this.links),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Transforms JSON data before creating a Transaction instance.
|
|
166
|
+
* Deserializes transaction-specific properties including postings, tags, links, and metadata.
|
|
167
|
+
*
|
|
168
|
+
* @param json - The JSON data to transform
|
|
169
|
+
* @returns The transformed data with:
|
|
170
|
+
* - postings converted to Posting instances
|
|
171
|
+
* - tags converted to Tag instances
|
|
172
|
+
* - links converted from array to Set<string>
|
|
173
|
+
* - metadata values converted to Value instances
|
|
174
|
+
*/
|
|
175
|
+
parseJSONData(json) {
|
|
176
|
+
const { postings, postingComments, tags, links, metadata, ...rest } = json;
|
|
177
|
+
return {
|
|
178
|
+
...rest,
|
|
179
|
+
postings: postings?.map((p) => new Posting(p)),
|
|
180
|
+
postingComments: postingComments ?? [],
|
|
181
|
+
tags: tags?.map((t) => new Tag(t)),
|
|
182
|
+
links: links ? new Set(links) : new Set(),
|
|
183
|
+
metadata: metadata
|
|
184
|
+
? Object.fromEntries(Object.entries(metadata).map(([key, val]) => [
|
|
185
|
+
key,
|
|
186
|
+
new Value(val),
|
|
187
|
+
]))
|
|
188
|
+
: undefined,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Ensure class conforms to NodeConstructor pattern
|
|
193
|
+
assertNodeConstructor(Transaction);
|