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,132 @@
|
|
|
1
|
+
"""Fava extensions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from rustfava.core.module_base import FavaModule
|
|
10
|
+
from rustfava.ext import ExtensionConfigError
|
|
11
|
+
from rustfava.ext import RustfavaExtensionError
|
|
12
|
+
from rustfava.ext import find_extensions
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
15
|
+
from collections.abc import Iterable
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
|
|
18
|
+
from rustfava.beans.abc import Directive
|
|
19
|
+
from rustfava.core import RustfavaLedger
|
|
20
|
+
from rustfava.ext import RustfavaExtensionBase
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ExtensionDetails:
|
|
25
|
+
"""The information about an extension that is needed for the frontend."""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
report_title: str | None
|
|
29
|
+
has_js_module: bool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ExtensionModule(FavaModule):
|
|
33
|
+
"""Fava extensions."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, ledger: RustfavaLedger) -> None:
|
|
36
|
+
super().__init__(ledger)
|
|
37
|
+
self._instances: dict[str, RustfavaExtensionBase] = {}
|
|
38
|
+
self._loaded_extensions: set[type[RustfavaExtensionBase]] = set()
|
|
39
|
+
self.errors: list[RustfavaExtensionError] = []
|
|
40
|
+
|
|
41
|
+
def load_file(self) -> None: # noqa: D102
|
|
42
|
+
self.errors = []
|
|
43
|
+
|
|
44
|
+
custom_entries = self.ledger.all_entries_by_type.Custom
|
|
45
|
+
|
|
46
|
+
seen = set()
|
|
47
|
+
for entry in (e for e in custom_entries if e.type == "fava-extension"):
|
|
48
|
+
extension = entry.values[0].value
|
|
49
|
+
if extension in seen: # pragma: no cover
|
|
50
|
+
self.errors.append(
|
|
51
|
+
RustfavaExtensionError(
|
|
52
|
+
entry.meta, f"Duplicate extension '{extension}'", entry
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
seen.add(extension)
|
|
58
|
+
extensions, errors = find_extensions(
|
|
59
|
+
Path(self.ledger.beancount_file_path).parent,
|
|
60
|
+
extension,
|
|
61
|
+
)
|
|
62
|
+
self.errors.extend(errors)
|
|
63
|
+
|
|
64
|
+
for cls in extensions:
|
|
65
|
+
ext_config = (
|
|
66
|
+
entry.values[1].value if len(entry.values) > 1 else None
|
|
67
|
+
)
|
|
68
|
+
if cls not in self._loaded_extensions:
|
|
69
|
+
self._loaded_extensions.add(cls)
|
|
70
|
+
try:
|
|
71
|
+
ext = cls(self.ledger, ext_config)
|
|
72
|
+
self._instances[ext.name] = ext
|
|
73
|
+
except ExtensionConfigError as error: # pragma: no cover
|
|
74
|
+
self.errors.append(
|
|
75
|
+
RustfavaExtensionError(entry.meta, str(error), entry)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def _exts(self) -> Iterable[RustfavaExtensionBase]:
|
|
80
|
+
return self._instances.values()
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def extension_details(self) -> Sequence[ExtensionDetails]:
|
|
84
|
+
"""Extension information to provide to the Frontend."""
|
|
85
|
+
return [
|
|
86
|
+
ExtensionDetails(ext.name, ext.report_title, ext.has_js_module)
|
|
87
|
+
for ext in self._exts
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
def get_extension(self, name: str) -> RustfavaExtensionBase | None:
|
|
91
|
+
"""Get the extension with the given name."""
|
|
92
|
+
return self._instances.get(name, None)
|
|
93
|
+
|
|
94
|
+
def after_load_file(self) -> None:
|
|
95
|
+
"""Run all `after_load_file` hooks."""
|
|
96
|
+
for ext in self._exts:
|
|
97
|
+
ext.after_load_file()
|
|
98
|
+
|
|
99
|
+
def before_request(self) -> None:
|
|
100
|
+
"""Run all `before_request` hooks."""
|
|
101
|
+
for ext in self._exts:
|
|
102
|
+
ext.before_request()
|
|
103
|
+
|
|
104
|
+
def after_entry_modified(self, entry: Directive, new_lines: str) -> None:
|
|
105
|
+
"""Run all `after_entry_modified` hooks."""
|
|
106
|
+
for ext in self._exts: # pragma: no cover
|
|
107
|
+
ext.after_entry_modified(entry, new_lines)
|
|
108
|
+
|
|
109
|
+
def after_insert_entry(self, entry: Directive) -> None:
|
|
110
|
+
"""Run all `after_insert_entry` hooks."""
|
|
111
|
+
for ext in self._exts: # pragma: no cover
|
|
112
|
+
ext.after_insert_entry(entry)
|
|
113
|
+
|
|
114
|
+
def after_delete_entry(self, entry: Directive) -> None:
|
|
115
|
+
"""Run all `after_delete_entry` hooks."""
|
|
116
|
+
for ext in self._exts: # pragma: no cover
|
|
117
|
+
ext.after_delete_entry(entry)
|
|
118
|
+
|
|
119
|
+
def after_insert_metadata(
|
|
120
|
+
self,
|
|
121
|
+
entry: Directive,
|
|
122
|
+
key: str,
|
|
123
|
+
value: str,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Run all `after_insert_metadata` hooks."""
|
|
126
|
+
for ext in self._exts: # pragma: no cover
|
|
127
|
+
ext.after_insert_metadata(entry, key, value)
|
|
128
|
+
|
|
129
|
+
def after_write_source(self, path: str, source: str) -> None:
|
|
130
|
+
"""Run all `after_write_source` hooks."""
|
|
131
|
+
for ext in self._exts:
|
|
132
|
+
ext.after_write_source(path, source)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""rustfava's options.
|
|
2
|
+
|
|
3
|
+
Options for rustfava can be specified through Custom entries in the Beancount file.
|
|
4
|
+
This module contains a list of possible options, the defaults and the code for
|
|
5
|
+
parsing the options.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from dataclasses import field
|
|
14
|
+
from dataclasses import fields
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
from babel.core import Locale
|
|
19
|
+
from babel.core import UnknownLocaleError
|
|
20
|
+
|
|
21
|
+
from rustfava.beans.funcs import get_position
|
|
22
|
+
from rustfava.helpers import BeancountError
|
|
23
|
+
from rustfava.util import get_translations
|
|
24
|
+
from rustfava.util.date import END_OF_YEAR
|
|
25
|
+
from rustfava.util.date import parse_fye_string
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
28
|
+
import datetime
|
|
29
|
+
from collections.abc import Sequence
|
|
30
|
+
from re import Pattern
|
|
31
|
+
|
|
32
|
+
from rustfava.beans.abc import Custom
|
|
33
|
+
from rustfava.util.date import FiscalYearEnd
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OptionError(BeancountError):
|
|
37
|
+
"""An error for one of the rustfava options."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class InsertEntryOption:
|
|
42
|
+
"""Insert option.
|
|
43
|
+
|
|
44
|
+
An option that determines where entries for matching accounts should be
|
|
45
|
+
inserted.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
date: datetime.date
|
|
49
|
+
re: Pattern[str]
|
|
50
|
+
filename: str
|
|
51
|
+
lineno: int
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MissingOptionError(ValueError): # noqa: D101
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
super().__init__("Custom entry is missing option name.")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class UnknownOptionError(ValueError): # noqa: D101
|
|
60
|
+
def __init__(self, key: str) -> None:
|
|
61
|
+
super().__init__(f"Unknown option `{key}`")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class NotARegularExpressionError(TypeError): # noqa: D101
|
|
65
|
+
def __init__(self, value: str) -> None:
|
|
66
|
+
super().__init__(f"Should be a regular expression: '{value}'.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class NotAStringOptionError(TypeError): # noqa: D101
|
|
70
|
+
def __init__(self, key: str) -> None:
|
|
71
|
+
super().__init__(f"Expected string value for option `{key}`")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class UnknownLocaleOptionError(ValueError): # noqa: D101
|
|
75
|
+
def __init__(self, value: str) -> None:
|
|
76
|
+
super().__init__(f"Unknown locale: '{value}'.")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class UnsupportedLanguageOptionError(ValueError): # noqa: D101
|
|
80
|
+
def __init__(self, value: str) -> None:
|
|
81
|
+
super().__init__(f"Rustfava has no translations for: '{value}'.")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class InvalidFiscalYearEndOptionError(ValueError): # noqa: D101
|
|
85
|
+
def __init__(self, value: str) -> None:
|
|
86
|
+
super().__init__(f"Invalid 'fiscal_year_end' option: '{value}'.")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class RustfavaOptions:
|
|
91
|
+
"""Options for rustfava that can be set in the Beancount file."""
|
|
92
|
+
|
|
93
|
+
account_journal_include_children: bool = True
|
|
94
|
+
auto_reload: bool = False
|
|
95
|
+
collapse_pattern: Sequence[Pattern[str]] = field(default_factory=list)
|
|
96
|
+
conversion_currencies: tuple[str, ...] = ()
|
|
97
|
+
currency_column: int = 61
|
|
98
|
+
default_file: str | None = None
|
|
99
|
+
default_page: str = "income_statement/"
|
|
100
|
+
fiscal_year_end: FiscalYearEnd = END_OF_YEAR
|
|
101
|
+
import_config: str | None = None
|
|
102
|
+
import_dirs: Sequence[str] = field(default_factory=list)
|
|
103
|
+
indent: int = 2
|
|
104
|
+
insert_entry: Sequence[InsertEntryOption] = field(default_factory=list)
|
|
105
|
+
invert_gains_losses_colors: bool = False
|
|
106
|
+
invert_income_liabilities_equity: bool = False
|
|
107
|
+
language: str | None = None
|
|
108
|
+
locale: str | None = None
|
|
109
|
+
show_accounts_with_zero_balance: bool = True
|
|
110
|
+
show_accounts_with_zero_transactions: bool = True
|
|
111
|
+
show_closed_accounts: bool = False
|
|
112
|
+
sidebar_show_queries: int = 5
|
|
113
|
+
unrealized: str = "Unrealized"
|
|
114
|
+
upcoming_events: int = 7
|
|
115
|
+
uptodate_indicator_grey_lookback_days: int = 60
|
|
116
|
+
use_external_editor: bool = False
|
|
117
|
+
|
|
118
|
+
def set_collapse_pattern(self, value: str) -> None:
|
|
119
|
+
"""Set the collapse_pattern option."""
|
|
120
|
+
try:
|
|
121
|
+
pattern = re.compile(value)
|
|
122
|
+
except re.error as err:
|
|
123
|
+
raise NotARegularExpressionError(value) from err
|
|
124
|
+
# It's typed as Sequence so that it's not externally mutated
|
|
125
|
+
self.collapse_pattern.append(pattern) # type: ignore[attr-defined]
|
|
126
|
+
|
|
127
|
+
def set_default_file(self, value: str, filename: str) -> None:
|
|
128
|
+
"""Set the default_file option."""
|
|
129
|
+
self.default_file = (
|
|
130
|
+
str((Path(filename).parent / value).absolute())
|
|
131
|
+
if value
|
|
132
|
+
else filename
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def set_fiscal_year_end(self, value: str) -> None:
|
|
136
|
+
"""Set the fiscal_year_end option."""
|
|
137
|
+
fye = parse_fye_string(value)
|
|
138
|
+
if fye is None:
|
|
139
|
+
raise InvalidFiscalYearEndOptionError(value)
|
|
140
|
+
self.fiscal_year_end = fye
|
|
141
|
+
|
|
142
|
+
def set_import_dirs(self, value: str) -> None:
|
|
143
|
+
"""Add an import directory."""
|
|
144
|
+
# It's typed as Sequence so that it's not externally mutated
|
|
145
|
+
self.import_dirs.append(value) # type: ignore[attr-defined]
|
|
146
|
+
|
|
147
|
+
def set_insert_entry(
|
|
148
|
+
self, value: str, date: datetime.date, filename: str, lineno: int
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Set the insert_entry option."""
|
|
151
|
+
try:
|
|
152
|
+
pattern = re.compile(value)
|
|
153
|
+
except re.error as err:
|
|
154
|
+
raise NotARegularExpressionError(value) from err
|
|
155
|
+
opt = InsertEntryOption(date, pattern, filename, lineno)
|
|
156
|
+
# It's typed as Sequence so that it's not externally mutated
|
|
157
|
+
self.insert_entry.append(opt) # type: ignore[attr-defined]
|
|
158
|
+
|
|
159
|
+
def set_language(self, value: str) -> None:
|
|
160
|
+
"""Set the locale option."""
|
|
161
|
+
try:
|
|
162
|
+
locale = Locale.parse(value)
|
|
163
|
+
if (
|
|
164
|
+
not locale.language == "en"
|
|
165
|
+
and get_translations(locale) is None
|
|
166
|
+
):
|
|
167
|
+
raise UnsupportedLanguageOptionError(value)
|
|
168
|
+
self.language = value
|
|
169
|
+
except UnknownLocaleError as err:
|
|
170
|
+
raise UnknownLocaleOptionError(value) from err
|
|
171
|
+
|
|
172
|
+
def set_locale(self, value: str) -> None:
|
|
173
|
+
"""Set the locale option."""
|
|
174
|
+
try:
|
|
175
|
+
Locale.parse(value)
|
|
176
|
+
self.locale = value
|
|
177
|
+
except UnknownLocaleError as err:
|
|
178
|
+
raise UnknownLocaleOptionError(value) from err
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
_fields = fields(RustfavaOptions)
|
|
182
|
+
All_OPTS = {f.name for f in _fields}
|
|
183
|
+
BOOL_OPTS = {f.name for f in _fields if str(f.type) == "bool"}
|
|
184
|
+
INT_OPTS = {f.name for f in _fields if str(f.type) == "int"}
|
|
185
|
+
TUPLE_OPTS = {f.name for f in _fields if f.type.startswith("tuple[str,")}
|
|
186
|
+
STR_OPTS = {f.name for f in _fields if f.type.startswith("str")}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def parse_option_custom_entry( # noqa: PLR0912
|
|
190
|
+
entry: Custom,
|
|
191
|
+
options: RustfavaOptions,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Parse a single custom fava-option entry and set option accordingly."""
|
|
194
|
+
key = str(entry.values[0].value).replace("-", "_")
|
|
195
|
+
if key not in All_OPTS:
|
|
196
|
+
raise UnknownOptionError(key)
|
|
197
|
+
|
|
198
|
+
value = entry.values[1].value if len(entry.values) > 1 else ""
|
|
199
|
+
if not isinstance(value, str):
|
|
200
|
+
raise NotAStringOptionError(key)
|
|
201
|
+
filename, lineno = get_position(entry)
|
|
202
|
+
|
|
203
|
+
if key == "collapse_pattern":
|
|
204
|
+
options.set_collapse_pattern(value)
|
|
205
|
+
elif key == "default_file":
|
|
206
|
+
options.set_default_file(value, filename)
|
|
207
|
+
elif key == "fiscal_year_end":
|
|
208
|
+
options.set_fiscal_year_end(value)
|
|
209
|
+
elif key == "import_dirs":
|
|
210
|
+
options.set_import_dirs(value)
|
|
211
|
+
elif key == "insert_entry":
|
|
212
|
+
options.set_insert_entry(value, entry.date, filename, lineno)
|
|
213
|
+
elif key == "language":
|
|
214
|
+
options.set_language(value)
|
|
215
|
+
elif key == "locale":
|
|
216
|
+
options.set_locale(value)
|
|
217
|
+
elif key in STR_OPTS:
|
|
218
|
+
setattr(options, key, value)
|
|
219
|
+
elif key in BOOL_OPTS:
|
|
220
|
+
setattr(options, key, value.lower() == "true")
|
|
221
|
+
elif key in INT_OPTS:
|
|
222
|
+
setattr(options, key, int(value))
|
|
223
|
+
else: # key in TUPLE_OPTS
|
|
224
|
+
setattr(options, key, tuple(value.strip().split(" ")))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def parse_options(
|
|
228
|
+
custom_entries: Sequence[Custom],
|
|
229
|
+
) -> tuple[RustfavaOptions, list[OptionError]]:
|
|
230
|
+
"""Parse custom entries for rustfava options.
|
|
231
|
+
|
|
232
|
+
The format for option entries is the following::
|
|
233
|
+
|
|
234
|
+
2016-04-01 custom "fava-option" "[name]" "[value]"
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
custom_entries: A list of Custom entries.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
A tuple (options, errors) where options is a dictionary of all options
|
|
241
|
+
to values, and errors contains possible parsing errors.
|
|
242
|
+
"""
|
|
243
|
+
options = RustfavaOptions()
|
|
244
|
+
errors = []
|
|
245
|
+
|
|
246
|
+
for entry in (e for e in custom_entries if e.type == "fava-option"):
|
|
247
|
+
try:
|
|
248
|
+
if not entry.values:
|
|
249
|
+
raise MissingOptionError
|
|
250
|
+
parse_option_custom_entry(entry, options)
|
|
251
|
+
except (IndexError, TypeError, ValueError) as err:
|
|
252
|
+
msg = f"Failed to parse fava-option entry: {err!s}"
|
|
253
|
+
errors.append(OptionError(entry.meta, msg, entry))
|
|
254
|
+
|
|
255
|
+
return options, errors
|