feat(jobs): leisure ticks name the day's diary and journal, two own hours stay empty and carry an echo
This commit is contained in:
1 parent
dea793dbd7
commit
eba0cc6168
2 files changed
+196
-14
No files matched your search
+113
-14
@@ -6,19 +6,26 @@
|
|||||||
`мета/бобер/досуг.md` и пишется моделью; механизм его только читает, поэтому
|
`мета/бобер/досуг.md` и пишется моделью; механизм его только читает, поэтому
|
||||||
переписать друг друга они не могут. Сколько досуговых слотов израсходовано,
|
переписать друг друга они не могут. Сколько досуговых слотов израсходовано,
|
||||||
считается из плана и текущего часа, а не из журнала - хранить нечего.
|
считается из плана и текущего часа, а не из журнала - хранить нечего.
|
||||||
|
|
||||||
|
Два своих часа в сутках пустые: дела в них не заходят вовсе, и к тику
|
||||||
|
приложено эхо - абзац из его же старого дневника или из мыслей Бобра. Тик
|
||||||
|
называет и файлы дня: дневник для того, что он думает, журнал для следов.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import UTC, datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from beaver_agent.vault import LEISURE, VAULT, ZONE, read
|
from beaver_agent.vault import JOURNAL, LEISURE, OWN_DIARY, THOUGHTS, VAULT, ZONE, read
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from beaver_gateway.jobs.scheduler import JobRun
|
from beaver_gateway.jobs.scheduler import JobRun
|
||||||
from beaver_gateway.storage.models import Conversation
|
from beaver_gateway.storage.models import Conversation
|
||||||
|
|
||||||
@@ -32,6 +39,15 @@ QUIET_TICKS = (4, 6, 8, 10)
|
|||||||
MINE_DEFAULT = (13, 15, 18, 21, 0, 2, 6)
|
MINE_DEFAULT = (13, 15, 18, 21, 0, 2, 6)
|
||||||
MINE_MIN = 7
|
MINE_MIN = 7
|
||||||
"""Меньше семи чистых слотов в сутках не бывает: недостающие механизм доберёт."""
|
"""Меньше семи чистых слотов в сутках не бывает: недостающие механизм доберёт."""
|
||||||
|
EMPTY_DEFAULT = (18, 0)
|
||||||
|
EMPTY_MIN = 2
|
||||||
|
"""Столько своих часов в сутках пустые; не названы в плане - механизм выберет."""
|
||||||
|
|
||||||
|
ECHO_MIN, ECHO_MAX = 200, 1200
|
||||||
|
"""Абзац для эха: короче - реплика без мысли, длиннее - уже чтение, а не повод.
|
||||||
|
Абзац с кодом или путём в бэктиках - рабочая запись, эхом он не становится."""
|
||||||
|
ECHO_AGE = timedelta(days=7)
|
||||||
|
"""Свой дневник отзывается эхом не раньше, чем через неделю."""
|
||||||
|
|
||||||
BUSY_RETRY = timedelta(minutes=5)
|
BUSY_RETRY = timedelta(minutes=5)
|
||||||
BUSY_ATTEMPTS = 3
|
BUSY_ATTEMPTS = 3
|
||||||
@@ -66,19 +82,34 @@ SWITCH_RE = re.compile(r"^досуг:\s*(\S+)", re.IGNORECASE)
|
|||||||
DAY_RE = re.compile(r"^сутки:\s*(\d{4}-\d{2}-\d{2})", re.IGNORECASE)
|
DAY_RE = re.compile(r"^сутки:\s*(\d{4}-\d{2}-\d{2})", re.IGNORECASE)
|
||||||
QUIET_RE = re.compile(r"^тихие часы:\s*(.+)", re.IGNORECASE)
|
QUIET_RE = re.compile(r"^тихие часы:\s*(.+)", re.IGNORECASE)
|
||||||
MINE_RE = re.compile(r"^мои часы:\s*(.+)", re.IGNORECASE)
|
MINE_RE = re.compile(r"^мои часы:\s*(.+)", re.IGNORECASE)
|
||||||
|
EMPTY_RE = re.compile(r"^пустые часы:\s*(.+)", re.IGNORECASE)
|
||||||
OFF = ("выкл", "off", "нет", "0", "false")
|
OFF = ("выкл", "off", "нет", "0", "false")
|
||||||
|
|
||||||
NO_PLAN = (
|
NO_PLAN = (
|
||||||
"Расписания на эти сутки нет - работаю по умолчанию. Разложи часы и запиши "
|
"Расписания на эти сутки нет - работаю по умолчанию. Разложи часы и запиши "
|
||||||
"в `{path}` строками «сутки:», «тихие часы:», «мои часы:»."
|
"в `{path}` строками «сутки:», «тихие часы:», «мои часы:», «пустые часы:»."
|
||||||
)
|
)
|
||||||
PLANNING = (
|
PLANNING = (
|
||||||
"Первый тик суток. Разложи оставшиеся часы: какие твои, какие смешанные, "
|
"Первый тик суток. Разложи оставшиеся часы: какие твои, какие смешанные, "
|
||||||
"какие четыре часа тихого окна оставить. Запиши в `{path}` строками "
|
"какие четыре часа тихого окна оставить. Запиши в `{path}` строками "
|
||||||
"«сутки:», «тихие часы:», «мои часы:». Своих - минимум {min}; запишешь "
|
"«сутки:», «тихие часы:», «мои часы:», «пустые часы:». Своих - минимум {min}, "
|
||||||
"меньше - механизм доберёт сам."
|
"из них пустых - {empty}; запишешь меньше - механизм доберёт сам."
|
||||||
)
|
)
|
||||||
TAIL = "Задания нет. Чем занять час - решаешь ты, «ничем» - тоже ответ."
|
TAIL = "Задания нет. Чем занять час - решаешь ты, «ничем» - тоже ответ."
|
||||||
|
KINDS = {
|
||||||
|
"mine": "досуг - час твой, дела Бобра сюда не лезут.",
|
||||||
|
"mixed": "смешанный - можно и своё, и его.",
|
||||||
|
"empty": (
|
||||||
|
"пустой - час без дел вообще: прод, логи, хвосты, зонды и разбор "
|
||||||
|
"промахов сюда не заходят, ни его, ни твои. Думать, читать, писать - "
|
||||||
|
"или ничего."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
FILES = (
|
||||||
|
"Дневник - `{diary}`: то, что ты думаешь, и только оно. След тика, логи "
|
||||||
|
"и промахи - строкой в `{journal}`."
|
||||||
|
)
|
||||||
|
ECHO = "Эхо - `{path}`:\n{quote}\nЭто повод, не задание: можно спорить, можно мимо."
|
||||||
PLAN_PATH = str(LEISURE.relative_to(VAULT))
|
PLAN_PATH = str(LEISURE.relative_to(VAULT))
|
||||||
|
|
||||||
|
|
||||||
@@ -91,6 +122,8 @@ class Plan:
|
|||||||
mine: tuple[int, ...]
|
mine: tuple[int, ...]
|
||||||
written: bool
|
written: bool
|
||||||
"""План прочитан из файла на эти сутки; иначе он собран по умолчанию."""
|
"""План прочитан из файла на эти сутки; иначе он собран по умолчанию."""
|
||||||
|
empty: tuple[int, ...] = ()
|
||||||
|
"""Свои часы, в которые дела не заходят вовсе; всегда часть ``mine``."""
|
||||||
|
|
||||||
|
|
||||||
def quiet_hour(hour: int) -> bool:
|
def quiet_hour(hour: int) -> bool:
|
||||||
@@ -135,13 +168,16 @@ def enabled(text: str | None) -> bool:
|
|||||||
def plan_of(text: str | None, day: str) -> Plan:
|
def plan_of(text: str | None, day: str) -> Plan:
|
||||||
"""План на ``day`` из файла; чего нет или что не сходится - берётся дефолт."""
|
"""План на ``day`` из файла; чего нет или что не сходится - берётся дефолт."""
|
||||||
quiet, mine, written = QUIET_TICKS, MINE_DEFAULT, False
|
quiet, mine, written = QUIET_TICKS, MINE_DEFAULT, False
|
||||||
|
empty: tuple[int, ...] = ()
|
||||||
if text is not None and _first(text, DAY_RE) == day:
|
if text is not None and _first(text, DAY_RE) == day:
|
||||||
written = True
|
written = True
|
||||||
raw_quiet = _first(text, QUIET_RE)
|
raw_quiet = _first(text, QUIET_RE)
|
||||||
raw_mine = _first(text, MINE_RE)
|
raw_mine = _first(text, MINE_RE)
|
||||||
|
raw_empty = _first(text, EMPTY_RE)
|
||||||
named = _hours(raw_quiet) if raw_quiet else ()
|
named = _hours(raw_quiet) if raw_quiet else ()
|
||||||
quiet = tuple(h for h in named if quiet_hour(h)) or QUIET_TICKS
|
quiet = tuple(h for h in named if quiet_hour(h)) or QUIET_TICKS
|
||||||
mine = _hours(raw_mine) if raw_mine else ()
|
mine = _hours(raw_mine) if raw_mine else ()
|
||||||
|
empty = _hours(raw_empty) if raw_empty else ()
|
||||||
ticks = tick_hours(quiet)
|
ticks = tick_hours(quiet)
|
||||||
kept = [h for h in mine if h in ticks]
|
kept = [h for h in mine if h in ticks]
|
||||||
for candidate in (*MINE_DEFAULT, *ticks):
|
for candidate in (*MINE_DEFAULT, *ticks):
|
||||||
@@ -150,11 +186,18 @@ def plan_of(text: str | None, day: str) -> Plan:
|
|||||||
if candidate in ticks and candidate not in kept:
|
if candidate in ticks and candidate not in kept:
|
||||||
kept.append(candidate)
|
kept.append(candidate)
|
||||||
order = {h: i for i, h in enumerate(ticks)}
|
order = {h: i for i, h in enumerate(ticks)}
|
||||||
|
hollow = [h for h in empty if h in kept]
|
||||||
|
for candidate in (*EMPTY_DEFAULT, *reversed(kept)):
|
||||||
|
if len(hollow) >= EMPTY_MIN:
|
||||||
|
break
|
||||||
|
if candidate in kept and candidate not in hollow:
|
||||||
|
hollow.append(candidate)
|
||||||
return Plan(
|
return Plan(
|
||||||
day=day,
|
day=day,
|
||||||
quiet=quiet,
|
quiet=quiet,
|
||||||
mine=tuple(sorted(kept, key=lambda h: order[h])),
|
mine=tuple(sorted(kept, key=lambda h: order[h])),
|
||||||
written=written,
|
written=written,
|
||||||
|
empty=tuple(sorted(hollow, key=lambda h: order[h])),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -173,7 +216,50 @@ def _left(now: datetime) -> str:
|
|||||||
return f"Сутки заканчиваются через {hours} ч (в {end:%H:%M %d.%m})."
|
return f"Сутки заканчиваются через {hours} ч (в {end:%H:%M %d.%m})."
|
||||||
|
|
||||||
|
|
||||||
def brief(now: datetime, plan: Plan) -> str:
|
def kind_of(hour: int, plan: Plan) -> str:
|
||||||
|
if hour in plan.empty:
|
||||||
|
return "empty"
|
||||||
|
return "mine" if hour in plan.mine else "mixed"
|
||||||
|
|
||||||
|
|
||||||
|
def echo(
|
||||||
|
now: datetime, *, diary: Path = OWN_DIARY, thoughts: Path = THOUGHTS
|
||||||
|
) -> tuple[Path, str] | None:
|
||||||
|
"""Абзац для пустого часа: из своего старого дневника или из мыслей Бобра.
|
||||||
|
|
||||||
|
Выбор случаен, но привязан к часу тика: тот же тик даёт то же эхо.
|
||||||
|
"""
|
||||||
|
stale = f"{(now - ECHO_AGE):%Y-%m-%d}.md"
|
||||||
|
own = [p for p in diary.glob("????-??-??.md") if p.name < stale]
|
||||||
|
files = sorted([*own, *thoughts.rglob("*.md")])
|
||||||
|
rng = random.Random(f"{now:%Y-%m-%d %H}") # noqa: S311
|
||||||
|
rng.shuffle(files)
|
||||||
|
for path in files:
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
found = [
|
||||||
|
block.strip()
|
||||||
|
for block in re.split(r"\n\s*\n", text)
|
||||||
|
if ECHO_MIN <= len(block.strip()) <= ECHO_MAX
|
||||||
|
and block.lstrip()[:1] not in "#->|"
|
||||||
|
and "`" not in block
|
||||||
|
]
|
||||||
|
if found:
|
||||||
|
return path, rng.choice(found)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _files(now: datetime) -> str:
|
||||||
|
name = f"{now:%Y-%m-%d}.md"
|
||||||
|
return FILES.format(
|
||||||
|
diary=(OWN_DIARY / name).relative_to(VAULT),
|
||||||
|
journal=(JOURNAL / name).relative_to(VAULT),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def brief(now: datetime, plan: Plan, *, heard: tuple[Path, str] | None = None) -> str:
|
||||||
"""Текст инжекта: конверта в таком тёрне нет, время несёт он сам."""
|
"""Текст инжекта: конверта в таком тёрне нет, время несёт он сам."""
|
||||||
hour = now.hour
|
hour = now.hour
|
||||||
ticks = tick_hours(plan.quiet)
|
ticks = tick_hours(plan.quiet)
|
||||||
@@ -183,12 +269,7 @@ def brief(now: datetime, plan: Plan) -> str:
|
|||||||
spent = [h for h in plan.mine if order[h] < here]
|
spent = [h for h in plan.mine if order[h] < here]
|
||||||
ahead = [h for h in plan.mine if order[h] > here]
|
ahead = [h for h in plan.mine if order[h] > here]
|
||||||
lines = [
|
lines = [
|
||||||
f"Тик {_stamp(now)}. Тип: {'досуг' if mine else 'смешанный'}"
|
f"Тик {_stamp(now)}. Тип: {KINDS[kind_of(hour, plan)]}",
|
||||||
+ (
|
|
||||||
" - час твой, дела Бобра сюда не лезут."
|
|
||||||
if mine
|
|
||||||
else " - можно и своё, и его."
|
|
||||||
),
|
|
||||||
_left(now),
|
_left(now),
|
||||||
"Мои часы: "
|
"Мои часы: "
|
||||||
+ ", ".join(f"{h:02d}" for h in plan.mine)
|
+ ", ".join(f"{h:02d}" for h in plan.mine)
|
||||||
@@ -198,6 +279,11 @@ def brief(now: datetime, plan: Plan) -> str:
|
|||||||
if ahead
|
if ahead
|
||||||
else ", впереди больше нет"
|
else ", впереди больше нет"
|
||||||
)
|
)
|
||||||
|
+ (
|
||||||
|
". Пустые: " + ", ".join(f"{h:02d}" for h in plan.empty)
|
||||||
|
if plan.empty
|
||||||
|
else ""
|
||||||
|
)
|
||||||
+ ".",
|
+ ".",
|
||||||
f"Тихое окно {QUIET_FROM:02d}:00-{QUIET_UNTIL - 1:02d}:59, тикают "
|
f"Тихое окно {QUIET_FROM:02d}:00-{QUIET_UNTIL - 1:02d}:59, тикают "
|
||||||
+ ", ".join(f"{h:02d}" for h in plan.quiet)
|
+ ", ".join(f"{h:02d}" for h in plan.quiet)
|
||||||
@@ -206,7 +292,16 @@ def brief(now: datetime, plan: Plan) -> str:
|
|||||||
if not plan.written:
|
if not plan.written:
|
||||||
lines.append(NO_PLAN.format(path=PLAN_PATH))
|
lines.append(NO_PLAN.format(path=PLAN_PATH))
|
||||||
if here == 0:
|
if here == 0:
|
||||||
lines.append(PLANNING.format(path=PLAN_PATH, min=MINE_MIN))
|
lines.append(PLANNING.format(path=PLAN_PATH, min=MINE_MIN, empty=EMPTY_MIN))
|
||||||
|
lines.append(_files(now))
|
||||||
|
if heard is not None:
|
||||||
|
path, quote = heard
|
||||||
|
lines.append(
|
||||||
|
ECHO.format(
|
||||||
|
path=path.relative_to(VAULT),
|
||||||
|
quote="\n".join(f"> {line}" for line in quote.splitlines()),
|
||||||
|
)
|
||||||
|
)
|
||||||
lines.append(TAIL)
|
lines.append(TAIL)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
@@ -268,12 +363,16 @@ async def tick(run: JobRun) -> None:
|
|||||||
_log.info("досуг: мастер занят, сдвиг на %s (попытка %s)", BUSY_RETRY, attempt)
|
_log.info("досуг: мастер занят, сдвиг на %s (попытка %s)", BUSY_RETRY, attempt)
|
||||||
return
|
return
|
||||||
riding = await _riding(run, master, now)
|
riding = await _riding(run, master, now)
|
||||||
|
kind = kind_of(now.hour, plan)
|
||||||
|
heard = echo(now) if kind == "empty" else None
|
||||||
await run.inject_master(
|
await run.inject_master(
|
||||||
brief(now, plan), urgency="normal" if riding else "wake", origin="досуг"
|
brief(now, plan, heard=heard),
|
||||||
|
urgency="normal" if riding else "wake",
|
||||||
|
origin="досуг",
|
||||||
)
|
)
|
||||||
_log.info(
|
_log.info(
|
||||||
"досуг: тик %02d:00 (%s), слияние с отложенным инжектом: %s",
|
"досуг: тик %02d:00 (%s), слияние с отложенным инжектом: %s",
|
||||||
now.hour,
|
now.hour,
|
||||||
"досуг" if now.hour in plan.mine else "смешанный",
|
kind,
|
||||||
riding,
|
riding,
|
||||||
)
|
)
|
||||||
@@ -1,17 +1,22 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from beaver_agent.jobs.leisure import (
|
from beaver_agent.jobs.leisure import (
|
||||||
|
EMPTY_DEFAULT,
|
||||||
MINE_MIN,
|
MINE_MIN,
|
||||||
QUIET_TICKS,
|
QUIET_TICKS,
|
||||||
Plan,
|
Plan,
|
||||||
brief,
|
brief,
|
||||||
day_of,
|
day_of,
|
||||||
|
echo,
|
||||||
enabled,
|
enabled,
|
||||||
|
kind_of,
|
||||||
plan_of,
|
plan_of,
|
||||||
quiet_hour,
|
quiet_hour,
|
||||||
tick_hours,
|
tick_hours,
|
||||||
)
|
)
|
||||||
|
from beaver_agent.vault import VAULT
|
||||||
|
|
||||||
ZONE = ZoneInfo("Europe/Warsaw")
|
ZONE = ZoneInfo("Europe/Warsaw")
|
||||||
|
|
||||||
@@ -121,3 +126,81 @@ def test_last_night_tick_says_nothing_is_left_ahead():
|
|||||||
assert "впереди больше нет" in text
|
assert "впереди больше нет" in text
|
||||||
assert "израсходовано 2 из 2" in text
|
assert "израсходовано 2 из 2" in text
|
||||||
assert "Сутки заканчиваются через 6 ч" in text
|
assert "Сутки заканчиваются через 6 ч" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_two_of_my_hours_are_empty_named_or_not():
|
||||||
|
named = plan_of(
|
||||||
|
"сутки: 2026-09-08\nмои часы: 13, 15, 18, 21, 00, 02, 06\n"
|
||||||
|
"пустые часы: 21, 02\n",
|
||||||
|
"2026-09-08",
|
||||||
|
)
|
||||||
|
assert named.empty == (21, 2)
|
||||||
|
|
||||||
|
unnamed = plan_of(None, "2026-09-08")
|
||||||
|
assert unnamed.empty == EMPTY_DEFAULT
|
||||||
|
assert set(unnamed.empty) <= set(unnamed.mine)
|
||||||
|
|
||||||
|
foreign = plan_of(
|
||||||
|
"сутки: 2026-09-08\nмои часы: 13, 14, 15, 16, 17, 19, 20\n"
|
||||||
|
"пустые часы: 22, 20\n",
|
||||||
|
"2026-09-08",
|
||||||
|
)
|
||||||
|
assert foreign.empty == (19, 20)
|
||||||
|
assert kind_of(20, foreign) == "empty"
|
||||||
|
assert kind_of(13, foreign) == "mine"
|
||||||
|
assert kind_of(21, foreign) == "mixed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_empty_hour_carries_no_work_but_an_echo_and_the_files_of_the_day():
|
||||||
|
plan = plan_of(None, "2026-09-08")
|
||||||
|
heard = (VAULT / "мета" / "бобер" / "дневник" / "2026-08-30.md", "Раз.\nДва.")
|
||||||
|
text = brief(at("2026-09-08 18:00"), plan, heard=heard)
|
||||||
|
assert "Тип: пустой - час без дел вообще" in text
|
||||||
|
assert "Пустые: 18, 00." in text
|
||||||
|
assert "Эхо - `мета/бобер/дневник/2026-08-30.md`:\n> Раз.\n> Два.\n" in text
|
||||||
|
assert "Дневник - `мета/бобер/дневник/2026-09-08.md`" in text
|
||||||
|
assert "`мета/бобер/журнал/2026-09-08.md`" in text
|
||||||
|
|
||||||
|
night = brief(at("2026-09-09 00:00"), plan)
|
||||||
|
assert "Тип: пустой" in night
|
||||||
|
assert "мета/бобер/дневник/2026-09-09.md" in night
|
||||||
|
assert "Эхо" not in night
|
||||||
|
|
||||||
|
mine = brief(at("2026-09-08 13:00"), plan)
|
||||||
|
assert "Тип: досуг" in mine
|
||||||
|
assert "мета/бобер/журнал/2026-09-08.md" in mine
|
||||||
|
|
||||||
|
|
||||||
|
def test_echo_is_a_paragraph_of_an_old_diary_day_or_a_thought(tmp_path: Path):
|
||||||
|
diary = tmp_path / "дневник"
|
||||||
|
thoughts = tmp_path / "мысли" / "2026"
|
||||||
|
diary.mkdir()
|
||||||
|
thoughts.mkdir(parents=True)
|
||||||
|
old = "Думал про то, зачем мне вообще помнить вчерашний день. " * 5
|
||||||
|
fresh = "Свежая запись, которой ещё рано возвращаться ко мне эхом. " * 5
|
||||||
|
(diary / "2026-08-30.md").write_text(
|
||||||
|
f"## 2026-08-30, 01:07\n\n{old}\n\n- 02:07 30.08 лог, а не мысль {'x' * 220}\n"
|
||||||
|
"\nкоротко\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(diary / "2026-09-07.md").write_text(fresh, encoding="utf-8")
|
||||||
|
now = at("2026-09-08 18:00")
|
||||||
|
assert echo(now, diary=diary, thoughts=thoughts) == (
|
||||||
|
diary / "2026-08-30.md",
|
||||||
|
old.strip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
thought = "Заметка Бобра о том, почему скука полезнее расписания. " * 5
|
||||||
|
(diary / "2026-08-30.md").unlink()
|
||||||
|
(thoughts / "скука.md").write_text(
|
||||||
|
f"---\ntags: [мысли]\n---\n\n{thought}\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
assert echo(now, diary=diary, thoughts=thoughts) == (
|
||||||
|
thoughts / "скука.md",
|
||||||
|
thought.strip(),
|
||||||
|
)
|
||||||
|
assert echo(now, diary=diary, thoughts=thoughts) == echo(
|
||||||
|
now, diary=diary, thoughts=thoughts
|
||||||
|
)
|
||||||
|
(thoughts / "скука.md").unlink()
|
||||||
|
assert echo(now, diary=diary, thoughts=thoughts) is None
|
||||||
Reference in new issue
Block a user