@real1ty-obsidian-plugins/utils 2.3.0 → 2.5.0

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.
Files changed (69) hide show
  1. package/dist/core/evaluator/base.d.ts +22 -0
  2. package/dist/core/evaluator/base.d.ts.map +1 -0
  3. package/dist/core/evaluator/base.js +52 -0
  4. package/dist/core/evaluator/base.js.map +1 -0
  5. package/dist/core/evaluator/color.d.ts +19 -0
  6. package/dist/core/evaluator/color.d.ts.map +1 -0
  7. package/dist/core/evaluator/color.js +25 -0
  8. package/dist/core/evaluator/color.js.map +1 -0
  9. package/dist/core/evaluator/excluded.d.ts +32 -0
  10. package/dist/core/evaluator/excluded.d.ts.map +1 -0
  11. package/dist/core/evaluator/excluded.js +41 -0
  12. package/dist/core/evaluator/excluded.js.map +1 -0
  13. package/dist/core/evaluator/filter.d.ts +15 -0
  14. package/dist/core/evaluator/filter.d.ts.map +1 -0
  15. package/dist/core/evaluator/filter.js +27 -0
  16. package/dist/core/evaluator/filter.js.map +1 -0
  17. package/dist/core/evaluator/included.d.ts +36 -0
  18. package/dist/core/evaluator/included.d.ts.map +1 -0
  19. package/dist/core/evaluator/included.js +51 -0
  20. package/dist/core/evaluator/included.js.map +1 -0
  21. package/dist/core/evaluator/index.d.ts +6 -0
  22. package/dist/core/evaluator/index.d.ts.map +1 -0
  23. package/dist/core/evaluator/index.js +6 -0
  24. package/dist/core/evaluator/index.js.map +1 -0
  25. package/dist/core/expression-utils.d.ts +17 -0
  26. package/dist/core/expression-utils.d.ts.map +1 -0
  27. package/dist/core/expression-utils.js +40 -0
  28. package/dist/core/expression-utils.js.map +1 -0
  29. package/dist/core/index.d.ts +2 -1
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +2 -1
  32. package/dist/core/index.js.map +1 -1
  33. package/package.json +3 -5
  34. package/src/async/async.ts +117 -0
  35. package/src/async/batch-operations.ts +53 -0
  36. package/src/async/index.ts +2 -0
  37. package/src/core/evaluator/base.ts +71 -0
  38. package/src/core/evaluator/color.ts +37 -0
  39. package/src/core/evaluator/excluded.ts +63 -0
  40. package/src/core/evaluator/filter.ts +35 -0
  41. package/src/core/evaluator/included.ts +74 -0
  42. package/src/core/evaluator/index.ts +5 -0
  43. package/src/core/expression-utils.ts +53 -0
  44. package/src/core/generate.ts +22 -0
  45. package/src/core/index.ts +3 -0
  46. package/src/date/date-recurrence.ts +244 -0
  47. package/src/date/date.ts +111 -0
  48. package/src/date/index.ts +2 -0
  49. package/src/file/child-reference.ts +76 -0
  50. package/src/file/file-operations.ts +197 -0
  51. package/src/file/file.ts +570 -0
  52. package/src/file/frontmatter.ts +80 -0
  53. package/src/file/index.ts +6 -0
  54. package/src/file/link-parser.ts +18 -0
  55. package/src/file/templater.ts +75 -0
  56. package/src/index.ts +14 -0
  57. package/src/settings/index.ts +2 -0
  58. package/src/settings/settings-store.ts +88 -0
  59. package/src/settings/settings-ui-builder.ts +507 -0
  60. package/src/string/index.ts +1 -0
  61. package/src/string/string.ts +26 -0
  62. package/src/testing/index.ts +23 -0
  63. package/src/testing/mocks/obsidian.ts +331 -0
  64. package/src/testing/mocks/utils.ts +113 -0
  65. package/src/testing/setup.ts +19 -0
  66. package/dist/core/evaluator-base.d.ts +0 -52
  67. package/dist/core/evaluator-base.d.ts.map +0 -1
  68. package/dist/core/evaluator-base.js +0 -84
  69. package/dist/core/evaluator-base.js.map +0 -1
