farora 0.5.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.
- farora/__init__.py +14 -0
- farora/cli.py +287 -0
- farora/core/__init__.py +339 -0
- farora/core/assets.py +69 -0
- farora/core/doctor.py +219 -0
- farora/core/errors.py +139 -0
- farora/core/events.py +187 -0
- farora/core/gc.py +212 -0
- farora/core/hashing.py +20 -0
- farora/core/ids.py +116 -0
- farora/core/models.py +153 -0
- farora/core/notes.py +105 -0
- farora/core/operations.py +412 -0
- farora/core/ports.py +235 -0
- farora/core/ranking.py +159 -0
- farora/core/reconcile.py +382 -0
- farora/core/service.py +1549 -0
- farora/core/timestamps.py +137 -0
- farora/core/undo.py +465 -0
- farora/doctor.py +706 -0
- farora/gc.py +272 -0
- farora/gitsync/__init__.py +464 -0
- farora/gitsync/engine.py +695 -0
- farora/gitsync/merge.py +809 -0
- farora/gitsync/oplog.py +241 -0
- farora/gitsync/renumber.py +239 -0
- farora/gitsync/runner.py +358 -0
- farora/gitsync/settings.py +277 -0
- farora/store_local/__init__.py +115 -0
- farora/store_local/allocation.py +175 -0
- farora/store_local/atomic.py +88 -0
- farora/store_local/config.py +131 -0
- farora/store_local/conflicts.py +81 -0
- farora/store_local/frontmatter.py +214 -0
- farora/store_local/journal.py +97 -0
- farora/store_local/locking.py +95 -0
- farora/store_local/paths.py +250 -0
- farora/store_local/repository.py +616 -0
- farora/store_local/suppression.py +117 -0
- farora/store_local/toml_codec.py +70 -0
- farora/store_local/watcher.py +148 -0
- farora/tui/__init__.py +349 -0
- farora/tui/app.py +1639 -0
- farora/tui/app.tcss +376 -0
- farora/tui/calendars.py +173 -0
- farora/tui/clipboard.py +61 -0
- farora/tui/commands.py +335 -0
- farora/tui/confirm.py +164 -0
- farora/tui/creation.py +123 -0
- farora/tui/detail.py +288 -0
- farora/tui/doby.py +119 -0
- farora/tui/doby_picker.py +244 -0
- farora/tui/filters.py +303 -0
- farora/tui/forest.py +141 -0
- farora/tui/gitscreen.py +533 -0
- farora/tui/hierarchy.py +216 -0
- farora/tui/keymap.py +299 -0
- farora/tui/labels.py +163 -0
- farora/tui/menus.py +259 -0
- farora/tui/panes.py +372 -0
- farora/tui/pickers.py +318 -0
- farora/tui/prompts.py +317 -0
- farora/tui/repair.py +99 -0
- farora/tui/search.py +107 -0
- farora/tui/statuses.py +66 -0
- farora/tui/tree.py +338 -0
- farora/tui/ui_state.py +103 -0
- farora-0.5.0.dist-info/METADATA +118 -0
- farora-0.5.0.dist-info/RECORD +72 -0
- farora-0.5.0.dist-info/WHEEL +4 -0
- farora-0.5.0.dist-info/entry_points.txt +2 -0
- farora-0.5.0.dist-info/licenses/LICENSE +59 -0
farora/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Farora — a local-first, plain-text task tracker."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version as _version
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# `pyproject.toml`'s `[project].version` is the single source of truth. Reading it
|
|
8
|
+
# back from the installed distribution metadata is what keeps `farora --version`
|
|
9
|
+
# and the built wheel from ever disagreeing — never hardcode a number here.
|
|
10
|
+
__version__ = _version("farora")
|
|
11
|
+
except PackageNotFoundError: # pragma: no cover - source tree with nothing installed
|
|
12
|
+
__version__ = "0.0.0+unknown"
|
|
13
|
+
|
|
14
|
+
__all__ = ["__version__"]
|
farora/cli.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""Entry point for the ``farora`` console script.
|
|
2
|
+
|
|
3
|
+
Exposes three commands: bare ``farora`` (launch the TUI), ``farora doctor``
|
|
4
|
+
(validate and reconcile a repo) and ``farora gc`` (purge expired tombstones).
|
|
5
|
+
The remaining command bodies land with their milestones.
|
|
6
|
+
|
|
7
|
+
This module is also the **composition root**: the one place that knows both that
|
|
8
|
+
tasks live in a local folder and that a Textual app is going to show them. It
|
|
9
|
+
builds the store adapter, injects it into the core's service, and hands that
|
|
10
|
+
service to the TUI — which is why neither of those packages imports the other
|
|
11
|
+
(``04-architecture.md``: ``tui -> core <- store``).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from collections.abc import Sequence
|
|
20
|
+
from dataclasses import replace
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from . import __version__
|
|
24
|
+
|
|
25
|
+
DEFAULT_REPO = "~/.farora"
|
|
26
|
+
|
|
27
|
+
REPO_ENV = "FARORA_REPO"
|
|
28
|
+
"""``$FARORA_REPO`` — the repo when ``--repo`` is not given (FR-X1)."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
32
|
+
"""Build the ``farora`` argument parser."""
|
|
33
|
+
parser = argparse.ArgumentParser(
|
|
34
|
+
prog="farora",
|
|
35
|
+
description="A local-first, plain-text task tracker.",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument("--version", action="version", version=f"farora {__version__}")
|
|
38
|
+
_add_repo_argument(parser, default=None)
|
|
39
|
+
|
|
40
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
41
|
+
|
|
42
|
+
doctor = subparsers.add_parser(
|
|
43
|
+
"doctor",
|
|
44
|
+
help="validate and reconcile a repo",
|
|
45
|
+
description="Validate repo structure and reconcile sync conflicts.",
|
|
46
|
+
)
|
|
47
|
+
_add_repo_argument(doctor)
|
|
48
|
+
doctor.set_defaults(handler=_cmd_doctor)
|
|
49
|
+
|
|
50
|
+
gc = subparsers.add_parser(
|
|
51
|
+
"gc",
|
|
52
|
+
help="purge expired tombstones",
|
|
53
|
+
description="Purge journals of tasks deleted longer ago than the retention window.",
|
|
54
|
+
)
|
|
55
|
+
_add_repo_argument(gc)
|
|
56
|
+
gc.add_argument(
|
|
57
|
+
"--older-than",
|
|
58
|
+
metavar="DURATION",
|
|
59
|
+
help="retention window override (default: from config, 30d)",
|
|
60
|
+
)
|
|
61
|
+
gc.set_defaults(handler=_cmd_gc)
|
|
62
|
+
|
|
63
|
+
parser.set_defaults(handler=_cmd_tui)
|
|
64
|
+
return parser
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _add_repo_argument(
|
|
68
|
+
parser: argparse.ArgumentParser, default: str | None = argparse.SUPPRESS
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Add ``--repo`` to a parser, accepted both before and after a subcommand.
|
|
71
|
+
|
|
72
|
+
Subparsers suppress the default so that an unpassed ``--repo`` does not
|
|
73
|
+
overwrite one already parsed off the main parser.
|
|
74
|
+
"""
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"--repo",
|
|
77
|
+
metavar="PATH",
|
|
78
|
+
default=default,
|
|
79
|
+
help=f"repo path (default: $FARORA_REPO, else {DEFAULT_REPO})",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def resolve_repo(repo: str | None) -> Path:
|
|
84
|
+
"""Which repo to work on: ``--repo``, else ``$FARORA_REPO``, else ``~/.farora`` (FR-X1).
|
|
85
|
+
|
|
86
|
+
The one answer, for every entry point — the TUI, ``doctor`` and ``gc`` all call
|
|
87
|
+
this and none of them reads ``$FARORA_REPO`` for itself. Everything that hangs off
|
|
88
|
+
a repo path hangs off this one: the single-writer lock, the startup check, and the
|
|
89
|
+
machine-local cache key that ``ui_state_path`` derives.
|
|
90
|
+
"""
|
|
91
|
+
return Path(repo or os.environ.get(REPO_ENV) or DEFAULT_REPO).expanduser()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _cmd_tui(args: argparse.Namespace) -> int:
|
|
95
|
+
"""Launch the TUI over a local repo — the composition root's whole job.
|
|
96
|
+
|
|
97
|
+
Imports are deferred because this is the only command that needs Textual, and
|
|
98
|
+
``farora gc`` should not pay to import a UI it will not draw.
|
|
99
|
+
"""
|
|
100
|
+
from contextlib import ExitStack, suppress
|
|
101
|
+
from functools import partial
|
|
102
|
+
|
|
103
|
+
from farora.core import FaroraError, Retention, StoreIoError, TaskService
|
|
104
|
+
from farora.doctor import light, startup_notice
|
|
105
|
+
from farora.gc import opportunistic
|
|
106
|
+
from farora.gitsync import GitSync
|
|
107
|
+
from farora.gitsync.oplog import OpLog
|
|
108
|
+
from farora.gitsync.runner import GitRunner
|
|
109
|
+
from farora.store_local import (
|
|
110
|
+
LocalTaskRepository,
|
|
111
|
+
device_id,
|
|
112
|
+
init_repo,
|
|
113
|
+
load_config,
|
|
114
|
+
repo_lock,
|
|
115
|
+
ui_state_path,
|
|
116
|
+
)
|
|
117
|
+
from farora.tui import Commands, FaroraApp, choose_spawner, viewer_status
|
|
118
|
+
|
|
119
|
+
repo = resolve_repo(args.repo)
|
|
120
|
+
with ExitStack() as stack:
|
|
121
|
+
try:
|
|
122
|
+
init_repo(repo) # idempotent: this is also how the default repo first exists
|
|
123
|
+
repository = LocalTaskRepository(repo)
|
|
124
|
+
# One machine id, minted once: the service's actor and git tracking's fallback
|
|
125
|
+
# committer identity (A8) are the same machine, so a commit farora authors and
|
|
126
|
+
# the journal line it publishes name one and the same author.
|
|
127
|
+
actor = device_id()
|
|
128
|
+
service = TaskService(repository, actor=actor)
|
|
129
|
+
# `~/.config/farora/config.toml` is machine-local state, so reading it is
|
|
130
|
+
# the store adapter's; deciding what `e` and `v` run is the TUI's
|
|
131
|
+
# (`06-ux-and-keymap.md` §4), and how long a tombstone is kept is FR-D4's.
|
|
132
|
+
# This is the only place those facts meet — which is why `farora.tui` can
|
|
133
|
+
# own the editor without importing the thing that knows where a config
|
|
134
|
+
# file lives.
|
|
135
|
+
config = load_config()
|
|
136
|
+
commands = Commands.from_config(config)
|
|
137
|
+
retention = Retention.from_config(config)
|
|
138
|
+
# Before the check, and before the app: FR-X3's guard covers every write
|
|
139
|
+
# this process makes, and the startup check is the first of them.
|
|
140
|
+
stack.enter_context(repo_lock(repo))
|
|
141
|
+
notice = startup_notice(light(repository, service))
|
|
142
|
+
except FaroraError as exc:
|
|
143
|
+
print(f"farora: {exc}", file=sys.stderr)
|
|
144
|
+
return 1
|
|
145
|
+
|
|
146
|
+
# §5's "opportunistically on ordinary runs" — decision #2's whole scheduler.
|
|
147
|
+
#
|
|
148
|
+
# **After the check, and that ordering is load-bearing.** gc reads a tombstone
|
|
149
|
+
# as "the log's terminal entry is a delete", which is a claim about the *merged*
|
|
150
|
+
# log; `light` has just unioned in every conflict copy of every journal, so the
|
|
151
|
+
# log gc reads here is the one that has heard from the other machine.
|
|
152
|
+
#
|
|
153
|
+
# Failure is swallowed on purpose, and only here. A repo whose journals cannot
|
|
154
|
+
# be unlinked — a read-only folder, a full disk — is a repo whose tasks the user
|
|
155
|
+
# can still read and still change, and refusing to open their tracker because
|
|
156
|
+
# housekeeping did not go through would be Farora holding their work hostage to
|
|
157
|
+
# its own tidiness. It costs one keeping-things-too-long, and the next run tries
|
|
158
|
+
# again.
|
|
159
|
+
with suppress(StoreIoError):
|
|
160
|
+
opportunistic(repository, service, retention)
|
|
161
|
+
# Git tracking's composition root (technical-design §1, A14). Built here, after
|
|
162
|
+
# the lock, because the engine only ever runs while this process — the single
|
|
163
|
+
# writer — holds the repo, and torn down with the app it belongs to. The facade
|
|
164
|
+
# is handed to the app as the one thing it is: a controller answering the TUI's
|
|
165
|
+
# protocol. The app never learns it is git — only that something syncs — and it
|
|
166
|
+
# is the app's `on_mount` that starts the engine's loop as a worker so Textual
|
|
167
|
+
# owns its lifetime. A repo with no git configured makes every tick a silent
|
|
168
|
+
# no-op, so wiring this unconditionally costs an unconfigured user nothing.
|
|
169
|
+
sync = GitSync(repository.layout, service, actor, GitRunner(repo), OpLog.for_repo(repo))
|
|
170
|
+
FaroraApp(
|
|
171
|
+
service,
|
|
172
|
+
ui_state_path(repo),
|
|
173
|
+
# The capability, handed over as the one method it is
|
|
174
|
+
# (`04-architecture.md` §2): the app learns how to reach a task's file and
|
|
175
|
+
# still not that the file is in a folder.
|
|
176
|
+
body_path=repository.body_path,
|
|
177
|
+
panes=partial(choose_spawner, commands),
|
|
178
|
+
viewer=partial(viewer_status, commands),
|
|
179
|
+
notice=notice,
|
|
180
|
+
sync=sync,
|
|
181
|
+
).run()
|
|
182
|
+
return 0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
186
|
+
"""Validate and reconcile a repo (FR-X4, ``05-storage-and-data-model.md`` §7).
|
|
187
|
+
|
|
188
|
+
Deliberately does **not** ``init_repo``: ``farora`` creates the repo it is asked
|
|
189
|
+
for, because a missing ``~/.farora`` on a first run is not a mistake, but a doctor
|
|
190
|
+
that created its patient would answer ``farora doctor --repo ~/typo`` by declaring
|
|
191
|
+
an empty folder healthy. Here the marker's absence is the finding.
|
|
192
|
+
|
|
193
|
+
Exits 1 when anything is left for the user, the way a linter does — what doctor
|
|
194
|
+
fixed by itself is not a failure, and what it could not is.
|
|
195
|
+
"""
|
|
196
|
+
from farora.core import FaroraError, TaskService
|
|
197
|
+
from farora.doctor import check, render
|
|
198
|
+
from farora.store_local import LocalTaskRepository, device_id, repo_lock
|
|
199
|
+
|
|
200
|
+
repo = resolve_repo(args.repo)
|
|
201
|
+
try:
|
|
202
|
+
repository = LocalTaskRepository(repo)
|
|
203
|
+
service = TaskService(repository, actor=device_id())
|
|
204
|
+
with repo_lock(repo):
|
|
205
|
+
diagnosis = check(repository, service, confirm=_ask)
|
|
206
|
+
except FaroraError as exc:
|
|
207
|
+
print(f"farora: {exc}", file=sys.stderr)
|
|
208
|
+
return 1
|
|
209
|
+
print(render(diagnosis, repository.root))
|
|
210
|
+
return 1 if diagnosis.outstanding else 0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _ask(question: str) -> bool:
|
|
214
|
+
"""§7's "after confirmation", at a terminal — and a firm no without one.
|
|
215
|
+
|
|
216
|
+
A doctor whose stdin is a pipe cannot ask, so it does not assume. The finding is
|
|
217
|
+
reported instead and the file is left alone, which is the only safe reading of
|
|
218
|
+
silence when the question is "shall I journal this task as deleted?".
|
|
219
|
+
"""
|
|
220
|
+
if not sys.stdin.isatty():
|
|
221
|
+
return False
|
|
222
|
+
try:
|
|
223
|
+
return input(f"{question} [y/N] ").strip().lower() in {"y", "yes"}
|
|
224
|
+
except EOFError:
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _cmd_gc(args: argparse.Namespace) -> int:
|
|
229
|
+
"""Purge expired tombstones on demand (FR-D4, ``05-storage-and-data-model.md`` §5).
|
|
230
|
+
|
|
231
|
+
Does not ``init_repo``, for :func:`_cmd_doctor`'s reason, and takes the repo lock for
|
|
232
|
+
:func:`_cmd_tui`'s: this removes files.
|
|
233
|
+
|
|
234
|
+
**``[gc] enabled = false`` does not stop this**, and ``--older-than`` beats the
|
|
235
|
+
config's window. The switch governs the sweep an ordinary run does on its own
|
|
236
|
+
(:func:`~farora.gc.opportunistic`); a user typing ``farora gc`` is asking, and the
|
|
237
|
+
only reading of the flag under which they could not is one where turning the
|
|
238
|
+
automatic sweep off also takes the manual one away. It is said out loud rather than
|
|
239
|
+
done quietly — see :data:`~farora.gc.DISABLED_NOTE`.
|
|
240
|
+
"""
|
|
241
|
+
from farora.core import FaroraError, Retention, TaskService, parse_duration
|
|
242
|
+
from farora.gc import DISABLED_NOTE, collect, render
|
|
243
|
+
from farora.store_local import LocalTaskRepository, device_id, load_config, repo_lock
|
|
244
|
+
|
|
245
|
+
repo = resolve_repo(args.repo)
|
|
246
|
+
try:
|
|
247
|
+
retention = Retention.from_config(load_config())
|
|
248
|
+
if args.older_than is not None:
|
|
249
|
+
retention = replace(retention, window=parse_duration(args.older_than), complaint="")
|
|
250
|
+
except FaroraError as exc:
|
|
251
|
+
print(f"farora: {exc}", file=sys.stderr)
|
|
252
|
+
return 1
|
|
253
|
+
except ValueError as exc:
|
|
254
|
+
# `--older-than lastweek`. An unusable window is the one thing gc must not
|
|
255
|
+
# guess at, and argparse's own exit code for a bad argument says so.
|
|
256
|
+
print(f"farora: --older-than: {exc}", file=sys.stderr)
|
|
257
|
+
return 2
|
|
258
|
+
if retention.complaint:
|
|
259
|
+
# The config's window is unreadable and nobody named one on the command line.
|
|
260
|
+
# `Retention.from_config` has already refused to invent a number; this is only
|
|
261
|
+
# where the user hears about it.
|
|
262
|
+
print(f"farora: {retention.complaint}", file=sys.stderr)
|
|
263
|
+
return 1
|
|
264
|
+
if not retention.enabled:
|
|
265
|
+
print(f"farora: {DISABLED_NOTE}", file=sys.stderr)
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
repository = LocalTaskRepository(repo)
|
|
269
|
+
service = TaskService(repository, actor=device_id())
|
|
270
|
+
with repo_lock(repo):
|
|
271
|
+
sweep = collect(repository, service, window=retention.window)
|
|
272
|
+
except FaroraError as exc:
|
|
273
|
+
print(f"farora: {exc}", file=sys.stderr)
|
|
274
|
+
return 1
|
|
275
|
+
print(render(sweep, repository.root))
|
|
276
|
+
return 0
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
280
|
+
"""Parse ``argv`` and dispatch. Returns the process exit code."""
|
|
281
|
+
parser = build_parser()
|
|
282
|
+
args = parser.parse_args(argv)
|
|
283
|
+
return args.handler(args)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
if __name__ == "__main__":
|
|
287
|
+
sys.exit(main())
|
farora/core/__init__.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""Domain core: models, operation surface, ``TaskService``, events, undo.
|
|
2
|
+
|
|
3
|
+
This package is the hexagon's inside. It must not import ``farora.tui`` or
|
|
4
|
+
``farora.store_local`` — the dependency direction is ``tui -> core <- store``,
|
|
5
|
+
enforced by ``tests/core/test_import_boundary.py``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from farora.core.assets import rewrite_asset_links
|
|
9
|
+
from farora.core.doctor import (
|
|
10
|
+
Diagnosis,
|
|
11
|
+
Finding,
|
|
12
|
+
FindingKind,
|
|
13
|
+
merge_journal_lines,
|
|
14
|
+
orphans,
|
|
15
|
+
unknown_statuses,
|
|
16
|
+
)
|
|
17
|
+
from farora.core.errors import (
|
|
18
|
+
HTTP_STATUS,
|
|
19
|
+
CrossProjectError,
|
|
20
|
+
CycleError,
|
|
21
|
+
ErrorCode,
|
|
22
|
+
FaroraError,
|
|
23
|
+
InvalidSlugError,
|
|
24
|
+
InvalidStatusError,
|
|
25
|
+
NotFoundError,
|
|
26
|
+
RepoLockedError,
|
|
27
|
+
SlugTakenError,
|
|
28
|
+
StaleError,
|
|
29
|
+
StatusExistsError,
|
|
30
|
+
StoreIoError,
|
|
31
|
+
error_for,
|
|
32
|
+
)
|
|
33
|
+
from farora.core.events import (
|
|
34
|
+
PROJECT_JOURNAL_OPS,
|
|
35
|
+
Actor,
|
|
36
|
+
ChangeKind,
|
|
37
|
+
DomainEvent,
|
|
38
|
+
FieldChange,
|
|
39
|
+
JournalEntry,
|
|
40
|
+
JournalOp,
|
|
41
|
+
RepoChange,
|
|
42
|
+
changed_fields,
|
|
43
|
+
field_payload,
|
|
44
|
+
fields_payload,
|
|
45
|
+
)
|
|
46
|
+
from farora.core.gc import (
|
|
47
|
+
DEFAULT_ENABLED,
|
|
48
|
+
DEFAULT_WINDOW,
|
|
49
|
+
DURATION_FORMS,
|
|
50
|
+
ENABLED_KEY,
|
|
51
|
+
GC_SECTION,
|
|
52
|
+
RETENTION_KEY,
|
|
53
|
+
Retention,
|
|
54
|
+
deleted_at,
|
|
55
|
+
expired,
|
|
56
|
+
format_duration,
|
|
57
|
+
parse_duration,
|
|
58
|
+
)
|
|
59
|
+
from farora.core.hashing import HASH_ALGORITHM, content_hash
|
|
60
|
+
from farora.core.ids import (
|
|
61
|
+
SLUG_MAX_LENGTH,
|
|
62
|
+
SLUG_MIN_LENGTH,
|
|
63
|
+
SLUG_PATTERN,
|
|
64
|
+
TASK_ID_PATTERN,
|
|
65
|
+
OpId,
|
|
66
|
+
Slug,
|
|
67
|
+
TaskId,
|
|
68
|
+
Uid,
|
|
69
|
+
Uuid7,
|
|
70
|
+
format_task_id,
|
|
71
|
+
is_valid_slug,
|
|
72
|
+
new_op_id,
|
|
73
|
+
new_uid,
|
|
74
|
+
parse_task_id,
|
|
75
|
+
project_of,
|
|
76
|
+
validate_slug,
|
|
77
|
+
)
|
|
78
|
+
from farora.core.models import (
|
|
79
|
+
BUILTIN_STATUSES,
|
|
80
|
+
DEFAULT_STATUS,
|
|
81
|
+
FORMAT_VERSION,
|
|
82
|
+
SCHEMA_VERSION,
|
|
83
|
+
Name,
|
|
84
|
+
Project,
|
|
85
|
+
Rank,
|
|
86
|
+
RepoInfo,
|
|
87
|
+
Status,
|
|
88
|
+
StatusKey,
|
|
89
|
+
StatusKind,
|
|
90
|
+
Task,
|
|
91
|
+
)
|
|
92
|
+
from farora.core.notes import (
|
|
93
|
+
BLOCK_SEPARATOR,
|
|
94
|
+
NOTE_TIMESTAMP_FORMAT,
|
|
95
|
+
NoteLocator,
|
|
96
|
+
append_note,
|
|
97
|
+
find_note_block,
|
|
98
|
+
format_note_block,
|
|
99
|
+
remove_note_block,
|
|
100
|
+
)
|
|
101
|
+
from farora.core.operations import (
|
|
102
|
+
FIRST,
|
|
103
|
+
LAST,
|
|
104
|
+
OPERATION_ADAPTER,
|
|
105
|
+
ROOT,
|
|
106
|
+
After,
|
|
107
|
+
AnyOperation,
|
|
108
|
+
Before,
|
|
109
|
+
First,
|
|
110
|
+
Last,
|
|
111
|
+
Mutation,
|
|
112
|
+
Operation,
|
|
113
|
+
OperationKind,
|
|
114
|
+
Position,
|
|
115
|
+
ProjectCreateOp,
|
|
116
|
+
ProjectGetOp,
|
|
117
|
+
ProjectListOp,
|
|
118
|
+
ProjectRenameOp,
|
|
119
|
+
RepoInfoOp,
|
|
120
|
+
StatusAddOp,
|
|
121
|
+
TaskCopyOp,
|
|
122
|
+
TaskCreateOp,
|
|
123
|
+
TaskDeleteOp,
|
|
124
|
+
TaskGetOp,
|
|
125
|
+
TaskListBodiesOp,
|
|
126
|
+
TaskListOp,
|
|
127
|
+
TaskLogOp,
|
|
128
|
+
TaskMoveOp,
|
|
129
|
+
TaskNoteOp,
|
|
130
|
+
TaskReadBodyOp,
|
|
131
|
+
TaskRenameOp,
|
|
132
|
+
TaskRestoreOp,
|
|
133
|
+
TaskSetDobyOp,
|
|
134
|
+
TaskSetStatusOp,
|
|
135
|
+
TaskTransferOp,
|
|
136
|
+
TaskWriteBodyOp,
|
|
137
|
+
after,
|
|
138
|
+
before,
|
|
139
|
+
parse_operation,
|
|
140
|
+
)
|
|
141
|
+
from farora.core.ports import DeferredWrites, LocalFileAccess, TaskRepository
|
|
142
|
+
from farora.core.ranking import (
|
|
143
|
+
MAX_RANK_LENGTH,
|
|
144
|
+
RANK_HEAL_REASON,
|
|
145
|
+
healed_ranks,
|
|
146
|
+
needs_rank_heal,
|
|
147
|
+
position_for_rank,
|
|
148
|
+
rank_between,
|
|
149
|
+
rank_for_position,
|
|
150
|
+
sort_key,
|
|
151
|
+
sort_siblings,
|
|
152
|
+
)
|
|
153
|
+
from farora.core.reconcile import (
|
|
154
|
+
ARBITRATED,
|
|
155
|
+
FIELD_OPS,
|
|
156
|
+
Arbitration,
|
|
157
|
+
Reconciliation,
|
|
158
|
+
arbitrate,
|
|
159
|
+
arbitrate_conflict,
|
|
160
|
+
group_edits,
|
|
161
|
+
history_of,
|
|
162
|
+
journaled,
|
|
163
|
+
last_body_hash,
|
|
164
|
+
last_known,
|
|
165
|
+
last_known_task,
|
|
166
|
+
newest,
|
|
167
|
+
)
|
|
168
|
+
from farora.core.service import Listener, TaskService
|
|
169
|
+
from farora.core.timestamps import (
|
|
170
|
+
DueDate,
|
|
171
|
+
Timestamp,
|
|
172
|
+
format_date,
|
|
173
|
+
format_rfc3339,
|
|
174
|
+
normalize,
|
|
175
|
+
now,
|
|
176
|
+
parse_date,
|
|
177
|
+
parse_rfc3339,
|
|
178
|
+
)
|
|
179
|
+
from farora.core.undo import UndoEntry, UndoKind, UndoOutcome, UndoStack
|
|
180
|
+
|
|
181
|
+
__all__ = [
|
|
182
|
+
"ARBITRATED",
|
|
183
|
+
"Actor",
|
|
184
|
+
"After",
|
|
185
|
+
"AnyOperation",
|
|
186
|
+
"Arbitration",
|
|
187
|
+
"BLOCK_SEPARATOR",
|
|
188
|
+
"BUILTIN_STATUSES",
|
|
189
|
+
"Before",
|
|
190
|
+
"ChangeKind",
|
|
191
|
+
"CrossProjectError",
|
|
192
|
+
"CycleError",
|
|
193
|
+
"DEFAULT_ENABLED",
|
|
194
|
+
"DEFAULT_STATUS",
|
|
195
|
+
"DEFAULT_WINDOW",
|
|
196
|
+
"DURATION_FORMS",
|
|
197
|
+
"DeferredWrites",
|
|
198
|
+
"Diagnosis",
|
|
199
|
+
"DomainEvent",
|
|
200
|
+
"DueDate",
|
|
201
|
+
"ENABLED_KEY",
|
|
202
|
+
"ErrorCode",
|
|
203
|
+
"FIELD_OPS",
|
|
204
|
+
"FIRST",
|
|
205
|
+
"FORMAT_VERSION",
|
|
206
|
+
"FaroraError",
|
|
207
|
+
"FieldChange",
|
|
208
|
+
"Finding",
|
|
209
|
+
"FindingKind",
|
|
210
|
+
"First",
|
|
211
|
+
"GC_SECTION",
|
|
212
|
+
"HASH_ALGORITHM",
|
|
213
|
+
"HTTP_STATUS",
|
|
214
|
+
"InvalidSlugError",
|
|
215
|
+
"InvalidStatusError",
|
|
216
|
+
"JournalEntry",
|
|
217
|
+
"JournalOp",
|
|
218
|
+
"LAST",
|
|
219
|
+
"Last",
|
|
220
|
+
"Listener",
|
|
221
|
+
"LocalFileAccess",
|
|
222
|
+
"MAX_RANK_LENGTH",
|
|
223
|
+
"Mutation",
|
|
224
|
+
"NOTE_TIMESTAMP_FORMAT",
|
|
225
|
+
"Name",
|
|
226
|
+
"NotFoundError",
|
|
227
|
+
"NoteLocator",
|
|
228
|
+
"OPERATION_ADAPTER",
|
|
229
|
+
"OpId",
|
|
230
|
+
"Operation",
|
|
231
|
+
"OperationKind",
|
|
232
|
+
"PROJECT_JOURNAL_OPS",
|
|
233
|
+
"Position",
|
|
234
|
+
"Project",
|
|
235
|
+
"ProjectCreateOp",
|
|
236
|
+
"ProjectGetOp",
|
|
237
|
+
"ProjectListOp",
|
|
238
|
+
"ProjectRenameOp",
|
|
239
|
+
"RANK_HEAL_REASON",
|
|
240
|
+
"RETENTION_KEY",
|
|
241
|
+
"ROOT",
|
|
242
|
+
"Rank",
|
|
243
|
+
"Reconciliation",
|
|
244
|
+
"RepoChange",
|
|
245
|
+
"RepoInfo",
|
|
246
|
+
"RepoInfoOp",
|
|
247
|
+
"RepoLockedError",
|
|
248
|
+
"Retention",
|
|
249
|
+
"SCHEMA_VERSION",
|
|
250
|
+
"SLUG_MAX_LENGTH",
|
|
251
|
+
"SLUG_MIN_LENGTH",
|
|
252
|
+
"SLUG_PATTERN",
|
|
253
|
+
"Slug",
|
|
254
|
+
"SlugTakenError",
|
|
255
|
+
"StaleError",
|
|
256
|
+
"Status",
|
|
257
|
+
"StatusAddOp",
|
|
258
|
+
"StatusExistsError",
|
|
259
|
+
"StatusKey",
|
|
260
|
+
"StatusKind",
|
|
261
|
+
"StoreIoError",
|
|
262
|
+
"TASK_ID_PATTERN",
|
|
263
|
+
"Task",
|
|
264
|
+
"TaskCopyOp",
|
|
265
|
+
"TaskCreateOp",
|
|
266
|
+
"TaskDeleteOp",
|
|
267
|
+
"TaskGetOp",
|
|
268
|
+
"TaskId",
|
|
269
|
+
"TaskListBodiesOp",
|
|
270
|
+
"TaskListOp",
|
|
271
|
+
"TaskLogOp",
|
|
272
|
+
"TaskMoveOp",
|
|
273
|
+
"TaskNoteOp",
|
|
274
|
+
"TaskReadBodyOp",
|
|
275
|
+
"TaskRenameOp",
|
|
276
|
+
"TaskRepository",
|
|
277
|
+
"TaskRestoreOp",
|
|
278
|
+
"TaskService",
|
|
279
|
+
"TaskSetDobyOp",
|
|
280
|
+
"TaskSetStatusOp",
|
|
281
|
+
"TaskTransferOp",
|
|
282
|
+
"TaskWriteBodyOp",
|
|
283
|
+
"Timestamp",
|
|
284
|
+
"Uid",
|
|
285
|
+
"UndoEntry",
|
|
286
|
+
"UndoKind",
|
|
287
|
+
"UndoOutcome",
|
|
288
|
+
"UndoStack",
|
|
289
|
+
"Uuid7",
|
|
290
|
+
"after",
|
|
291
|
+
"append_note",
|
|
292
|
+
"arbitrate",
|
|
293
|
+
"arbitrate_conflict",
|
|
294
|
+
"before",
|
|
295
|
+
"changed_fields",
|
|
296
|
+
"content_hash",
|
|
297
|
+
"deleted_at",
|
|
298
|
+
"error_for",
|
|
299
|
+
"expired",
|
|
300
|
+
"field_payload",
|
|
301
|
+
"fields_payload",
|
|
302
|
+
"find_note_block",
|
|
303
|
+
"format_date",
|
|
304
|
+
"format_duration",
|
|
305
|
+
"format_note_block",
|
|
306
|
+
"format_rfc3339",
|
|
307
|
+
"format_task_id",
|
|
308
|
+
"group_edits",
|
|
309
|
+
"healed_ranks",
|
|
310
|
+
"history_of",
|
|
311
|
+
"is_valid_slug",
|
|
312
|
+
"journaled",
|
|
313
|
+
"last_body_hash",
|
|
314
|
+
"last_known",
|
|
315
|
+
"last_known_task",
|
|
316
|
+
"merge_journal_lines",
|
|
317
|
+
"needs_rank_heal",
|
|
318
|
+
"new_op_id",
|
|
319
|
+
"new_uid",
|
|
320
|
+
"newest",
|
|
321
|
+
"normalize",
|
|
322
|
+
"now",
|
|
323
|
+
"orphans",
|
|
324
|
+
"parse_date",
|
|
325
|
+
"parse_duration",
|
|
326
|
+
"parse_operation",
|
|
327
|
+
"parse_rfc3339",
|
|
328
|
+
"parse_task_id",
|
|
329
|
+
"position_for_rank",
|
|
330
|
+
"project_of",
|
|
331
|
+
"rank_between",
|
|
332
|
+
"rank_for_position",
|
|
333
|
+
"remove_note_block",
|
|
334
|
+
"rewrite_asset_links",
|
|
335
|
+
"sort_key",
|
|
336
|
+
"sort_siblings",
|
|
337
|
+
"unknown_statuses",
|
|
338
|
+
"validate_slug",
|
|
339
|
+
]
|