imdone-core 2.0.0 → 2.0.1
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/.dependency-cruiser.cjs +412 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- package/.github/workflows/ci.yml +24 -0
- package/.imdoneignore +13 -0
- package/.mocharc.json +10 -0
- package/.prettierrc +6 -0
- package/CHANGELOG.md +160 -0
- package/_index.md +7 -0
- package/_navbar.md +1 -0
- package/build.js +57 -0
- package/esm-migration.md +446 -0
- package/index.html +184 -0
- package/lib/FileProjectContext.js +13 -0
- package/lib/ProjectContext.js +43 -0
- package/lib/adapters/date-time.js +15 -0
- package/lib/adapters/environment.js +3 -0
- package/lib/adapters/file-gateway.js +154 -0
- package/lib/adapters/git-download.js +55 -0
- package/lib/adapters/index.js +9 -0
- package/lib/adapters/logger.js +136 -0
- package/lib/adapters/markdown.js +180 -0
- package/lib/adapters/parsers/DateLanguageParser.js +12 -0
- package/lib/adapters/parsers/content-transformer.js +116 -0
- package/lib/adapters/parsers/file/LocalFileParserFactory.js +36 -0
- package/lib/adapters/parsers/file/ReadStreamCodeParser.js +126 -0
- package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +17 -0
- package/lib/adapters/parsers/file/ReadStreamTaskParser.js +106 -0
- package/lib/adapters/parsers/file/index.js +4 -0
- package/lib/adapters/parsers/function-parser.js +25 -0
- package/lib/adapters/parsers/index.js +5 -0
- package/lib/adapters/parsers/task/CardContentParser.js +367 -0
- package/lib/adapters/parsers/task/CheckStyleTaskParser.js +28 -0
- package/lib/adapters/parsers/task/CodeStyleTaskParser.js +24 -0
- package/lib/adapters/parsers/task/HashStyleTaskParser.js +27 -0
- package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +24 -0
- package/lib/adapters/parsers/task/TaskParser.js +38 -0
- package/lib/adapters/parsers/task/index.js +6 -0
- package/lib/adapters/storage/config.js +44 -0
- package/lib/adapters/storage/index.js +2 -0
- package/lib/adapters/storage/tags.js +29 -0
- package/lib/adapters/yaml.js +9 -0
- package/lib/card.js +299 -0
- package/lib/checksum.js +22 -0
- package/lib/config.js +249 -0
- package/lib/constants.js +68 -0
- package/lib/context/ApplicationContext.js +35 -0
- package/lib/context/index.js +1 -0
- package/lib/default-settings.js +37 -0
- package/lib/domain/entities/View.js +23 -0
- package/lib/domain/entities/index.js +1 -0
- package/lib/domain/index.js +2 -0
- package/lib/domain/usecases/GetViewsUsecase.js +44 -0
- package/lib/domain/usecases/SaveViewUsecase.js +51 -0
- package/lib/domain/usecases/index.js +2 -0
- package/lib/file.js +1391 -0
- package/lib/get-tasks-to-modify.js +117 -0
- package/lib/languages.js +113 -0
- package/lib/list.js +56 -0
- package/lib/migrate-config.js +165 -0
- package/lib/mixins/index.js +2 -0
- package/lib/mixins/repo-fs-store.js +359 -0
- package/lib/mixins/repo-watched-fs-store.js +206 -0
- package/lib/plugins/archive-plugin.js +72 -0
- package/lib/plugins/default-board-actions-plugin.js +56 -0
- package/lib/plugins/default-board-properties-plugin.js +101 -0
- package/lib/plugins/default-plugins.js +7 -0
- package/lib/plugins/epic-plugin.js +177 -0
- package/lib/plugins/extension-plugin.js +94 -0
- package/lib/plugins/index.js +9 -0
- package/lib/plugins/persist-tags-plugin.js +14 -0
- package/lib/plugins/plugin-manager.js +458 -0
- package/lib/plugins/plugin-registry.js +57 -0
- package/lib/project-factory.js +42 -0
- package/lib/project.js +864 -0
- package/lib/repository.js +1514 -0
- package/lib/task.js +780 -0
- package/lib/tools.js +176 -0
- package/lib/usecases/actions.js +32 -0
- package/lib/usecases/get-imdone-url.js +6 -0
- package/lib/usecases/get-project-tags.js +7 -0
- package/lib/usecases/get-tasks-in-file.js +26 -0
- package/lib/usecases/index.js +4 -0
- package/package.json +2 -6
- package/scripts/delete-markdown-indexes.js +87 -0
- package/scripts/generate-index.js +35 -0
- package/scripts/index-markdown.js +55 -0
- package/tsconfig.json +15 -0
- package/types/FileProjectContext.d.ts +4 -0
- package/types/ProjectContext.d.ts +9 -0
- package/types/adapters/date-time.d.ts +2 -0
- package/types/adapters/environment.d.ts +1 -0
- package/types/adapters/file-gateway.d.ts +51 -0
- package/types/adapters/git-download.d.ts +1 -0
- package/types/adapters/index.d.ts +9 -0
- package/types/adapters/logger.d.ts +24 -0
- package/types/adapters/markdown.d.ts +13 -0
- package/types/adapters/parsers/DateLanguageParser.d.ts +1 -0
- package/types/adapters/parsers/content-transformer.d.ts +10 -0
- package/types/adapters/parsers/file/LocalFileParserFactory.d.ts +5 -0
- package/types/adapters/parsers/file/ReadStreamCodeParser.d.ts +13 -0
- package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +7 -0
- package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +19 -0
- package/types/adapters/parsers/file/index.d.ts +4 -0
- package/types/adapters/parsers/function-parser.d.ts +1 -0
- package/types/adapters/parsers/index.d.ts +5 -0
- package/types/adapters/parsers/task/CardContentParser.d.ts +45 -0
- package/types/adapters/parsers/task/CheckStyleTaskParser.d.ts +12 -0
- package/types/adapters/parsers/task/CodeStyleTaskParser.d.ts +14 -0
- package/types/adapters/parsers/task/HashStyleTaskParser.d.ts +13 -0
- package/types/adapters/parsers/task/MarkdownStyleTaskParser.d.ts +14 -0
- package/types/adapters/parsers/task/TaskParser.d.ts +7 -0
- package/types/adapters/parsers/task/index.d.ts +6 -0
- package/types/adapters/storage/config.d.ts +6 -0
- package/types/adapters/storage/index.d.ts +2 -0
- package/types/adapters/storage/tags.d.ts +3 -0
- package/types/adapters/yaml.d.ts +2 -0
- package/types/card.d.ts +166 -0
- package/types/checksum.d.ts +8 -0
- package/types/config.d.ts +61 -0
- package/types/constants.d.ts +46 -0
- package/types/context/ApplicationContext.d.ts +24 -0
- package/types/context/index.d.ts +1 -0
- package/types/default-settings.d.ts +2 -0
- package/types/domain/entities/FileProjectContext.d.ts +5 -0
- package/types/domain/entities/View.d.ts +17 -0
- package/types/domain/entities/index.d.ts +1 -0
- package/types/domain/index.d.ts +2 -0
- package/types/domain/usecases/GetViewsUsecase.d.ts +4 -0
- package/types/domain/usecases/SaveViewUsecase.d.ts +8 -0
- package/types/domain/usecases/index.d.ts +2 -0
- package/types/file.d.ts +344 -0
- package/types/get-tasks-to-modify.d.ts +3 -0
- package/types/index.d.ts +23 -0
- package/types/languages.d.ts +649 -0
- package/types/list.d.ts +30 -0
- package/types/migrate-config.d.ts +1 -0
- package/types/mixins/index.d.ts +2 -0
- package/types/mixins/repo-fs-store.d.ts +2 -0
- package/types/mixins/repo-watched-fs-store.d.ts +2 -0
- package/types/plugins/archive-plugin.d.ts +8 -0
- package/types/plugins/default-board-actions-plugin.d.ts +9 -0
- package/types/plugins/default-board-properties-plugin.d.ts +16 -0
- package/types/plugins/default-plugins.d.ts +6 -0
- package/types/plugins/epic-plugin.d.ts +19 -0
- package/types/plugins/extension-plugin.d.ts +15 -0
- package/types/plugins/index.d.ts +9 -0
- package/types/plugins/persist-tags-plugin.d.ts +4 -0
- package/types/plugins/plugin-manager.d.ts +68 -0
- package/types/plugins/plugin-registry.d.ts +1 -0
- package/types/project-factory.d.ts +14 -0
- package/types/project.d.ts +158 -0
- package/types/repository.d.ts +996 -0
- package/types/task.d.ts +202 -0
- package/types/tools.d.ts +29 -0
- package/types/usecases/actions.d.ts +7 -0
- package/types/usecases/get-imdone-url.d.ts +1 -0
- package/types/usecases/get-project-tags.d.ts +1 -0
- package/types/usecases/get-tasks-in-file.d.ts +4 -0
- package/types/usecases/index.d.ts +4 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Eta } from "eta"
|
|
2
|
+
import uniqid from 'uniqid';
|
|
3
|
+
|
|
4
|
+
function removeDisplayComments(description) {
|
|
5
|
+
return description.replace(/<!--\s*\[([\s\S]*?)\]\s*-->/g, '$1');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function escapeCodeBlocks(content) {
|
|
9
|
+
const codeBlockRegex = /(```[\s\S]*?```|`[^`]*`)/g;
|
|
10
|
+
let codeBlocks = [];
|
|
11
|
+
|
|
12
|
+
// Replace code blocks with unique keys
|
|
13
|
+
content = content.replace(codeBlockRegex, (match) => {
|
|
14
|
+
const key = `__CODE_BLOCK_${codeBlocks.length}__`;
|
|
15
|
+
codeBlocks.push(match); // Maintain order
|
|
16
|
+
return key;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return { content, codeBlocks };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function restoreCodeBlocks(content, codeBlocks) {
|
|
23
|
+
// Restore code blocks in order using stored array
|
|
24
|
+
codeBlocks.forEach((code, index) => {
|
|
25
|
+
content = content.replace(`__CODE_BLOCK_${index}__`, code);
|
|
26
|
+
});
|
|
27
|
+
return content;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Interpolates the content with the provided data.
|
|
32
|
+
* @param {string} content - The content to interpolate.
|
|
33
|
+
* @param {object} data - The data to use for interpolation.
|
|
34
|
+
* @param {object} opts - The options for interpolation.
|
|
35
|
+
* @returns {object} - The interpolated content, whether there was interpolation, and error tokens.
|
|
36
|
+
*/
|
|
37
|
+
export function interpolate(content, data, opts = { tags: ['${', '}'] }) {
|
|
38
|
+
const errorTokens = {};
|
|
39
|
+
const { tags } = opts;
|
|
40
|
+
// /(?<![`\\])\${([\s\S]+?)}/g
|
|
41
|
+
const regex = new RegExp(`(?<![\`\\\\])\\${tags[0]}([\\s\\S]+?)${tags[1]}`, 'g');
|
|
42
|
+
let hasInterpolation = false;
|
|
43
|
+
|
|
44
|
+
content = content.replace(regex, (match, p1) => {
|
|
45
|
+
try {
|
|
46
|
+
const eta = new Eta({ tags, useWith: true, autoEscape: false });
|
|
47
|
+
const [startTag, endTag] = opts.tags;
|
|
48
|
+
const template = `${startTag}= ${p1} ${endTag}`;
|
|
49
|
+
const result = eta.renderString(template, data);
|
|
50
|
+
if (result !== match) {
|
|
51
|
+
hasInterpolation = true;
|
|
52
|
+
}
|
|
53
|
+
return result || '';
|
|
54
|
+
} catch (e) {
|
|
55
|
+
const token = `__ERROR_TOKEN_${uniqid()}__`;
|
|
56
|
+
errorTokens[token] = match;
|
|
57
|
+
return token || '';
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return { content, hasInterpolation, errorTokens };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function encodeMarkdownLinks(content) {
|
|
65
|
+
return content.replace(/(?<!!)\[(.*?)\]\((.*?)( ".*?")?\)/g, (match, p1, p2, p3) => {
|
|
66
|
+
// URI encode file links
|
|
67
|
+
const title = p3 || '';
|
|
68
|
+
const encodedUrl = p2.replace(/\s/g, '%20').replace(/\)/g, '%29').replace(/\(/g, '%28');
|
|
69
|
+
const link = `[${p1}](${encodedUrl}${title})`;
|
|
70
|
+
|
|
71
|
+
// If there is more than one space in the URL, assume there's no title
|
|
72
|
+
if ((p2.match(/\s/g) || []).length > 1) {
|
|
73
|
+
return `[${p1}](${encodedUrl})`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return link.replace(/%20(".*?"\))$/, ' $1');
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function format(content, data, mustache) {
|
|
81
|
+
const errorTokens = {};
|
|
82
|
+
|
|
83
|
+
if (mustache) content = removeDisplayComments(content);
|
|
84
|
+
|
|
85
|
+
// Escape code blocks before processing interpolation
|
|
86
|
+
const { content: escapedContent, codeBlocks } = escapeCodeBlocks(content);
|
|
87
|
+
|
|
88
|
+
let result = escapedContent;
|
|
89
|
+
let hasInterpolation = true;
|
|
90
|
+
|
|
91
|
+
while (hasInterpolation) {
|
|
92
|
+
const interpolationResult = interpolate(result, data, { tags: ['${', '}'] });
|
|
93
|
+
result = interpolationResult.content;
|
|
94
|
+
hasInterpolation = interpolationResult.hasInterpolation;
|
|
95
|
+
Object.assign(errorTokens, interpolationResult.errorTokens);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (mustache) {
|
|
99
|
+
hasInterpolation = true;
|
|
100
|
+
while (hasInterpolation) {
|
|
101
|
+
const interpolationResult = interpolate(result, data, { tags: ['{{', '}}'] });
|
|
102
|
+
result = interpolationResult.content;
|
|
103
|
+
hasInterpolation = interpolationResult.hasInterpolation;
|
|
104
|
+
Object.assign(errorTokens, interpolationResult.errorTokens);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
result = restoreCodeBlocks(result, codeBlocks);
|
|
109
|
+
|
|
110
|
+
// Restore error tokens
|
|
111
|
+
Object.keys(errorTokens).forEach(token => {
|
|
112
|
+
result = result.replace(new RegExp(token, 'g'), errorTokens[token]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs'
|
|
2
|
+
import { ReadStreamMarkdownParser } from './ReadStreamMarkdownParser.js'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { languages } from '../../../languages.js'
|
|
5
|
+
import { ReadStreamCodeParser } from './ReadStreamCodeParser.js'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function getLang(config, filePath) {
|
|
9
|
+
const languages = getLanguages(config)
|
|
10
|
+
var lang = languages[path.extname(filePath)]
|
|
11
|
+
return lang || { name: 'text', symbol: '' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getLanguages(config) {
|
|
15
|
+
const configLanguages = config.languages || {}
|
|
16
|
+
return { ...languages, ...configLanguages }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function getFileParser (filePath, config, readStream) {
|
|
20
|
+
if (!readStream) {
|
|
21
|
+
readStream = createReadStream(filePath)
|
|
22
|
+
}
|
|
23
|
+
const lang = getLang(config, filePath)
|
|
24
|
+
if (lang.name === 'text') {
|
|
25
|
+
return new ReadStreamMarkdownParser(readStream, config)
|
|
26
|
+
} else {
|
|
27
|
+
const parser = new ReadStreamCodeParser(
|
|
28
|
+
readStream,
|
|
29
|
+
filePath,
|
|
30
|
+
lang,
|
|
31
|
+
config.lists
|
|
32
|
+
)
|
|
33
|
+
await parser.init()
|
|
34
|
+
return parser
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { ReadStreamTaskParser } from './ReadStreamTaskParser.js'
|
|
3
|
+
import { CodeStyleTaskParser } from '../task/CodeStyleTaskParser.js'
|
|
4
|
+
import { languages } from '../../../languages.js'
|
|
5
|
+
import extractComments from 'multilang-extract-comments'
|
|
6
|
+
import eol from 'eol'
|
|
7
|
+
import { Readable } from 'node:stream'
|
|
8
|
+
|
|
9
|
+
// TODO Move to readstream parsing for code to improve performance
|
|
10
|
+
// <!--
|
|
11
|
+
// #important
|
|
12
|
+
// #imdone-1.55.0
|
|
13
|
+
// order:-205
|
|
14
|
+
// -->
|
|
15
|
+
export class ReadStreamCodeParser extends ReadStreamTaskParser {
|
|
16
|
+
constructor(readStream, filePath, lang, lists) {
|
|
17
|
+
super(readStream)
|
|
18
|
+
this.lists = lists
|
|
19
|
+
this.filePath = filePath
|
|
20
|
+
this.lang = lang
|
|
21
|
+
this.taskParsers = [new CodeStyleTaskParser()]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init() {
|
|
25
|
+
const code = await streamToString(this.readStream)
|
|
26
|
+
// TODO Getting tokens from lists should be done higher up in the controller
|
|
27
|
+
// <!--
|
|
28
|
+
// order:-10
|
|
29
|
+
// -->
|
|
30
|
+
const tokens = this.lists.filter(list => !list.filter).map(list => list.name)
|
|
31
|
+
this.cards = this.parseCards(code, tokens)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async readTask() {
|
|
35
|
+
return this.cards.shift()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
parseCards(code, tokens = ['TODO']) {
|
|
39
|
+
const comments = this.parseComments(code);
|
|
40
|
+
const cards = [];
|
|
41
|
+
|
|
42
|
+
Object.keys(comments).forEach((comment) => {
|
|
43
|
+
const { begin, end, content } = comments[comment];
|
|
44
|
+
const line = begin;
|
|
45
|
+
const lastLine = end; // TODO lastLine should not be - 1
|
|
46
|
+
// <!--
|
|
47
|
+
// order:-140
|
|
48
|
+
// -->
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
|
|
51
|
+
// Check if the comment contains "TODO"
|
|
52
|
+
lines.forEach(codeLine => {
|
|
53
|
+
if (tokens.find((token) => codeLine.includes(token))) {
|
|
54
|
+
// Get all lines from start to end
|
|
55
|
+
|
|
56
|
+
cards.push({
|
|
57
|
+
content,
|
|
58
|
+
line,
|
|
59
|
+
lastLine,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
return cards;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
parseComments(code) {
|
|
68
|
+
const options = getLanguageOptions(this.filePath);
|
|
69
|
+
const comments = extractComments(code, options);
|
|
70
|
+
return comments;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
function getLanguageOptions(filePath) {
|
|
77
|
+
const extension = path.extname(filePath)
|
|
78
|
+
const language = languages[extension]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const {
|
|
82
|
+
name,
|
|
83
|
+
symbol,
|
|
84
|
+
block,
|
|
85
|
+
} = language ||
|
|
86
|
+
{
|
|
87
|
+
name: 'text',
|
|
88
|
+
symbol: '',
|
|
89
|
+
block: {
|
|
90
|
+
start: undefined,
|
|
91
|
+
end: undefined,
|
|
92
|
+
ignore: undefined,
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
// Use lang to get comment patterns
|
|
96
|
+
|
|
97
|
+
const {
|
|
98
|
+
start,
|
|
99
|
+
ignore: middle,
|
|
100
|
+
end
|
|
101
|
+
} = block || {};
|
|
102
|
+
|
|
103
|
+
return language && {
|
|
104
|
+
pattern: {
|
|
105
|
+
name,
|
|
106
|
+
nameMatchers: extension && [extension] || [],
|
|
107
|
+
singleLineComment: symbol && [{ start: symbol }] || [],
|
|
108
|
+
multiLineComment: start && end && [{ start, middle, end }] || [],
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function streamToString(stream) {
|
|
114
|
+
const chunks = []
|
|
115
|
+
|
|
116
|
+
for await (const chunk of stream) {
|
|
117
|
+
chunks.push(Buffer.from(chunk))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return Buffer.concat(chunks).toString('utf-8')
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function stringToStream(string) {
|
|
124
|
+
const lines = eol.split(string).map((line) => line + String(eol.auto))
|
|
125
|
+
return Readable.from(lines)
|
|
126
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ReadStreamTaskParser } from './ReadStreamTaskParser.js'
|
|
2
|
+
import { HashStyleTaskParser } from '../task/HashStyleTaskParser.js'
|
|
3
|
+
import { MarkdownStyleTaskParser } from '../task/MarkdownStyleTaskParser.js'
|
|
4
|
+
// const CheckStyleTaskParser = require('../task/CheckStyleTaskParser')
|
|
5
|
+
|
|
6
|
+
export class ReadStreamMarkdownParser extends ReadStreamTaskParser {
|
|
7
|
+
constructor(readStream, config) {
|
|
8
|
+
super(readStream, config)
|
|
9
|
+
this.taskParsers = [
|
|
10
|
+
new HashStyleTaskParser(config),
|
|
11
|
+
new MarkdownStyleTaskParser(config),
|
|
12
|
+
]
|
|
13
|
+
// if (config.isAddCheckBoxTasks()) {
|
|
14
|
+
// this.taskParsers.push(new CheckStyleTaskParser())
|
|
15
|
+
// }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as ReadLine from 'node:readline/promises';
|
|
2
|
+
import { logger } from '../../logger.js';
|
|
3
|
+
|
|
4
|
+
const blankLinesToEndTask = 2
|
|
5
|
+
|
|
6
|
+
export class ReadStreamTaskParser {
|
|
7
|
+
constructor(readStream) {
|
|
8
|
+
this.readStream = readStream
|
|
9
|
+
this.task = null
|
|
10
|
+
this.line = null
|
|
11
|
+
this.taskParsers = []
|
|
12
|
+
this.lineNo = 0
|
|
13
|
+
this.blankLines = 0
|
|
14
|
+
this.readInterface = ReadLine.createInterface({
|
|
15
|
+
input: this.readStream,
|
|
16
|
+
crlfDelay: Infinity,
|
|
17
|
+
console: false,
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async readTask() {
|
|
22
|
+
let taskOrStreamDone = false
|
|
23
|
+
this.task = this.nextTask || null
|
|
24
|
+
this.blankLines = 0
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
while (!taskOrStreamDone) {
|
|
28
|
+
this.line = await this.readLine()
|
|
29
|
+
let { value, done } = this.line
|
|
30
|
+
if (this.line && !done) {
|
|
31
|
+
this.lineNo++
|
|
32
|
+
const endOfTask = this.isEndOfTask(value)
|
|
33
|
+
if (this.task && endOfTask) {
|
|
34
|
+
taskOrStreamDone = true
|
|
35
|
+
} else if (this.task) {
|
|
36
|
+
this.addLineToTask(value)
|
|
37
|
+
} else {
|
|
38
|
+
this.task = this.getTaskFromLine(value)
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
taskOrStreamDone = true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.log('Error reading line')
|
|
46
|
+
throw error
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.trimLinesToEndTask()
|
|
50
|
+
return this.task
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async readLine() {
|
|
54
|
+
return this.readInterface[Symbol.asyncIterator]().next()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
isEndOfTask(line) {
|
|
58
|
+
if (line.trim() === '') {
|
|
59
|
+
this.blankLines++
|
|
60
|
+
} else {
|
|
61
|
+
this.blankLines = 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.nextTask = this.getTaskFromLine(line)
|
|
65
|
+
|
|
66
|
+
if (this.blankLines > blankLinesToEndTask - 1) {
|
|
67
|
+
this.nextTask = undefined
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.nextTask) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getTaskFromLine(line) {
|
|
79
|
+
let task
|
|
80
|
+
this.taskParsers.find((taskParser) => {
|
|
81
|
+
task = taskParser.parseLine(line, this.lineNo, this.task)
|
|
82
|
+
return task
|
|
83
|
+
})
|
|
84
|
+
return task
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
addLineToTask(line) {
|
|
88
|
+
this.task.description.push(line)
|
|
89
|
+
this.task.lastLine = this.lineNo
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
trimLinesToEndTask() {
|
|
93
|
+
if (!this.task) return
|
|
94
|
+
|
|
95
|
+
// Remove the empty strings from the end of the task description
|
|
96
|
+
while (this.task.description[this.task.description.length - 1] === '') {
|
|
97
|
+
this.task.description.pop()
|
|
98
|
+
}
|
|
99
|
+
this.task.lastLine = this.task.line + this.task.description.length
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
close() {
|
|
103
|
+
this.readInterface.close()
|
|
104
|
+
if (this.readStream.close) this.readStream.close()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function getFunctionSignature(fn) {
|
|
2
|
+
if (typeof fn !== "function") {
|
|
3
|
+
throw new TypeError("Expected a function");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const fnString = fn.toString().trim();
|
|
7
|
+
let name = fn.name || "anonymous";
|
|
8
|
+
let params = "";
|
|
9
|
+
|
|
10
|
+
// Match function parameters
|
|
11
|
+
const paramMatch = fnString.match(/\(([^)]*)\)/);
|
|
12
|
+
if (paramMatch) {
|
|
13
|
+
params = paramMatch[1].trim();
|
|
14
|
+
} else {
|
|
15
|
+
// Handle arrow functions without parentheses
|
|
16
|
+
const arrowMatch = fnString.match(/^(?:\S+\s+)?(\w+)?\s*=?\s*(?:async\s*)?\(?([^)=]*)\)?\s*=>/);
|
|
17
|
+
if (arrowMatch) {
|
|
18
|
+
name = name || arrowMatch[1] || "anonymous";
|
|
19
|
+
params = arrowMatch[2].trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return `${name}(${params})`;
|
|
24
|
+
}
|
|
25
|
+
|