rustfava 0.1.0__py3-none-any.whl
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.
- rustfava/__init__.py +30 -0
- rustfava/_ctx_globals_class.py +55 -0
- rustfava/api_models.py +36 -0
- rustfava/application.py +534 -0
- rustfava/beans/__init__.py +6 -0
- rustfava/beans/abc.py +327 -0
- rustfava/beans/account.py +79 -0
- rustfava/beans/create.py +377 -0
- rustfava/beans/flags.py +20 -0
- rustfava/beans/funcs.py +38 -0
- rustfava/beans/helpers.py +52 -0
- rustfava/beans/ingest.py +75 -0
- rustfava/beans/load.py +31 -0
- rustfava/beans/prices.py +151 -0
- rustfava/beans/protocols.py +82 -0
- rustfava/beans/str.py +454 -0
- rustfava/beans/types.py +63 -0
- rustfava/cli.py +187 -0
- rustfava/context.py +13 -0
- rustfava/core/__init__.py +729 -0
- rustfava/core/accounts.py +161 -0
- rustfava/core/attributes.py +145 -0
- rustfava/core/budgets.py +207 -0
- rustfava/core/charts.py +301 -0
- rustfava/core/commodities.py +37 -0
- rustfava/core/conversion.py +229 -0
- rustfava/core/documents.py +87 -0
- rustfava/core/extensions.py +132 -0
- rustfava/core/fava_options.py +255 -0
- rustfava/core/file.py +542 -0
- rustfava/core/filters.py +484 -0
- rustfava/core/group_entries.py +97 -0
- rustfava/core/ingest.py +509 -0
- rustfava/core/inventory.py +167 -0
- rustfava/core/misc.py +105 -0
- rustfava/core/module_base.py +18 -0
- rustfava/core/number.py +106 -0
- rustfava/core/query.py +180 -0
- rustfava/core/query_shell.py +301 -0
- rustfava/core/tree.py +265 -0
- rustfava/core/watcher.py +219 -0
- rustfava/ext/__init__.py +232 -0
- rustfava/ext/auto_commit.py +61 -0
- rustfava/ext/portfolio_list/PortfolioList.js +34 -0
- rustfava/ext/portfolio_list/__init__.py +29 -0
- rustfava/ext/portfolio_list/templates/PortfolioList.html +15 -0
- rustfava/ext/rustfava_ext_test/RustfavaExtTest.js +42 -0
- rustfava/ext/rustfava_ext_test/__init__.py +207 -0
- rustfava/ext/rustfava_ext_test/templates/RustfavaExtTest.html +45 -0
- rustfava/ext/rustfava_ext_test/templates/RustfavaExtTestInclude.html +1 -0
- rustfava/help/__init__.py +15 -0
- rustfava/help/_index.md +29 -0
- rustfava/help/beancount_syntax.md +156 -0
- rustfava/help/budgets.md +31 -0
- rustfava/help/conversion.md +29 -0
- rustfava/help/extensions.md +111 -0
- rustfava/help/features.md +179 -0
- rustfava/help/filters.md +103 -0
- rustfava/help/import.md +27 -0
- rustfava/help/options.md +289 -0
- rustfava/helpers.py +30 -0
- rustfava/internal_api.py +221 -0
- rustfava/json_api.py +952 -0
- rustfava/plugins/__init__.py +3 -0
- rustfava/plugins/link_documents.py +107 -0
- rustfava/plugins/tag_discovered_documents.py +44 -0
- rustfava/py.typed +0 -0
- rustfava/rustledger/__init__.py +31 -0
- rustfava/rustledger/constants.py +76 -0
- rustfava/rustledger/engine.py +485 -0
- rustfava/rustledger/loader.py +273 -0
- rustfava/rustledger/options.py +202 -0
- rustfava/rustledger/query.py +331 -0
- rustfava/rustledger/types.py +830 -0
- rustfava/serialisation.py +220 -0
- rustfava/static/app.css +2988 -0
- rustfava/static/app.css.map +7 -0
- rustfava/static/app.js +12854 -0
- rustfava/static/app.js.map +7 -0
- rustfava/static/beancount-JFV44ZVZ.css +5 -0
- rustfava/static/beancount-JFV44ZVZ.css.map +7 -0
- rustfava/static/beancount-VTTKRGSK.js +4642 -0
- rustfava/static/beancount-VTTKRGSK.js.map +7 -0
- rustfava/static/bql-MGFRUMBP.js +333 -0
- rustfava/static/bql-MGFRUMBP.js.map +7 -0
- rustfava/static/chunk-E7ZF4ASL.js +23061 -0
- rustfava/static/chunk-E7ZF4ASL.js.map +7 -0
- rustfava/static/chunk-V24TLQHT.js +12673 -0
- rustfava/static/chunk-V24TLQHT.js.map +7 -0
- rustfava/static/favicon.ico +0 -0
- rustfava/static/fira-mono-cyrillic-400-normal-BLAGXRCE.woff2 +0 -0
- rustfava/static/fira-mono-cyrillic-500-normal-EN7JUAAW.woff2 +0 -0
- rustfava/static/fira-mono-cyrillic-ext-400-normal-EX7VARTS.woff2 +0 -0
- rustfava/static/fira-mono-cyrillic-ext-500-normal-ZDPTUPRR.woff2 +0 -0
- rustfava/static/fira-mono-greek-400-normal-COGHKMOA.woff2 +0 -0
- rustfava/static/fira-mono-greek-500-normal-4EN2PKZT.woff2 +0 -0
- rustfava/static/fira-mono-greek-ext-400-normal-DYEQIJH7.woff2 +0 -0
- rustfava/static/fira-mono-greek-ext-500-normal-SG73CVKQ.woff2 +0 -0
- rustfava/static/fira-mono-latin-400-normal-NA3VLV7E.woff2 +0 -0
- rustfava/static/fira-mono-latin-500-normal-YC77GFWD.woff2 +0 -0
- rustfava/static/fira-mono-latin-ext-400-normal-DIKTZ5PW.woff2 +0 -0
- rustfava/static/fira-mono-latin-ext-500-normal-ZWY4UO4V.woff2 +0 -0
- rustfava/static/fira-mono-symbols2-400-normal-UITXT77Q.woff2 +0 -0
- rustfava/static/fira-mono-symbols2-500-normal-VWPC2EFN.woff2 +0 -0
- rustfava/static/fira-sans-cyrillic-400-normal-KLQMBCA6.woff2 +0 -0
- rustfava/static/fira-sans-cyrillic-500-normal-NFG7UD6J.woff2 +0 -0
- rustfava/static/fira-sans-cyrillic-ext-400-normal-GWO44OPC.woff2 +0 -0
- rustfava/static/fira-sans-cyrillic-ext-500-normal-SP47E5SC.woff2 +0 -0
- rustfava/static/fira-sans-greek-400-normal-UMQBTLC3.woff2 +0 -0
- rustfava/static/fira-sans-greek-500-normal-4ZKHN4FQ.woff2 +0 -0
- rustfava/static/fira-sans-greek-ext-400-normal-O2DVJAJZ.woff2 +0 -0
- rustfava/static/fira-sans-greek-ext-500-normal-SK6GNWGO.woff2 +0 -0
- rustfava/static/fira-sans-latin-400-normal-OYYTPMAV.woff2 +0 -0
- rustfava/static/fira-sans-latin-500-normal-SMQPZW5A.woff2 +0 -0
- rustfava/static/fira-sans-latin-ext-400-normal-OAUP3WK5.woff2 +0 -0
- rustfava/static/fira-sans-latin-ext-500-normal-LY3YDR5Y.woff2 +0 -0
- rustfava/static/fira-sans-vietnamese-400-normal-OBMQ72MR.woff2 +0 -0
- rustfava/static/fira-sans-vietnamese-500-normal-Y4NZR5EU.woff2 +0 -0
- rustfava/static/source-code-pro-cyrillic-400-normal-TO22V6M3.woff2 +0 -0
- rustfava/static/source-code-pro-cyrillic-500-normal-OGBWWWYW.woff2 +0 -0
- rustfava/static/source-code-pro-cyrillic-ext-400-normal-XH44UCIA.woff2 +0 -0
- rustfava/static/source-code-pro-cyrillic-ext-500-normal-3Z6MMVM6.woff2 +0 -0
- rustfava/static/source-code-pro-greek-400-normal-OUXXUQWK.woff2 +0 -0
- rustfava/static/source-code-pro-greek-500-normal-JA2Z5UXO.woff2 +0 -0
- rustfava/static/source-code-pro-greek-ext-400-normal-WCDKMX7U.woff2 +0 -0
- rustfava/static/source-code-pro-greek-ext-500-normal-ZHVI4VKW.woff2 +0 -0
- rustfava/static/source-code-pro-latin-400-normal-QOGTXED5.woff2 +0 -0
- rustfava/static/source-code-pro-latin-500-normal-X57QEOLQ.woff2 +0 -0
- rustfava/static/source-code-pro-latin-ext-400-normal-QXC74NBF.woff2 +0 -0
- rustfava/static/source-code-pro-latin-ext-500-normal-QGOY7MTT.woff2 +0 -0
- rustfava/static/source-code-pro-vietnamese-400-normal-NPDCDTBA.woff2 +0 -0
- rustfava/static/source-code-pro-vietnamese-500-normal-M6PJKTR5.woff2 +0 -0
- rustfava/static/tree-sitter-beancount-MLXFQBZ5.wasm +0 -0
- rustfava/static/web-tree-sitter-RNOQ6E74.wasm +0 -0
- rustfava/template_filters.py +64 -0
- rustfava/templates/_journal_table.html +156 -0
- rustfava/templates/_layout.html +26 -0
- rustfava/templates/_query_table.html +88 -0
- rustfava/templates/beancount_file +18 -0
- rustfava/templates/help.html +23 -0
- rustfava/templates/macros/_account_macros.html +5 -0
- rustfava/templates/macros/_commodity_macros.html +13 -0
- rustfava/translations/bg/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/bg/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/ca/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/ca/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/de/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/de/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/es/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/es/LC_MESSAGES/messages.po +619 -0
- rustfava/translations/fa/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/fa/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/fr/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/fr/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/ja/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/ja/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/nl/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/nl/LC_MESSAGES/messages.po +617 -0
- rustfava/translations/pt/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/pt/LC_MESSAGES/messages.po +617 -0
- rustfava/translations/pt_BR/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/pt_BR/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/ru/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/ru/LC_MESSAGES/messages.po +617 -0
- rustfava/translations/sk/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/sk/LC_MESSAGES/messages.po +623 -0
- rustfava/translations/sv/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/sv/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/uk/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/uk/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/zh/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/zh/LC_MESSAGES/messages.po +618 -0
- rustfava/translations/zh_Hant_TW/LC_MESSAGES/messages.mo +0 -0
- rustfava/translations/zh_Hant_TW/LC_MESSAGES/messages.po +618 -0
- rustfava/util/__init__.py +157 -0
- rustfava/util/date.py +576 -0
- rustfava/util/excel.py +118 -0
- rustfava/util/ranking.py +79 -0
- rustfava/util/sets.py +18 -0
- rustfava/util/unreachable.py +20 -0
- rustfava-0.1.0.dist-info/METADATA +102 -0
- rustfava-0.1.0.dist-info/RECORD +187 -0
- rustfava-0.1.0.dist-info/WHEEL +5 -0
- rustfava-0.1.0.dist-info/entry_points.txt +2 -0
- rustfava-0.1.0.dist-info/licenses/AUTHORS +11 -0
- rustfava-0.1.0.dist-info/licenses/LICENSE +21 -0
- rustfava-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Query adapter for rustledger - replaces beanquery."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rustfava.rustledger.engine import RustledgerEngine
|
|
10
|
+
from rustfava.rustledger.types import RLAmount
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from rustfava.beans.abc import Directive
|
|
18
|
+
from rustfava.beans.types import BeancountOptions
|
|
19
|
+
from rustfava.helpers import BeancountError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ColumnDescription:
|
|
24
|
+
"""Description of a query result column.
|
|
25
|
+
|
|
26
|
+
Compatible with beanquery's column description.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
name: str
|
|
30
|
+
datatype: type
|
|
31
|
+
|
|
32
|
+
def __iter__(self) -> Iterator[Any]:
|
|
33
|
+
"""Allow tuple unpacking."""
|
|
34
|
+
yield self.name
|
|
35
|
+
yield self.datatype
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RLCursor:
|
|
39
|
+
"""Cursor for rustledger query results.
|
|
40
|
+
|
|
41
|
+
Compatible with beanquery.Cursor interface.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
columns: list[dict[str, str]],
|
|
47
|
+
rows: list[list[Any]],
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Initialize cursor with query results."""
|
|
50
|
+
self._columns = columns
|
|
51
|
+
self._rows = rows
|
|
52
|
+
self._index = 0
|
|
53
|
+
|
|
54
|
+
# Build description (like DB-API cursor.description)
|
|
55
|
+
self.description = tuple(
|
|
56
|
+
ColumnDescription(
|
|
57
|
+
name=col["name"],
|
|
58
|
+
datatype=_datatype_from_string(col.get("datatype", "object")),
|
|
59
|
+
)
|
|
60
|
+
for col in columns
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def fetchall(self) -> list[tuple[Any, ...]]:
|
|
64
|
+
"""Fetch all remaining rows."""
|
|
65
|
+
rows = [
|
|
66
|
+
tuple(_convert_row_value(v, self._columns[i]) for i, v in enumerate(row))
|
|
67
|
+
for row in self._rows[self._index :]
|
|
68
|
+
]
|
|
69
|
+
self._index = len(self._rows)
|
|
70
|
+
return rows
|
|
71
|
+
|
|
72
|
+
def fetchone(self) -> tuple[Any, ...] | None:
|
|
73
|
+
"""Fetch next row."""
|
|
74
|
+
if self._index >= len(self._rows):
|
|
75
|
+
return None
|
|
76
|
+
row = self._rows[self._index]
|
|
77
|
+
self._index += 1
|
|
78
|
+
return tuple(
|
|
79
|
+
_convert_row_value(v, self._columns[i]) for i, v in enumerate(row)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def __iter__(self) -> Iterator[tuple[Any, ...]]:
|
|
83
|
+
"""Iterate over rows."""
|
|
84
|
+
for row in self._rows[self._index :]:
|
|
85
|
+
yield tuple(
|
|
86
|
+
_convert_row_value(v, self._columns[i]) for i, v in enumerate(row)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Type mapping from rustledger strings to Python types
|
|
91
|
+
_DATATYPE_MAP: dict[str, type] = {
|
|
92
|
+
"str": str,
|
|
93
|
+
"int": int,
|
|
94
|
+
"Decimal": Decimal,
|
|
95
|
+
"bool": bool,
|
|
96
|
+
"date": str, # Keep as string, Fava handles conversion
|
|
97
|
+
"set": frozenset,
|
|
98
|
+
"object": object,
|
|
99
|
+
"Amount": RLAmount,
|
|
100
|
+
"Position": object,
|
|
101
|
+
"Inventory": dict,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _datatype_from_string(datatype: str) -> type:
|
|
106
|
+
"""Convert datatype string to Python type."""
|
|
107
|
+
return _DATATYPE_MAP.get(datatype, object)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _convert_row_value(value: Any, column: dict[str, str]) -> Any:
|
|
111
|
+
"""Convert a row value based on column type."""
|
|
112
|
+
if value is None:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
datatype = column.get("datatype", "object")
|
|
116
|
+
|
|
117
|
+
if datatype == "Decimal" and isinstance(value, str):
|
|
118
|
+
return Decimal(value)
|
|
119
|
+
if datatype == "Amount" and isinstance(value, dict):
|
|
120
|
+
return RLAmount.from_json(value)
|
|
121
|
+
if datatype == "set" and isinstance(value, list):
|
|
122
|
+
return frozenset(value)
|
|
123
|
+
if datatype == "Inventory" and isinstance(value, dict):
|
|
124
|
+
# Inventory comes as {"positions": [{"units": {"number": "...", "currency": "..."}}]}
|
|
125
|
+
# Convert to simpler format for Fava
|
|
126
|
+
positions = value.get("positions", [])
|
|
127
|
+
result = {}
|
|
128
|
+
for pos in positions:
|
|
129
|
+
units = pos.get("units", {})
|
|
130
|
+
currency = units.get("currency", "")
|
|
131
|
+
number = units.get("number", "0")
|
|
132
|
+
if currency:
|
|
133
|
+
result[currency] = Decimal(number)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
return value
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class RLQueryError(Exception):
|
|
140
|
+
"""Error from rustledger query execution."""
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ParseError(RLQueryError):
|
|
144
|
+
"""BQL parse error."""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class CompilationError(RLQueryError):
|
|
148
|
+
"""BQL compilation error."""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def connect(
|
|
152
|
+
connection_string: str,
|
|
153
|
+
entries: Sequence[Directive],
|
|
154
|
+
errors: Sequence[BeancountError],
|
|
155
|
+
options: BeancountOptions,
|
|
156
|
+
) -> RLConnection:
|
|
157
|
+
"""Create a connection for running queries.
|
|
158
|
+
|
|
159
|
+
This matches beanquery.connect() interface.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
connection_string: Ignored (beancount: prefix for compatibility)
|
|
163
|
+
entries: List of directives
|
|
164
|
+
errors: List of errors (ignored)
|
|
165
|
+
options: Beancount options
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
A connection object for executing queries
|
|
169
|
+
"""
|
|
170
|
+
return RLConnection(entries, options)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class RLConnection:
|
|
174
|
+
"""Connection for executing BQL queries against entries.
|
|
175
|
+
|
|
176
|
+
Unlike the beancount approach that re-serializes entries,
|
|
177
|
+
we directly call rustledger with the original source.
|
|
178
|
+
This requires the source to be stored or re-read.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
entries: Sequence[Directive],
|
|
184
|
+
options: BeancountOptions,
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Initialize connection."""
|
|
187
|
+
self._entries = entries
|
|
188
|
+
self._options = options
|
|
189
|
+
self._engine = RustledgerEngine.get_instance()
|
|
190
|
+
self._source: str | None = None
|
|
191
|
+
|
|
192
|
+
def set_source(self, source: str) -> None:
|
|
193
|
+
"""Set the source for queries.
|
|
194
|
+
|
|
195
|
+
Since rustledger queries operate on source text (not serialized entries),
|
|
196
|
+
we need the original source.
|
|
197
|
+
"""
|
|
198
|
+
self._source = source
|
|
199
|
+
|
|
200
|
+
def execute(self, query_string: str) -> RLCursor:
|
|
201
|
+
"""Execute a BQL query.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
query_string: BQL query string
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
A cursor with query results
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
ParseError: If the query cannot be parsed
|
|
211
|
+
CompilationError: If the query cannot be compiled
|
|
212
|
+
RuntimeError: If source is not set
|
|
213
|
+
"""
|
|
214
|
+
if self._source is None:
|
|
215
|
+
# Fall back to re-serializing entries (slower but works)
|
|
216
|
+
self._source = _entries_to_source(self._entries)
|
|
217
|
+
|
|
218
|
+
result = self._engine.query(self._source, query_string)
|
|
219
|
+
|
|
220
|
+
errors = result.get("errors", [])
|
|
221
|
+
if errors:
|
|
222
|
+
error_msg = errors[0].get("message", "Unknown error")
|
|
223
|
+
if "parse" in error_msg.lower():
|
|
224
|
+
raise ParseError(error_msg)
|
|
225
|
+
raise CompilationError(error_msg)
|
|
226
|
+
|
|
227
|
+
return RLCursor(
|
|
228
|
+
columns=result.get("columns", []),
|
|
229
|
+
rows=result.get("rows", []),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _entries_to_source(entries: Sequence[Directive]) -> str:
|
|
234
|
+
"""Convert entries back to beancount source for querying.
|
|
235
|
+
|
|
236
|
+
This is a fallback when the original source isn't available.
|
|
237
|
+
"""
|
|
238
|
+
lines = []
|
|
239
|
+
for entry in entries:
|
|
240
|
+
line = _directive_to_source(entry)
|
|
241
|
+
if line:
|
|
242
|
+
lines.append(line)
|
|
243
|
+
return "\n".join(lines)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _directive_to_source(directive: Directive) -> str:
|
|
247
|
+
"""Convert a directive to beancount source line."""
|
|
248
|
+
date = directive.date.isoformat()
|
|
249
|
+
dtype = type(directive).__name__.lower().removeprefix("rl")
|
|
250
|
+
|
|
251
|
+
if dtype == "open":
|
|
252
|
+
currencies = " ".join(getattr(directive, "currencies", []))
|
|
253
|
+
account = getattr(directive, "account", "")
|
|
254
|
+
return f'{date} open {account} {currencies}'.strip()
|
|
255
|
+
|
|
256
|
+
if dtype == "close":
|
|
257
|
+
account = getattr(directive, "account", "")
|
|
258
|
+
return f'{date} close {account}'
|
|
259
|
+
|
|
260
|
+
if dtype == "balance":
|
|
261
|
+
amt = getattr(directive, "amount", None)
|
|
262
|
+
account = getattr(directive, "account", "")
|
|
263
|
+
if amt:
|
|
264
|
+
return f'{date} balance {account} {amt.number} {amt.currency}'
|
|
265
|
+
return f'{date} balance {account}'
|
|
266
|
+
|
|
267
|
+
if dtype == "transaction":
|
|
268
|
+
flag = getattr(directive, "flag", "*")
|
|
269
|
+
payee = getattr(directive, "payee", None)
|
|
270
|
+
narration = getattr(directive, "narration", "")
|
|
271
|
+
|
|
272
|
+
if payee:
|
|
273
|
+
header = f'{date} {flag} "{payee}" "{narration}"'
|
|
274
|
+
else:
|
|
275
|
+
header = f'{date} {flag} "{narration}"'
|
|
276
|
+
|
|
277
|
+
posting_lines = []
|
|
278
|
+
for p in getattr(directive, "postings", []):
|
|
279
|
+
if p.units:
|
|
280
|
+
posting_lines.append(f' {p.account} {p.units.number} {p.units.currency}')
|
|
281
|
+
else:
|
|
282
|
+
posting_lines.append(f' {p.account}')
|
|
283
|
+
|
|
284
|
+
return header + "\n" + "\n".join(posting_lines)
|
|
285
|
+
|
|
286
|
+
if dtype == "price":
|
|
287
|
+
amt = getattr(directive, "amount", None)
|
|
288
|
+
currency = getattr(directive, "currency", "")
|
|
289
|
+
if amt:
|
|
290
|
+
return f'{date} price {currency} {amt.number} {amt.currency}'
|
|
291
|
+
return f'{date} price {currency}'
|
|
292
|
+
|
|
293
|
+
if dtype == "commodity":
|
|
294
|
+
currency = getattr(directive, "currency", "")
|
|
295
|
+
return f'{date} commodity {currency}'
|
|
296
|
+
|
|
297
|
+
if dtype == "event":
|
|
298
|
+
event_type = getattr(directive, "type", "")
|
|
299
|
+
desc = getattr(directive, "description", "")
|
|
300
|
+
return f'{date} event "{event_type}" "{desc}"'
|
|
301
|
+
|
|
302
|
+
if dtype == "note":
|
|
303
|
+
comment = getattr(directive, "comment", "")
|
|
304
|
+
account = getattr(directive, "account", "")
|
|
305
|
+
return f'{date} note {account} "{comment}"'
|
|
306
|
+
|
|
307
|
+
if dtype == "document":
|
|
308
|
+
filename = getattr(directive, "filename", "")
|
|
309
|
+
account = getattr(directive, "account", "")
|
|
310
|
+
return f'{date} document {account} "{filename}"'
|
|
311
|
+
|
|
312
|
+
if dtype == "pad":
|
|
313
|
+
source_account = getattr(directive, "source_account", "")
|
|
314
|
+
account = getattr(directive, "account", "")
|
|
315
|
+
return f'{date} pad {account} {source_account}'
|
|
316
|
+
|
|
317
|
+
if dtype == "query":
|
|
318
|
+
name = getattr(directive, "name", "")
|
|
319
|
+
query_string = getattr(directive, "query_string", "")
|
|
320
|
+
return f'{date} query "{name}" "{query_string}"'
|
|
321
|
+
|
|
322
|
+
if dtype == "custom":
|
|
323
|
+
# Skip fava-specific custom directives that rustledger can't parse
|
|
324
|
+
custom_type = getattr(directive, "type", "")
|
|
325
|
+
if custom_type.startswith("fava"):
|
|
326
|
+
return ""
|
|
327
|
+
values = getattr(directive, "values", [])
|
|
328
|
+
values_str = " ".join(f'"{v}"' for v in values)
|
|
329
|
+
return f'{date} custom "{custom_type}" {values_str}'
|
|
330
|
+
|
|
331
|
+
return ""
|