maro-plugin-tasks 1.0.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.
- package/.eslintrc.json +118 -0
- package/package.json +32 -0
- package/src/commands/open.ts +16 -0
- package/src/index.ts +18 -0
- package/src/lib/action.ts +32 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/repo_task_tracker.ts +175 -0
- package/src/lib/task.ts +77 -0
- package/src/lib/tracker.ts +15 -0
- package/src/lib/utils.ts +29 -0
- package/tsconfig.json +38 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"node": true,
|
|
4
|
+
"es2021": true
|
|
5
|
+
},
|
|
6
|
+
"ignorePatterns": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"extends": [
|
|
10
|
+
"eslint:recommended",
|
|
11
|
+
"plugin:de-morgan/recommended-legacy",
|
|
12
|
+
"plugin:@typescript-eslint/recommended",
|
|
13
|
+
"plugin:math/recommended-legacy",
|
|
14
|
+
"plugin:promise/recommended"
|
|
15
|
+
],
|
|
16
|
+
"plugins": [
|
|
17
|
+
"promise",
|
|
18
|
+
"@stylistic",
|
|
19
|
+
"de-morgan",
|
|
20
|
+
"import",
|
|
21
|
+
"unused-imports"
|
|
22
|
+
],
|
|
23
|
+
"parser": "@typescript-eslint/parser",
|
|
24
|
+
"parserOptions": {
|
|
25
|
+
"ecmaVersion": 12,
|
|
26
|
+
"sourceType": "module"
|
|
27
|
+
},
|
|
28
|
+
"rules": {
|
|
29
|
+
"no-inner-declarations": "off",
|
|
30
|
+
"import/no-deprecated": "error",
|
|
31
|
+
"import/no-cycle": "error",
|
|
32
|
+
"import/no-duplicates": "error",
|
|
33
|
+
"import/enforce-node-protocol-usage": [
|
|
34
|
+
"error",
|
|
35
|
+
"always"
|
|
36
|
+
],
|
|
37
|
+
"unused-imports/no-unused-imports": "error",
|
|
38
|
+
"@stylistic/arrow-parens": 2,
|
|
39
|
+
"@stylistic/brace-style": 2,
|
|
40
|
+
"@stylistic/comma-dangle": 2,
|
|
41
|
+
"@stylistic/comma-spacing": 2,
|
|
42
|
+
"@stylistic/dot-location": [
|
|
43
|
+
2,
|
|
44
|
+
"property"
|
|
45
|
+
],
|
|
46
|
+
"@stylistic/func-call-spacing": 2,
|
|
47
|
+
"@stylistic/indent": [
|
|
48
|
+
"error",
|
|
49
|
+
2
|
|
50
|
+
],
|
|
51
|
+
"@stylistic/indent-binary-ops": [
|
|
52
|
+
"error",
|
|
53
|
+
2
|
|
54
|
+
],
|
|
55
|
+
"@stylistic/key-spacing": 2,
|
|
56
|
+
"@stylistic/keyword-spacing": 2,
|
|
57
|
+
"@stylistic/member-delimiter-style": 2,
|
|
58
|
+
"@stylistic/no-extra-parens": 2,
|
|
59
|
+
"@stylistic/no-extra-semi": 2,
|
|
60
|
+
"@stylistic/no-floating-decimal": 2,
|
|
61
|
+
"@stylistic/no-mixed-spaces-and-tabs": 2,
|
|
62
|
+
"@stylistic/no-multiple-empty-lines": [
|
|
63
|
+
2,
|
|
64
|
+
{
|
|
65
|
+
"max": 1
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"@stylistic/no-tabs": 2,
|
|
69
|
+
"@stylistic/no-trailing-spaces": 2,
|
|
70
|
+
"@stylistic/no-whitespace-before-property": 2,
|
|
71
|
+
"@stylistic/nonblock-statement-body-position": 2,
|
|
72
|
+
"@stylistic/operator-linebreak": [
|
|
73
|
+
2,
|
|
74
|
+
"before"
|
|
75
|
+
],
|
|
76
|
+
"@stylistic/padding-line-between-statements": 2,
|
|
77
|
+
"@stylistic/quotes": [
|
|
78
|
+
"error",
|
|
79
|
+
"double"
|
|
80
|
+
],
|
|
81
|
+
"@stylistic/semi": [
|
|
82
|
+
"error"
|
|
83
|
+
],
|
|
84
|
+
"@stylistic/semi-spacing": 2,
|
|
85
|
+
"@stylistic/space-before-blocks": 2,
|
|
86
|
+
"@stylistic/space-in-parens": 2,
|
|
87
|
+
"@stylistic/space-infix-ops": 2,
|
|
88
|
+
"@stylistic/space-unary-ops": 2,
|
|
89
|
+
"@stylistic/spaced-comment": 2,
|
|
90
|
+
"@stylistic/template-curly-spacing": 2,
|
|
91
|
+
"@stylistic/type-annotation-spacing": 2,
|
|
92
|
+
"@stylistic/type-generic-spacing": 2,
|
|
93
|
+
"@stylistic/type-named-tuple-spacing": 2,
|
|
94
|
+
"@typescript-eslint/no-unused-vars": [
|
|
95
|
+
"error",
|
|
96
|
+
{
|
|
97
|
+
"varsIgnorePattern": "_+"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
101
|
+
"@typescript-eslint/ban-types": "off",
|
|
102
|
+
"arrow-spacing": "error",
|
|
103
|
+
"dot-notation": "error",
|
|
104
|
+
"eqeqeq": "error",
|
|
105
|
+
"no-alert": "error",
|
|
106
|
+
"no-const-assign": "error",
|
|
107
|
+
"no-multi-spaces": "error",
|
|
108
|
+
"no-multiple-empty-lines": "error",
|
|
109
|
+
"prefer-const": "error",
|
|
110
|
+
"quotes": [
|
|
111
|
+
"error",
|
|
112
|
+
"double",
|
|
113
|
+
{
|
|
114
|
+
"allowTemplateLiterals": true
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "maro-plugin-tasks",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "./dist/lib/index.js",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc --project ./tsconfig.json",
|
|
8
|
+
"start": "node ./dist/src/index.js",
|
|
9
|
+
"clean": "rm -rf node_modules package-lock.json"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@maro/maro": "2.0.0"
|
|
13
|
+
},
|
|
14
|
+
"maro": {
|
|
15
|
+
"plugin": "./dist/src/index.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"description": "",
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.9.3",
|
|
23
|
+
"@typescript-eslint/eslint-plugin": "8.54.0",
|
|
24
|
+
"@stylistic/eslint-plugin": "2.6.5",
|
|
25
|
+
"eslint": "8.57.1",
|
|
26
|
+
"eslint-plugin-de-morgan": "2.0.0",
|
|
27
|
+
"eslint-plugin-import": "2.32.0",
|
|
28
|
+
"eslint-plugin-math": "0.13.1",
|
|
29
|
+
"eslint-plugin-promise": "7.2.1",
|
|
30
|
+
"eslint-plugin-unused-imports": "4.3.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getTasksFromDir } from "src/lib/utils";
|
|
2
|
+
|
|
3
|
+
import { Command } from "@maro/maro";
|
|
4
|
+
|
|
5
|
+
const OpenCommand: Command = {
|
|
6
|
+
name: "open",
|
|
7
|
+
description: "Open task from cwd in editor",
|
|
8
|
+
run: async ({ ctx }) => {
|
|
9
|
+
const tasks = getTasksFromDir(ctx.cwd);
|
|
10
|
+
const task = await ctx.ui.promptChoice(tasks, { message: "Choose task to open" });
|
|
11
|
+
task.openInEditor();
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default OpenCommand;
|
|
16
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import OpenCommand from "./commands/open";
|
|
2
|
+
import { PluginExport } from "../../../dist/lib";
|
|
3
|
+
|
|
4
|
+
const plugin: PluginExport = {
|
|
5
|
+
name: "maro-plugin-tasks",
|
|
6
|
+
commands: [
|
|
7
|
+
{
|
|
8
|
+
name: "task",
|
|
9
|
+
description: "Track tasks asociated with TODOS",
|
|
10
|
+
aliases: [],
|
|
11
|
+
subcommands: [
|
|
12
|
+
OpenCommand
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default plugin;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Action, ActionRegistry, loading, MrCreateEvent } from "@maro/maro";
|
|
2
|
+
|
|
3
|
+
import { TaskTracker } from "./tracker";
|
|
4
|
+
import { getTasksFromDir } from "./utils";
|
|
5
|
+
|
|
6
|
+
export class CreateMissingTasksAction implements Action {
|
|
7
|
+
tracker: TaskTracker;
|
|
8
|
+
|
|
9
|
+
constructor(tracker: TaskTracker) {
|
|
10
|
+
this.tracker = tracker;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
register(): void {
|
|
14
|
+
ActionRegistry.on(MrCreateEvent, (event) => this.execute(event));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@loading("Generating missing issues")
|
|
18
|
+
async execute(event: MrCreateEvent) {
|
|
19
|
+
const repo = event.ctx;
|
|
20
|
+
const tasks = getTasksFromDir(repo.dir);
|
|
21
|
+
|
|
22
|
+
await Promise.all(
|
|
23
|
+
tasks.map(async (t) => {
|
|
24
|
+
if (this.tracker.isTracked(t)) return;
|
|
25
|
+
await this.tracker.track(t);
|
|
26
|
+
await repo.add(t.file_location.file);
|
|
27
|
+
}));
|
|
28
|
+
await repo.commit("fix: add issues");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Repo } from "@maro/maro";
|
|
2
|
+
|
|
3
|
+
import { Task } from "./task";
|
|
4
|
+
import { TaskTracker } from "./tracker";
|
|
5
|
+
|
|
6
|
+
export function sleep(ms: number) {
|
|
7
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const TASK_FILE = "TASK.md";
|
|
11
|
+
|
|
12
|
+
// TODO: for now Tasks change status manually outside the app,
|
|
13
|
+
// add support for trackers to change task status, could be useful for RepoActions
|
|
14
|
+
const TASK_STATUS = {
|
|
15
|
+
OPEN: "OPEN",
|
|
16
|
+
CLOSED: "CLOSED"
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export class RepoTaskTracker extends TaskTracker {
|
|
20
|
+
repo: Repo;
|
|
21
|
+
|
|
22
|
+
constructor(repo: Repo) {
|
|
23
|
+
super();
|
|
24
|
+
this.repo = repo;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override addIdToTodo(id: string): string {
|
|
28
|
+
return `(${id})`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override async save(task: Task): Promise<string> {
|
|
32
|
+
const d = new Date();
|
|
33
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
34
|
+
const YYYY = d.getFullYear();
|
|
35
|
+
const MM = pad(d.getMonth() + 1);
|
|
36
|
+
const DD = pad(d.getDate());
|
|
37
|
+
const HH = pad(d.getHours());
|
|
38
|
+
const mm = pad(d.getMinutes());
|
|
39
|
+
const SS = pad(d.getSeconds());
|
|
40
|
+
await sleep(1 * 1000);
|
|
41
|
+
const id = `${YYYY}${MM}${DD}-${HH}${mm}${SS}`;
|
|
42
|
+
|
|
43
|
+
const tasks_dir = this.repo.dir.sub("tasks");
|
|
44
|
+
tasks_dir.create();
|
|
45
|
+
const task_dir = tasks_dir.sub(id);
|
|
46
|
+
task_dir.createFile(TASK_FILE).write(this.toMdString(task));
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override isTracked(task: Task): boolean {
|
|
51
|
+
const todoLine = task.getTodo();
|
|
52
|
+
const regex = /TODO[^:]*\([^)]*\)[^:]*:/;
|
|
53
|
+
return regex.test(todoLine);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private toMdString(task: Task) {
|
|
57
|
+
const relative = task.getPathInProject();
|
|
58
|
+
return `# ${task.title}
|
|
59
|
+
|
|
60
|
+
- PROJECT: ${task.project}
|
|
61
|
+
- FILE-LOCATION: ${relative}:${task.file_location.row}:${task.file_location.col}
|
|
62
|
+
- STATUS: ${TASK_STATUS.OPEN}
|
|
63
|
+
|
|
64
|
+
${task.description} `;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// async getTasks(project?: string) {
|
|
68
|
+
// await this.update();
|
|
69
|
+
// const dirs = project ? [project] : readdirs(this.full_path).map((d) => d.name);
|
|
70
|
+
// const full_dirs = dirs.map((d) => path.join(this.full_path, d));
|
|
71
|
+
// return full_dirs.flatMap((d) => readdirs(d).map((task_dir) => Task.fromTaskFile(path.join(d, task_dir.name, TASK_FILE))));
|
|
72
|
+
// }
|
|
73
|
+
//
|
|
74
|
+
// addTask(task: Task) {
|
|
75
|
+
// const dir = path.join(this.full_path, task.project, task.id);
|
|
76
|
+
// const task_path = path.join(dir, TASK_FILE);
|
|
77
|
+
// createDirIfNotExists(dir);
|
|
78
|
+
// task.save(task_path);
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
// TODO: move to TaskRepo
|
|
82
|
+
// private static parseFileLocation(raw: string): FileLocation {
|
|
83
|
+
// const [file_path, row, col] = raw.split(":");
|
|
84
|
+
// if (!file_path) throw new Error(`Could not parse file location ${raw} file_path missing`);
|
|
85
|
+
// return { file_path, row: Number(row), col: Number(col) };
|
|
86
|
+
// }
|
|
87
|
+
|
|
88
|
+
// TODO: move to TaskRepo
|
|
89
|
+
// static fromTaskFile(file: string) {
|
|
90
|
+
// const raw = fs.readFileSync(file, "utf8");
|
|
91
|
+
//
|
|
92
|
+
// const tree = fromMarkdown(raw);
|
|
93
|
+
//
|
|
94
|
+
// let title: string | undefined;
|
|
95
|
+
// let id: string | undefined;
|
|
96
|
+
// let project: string | undefined;
|
|
97
|
+
// let status: TaskStatus | undefined;
|
|
98
|
+
// let priority: number | undefined;
|
|
99
|
+
// let fileLocation: FileLocation | undefined;
|
|
100
|
+
// let jiraId: string | undefined;
|
|
101
|
+
// let description = "";
|
|
102
|
+
//
|
|
103
|
+
// for (const node of tree.children) {
|
|
104
|
+
// if (node.type === "heading" && node.depth === 1) {
|
|
105
|
+
// if (!node.children[0] || !("value" in node.children[0])) continue;
|
|
106
|
+
// title = node.children[0]?.value.trim();
|
|
107
|
+
// continue;
|
|
108
|
+
// }
|
|
109
|
+
//
|
|
110
|
+
// if (node.type === "list") {
|
|
111
|
+
// for (const item of node.children) {
|
|
112
|
+
// if (!item.children[0]
|
|
113
|
+
// || !("children" in item.children[0])
|
|
114
|
+
// || !item.children[0].children[0]
|
|
115
|
+
// || !("value" in item.children[0].children[0])) continue;
|
|
116
|
+
// const text = item.children[0].children[0]?.value;
|
|
117
|
+
// const [key, rawVal, ...rest] = text.split(":");
|
|
118
|
+
// const value = [rawVal, ...rest].join(":")?.trim();
|
|
119
|
+
// switch (key) {
|
|
120
|
+
// case "ID":
|
|
121
|
+
// id = value;
|
|
122
|
+
// break;
|
|
123
|
+
// case "PROJECT":
|
|
124
|
+
// project = value;
|
|
125
|
+
// break;
|
|
126
|
+
// case "STATUS": {
|
|
127
|
+
// const statuses = Object.values(TASK_STATUS);
|
|
128
|
+
// if (!statuses.includes(value as TaskStatus)) throw new Error(`Invalid STATUS "${value}". Must be one of: ${statuses.join(", ")}`);
|
|
129
|
+
// status = value as TaskStatus;
|
|
130
|
+
// break;
|
|
131
|
+
// }
|
|
132
|
+
// case "PRIORITY":
|
|
133
|
+
// priority = Number(value);
|
|
134
|
+
// break;
|
|
135
|
+
// case "JIRA-ID":
|
|
136
|
+
// jiraId = value;
|
|
137
|
+
// break;
|
|
138
|
+
// case "FILE-LOCATION":
|
|
139
|
+
// fileLocation = this.parseFileLocation(value ?? "");
|
|
140
|
+
// break;
|
|
141
|
+
// }
|
|
142
|
+
// }
|
|
143
|
+
// continue;
|
|
144
|
+
// }
|
|
145
|
+
//
|
|
146
|
+
// if (node.type === "paragraph") {
|
|
147
|
+
// const text = node.children.map((c) => {
|
|
148
|
+
// if (!("value" in c)) return "";
|
|
149
|
+
// return c.value;
|
|
150
|
+
// }).join("").trim();
|
|
151
|
+
// if (text) description += (description ? "\n" : "") + text;
|
|
152
|
+
// }
|
|
153
|
+
// }
|
|
154
|
+
//
|
|
155
|
+
// if (!id) throw new Error(`Could not find id for task ${file}`);
|
|
156
|
+
// if (!project) throw new Error(`Could not find project for task ${file}`);
|
|
157
|
+
// if (!title) throw new Error(`Could not find title for task ${id} from project ${project}`);
|
|
158
|
+
// if (!priority) throw new Error(`Could not find priority for task ${id} from project ${project}`);
|
|
159
|
+
// if (!status) throw new Error(`Could not find status for task ${id} from project ${project}`);
|
|
160
|
+
//
|
|
161
|
+
// return new Task({
|
|
162
|
+
// md_path: file,
|
|
163
|
+
// id,
|
|
164
|
+
// title,
|
|
165
|
+
// project,
|
|
166
|
+
// description,
|
|
167
|
+
// tags: {
|
|
168
|
+
// status,
|
|
169
|
+
// priority,
|
|
170
|
+
// jira_id: jiraId,
|
|
171
|
+
// file_location: fileLocation
|
|
172
|
+
// }
|
|
173
|
+
// });
|
|
174
|
+
// }
|
|
175
|
+
}
|
package/src/lib/task.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Choice,
|
|
3
|
+
Dir,
|
|
4
|
+
openInEditor,
|
|
5
|
+
TextFile
|
|
6
|
+
} from "@maro/maro";
|
|
7
|
+
|
|
8
|
+
type FileLocation = {
|
|
9
|
+
file: TextFile;
|
|
10
|
+
row: number;
|
|
11
|
+
col: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class Task {
|
|
15
|
+
project: Dir;
|
|
16
|
+
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
file_location: FileLocation;
|
|
20
|
+
|
|
21
|
+
constructor({ title, file_location, project, description }: {
|
|
22
|
+
description?: string;
|
|
23
|
+
project: Dir;
|
|
24
|
+
title: string;
|
|
25
|
+
file_location: FileLocation;
|
|
26
|
+
}) {
|
|
27
|
+
const file = file_location.file;
|
|
28
|
+
if (!project.contains(file)) throw new Error(`${file} is not in ${project}`);
|
|
29
|
+
this.title = title;
|
|
30
|
+
this.file_location = file_location;
|
|
31
|
+
this.project = project;
|
|
32
|
+
this.description = description;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getTodo() {
|
|
36
|
+
const { file, row } = this.file_location;
|
|
37
|
+
const lines = file.read().split(/\r?\n/);
|
|
38
|
+
const normalized_row = row - 1;
|
|
39
|
+
return lines[normalized_row]!;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
editTodo(add: string) {
|
|
43
|
+
const todoLine = this.getTodo();
|
|
44
|
+
const { file, col, row } = this.file_location;
|
|
45
|
+
const normalized_row = row - 1;
|
|
46
|
+
const normalized_col = col - 1;
|
|
47
|
+
|
|
48
|
+
const prefix = todoLine.slice(0, normalized_col);
|
|
49
|
+
const afterTodo = todoLine.slice(normalized_col);
|
|
50
|
+
const colonIndex = afterTodo.indexOf(":");
|
|
51
|
+
|
|
52
|
+
const originalDecorations = colonIndex === -1
|
|
53
|
+
? afterTodo.slice("TODO".length)
|
|
54
|
+
: afterTodo.slice("TODO".length, colonIndex);
|
|
55
|
+
|
|
56
|
+
const originalBody = colonIndex === -1 ? "" : afterTodo.slice(colonIndex + 1).trim();
|
|
57
|
+
|
|
58
|
+
const newTodo = prefix + "TODO" + originalDecorations + add + ": " + originalBody;
|
|
59
|
+
const lines = file.read().split(/\r?\n/);
|
|
60
|
+
lines[normalized_row] = newTodo;
|
|
61
|
+
file.write(lines.join("\n"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
openInEditor() {
|
|
65
|
+
const { col, row } = this.file_location;
|
|
66
|
+
openInEditor(this.file_location.file, { line: row, column: col });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getPathInProject() {
|
|
70
|
+
return this.project.relativePathTo(this.file_location.file);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
toChoice(): Choice {
|
|
74
|
+
return { name: this.title, hint: `${this.file_location.file}:${this.file_location.row}:${this.file_location.col}` };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Task } from "./task";
|
|
2
|
+
|
|
3
|
+
export abstract class TaskTracker {
|
|
4
|
+
|
|
5
|
+
abstract save(task: Task): Promise<string>;
|
|
6
|
+
abstract addIdToTodo(id: string, task: Task): string;
|
|
7
|
+
abstract isTracked(task: Task): boolean;
|
|
8
|
+
|
|
9
|
+
async track(task: Task) {
|
|
10
|
+
if (this.isTracked(task)) return;
|
|
11
|
+
const id = await this.save(task);
|
|
12
|
+
task.editTodo(this.addIdToTodo(id, task) ?? "");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Dir, TextFile } from "@maro/maro";
|
|
2
|
+
|
|
3
|
+
import { Task } from "./task";
|
|
4
|
+
|
|
5
|
+
export const getTasksFromDir = (dir: Dir) => {
|
|
6
|
+
const files = dir.traverse();
|
|
7
|
+
const tasks: Task[] = [];
|
|
8
|
+
for (const f of files) tasks.push(...getTasksFromFile(dir, f));
|
|
9
|
+
return tasks;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const getTasksFromFile = (project: Dir, fileInDir: TextFile) => {
|
|
13
|
+
const lines = fileInDir.read().split(/\r?\n/);
|
|
14
|
+
const results: Task[] = [];
|
|
15
|
+
const regex = /TODO.*:\s*(.*)/g;
|
|
16
|
+
for (let row = 0; row < lines.length; row++) {
|
|
17
|
+
const line = lines[row]!;
|
|
18
|
+
let match;
|
|
19
|
+
while ((match = regex.exec(line)) !== null) {
|
|
20
|
+
const col = match.index;
|
|
21
|
+
const title = match[1]?.trim() ?? "";
|
|
22
|
+
const file_location = { file: fileInDir, row: row + 1, col: col + 1 };
|
|
23
|
+
// TODO: Task.description from file
|
|
24
|
+
results.push(new Task({ title, project, file_location }));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return results;
|
|
28
|
+
};
|
|
29
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": [
|
|
5
|
+
"es2021",
|
|
6
|
+
"dom"
|
|
7
|
+
],
|
|
8
|
+
"module": "commonjs",
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./",
|
|
11
|
+
"baseUrl": "./",
|
|
12
|
+
"strict": true,
|
|
13
|
+
"moduleResolution": "node",
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
"noImplicitAny": true,
|
|
18
|
+
"strictNullChecks": true,
|
|
19
|
+
"strictFunctionTypes": true,
|
|
20
|
+
"strictBindCallApply": true,
|
|
21
|
+
"strictPropertyInitialization": true,
|
|
22
|
+
"noImplicitThis": true,
|
|
23
|
+
"useUnknownInCatchVariables": true,
|
|
24
|
+
"experimentalDecorators": true,
|
|
25
|
+
"useDefineForClassFields": false,
|
|
26
|
+
"alwaysStrict": true,
|
|
27
|
+
"noUnusedLocals": true,
|
|
28
|
+
"noUnusedParameters": true,
|
|
29
|
+
"noImplicitReturns": true,
|
|
30
|
+
"noFallthroughCasesInSwitch": true,
|
|
31
|
+
"noImplicitOverride": true,
|
|
32
|
+
"allowUnusedLabels": false,
|
|
33
|
+
"allowUnreachableCode": false,
|
|
34
|
+
"noUncheckedIndexedAccess": true,
|
|
35
|
+
"noEmit": false,
|
|
36
|
+
"noEmitOnError": false
|
|
37
|
+
}
|
|
38
|
+
}
|