feat(diary): restore diary_start_week_day via configurable weekly naming
c63ec67 dropped diary_start_week_day, hardwiring the weekly diary to ISO
(Monday) weeks. Upstream vimwiki instead names a weekly note by the
week-start day's date (YYYY-MM-DD) and honours diary_start_week_day. Rather
than force one scheme, make it a per-wiki choice so migrators keep upstream
behaviour while existing nuwiki weekly files keep working:
- New per-wiki key `diary_weekly_style`: `iso` (default — `YYYY-Www`,
Monday-based, nuwiki's original) or `date`/`vimwiki` (`YYYY-MM-DD` of the
week-start day, upstream parity).
- Restored per-wiki key `diary_start_week_day` (`monday`..`sunday`, default
monday); applies only in `date` mode.
Implementation:
- nuwiki-core::date gains WeeklyStyle, WeekStart, and DiaryCalendar (owns
today/next/prev — date-mode snaps to the week-start and steps ±7 days;
iso/daily/monthly/yearly defer to the existing DiaryPeriod logic).
- WikiConfig gains the two fields (+ defaults, RawWiki, From) and a
diary_calendar() builder; commands.rs diary_open_relative and the
next/prev pivot use it.
Defaults preserve current behaviour (iso/monday), so no breaking change.
Tests: DiaryCalendar cases in nuwiki-core/tests/diary.rs (snap, ±7, sunday
start, iso delegation, daily) + config round-trip in
nuwiki-lsp/tests/index_and_config.rs. README + doc/nuwiki.txt + lua config
comment updated. fmt + clippy clean; all crate tests + config-parity green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -358,6 +358,132 @@ impl fmt::Display for DiaryPeriod {
|
||||
}
|
||||
}
|
||||
|
||||
/// How a *weekly* diary entry is named.
|
||||
///
|
||||
/// `Iso` — ISO-week label `YYYY-Www` (nuwiki's original scheme; the week
|
||||
/// always begins on Monday, `week_start` is ignored).
|
||||
/// `Date` — the week-start day's calendar date `YYYY-MM-DD`, matching
|
||||
/// upstream vimwiki; honours `week_start`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WeeklyStyle {
|
||||
Iso,
|
||||
Date,
|
||||
}
|
||||
|
||||
impl WeeklyStyle {
|
||||
/// Parse the config string. `date`/`vimwiki` → `Date`; anything else
|
||||
/// (including `iso`/`week`/empty) → `Iso`.
|
||||
pub fn parse(s: &str) -> Self {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"date" | "vimwiki" | "day" => Self::Date,
|
||||
_ => Self::Iso,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Day a week begins on, in ISO numbering (Monday = 1 … Sunday = 7).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct WeekStart(u8);
|
||||
|
||||
impl WeekStart {
|
||||
/// Parse a weekday name (`monday`..`sunday`); unknown → Monday.
|
||||
pub fn parse(s: &str) -> Self {
|
||||
let n = match s.trim().to_ascii_lowercase().as_str() {
|
||||
"tuesday" => 2,
|
||||
"wednesday" => 3,
|
||||
"thursday" => 4,
|
||||
"friday" => 5,
|
||||
"saturday" => 6,
|
||||
"sunday" => 7,
|
||||
_ => 1, // monday (default)
|
||||
};
|
||||
Self(n)
|
||||
}
|
||||
|
||||
pub fn monday() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
|
||||
fn iso(self) -> i64 {
|
||||
self.0 as i64
|
||||
}
|
||||
}
|
||||
|
||||
/// Diary navigation policy: frequency plus, for weekly diaries, the naming
|
||||
/// scheme and week-start day. Built from a wiki's `diary_*` config so the
|
||||
/// LSP commands can compute today / next / prev without re-deriving the
|
||||
/// rules. `Daily`/`Monthly`/`Yearly` ignore the weekly fields.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DiaryCalendar {
|
||||
pub freq: DiaryFrequency,
|
||||
pub weekly_style: WeeklyStyle,
|
||||
pub week_start: WeekStart,
|
||||
}
|
||||
|
||||
impl DiaryCalendar {
|
||||
pub fn new(freq: DiaryFrequency, weekly_style: WeeklyStyle, week_start: WeekStart) -> Self {
|
||||
Self {
|
||||
freq,
|
||||
weekly_style,
|
||||
week_start,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_weekly_date(&self) -> bool {
|
||||
self.freq == DiaryFrequency::Weekly && self.weekly_style == WeeklyStyle::Date
|
||||
}
|
||||
|
||||
/// "Now" as a diary period for this calendar. Date-mode weekly snaps
|
||||
/// today back to the most recent week-start day (a plain `Day`, so the
|
||||
/// file is `YYYY-MM-DD` like upstream); everything else defers to
|
||||
/// [`DiaryPeriod::today_utc`].
|
||||
pub fn today(&self) -> DiaryPeriod {
|
||||
if self.is_weekly_date() {
|
||||
DiaryPeriod::Day(self.week_start_of(DiaryDate::today_utc()))
|
||||
} else {
|
||||
DiaryPeriod::today_utc(self.freq)
|
||||
}
|
||||
}
|
||||
|
||||
/// The period one cadence-step after `p`.
|
||||
pub fn next(&self, p: DiaryPeriod) -> DiaryPeriod {
|
||||
self.step(p, 1)
|
||||
}
|
||||
|
||||
/// The period one cadence-step before `p`.
|
||||
pub fn prev(&self, p: DiaryPeriod) -> DiaryPeriod {
|
||||
self.step(p, -1)
|
||||
}
|
||||
|
||||
fn step(&self, p: DiaryPeriod, dir: i64) -> DiaryPeriod {
|
||||
// Date-mode weekly: the period is a `Day` on a week-start; a step is
|
||||
// ±7 days (re-snapped so an off-week-start pivot still lands cleanly).
|
||||
if self.is_weekly_date() {
|
||||
if let DiaryPeriod::Day(d) = p {
|
||||
let start = self.week_start_of(d);
|
||||
return DiaryPeriod::Day(add_days(start, 7 * dir));
|
||||
}
|
||||
}
|
||||
if dir >= 0 {
|
||||
p.next()
|
||||
} else {
|
||||
p.prev()
|
||||
}
|
||||
}
|
||||
|
||||
/// The most recent `week_start` weekday on or before `d`.
|
||||
fn week_start_of(&self, d: DiaryDate) -> DiaryDate {
|
||||
let wd = iso_weekday(&d) as i64; // 1..=7
|
||||
let back = (wd - self.week_start.iso()).rem_euclid(7);
|
||||
add_days(d, -back)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shift a calendar date by `delta` days (may be negative).
|
||||
fn add_days(d: DiaryDate, delta: i64) -> DiaryDate {
|
||||
from_days(to_days(d.year, d.month, d.day) + delta)
|
||||
}
|
||||
|
||||
/// Day-of-week with Monday=1 … Sunday=7 (ISO 8601).
|
||||
fn iso_weekday(d: &DiaryDate) -> u8 {
|
||||
// Zeller-style via days-from-epoch: 1970-01-01 was a Thursday.
|
||||
|
||||
Reference in New Issue
Block a user