98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
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<Task[]> {
|
|
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<string, boolean>(exclude, {}) : () => false;
|
|
const filter = query && jsMapper<Task, boolean>(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<T>(directory: string, visitor: (path: string) => Promise<T[]>, excludeFn: (path: string) => boolean): Promise<T[]> {
|
|
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<Task[]> {
|
|
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;
|
|
}
|
|
|
|
|