|
| 1 | +# Spec: Consolidate Template and Date Replacement Logic (DRY) — #211 |
| 2 | + |
| 3 | +## Goal |
| 4 | +Extract duplicated `${variable}` template resolution into a single `TemplateEngine` service so that adding or changing a template variable requires editing exactly one place. |
| 5 | + |
| 6 | +## Why now |
| 7 | +feat-210 (rich domain models) is in progress. Once its types land on `develop`, a `TemplateEngine` becomes a natural next refactoring step — the timing avoids double-churn on `src/model/`. |
| 8 | + |
| 9 | +## In scope |
| 10 | +- Extract `replaceDateFormats` and `replaceDateTemplatesWithMomentsFormats` from `src/util/dates.ts` into a new `TemplateEngine` module. |
| 11 | +- Replace the two parallel switch/case blocks with a single central variable map (`TEMPLATE_VARIABLE_MAP`) that holds both the moment format string and resolution logic per variable. |
| 12 | +- Expose two public functions from the engine: |
| 13 | + - `resolveDate(template, date, locale?)` — replaces `${var}` with actual formatted values (replaces `replaceDateFormats`) |
| 14 | + - `toMomentFormat(template)` — replaces `${var}` with moment format tokens (replaces `replaceDateTemplatesWithMomentsFormats`) |
| 15 | +- Delete the duplicate implementation in `src/test/direct/path-parse-with-date.ts:124`. |
| 16 | +- Update all callers: `src/journal/paths.ts`, `src/vscode/conf.ts`, `src/features/sync/sync-daily-links.ts`. |
| 17 | +- Keep `replaceVariableValue` in `src/util/strings.ts` (generic key/value; not template-engine-specific). |
| 18 | +- Update barrel exports in `src/util/index.ts`. |
| 19 | + |
| 20 | +## Out of scope |
| 21 | +- Changing the set of supported template variables (no new variables in this PR). |
| 22 | +- Replacing `replaceVariableValue` with the engine. |
| 23 | +- Any changes to `src/vscode/conf.ts` beyond updating the import path. |
| 24 | +- feat-210 rich domain model changes. |
| 25 | + |
| 26 | +## Acceptance criteria |
| 27 | +1. `replaceDateFormats` and `replaceDateTemplatesWithMomentsFormats` no longer exist in `src/util/dates.ts`. |
| 28 | +2. Adding a new template variable requires editing only `TEMPLATE_VARIABLE_MAP` (one location). |
| 29 | +3. All existing tests pass without modification to test assertions. |
| 30 | +4. The duplicate in `src/test/direct/path-parse-with-date.ts` is removed; that file imports from the engine instead. |
| 31 | +5. `npm run compile` (or equivalent) succeeds. |
| 32 | +6. No behavior change — output of `resolveDate` matches the old `replaceDateFormats` output for all supported variables. |
| 33 | + |
| 34 | +## Entities / contracts |
| 35 | + |
| 36 | +### TEMPLATE_VARIABLE_MAP |
| 37 | +```typescript |
| 38 | +// Central map — the single source of truth |
| 39 | +interface TemplateVariableEntry { |
| 40 | + momentFormat: string; // used by toMomentFormat |
| 41 | +} |
| 42 | +const TEMPLATE_VARIABLE_MAP: Record<string, TemplateVariableEntry> = { |
| 43 | + year: { momentFormat: 'YYYY' }, |
| 44 | + month: { momentFormat: 'MM' }, |
| 45 | + day: { momentFormat: 'DD' }, |
| 46 | + localTime: { momentFormat: 'LT' }, |
| 47 | + localDate: { momentFormat: 'LL' }, |
| 48 | + weekday: { momentFormat: 'dddd' }, |
| 49 | + week: { momentFormat: 'w' }, // only in resolveDate currently; add to toMomentFormat for symmetry |
| 50 | +}; |
| 51 | +``` |
| 52 | + |
| 53 | +### Public API (new module) |
| 54 | +```typescript |
| 55 | +export function resolveDate(template: string, date: Date, locale?: string): string |
| 56 | +export function toMomentFormat(template: string): string |
| 57 | +// regex stays module-private |
| 58 | +const TEMPLATE_VAR_REGEX = /\$\{(?:(year|month|day|localTime|localDate|weekday|week)|(d:[\s\S]+?))\}/g; |
| 59 | +``` |
| 60 | + |
| 61 | +### Module location — OPEN QUESTION |
| 62 | +Issue suggests `src/core/templates/engine.ts` (new `core` layer). No `src/core/` directory exists today. Alternative: `src/util/template-engine.ts` (fits existing structure; minimal disruption). **Preferred: `src/util/template-engine.ts`** unless the author wants to introduce the `core` layer for future domain-pure modules. |
| 63 | + |
| 64 | +## Constraints |
| 65 | +- Moment.js is the only date library in use; stay with it. |
| 66 | +- No new runtime dependencies. |
| 67 | +- Must not break `src/test/direct/replace-variables-in-string.ts` (imports `replaceDateFormats` from `../../util/dates` — update import). |
| 68 | + |
| 69 | +## Open questions |
| 70 | +1. `src/core/templates/engine.ts` vs `src/util/template-engine.ts`? The `core` layer would be a new architectural boundary. Is that intentional for #211 or a future concern? |
| 71 | +2. `week` variable is present in `replaceDateFormats` but absent from `replaceDateTemplatesWithMomentsFormats`. Intentional gap or bug? Should `toMomentFormat` also map `${week}` → `'w'`? |
| 72 | + |
| 73 | +## Related issues |
| 74 | +- Blocks: none |
| 75 | +- Related: #210 (rich domain models — avoid double-churn; implement after feat-210 lands) |
| 76 | +- Related: #209 (domain module naming — completed; barrel keys already aligned) |
0 commit comments