import fs from "fs"; import dayjs from "dayjs"; import { createInterface } from "readline"; import { DynamicTask } from "../model"; import { Task, TaskPriority } from "../types/task"; import { jsMapper } from "../util/code"; /** * Returns all tasks from specified directory and filters them with optional query. */ export async function loadTasks(directories: string[], query?: string, exclude?: string): Promise { const ctx = { now: dayjs(), LOWEST: TaskPriority.LOWEST, LOW: TaskPriority.LOW, NORMAL: TaskPriority.NORMAL, MEDIUM: TaskPriority.MEDIUM, HIGH: TaskPriority.HIGH, HIGHEST: TaskPriority.HIGHEST }; const excludeFn = exclude ? jsMapper(exclude, {}) : () => false; const filter = query && jsMapper(query, ctx); const tasks = await Promise.all(directories.map(readTasksFromDirectory(excludeFn))); return tasks.flat().filter(t => filter ? filter(t) : true); } /** * Read all files in specific directory and returns all tasks from those files. */ function readTasksFromDirectory(excludeFn: (path: string) => boolean) { return async (directory: string) => walk(directory, readTasksFromFile, excludeFn); } /** * Walks through a specific directory recursively and invokes visitor on each file. * Returns a flat list of items returned by visitors. */ async function walk(directory: string, visitor: (path: string) => Promise, excludeFn: (path: string) => boolean): Promise { const list = []; for(const file of fs.readdirSync(directory)) { const path = `${directory}/${file}`; if (fs.statSync(path).isDirectory()) { const items = await walk(path, visitor, excludeFn); list.push(...items); } else if (path.endsWith("md") || (path.endsWith("MD"))) { if (!excludeFn(path)) { const items = await visitor(path); list.push(...items); } } } return list; } /** * Reads all tasks from file path. */ async function readTasksFromFile(path: string): Promise { const fileStream = fs.createReadStream(path); const lines = createInterface({ input: fileStream, crlfDelay: Infinity }); const list: Task[] = []; let lineNumber = 1; for await (const line of lines) { try { const task = DynamicTask.parse(line, path, lineNumber); if(task) { list.push(task); } } catch(e: any) { console.warn(`Parsing error in file '${path}', for line:`); console.warn(line); if(e.location) { console.warn(' '.repeat(e.location.start.column + 2) + "^" + '~'.repeat(e.location.end.column - e.location.start.column - 1) + "^") } console.warn(e.message); console.warn("This line will be ignored. Please check the source and adjust it accordingly."); } finally { lineNumber++; } } return list; }