@@ -0,0 +1,26 @@
1
+ export const capitalize = (str: string): string => {
2
+ if (!str) return str;
3
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
4
+ };
5
+
6
+ export const generateDuplicatedTitle = (originalTitle: string): string => {
7
+ // Check if title already has a counter pattern like " (2)", " - Copy", etc.
8
+ const counterMatch = originalTitle.match(/^(.*?)(?:\s*\((\d+)\)|\s*-?\s*Copy(?:\s*(\d+))?)$/);
9
+
10
+ if (counterMatch) {
11
+ const baseName = counterMatch[1];
12
+ const existingCounter = counterMatch[2] || counterMatch[3];
13
+ const nextCounter = existingCounter ? parseInt(existingCounter, 10) + 1 : 2;
14
+ return `${baseName} (${nextCounter})`;
15
+ } else {
16
+ return `${originalTitle} (2)`;
17
+ }
18
+ };
19
+
20
+ export const pluralize = (count: number): string => {
21
+ return count === 1 ? "" : "s";
22
+ };
23
+
24
+ export const getWeekDirection = (weeks: number): "next" | "previous" => {
25
+ return weeks > 0 ? "next" : "previous";
26
+ };
@@ -0,0 +1,23 @@
1
+ export * from "./mocks/obsidian";
2
+ // Re-export commonly used combinations
3
+ export {
4
+ createMockApp,
5
+ createMockFile,
6
+ createMockFileCache,
7
+ Modal,
8
+ Notice,
9
+ Plugin,
10
+ PluginSettingTab,
11
+ Setting,
12
+ TFile,
13
+ } from "./mocks/obsidian";
14
+ export * from "./mocks/utils";
15
+ export {
16
+ mockFileOperations,
17
+ mockLinkParser,
18
+ resetAllMocks,
19
+ setupDefaultMockImplementations,
20
+ setupMockImplementation,
21
+ setupMockReturnValue,
22
+ verifyMockCalls,
23
+ } from "./mocks/utils";
@@ -0,0 +1,331 @@
1
+ import { vi } from "vitest";
2
+
3
+ // Base Plugin class mock
4
+ export class Plugin {
5
+ app: any;
6
+ manifest: any;
7
+ settings: any;
8
+
9
+ constructor(app: any, manifest: any) {
10
+ this.app = app;
11
+ this.manifest = manifest;
12
+ }
13
+
14
+ // Core plugin methods
15
+ addSettingTab = vi.fn();
16
+ registerEvent = vi.fn();
17
+ loadData = vi.fn().mockResolvedValue({});
18
+ saveData = vi.fn().mockResolvedValue(undefined);
19
+ onload = vi.fn();
20
+ onunload = vi.fn();
21
+
22
+ // UI methods
23
+ addRibbonIcon = vi.fn();
24
+ addStatusBarItem = vi.fn();
25
+ addCommand = vi.fn();
26
+ removeCommand = vi.fn();
27
+
28
+ // Event methods
29
+ registerDomEvent = vi.fn();
30
+ registerCodeMirror = vi.fn();
31
+ registerEditorExtension = vi.fn();
32
+ registerMarkdownPostProcessor = vi.fn();
33
+ registerMarkdownCodeBlockProcessor = vi.fn();
34
+ registerObsidianProtocolHandler = vi.fn();
35
+ registerEditorSuggest = vi.fn();
36
+ registerHoverLinkSource = vi.fn();
37
+
38
+ // Interval methods
39
+ registerInterval = vi.fn();
40
+
41
+ // View and extension methods
42
+ registerView = vi.fn();
43
+ registerExtensions = vi.fn();
44
+
45
+ // Lifecycle methods
46
+ onUserEnable = vi.fn();
47
+ load = vi.fn();
48
+ unload = vi.fn();
49
+
50
+ // Other methods
51
+ addChild = vi.fn();
52
+ removeChild = vi.fn();
53
+ register = vi.fn();
54
+ }
55
+
56
+ // PluginSettingTab mock
57
+ export class PluginSettingTab {
58
+ app: any;
59
+ plugin: any;
60
+ containerEl: HTMLElement;
61
+
62
+ constructor(app: any, plugin: any) {
63
+ this.app = app;
64
+ this.plugin = plugin;
65
+ this.containerEl = document.createElement("div");
66
+ }
67
+
68
+ display = vi.fn();
69
+ }
70
+
71
+ // ItemView mock
72
+ export class ItemView {
73
+ app: any;
74
+ leaf: any;
75
+ containerEl: HTMLElement;
76
+
77
+ constructor(leaf: any) {
78
+ this.leaf = leaf;
79
+ this.app = leaf?.app;
80
+ this.containerEl = document.createElement("div");
81
+ }
82
+
83
+ // Don't override onOpen/onClose - let subclasses implement them
84
+ // These methods are implemented by MountableView mixin
85
+
86
+ // Don't provide default implementations for these methods
87
+ // Let subclasses implement them
88
+ getViewType(): string {
89
+ return "mock-view";
90
+ }
91
+
92
+ getDisplayText(): string {
93
+ return "Mock View";
94
+ }
95
+
96
+ getIcon(): string {
97
+ return "mock-icon";
98
+ }
99
+
100
+ getState = vi.fn().mockReturnValue({});
101
+ setState = vi.fn().mockResolvedValue(undefined);
102
+ }
103
+
104
+ // Setting component mock
105
+ export class Setting {
106
+ settingEl: HTMLElement;
107
+ nameEl: HTMLElement;
108
+ descEl: HTMLElement;
109
+ controlEl: HTMLElement;
110
+
111
+ constructor(_containerEl: HTMLElement) {
112
+ this.settingEl = document.createElement("div");
113
+ this.nameEl = document.createElement("div");
114
+ this.descEl = document.createElement("div");
115
+ this.controlEl = document.createElement("div");
116
+ }
117
+
118
+ setName = vi.fn().mockReturnThis();
119
+ setDesc = vi.fn().mockReturnThis();
120
+ addText = vi.fn().mockReturnThis();
121
+ addTextArea = vi.fn().mockReturnThis();
122
+ }
123
+
124
+ // TFolder mock
125
+ export class TFolder {
126
+ path: string;
127
+ name: string;
128
+ children: any[];
129
+ vault: any;
130
+ parent: TFolder | null;
131
+
132
+ constructor(path: string) {
133
+ this.path = path;
134
+ this.name = path.split("/").pop() || "";
135
+ this.children = [];
136
+ this.vault = {};
137
+ this.parent = null;
138
+ }
139
+
140
+ isRoot(): boolean {
141
+ return this.path === "" || this.path === "/";
142
+ }
143
+ }
144
+
145
+ // TFile mock with full interface
146
+ export class TFile {
147
+ path: string;
148
+ name: string;
149
+ basename: string;
150
+ extension: string;
151
+ stat: any;
152
+ vault: any;
153
+ parent: TFolder | null;
154
+
155
+ constructor(path: string, parentPath?: string) {
156
+ this.path = path;
157
+ this.name = path.split("/").pop() || "";
158
+ this.basename = this.name.replace(/\.[^/.]+$/, ""); // Remove extension
159
+ this.extension = path.split(".").pop() || "md";
160
+ this.stat = {};
161
+ this.vault = {};
162
+
163
+ // Set parent based on path or explicit parentPath
164
+ if (parentPath !== undefined) {
165
+ this.parent = parentPath ? new TFolder(parentPath) : null;
166
+ } else {
167
+ // Derive parent from path
168
+ const lastSlash = path.lastIndexOf("/");
169
+ if (lastSlash > 0) {
170
+ this.parent = new TFolder(path.substring(0, lastSlash));
171
+ } else {
172
+ this.parent = null;
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ // Modal mock
179
+ export class Modal {
180
+ app: any;
181
+ containerEl: HTMLElement;
182
+ titleEl: HTMLElement;
183
+ contentEl: HTMLElement;
184
+
185
+ constructor(app: any) {
186
+ this.app = app;
187
+ this.containerEl = document.createElement("div");
188
+ this.titleEl = document.createElement("div");
189
+ this.contentEl = document.createElement("div");
190
+ }
191
+
192
+ open = vi.fn();
193
+ close = vi.fn();
194
+ onOpen = vi.fn();
195
+ onClose = vi.fn();
196
+ }
197
+
198
+ // Notice mock
199
+ export class Notice {
200
+ constructor(message: string) {
201
+ console.log(`Notice: ${message}`);
202
+ }
203
+ }
204
+
205
+ // MarkdownRenderer mock
206
+ export const MarkdownRenderer = {
207
+ render: vi.fn().mockResolvedValue(undefined),
208
+ };
209
+
210
+ // Debounce function mock
211
+ export function debounce<T extends (...args: any[]) => any>(
212
+ func: T,
213
+ wait: number,
214
+ immediate?: boolean
215
+ ): T {
216
+ let timeout: ReturnType<typeof setTimeout> | null = null;
217
+
218
+ return ((...args: Parameters<T>) => {
219
+ const later = () => {
220
+ timeout = null;
221
+ if (!immediate) func(...args);
222
+ };
223
+
224
+ const callNow = immediate && !timeout;
225
+
226
+ if (timeout !== null) {
227
+ clearTimeout(timeout);
228
+ }
229
+
230
+ timeout = setTimeout(later, wait);
231
+
232
+ if (callNow) func(...args);
233
+ }) as T;
234
+ }
235
+
236
+ // normalizePath mock - simple path normalization for tests
237
+ export function normalizePath(path: string): string {
238
+ // Basic normalization: replace backslashes with forward slashes
239
+ // and remove duplicate slashes
240
+ return path.replace(/\\/g, "/").replace(/\/+/g, "/");
241
+ }
242
+
243
+ // App mock
244
+ export const App = vi.fn();
245
+
246
+ // Mock interfaces for TypeScript
247
+ export interface MockApp {
248
+ fileManager: {
249
+ processFrontMatter: ReturnType<typeof vi.fn>;
250
+ };
251
+ metadataCache: {
252
+ getFileCache: ReturnType<typeof vi.fn>;
253
+ };
254
+ vault: {
255
+ getAbstractFileByPath: ReturnType<typeof vi.fn>;
256
+ on: ReturnType<typeof vi.fn>;
257
+ read: ReturnType<typeof vi.fn>;
258
+ modify: ReturnType<typeof vi.fn>;
259
+ create: ReturnType<typeof vi.fn>;
260
+ delete: ReturnType<typeof vi.fn>;
261
+ rename: ReturnType<typeof vi.fn>;
262
+ getFiles: ReturnType<typeof vi.fn>;
263
+ getMarkdownFiles: ReturnType<typeof vi.fn>;
264
+ getFolderByPath: ReturnType<typeof vi.fn>;
265
+ };
266
+ workspace: {
267
+ getActiveFile: ReturnType<typeof vi.fn>;
268
+ on: ReturnType<typeof vi.fn>;
269
+ };
270
+ }
271
+
272
+ // Helper function to create a fully mocked app
273
+ export function createMockApp(): MockApp {
274
+ return {
275
+ fileManager: {
276
+ processFrontMatter: vi.fn(),
277
+ },
278
+ metadataCache: {
279
+ getFileCache: vi.fn(),
280
+ },
281
+ vault: {
282
+ getAbstractFileByPath: vi.fn(),
283
+ on: vi.fn(),
284
+ read: vi.fn(),
285
+ modify: vi.fn(),
286
+ create: vi.fn(),
287
+ delete: vi.fn(),
288
+ rename: vi.fn(),
289
+ getFiles: vi.fn().mockReturnValue([]),
290
+ getMarkdownFiles: vi.fn().mockReturnValue([]),
291
+ getFolderByPath: vi.fn(),
292
+ },
293
+ workspace: {
294
+ getActiveFile: vi.fn(),
295
+ on: vi.fn(),
296
+ },
297
+ };
298
+ }
299
+
300
+ // Helper to create mock TFile instances
301
+ export function createMockFile(
302
+ path: string,
303
+ options?: {
304
+ basename?: string;
305
+ parentPath?: string;
306
+ extension?: string;
307
+ }
308
+ ): TFile {
309
+ const file = new TFile(path, options?.parentPath);
310
+ if (options?.basename) {
311
+ file.basename = options.basename;
312
+ }
313
+ if (options?.extension) {
314
+ file.extension = options.extension;
315
+ }
316
+ return file;
317
+ }
318
+
319
+ // Helper to create mock file cache
320
+ export function createMockFileCache(frontmatter?: Record<string, any>) {
321
+ return {
322
+ frontmatter: frontmatter || {},
323
+ frontmatterPosition: frontmatter ? { start: { line: 0 }, end: { line: 3 } } : null,
324
+ sections: [],
325
+ headings: [],
326
+ links: [],
327
+ embeds: [],
328
+ tags: [],
329
+ listItems: [],
330
+ };
331
+ }
@@ -0,0 +1,113 @@
1
+ import { expect, vi } from "vitest";
2
+
3
+ // File operations mocks
4
+ export const mockFileOperations = {
5
+ arraysEqual: vi.fn(),
6
+ normalizeArray: vi.fn(),
7
+ createFileLink: vi.fn(),
8
+ };
9
+
10
+ // Link parser mocks
11
+ export const mockLinkParser = {
12
+ extractFilePathFromLink: vi.fn(),
13
+ };
14
+
15
+ // Default mock implementations that match the actual behavior
16
+ export function setupDefaultMockImplementations() {
17
+ // Set up file operations mocks
18
+ mockFileOperations.normalizeArray.mockImplementation((arr) =>
19
+ Array.isArray(arr) ? arr : arr ? [arr] : []
20
+ );
21
+
22
+ mockFileOperations.arraysEqual.mockImplementation(
23
+ (a, b) => JSON.stringify(a) === JSON.stringify(b)
24
+ );
25
+
26
+ mockFileOperations.createFileLink.mockImplementation((file) => {
27
+ if (!file) return "[[Unknown File]]";
28
+
29
+ const basename =
30
+ file.basename ||
31
+ file.path
32
+ ?.split("/")
33
+ .pop()
34
+ ?.replace(/\.[^/.]+$/, "") ||
35
+ "";
36
+ const parentPath = file.parent?.path;
37
+
38
+ if (!parentPath || parentPath === "/" || parentPath === "") {
39
+ return `[[${basename}]]`;
40
+ }
41
+
42
+ return `[[${parentPath}/${basename}|${basename}]]`;
43
+ });
44
+
45
+ // Set up link parser mocks
46
+ mockLinkParser.extractFilePathFromLink.mockImplementation((link) => {
47
+ if (!link || typeof link !== "string") return null;
48
+
49
+ // Handle text that contains a link
50
+ const linkMatch = link.match(/\[\[([^\]]+)\]\]/);
51
+ if (linkMatch) {
52
+ const content = linkMatch[1];
53
+ // Remove display name if present
54
+ const filePart = content.split("|")[0].trim();
55
+ if (!filePart) return null;
56
+
57
+ // Add .md extension if not present
58
+ return filePart.endsWith(".md") ? filePart : `${filePart}.md`;
59
+ }
60
+
61
+ return null;
62
+ });
63
+ }
64
+
65
+ // Reset all mocks
66
+ export function resetAllMocks() {
67
+ Object.values(mockFileOperations).forEach((mock) => {
68
+ mock.mockReset();
69
+ });
70
+ Object.values(mockLinkParser).forEach((mock) => {
71
+ mock.mockReset();
72
+ });
73
+ }
74
+
75
+ // Helper to setup mocks with specific implementations
76
+ export function setupMockImplementation(
77
+ mockName: keyof typeof mockFileOperations | keyof typeof mockLinkParser,
78
+ implementation: (...args: any[]) => any
79
+ ) {
80
+ if (mockName in mockFileOperations) {
81
+ (mockFileOperations as any)[mockName].mockImplementation(implementation);
82
+ } else if (mockName in mockLinkParser) {
83
+ (mockLinkParser as any)[mockName].mockImplementation(implementation);
84
+ }
85
+ }
86
+
87
+ // Helper to setup mock return values
88
+ export function setupMockReturnValue(
89
+ mockName: keyof typeof mockFileOperations | keyof typeof mockLinkParser,
90
+ value: any
91
+ ) {
92
+ if (mockName in mockFileOperations) {
93
+ (mockFileOperations as any)[mockName].mockReturnValue(value);
94
+ } else if (mockName in mockLinkParser) {
95
+ (mockLinkParser as any)[mockName].mockReturnValue(value);
96
+ }
97
+ }
98
+
99
+ // Helper to verify mock calls
100
+ export function verifyMockCalls(
101
+ mockName: keyof typeof mockFileOperations | keyof typeof mockLinkParser,
102
+ expectedCalls: any[][]
103
+ ) {
104
+ const mock =
105
+ mockName in mockFileOperations
106
+ ? (mockFileOperations as any)[mockName]
107
+ : (mockLinkParser as any)[mockName];
108
+
109
+ expect(mock).toHaveBeenCalledTimes(expectedCalls.length);
110
+ expectedCalls.forEach((args, index) => {
111
+ expect(mock).toHaveBeenNthCalledWith(index + 1, ...args);
112
+ });
113
+ }
@@ -0,0 +1,19 @@
1
+ import { vi } from "vitest";
2
+ import { mockFileOperations, mockLinkParser } from "./mocks/utils";
3
+
4
+ // Global test setup that can be imported once per test file
5
+ export function setupTestEnvironment() {
6
+ // Mock the utils modules
7
+ vi.mock("@obsidian-plugins/utils/file-operations", () => mockFileOperations);
8
+ vi.mock("@obsidian-plugins/utils/link-parser", () => mockLinkParser);
9
+
10
+ // Mock any plugin-specific components
11
+ vi.mock("../src/components/settings-tab", () => ({
12
+ TreePropertiesManagerSettingTab: class MockSettingTab {},
13
+ }));
14
+
15
+ // Return cleanup function
16
+ return () => {
17
+ vi.clearAllMocks();
18
+ };
19
+ }
@@ -1,52 +0,0 @@
1
- import type { BehaviorSubject } from "rxjs";
2
- export interface BaseRule {
3
- id: string;
4
- expression: string;
5
- enabled: boolean;
6
- }
7
- /**
8
- * Generic base class for evaluating JavaScript expressions against frontmatter objects.
9
- * Provides reactive compilation of rules via RxJS subscription and safe evaluation.
10
- */
11
- export declare abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
12
- protected compiledRules: Array<TRule & {
13
- fn: (frontmatter: Record<string, unknown>) => boolean;
14
- }>;
15
- private settingsSubscription;
16
- constructor(settingsStore: BehaviorSubject<TSettings>);
17
- /**
18
- * Extract rules from settings object. Must be implemented by subclasses.
19
- */
20
- protected abstract extractRules(settings: TSettings): TRule[];
21
- /**
22
- * Compile rules into executable functions with error handling.
23
- */
24
- private compileRules;
25
- /**
26
- * Evaluate a single rule against frontmatter. Returns the result or undefined if error.
27
- */
28
- protected evaluateRule(rule: TRule & {
29
- fn: (frontmatter: Record<string, unknown>) => boolean;
30
- }, frontmatter: Record<string, unknown>): boolean | undefined;
31
- /**
32
- * Convert evaluation result to boolean - only explicit true is considered truthy.
33
- */
34
- protected isTruthy(result: boolean | undefined): boolean;
35
- /**
36
- * Clean up subscriptions and compiled rules.
37
- */
38
- destroy(): void;
39
- /**
40
- * Get the number of active (compiled) rules.
41
- */
42
- getActiveRuleCount(): number;
43
- /**
44
- * Get information about all rules including their validity.
45
- */
46
- getRuleInfo(): Array<{
47
- expression: string;
48
- isValid: boolean;
49
- enabled: boolean;
50
- }>;
51
- }
52
- //# sourceMappingURL=evaluator-base.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"evaluator-base.d.ts","sourceRoot":"","sources":["../../src/core/evaluator-base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,MAAM,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,8BAAsB,aAAa,CAAC,KAAK,SAAS,QAAQ,EAAE,SAAS;IACpE,SAAS,CAAC,aAAa,EAAE,KAAK,CAC7B,KAAK,GAAG;QAAE,EAAE,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;KAAE,CACjE,CAAM;IACP,OAAO,CAAC,oBAAoB,CAA6B;gBAE7C,aAAa,EAAE,eAAe,CAAC,SAAS,CAAC;IAUrD;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE;IAE7D;;OAEG;IACH,OAAO,CAAC,YAAY;IA6BpB;;OAEG;IACH,SAAS,CAAC,YAAY,CACrB,IAAI,EAAE,KAAK,GAAG;QAAE,EAAE,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAA;KAAE,EACvE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,GAAG,SAAS;IAStB;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO;IAIxD;;OAEG;IACH,OAAO,IAAI,IAAI;IAQf;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;OAEG;IACH,WAAW,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;CAShF"}
@@ -1,84 +0,0 @@
1
- /**
2
- * Generic base class for evaluating JavaScript expressions against frontmatter objects.
3
- * Provides reactive compilation of rules via RxJS subscription and safe evaluation.
4
- */
5
- export class BaseEvaluator {
6
- constructor(settingsStore) {
7
- this.compiledRules = [];
8
- this.settingsSubscription = null;
9
- const initialRules = this.extractRules(settingsStore.value);
10
- this.compileRules(initialRules);
11
- this.settingsSubscription = settingsStore.subscribe((settings) => {
12
- const newRules = this.extractRules(settings);
13
- this.compileRules(newRules);
14
- });
15
- }
16
- /**
17
- * Compile rules into executable functions with error handling.
18
- */
19
- compileRules(rules) {
20
- this.compiledRules = [];
21
- for (const rule of rules) {
22
- if (!rule.enabled || !rule.expression.trim())
23
- continue;
24
- try {
25
- const cleanExpression = rule.expression.trim();
26
- // Create a function that takes 'fm' (frontmatter) as parameter
27
- // and evaluates the expression in that context
28
- const fn = new Function("fm", `return (${cleanExpression});`);
29
- // Test the function with a dummy object to catch syntax errors early
30
- fn({});
31
- this.compiledRules.push(Object.assign(Object.assign({}, rule), { expression: cleanExpression, fn }));
32
- }
33
- catch (error) {
34
- console.warn(`Invalid rule expression "${rule.expression}":`, error);
35
- }
36
- }
37
- }
38
- /**
39
- * Evaluate a single rule against frontmatter. Returns the result or undefined if error.
40
- */
41
- evaluateRule(rule, frontmatter) {
42
- try {
43
- return rule.fn(frontmatter);
44
- }
45
- catch (error) {
46
- console.warn(`Error evaluating rule "${rule.expression}":`, error);
47
- return undefined;
48
- }
49
- }
50
- /**
51
- * Convert evaluation result to boolean - only explicit true is considered truthy.
52
- */
53
- isTruthy(result) {
54
- return result === true;
55
- }
56
- /**
57
- * Clean up subscriptions and compiled rules.
58
- */
59
- destroy() {
60
- if (this.settingsSubscription) {
61
- this.settingsSubscription.unsubscribe();
62
- this.settingsSubscription = null;
63
- }
64
- this.compiledRules = [];
65
- }
66
- /**
67
- * Get the number of active (compiled) rules.
68
- */
69
- getActiveRuleCount() {
70
- return this.compiledRules.length;
71
- }
72
- /**
73
- * Get information about all rules including their validity.
74
- */
75
- getRuleInfo() {
76
- const validExpressions = new Set(this.compiledRules.map((r) => r.expression));
77
- return this.compiledRules.map((rule) => ({
78
- expression: rule.expression,
79
- isValid: validExpressions.has(rule.expression),
80
- enabled: rule.enabled,
81
- }));
82
- }
83
- }
84
- //# sourceMappingURL=evaluator-base.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"evaluator-base.js","sourceRoot":"","sources":["../../src/core/evaluator-base.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorSubject, Subscription } from \"rxjs\";\n\nexport interface BaseRule {\n\tid: string;\n\texpression: string;\n\tenabled: boolean;\n}\n\n/**\n * Generic base class for evaluating JavaScript expressions against frontmatter objects.\n * Provides reactive compilation of rules via RxJS subscription and safe evaluation.\n */\nexport abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {\n\tprotected compiledRules: Array<\n\t\tTRule & { fn: (frontmatter: Record<string, unknown>) => boolean }\n\t> = [];\n\tprivate settingsSubscription: Subscription | null = null;\n\n\tconstructor(settingsStore: BehaviorSubject<TSettings>) {\n\t\tconst initialRules = this.extractRules(settingsStore.value);\n\t\tthis.compileRules(initialRules);\n\n\t\tthis.settingsSubscription = settingsStore.subscribe((settings) => {\n\t\t\tconst newRules = this.extractRules(settings);\n\t\t\tthis.compileRules(newRules);\n\t\t});\n\t}\n\n\t/**\n\t * Extract rules from settings object. Must be implemented by subclasses.\n\t */\n\tprotected abstract extractRules(settings: TSettings): TRule[];\n\n\t/**\n\t * Compile rules into executable functions with error handling.\n\t */\n\tprivate compileRules(rules: TRule[]): void {\n\t\tthis.compiledRules = [];\n\n\t\tfor (const rule of rules) {\n\t\t\tif (!rule.enabled || !rule.expression.trim()) continue;\n\n\t\t\ttry {\n\t\t\t\tconst cleanExpression = rule.expression.trim();\n\n\t\t\t\t// Create a function that takes 'fm' (frontmatter) as parameter\n\t\t\t\t// and evaluates the expression in that context\n\t\t\t\tconst fn = new Function(\"fm\", `return (${cleanExpression});`) as (\n\t\t\t\t\tfrontmatter: Record<string, unknown>\n\t\t\t\t) => boolean;\n\n\t\t\t\t// Test the function with a dummy object to catch syntax errors early\n\t\t\t\tfn({});\n\n\t\t\t\tthis.compiledRules.push({\n\t\t\t\t\t...rule,\n\t\t\t\t\texpression: cleanExpression,\n\t\t\t\t\tfn,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(`Invalid rule expression \"${rule.expression}\":`, error);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Evaluate a single rule against frontmatter. Returns the result or undefined if error.\n\t */\n\tprotected evaluateRule(\n\t\trule: TRule & { fn: (frontmatter: Record<string, unknown>) => boolean },\n\t\tfrontmatter: Record<string, unknown>\n\t): boolean | undefined {\n\t\ttry {\n\t\t\treturn rule.fn(frontmatter);\n\t\t} catch (error) {\n\t\t\tconsole.warn(`Error evaluating rule \"${rule.expression}\":`, error);\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\t/**\n\t * Convert evaluation result to boolean - only explicit true is considered truthy.\n\t */\n\tprotected isTruthy(result: boolean | undefined): boolean {\n\t\treturn result === true;\n\t}\n\n\t/**\n\t * Clean up subscriptions and compiled rules.\n\t */\n\tdestroy(): void {\n\t\tif (this.settingsSubscription) {\n\t\t\tthis.settingsSubscription.unsubscribe();\n\t\t\tthis.settingsSubscription = null;\n\t\t}\n\t\tthis.compiledRules = [];\n\t}\n\n\t/**\n\t * Get the number of active (compiled) rules.\n\t */\n\tgetActiveRuleCount(): number {\n\t\treturn this.compiledRules.length;\n\t}\n\n\t/**\n\t * Get information about all rules including their validity.\n\t */\n\tgetRuleInfo(): Array<{ expression: string; isValid: boolean; enabled: boolean }> {\n\t\tconst validExpressions = new Set(this.compiledRules.map((r) => r.expression));\n\n\t\treturn this.compiledRules.map((rule) => ({\n\t\t\texpression: rule.expression,\n\t\t\tisValid: validExpressions.has(rule.expression),\n\t\t\tenabled: rule.enabled,\n\t\t}));\n\t}\n}\n"]}