mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(file-source): add path-based task recognition strategy
Implements path-based recognition for FileSource, allowing users to designate specific directories as task directories. Files within these paths are automatically recognized as tasks. Features: - Three matching modes: prefix, glob, and regex patterns - Configurable task paths via settings UI - Dynamic examples based on selected matching mode - Support for multiple path patterns The glob mode supports standard patterns: - * matches any chars except / - ** matches any chars including / - ? matches single char This completes the Phase 2 path strategy implementation for FileSource.
This commit is contained in:
parent
55fbc630ad
commit
5fc1ad0dba
3 changed files with 232 additions and 11 deletions
|
|
@ -220,14 +220,104 @@ function createRecognitionStrategiesSection(
|
|||
);
|
||||
}
|
||||
|
||||
// Template and Path strategies (Phase 2 placeholders)
|
||||
const futureContainer = containerEl.createDiv(
|
||||
// Path strategy
|
||||
const pathContainer = containerEl.createDiv(
|
||||
"file-source-strategy-container",
|
||||
);
|
||||
|
||||
new Setting(pathContainer)
|
||||
.setName(t("Path-based Recognition"))
|
||||
.setDesc(t("Recognize files as tasks based on their file path"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(config.recognitionStrategies.paths.enabled)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.fileSource.recognitionStrategies.paths.enabled = value;
|
||||
await plugin.saveSettings();
|
||||
// Refresh settings interface
|
||||
containerEl.empty();
|
||||
createFileSourceSettings(containerEl, plugin);
|
||||
}),
|
||||
);
|
||||
|
||||
if (config.recognitionStrategies.paths.enabled) {
|
||||
new Setting(pathContainer)
|
||||
.setName(t("Task Paths"))
|
||||
.setDesc(
|
||||
t("Paths that contain task files. One per line. Examples: Projects/, Tasks/2024/, Work/TODO/")
|
||||
)
|
||||
.addTextArea((text) => {
|
||||
text
|
||||
.setPlaceholder("Projects/\nTasks/\nWork/TODO/")
|
||||
.setValue(config.recognitionStrategies.paths.taskPaths.join("\n"))
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.fileSource.recognitionStrategies.paths.taskPaths =
|
||||
value
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
text.inputEl.rows = 4;
|
||||
text.inputEl.cols = 40;
|
||||
});
|
||||
|
||||
new Setting(pathContainer)
|
||||
.setName(t("Path Matching Mode"))
|
||||
.setDesc(t("How paths should be matched"))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("prefix", t("Prefix (e.g., Projects/ matches Projects/App.md)"))
|
||||
.addOption("glob", t("Glob pattern (e.g., Projects/**/*.md)"))
|
||||
.addOption("regex", t("Regular expression (advanced)"))
|
||||
.setValue(config.recognitionStrategies.paths.matchMode)
|
||||
.onChange(async (value: "prefix" | "regex" | "glob") => {
|
||||
plugin.settings.fileSource.recognitionStrategies.paths.matchMode = value;
|
||||
await plugin.saveSettings();
|
||||
// Refresh to show updated examples
|
||||
containerEl.empty();
|
||||
createFileSourceSettings(containerEl, plugin);
|
||||
}),
|
||||
);
|
||||
|
||||
// Add examples based on current mode
|
||||
const examples = pathContainer.createDiv("setting-item-description");
|
||||
examples.style.marginTop = "10px";
|
||||
|
||||
const currentMode = config.recognitionStrategies.paths.matchMode;
|
||||
let exampleText = "";
|
||||
|
||||
switch (currentMode) {
|
||||
case "prefix":
|
||||
exampleText = t("Examples:") + "\n" +
|
||||
"• Projects/ → " + t("matches all files under Projects folder") + "\n" +
|
||||
"• Tasks/2024/ → " + t("matches all files under Tasks/2024 folder");
|
||||
break;
|
||||
case "glob":
|
||||
exampleText = t("Examples:") + "\n" +
|
||||
"• Projects/**/*.md → " + t("all .md files in Projects and subfolders") + "\n" +
|
||||
"• Tasks/*.task.md → " + t("files ending with .task.md in Tasks folder") + "\n" +
|
||||
"• Work/*/TODO.md → " + t("TODO.md in any direct subfolder of Work");
|
||||
break;
|
||||
case "regex":
|
||||
exampleText = t("Examples:") + "\n" +
|
||||
"• ^Projects/.*\\.md$ → " + t("all .md files in Projects folder") + "\n" +
|
||||
"• ^Tasks/\\d{4}-\\d{2}-\\d{2} → " + t("files starting with date in Tasks");
|
||||
break;
|
||||
}
|
||||
|
||||
examples.createEl("pre", {
|
||||
text: exampleText,
|
||||
attr: { style: "font-size: 0.9em; color: var(--text-muted);" }
|
||||
});
|
||||
}
|
||||
|
||||
// Template strategy placeholder (keep for future)
|
||||
const templateContainer = containerEl.createDiv(
|
||||
"file-source-future-strategies",
|
||||
);
|
||||
futureContainer.createEl("p", {
|
||||
text: t(
|
||||
"Template-based and Path-based recognition strategies will be available in a future update.",
|
||||
),
|
||||
templateContainer.createEl("p", {
|
||||
text: t("Template-based recognition strategy will be available in a future update."),
|
||||
attr: { style: "color: var(--text-muted); font-style: italic;" },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ import type {
|
|||
FileSourceStats,
|
||||
UpdateDecision,
|
||||
FileTaskCache,
|
||||
RecognitionStrategy
|
||||
RecognitionStrategy,
|
||||
PathRecognitionConfig
|
||||
} from "../../types/file-source";
|
||||
|
||||
import { Events, emit, Seq, on } from "../events/Events";
|
||||
|
|
@ -319,18 +320,86 @@ export class FileSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if file matches path strategy (stub for Phase 2)
|
||||
* Check if file matches path strategy
|
||||
*/
|
||||
private matchesPathStrategy(
|
||||
filePath: string,
|
||||
fileContent: string,
|
||||
fileCache: CachedMetadata | null,
|
||||
config: any
|
||||
config: PathRecognitionConfig
|
||||
): boolean {
|
||||
// TODO: Implement in Phase 2
|
||||
if (!config.enabled || !config.taskPaths || config.taskPaths.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize path (use forward slashes)
|
||||
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||
|
||||
// Check each configured path pattern
|
||||
for (const pattern of config.taskPaths) {
|
||||
const normalizedPattern = pattern.replace(/\\/g, '/');
|
||||
|
||||
switch (config.matchMode) {
|
||||
case 'prefix':
|
||||
if (normalizedPath.startsWith(normalizedPattern)) {
|
||||
console.log(`[FileSource] Path matches prefix pattern: ${pattern} for ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'regex':
|
||||
try {
|
||||
const regex = new RegExp(normalizedPattern);
|
||||
if (regex.test(normalizedPath)) {
|
||||
console.log(`[FileSource] Path matches regex pattern: ${pattern} for ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[FileSource] Invalid regex pattern: ${pattern}`, e);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'glob':
|
||||
if (this.matchGlobPattern(normalizedPath, normalizedPattern)) {
|
||||
console.log(`[FileSource] Path matches glob pattern: ${pattern} for ${filePath}`);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a path against a glob pattern
|
||||
* Supports: * (any chars except /), ** (any chars), ? (single char)
|
||||
*/
|
||||
private matchGlobPattern(path: string, pattern: string): boolean {
|
||||
// Convert glob pattern to regular expression
|
||||
let regexPattern = pattern
|
||||
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special chars
|
||||
.replace(/\*\*/g, '§§§') // Temporary placeholder for **
|
||||
.replace(/\*/g, '[^/]*') // * matches any chars except /
|
||||
.replace(/§§§/g, '.*') // ** matches any chars
|
||||
.replace(/\?/g, '[^/]'); // ? matches single char
|
||||
|
||||
// If pattern ends with /, match all files in that directory
|
||||
if (pattern.endsWith('/')) {
|
||||
regexPattern = `^${regexPattern}.*`;
|
||||
} else {
|
||||
regexPattern = `^${regexPattern}$`;
|
||||
}
|
||||
|
||||
try {
|
||||
const regex = new RegExp(regexPattern);
|
||||
return regex.test(path);
|
||||
} catch (e) {
|
||||
console.warn(`[FileSource] Failed to compile glob pattern: ${pattern}`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file task
|
||||
*/
|
||||
|
|
@ -466,7 +535,13 @@ export class FileSource {
|
|||
return { name: "tag", criteria: "file-tags" };
|
||||
}
|
||||
|
||||
// TODO: Add template and path strategies in Phase 2
|
||||
// Check path strategy
|
||||
if (config.recognitionStrategies.paths.enabled &&
|
||||
this.matchesPathStrategy(filePath, fileContent, fileCache, config.recognitionStrategies.paths)) {
|
||||
return { name: "path", criteria: config.recognitionStrategies.paths.taskPaths.join(", ") };
|
||||
}
|
||||
|
||||
// TODO: Add template strategy in Phase 2
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
56
test-path-strategy.md
Normal file
56
test-path-strategy.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Test Path Strategy
|
||||
|
||||
This is a simple test to verify that the Path strategy implementation is working correctly.
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
### 1. FileSource.ts Updates
|
||||
- ✅ Added `PathRecognitionConfig` import
|
||||
- ✅ Implemented `matchesPathStrategy` method with support for:
|
||||
- **prefix** mode: Simple prefix matching (e.g., `Projects/` matches `Projects/App.md`)
|
||||
- **regex** mode: Regular expression matching
|
||||
- **glob** mode: Glob pattern matching (supports `*`, `**`, `?`)
|
||||
- ✅ Added `matchGlobPattern` helper method for glob pattern conversion
|
||||
- ✅ Updated strategy evaluation to include path strategy
|
||||
|
||||
### 2. FileSourceSettings.ts Updates
|
||||
- ✅ Replaced placeholder with full Path strategy configuration UI
|
||||
- ✅ Added TextArea for entering task paths (one per line)
|
||||
- ✅ Added Dropdown for selecting matching mode
|
||||
- ✅ Added dynamic examples based on selected mode
|
||||
- ✅ Kept Template strategy placeholder for future implementation
|
||||
|
||||
## Features
|
||||
|
||||
### Path Matching Modes
|
||||
|
||||
1. **Prefix Mode**
|
||||
- Simplest mode for basic directory matching
|
||||
- Example: `Projects/` matches all files under Projects folder
|
||||
|
||||
2. **Glob Mode**
|
||||
- Supports standard glob patterns
|
||||
- `*` matches any characters except `/`
|
||||
- `**` matches any characters including `/`
|
||||
- `?` matches single character
|
||||
- Examples:
|
||||
- `Projects/**/*.md` - all .md files in Projects and subfolders
|
||||
- `Tasks/*.task.md` - files ending with .task.md in Tasks folder
|
||||
|
||||
3. **Regex Mode**
|
||||
- Full regular expression support for advanced users
|
||||
- Example: `^Tasks/\d{4}-\d{2}-\d{2}` - files starting with date in Tasks
|
||||
|
||||
## Configuration
|
||||
|
||||
Users can now:
|
||||
1. Enable Path-based Recognition in Settings > Index > File as Task
|
||||
2. Enter multiple task paths (one per line)
|
||||
3. Choose matching mode (prefix, glob, or regex)
|
||||
4. See context-sensitive examples for the selected mode
|
||||
|
||||
## Build Status
|
||||
|
||||
✅ Build completed successfully
|
||||
✅ TypeScript compilation passed (with expected Jest type conflicts)
|
||||
✅ All functionality integrated into existing codebase
|
||||
Loading…
Reference in a new issue