harmonyc 0.18.0 → 0.18.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/cli/cli.d.ts +2 -0
- package/cli/cli.js +32 -0
- package/cli/run.d.ts +2 -0
- package/cli/run.js +21 -0
- package/cli/watch.d.ts +1 -0
- package/cli/watch.js +35 -0
- package/code_generator/VitestGenerator.d.ts +32 -0
- package/code_generator/VitestGenerator.js +221 -0
- package/code_generator/outFile.d.ts +20 -0
- package/code_generator/outFile.js +60 -0
- package/code_generator/test_phrases.d.ts +11 -0
- package/code_generator/test_phrases.js +27 -0
- package/compiler/compile.d.ts +9 -0
- package/compiler/compile.js +32 -0
- package/compiler/compiler.d.ts +10 -0
- package/compiler/compiler.js +53 -0
- package/filenames/filenames.d.ts +3 -0
- package/filenames/filenames.js +17 -0
- package/model/Router.d.ts +22 -0
- package/model/Router.js +54 -0
- package/model/model.d.ts +217 -0
- package/model/model.js +526 -0
- package/optimizations/autoLabel/autoLabel.d.ts +2 -0
- package/optimizations/autoLabel/autoLabel.js +19 -0
- package/package.json +1 -4
- package/parser/lexer.d.ts +21 -0
- package/parser/lexer.js +123 -0
- package/parser/lexer_rules.d.ts +33 -0
- package/parser/lexer_rules.js +74 -0
- package/parser/parser.d.ts +18 -0
- package/parser/parser.js +76 -0
- package/util/indent.d.ts +1 -0
- package/util/indent.js +5 -0
- package/util/iterators.d.ts +1 -0
- package/util/iterators.js +5 -0
- package/util/xmur3.d.ts +1 -0
- package/util/xmur3.js +12 -0
- package/vitest/index.d.ts +9 -0
- package/vitest/index.js +84 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare enum T {
|
|
2
|
+
Newline = "newline",
|
|
3
|
+
Comment = "comment",
|
|
4
|
+
/** the => */
|
|
5
|
+
ResponseArrow = "=>",
|
|
6
|
+
ErrorMark = "!!",
|
|
7
|
+
Words = "words",
|
|
8
|
+
Minus = "-",
|
|
9
|
+
Plus = "+",
|
|
10
|
+
Colon = ":",
|
|
11
|
+
Space = "space",
|
|
12
|
+
OpeningBracket = "[",
|
|
13
|
+
ClosingBracket = "]",
|
|
14
|
+
OpeningBrace = "{",
|
|
15
|
+
ClosingBrace = "}",
|
|
16
|
+
And = "&",
|
|
17
|
+
Slash = "/",
|
|
18
|
+
Semicolon = ";",
|
|
19
|
+
DoubleQuoteString = "double-quote string",
|
|
20
|
+
UnclosedDoubleQuoteString = "unclosed double-quote string",
|
|
21
|
+
BacktickString = "backtick string",
|
|
22
|
+
UnclosedBacktickString = "unclosed backtick string",
|
|
23
|
+
InvalidEmptyBacktickString = "invalid empty backtick string",
|
|
24
|
+
InvalidWhitespace = "invalid whitespace",
|
|
25
|
+
InvalidTab = "invalid tab",
|
|
26
|
+
MultilineString = "multiline string",
|
|
27
|
+
InvalidMultilineStringMark = "invalid multiline string mark",
|
|
28
|
+
Variable = "variable",
|
|
29
|
+
InvalidEmptyVariable = "invalid empty variable",
|
|
30
|
+
UnclosedVariable = "unclosed variable"
|
|
31
|
+
}
|
|
32
|
+
declare const rules: [boolean, RegExp, T][];
|
|
33
|
+
export default rules;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export var T;
|
|
2
|
+
(function (T) {
|
|
3
|
+
T["Newline"] = "newline";
|
|
4
|
+
T["Comment"] = "comment";
|
|
5
|
+
/** the => */
|
|
6
|
+
T["ResponseArrow"] = "=>";
|
|
7
|
+
T["ErrorMark"] = "!!";
|
|
8
|
+
T["Words"] = "words";
|
|
9
|
+
T["Minus"] = "-";
|
|
10
|
+
T["Plus"] = "+";
|
|
11
|
+
T["Colon"] = ":";
|
|
12
|
+
T["Space"] = "space";
|
|
13
|
+
T["OpeningBracket"] = "[";
|
|
14
|
+
T["ClosingBracket"] = "]";
|
|
15
|
+
T["OpeningBrace"] = "{";
|
|
16
|
+
T["ClosingBrace"] = "}";
|
|
17
|
+
T["And"] = "&";
|
|
18
|
+
T["Slash"] = "/";
|
|
19
|
+
T["Semicolon"] = ";";
|
|
20
|
+
T["DoubleQuoteString"] = "double-quote string";
|
|
21
|
+
T["UnclosedDoubleQuoteString"] = "unclosed double-quote string";
|
|
22
|
+
T["BacktickString"] = "backtick string";
|
|
23
|
+
T["UnclosedBacktickString"] = "unclosed backtick string";
|
|
24
|
+
T["InvalidEmptyBacktickString"] = "invalid empty backtick string";
|
|
25
|
+
T["InvalidWhitespace"] = "invalid whitespace";
|
|
26
|
+
T["InvalidTab"] = "invalid tab";
|
|
27
|
+
T["MultilineString"] = "multiline string";
|
|
28
|
+
T["InvalidMultilineStringMark"] = "invalid multiline string mark";
|
|
29
|
+
T["Variable"] = "variable";
|
|
30
|
+
T["InvalidEmptyVariable"] = "invalid empty variable";
|
|
31
|
+
T["UnclosedVariable"] = "unclosed variable";
|
|
32
|
+
})(T || (T = {}));
|
|
33
|
+
const rules = [
|
|
34
|
+
// false = ignore token
|
|
35
|
+
// if multiple patterns match, the former wins
|
|
36
|
+
// patterns must be y (sticky)
|
|
37
|
+
[true, /\n/y, T.Newline],
|
|
38
|
+
[true, /\t/y, T.InvalidTab],
|
|
39
|
+
[true, /[\x00-\x1f]/y, T.InvalidWhitespace],
|
|
40
|
+
[true, /^( )*[+]( |$)/my, T.Plus],
|
|
41
|
+
[true, /^( )*[-]( |$)/my, T.Minus],
|
|
42
|
+
[false, / /y, T.Space],
|
|
43
|
+
[false, /(#|\/\/).*?(?=\n|$)/y, T.Comment],
|
|
44
|
+
[true, /:(?=\s*(?:\n|$))/y, T.Colon],
|
|
45
|
+
[true, /\[/y, T.OpeningBracket],
|
|
46
|
+
[true, /\]/y, T.ClosingBracket],
|
|
47
|
+
[true, /\{/y, T.OpeningBrace],
|
|
48
|
+
[true, /\}/y, T.ClosingBrace],
|
|
49
|
+
[true, /\//y, T.Slash],
|
|
50
|
+
[true, /&/y, T.And],
|
|
51
|
+
[true, /;/y, T.Semicolon],
|
|
52
|
+
[true, /!!/y, T.ErrorMark],
|
|
53
|
+
[true, /=>/y, T.ResponseArrow],
|
|
54
|
+
[
|
|
55
|
+
true,
|
|
56
|
+
/"(?:[^"\\\n]|\\(?:[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/y,
|
|
57
|
+
T.DoubleQuoteString,
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
true,
|
|
61
|
+
/"(?:[^"\\\n]|\\(?:[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*/y,
|
|
62
|
+
T.UnclosedDoubleQuoteString,
|
|
63
|
+
],
|
|
64
|
+
[true, /``/y, T.InvalidEmptyBacktickString],
|
|
65
|
+
[true, /`[^`]+`/y, T.BacktickString],
|
|
66
|
+
[true, /`[^`]*/y, T.UnclosedBacktickString],
|
|
67
|
+
[true, /\$\{[^}\n]+\}/y, T.Variable],
|
|
68
|
+
[true, /\$\{\}/y, T.InvalidEmptyVariable],
|
|
69
|
+
[true, /\$\{[^}\n]*/y, T.UnclosedVariable],
|
|
70
|
+
[true, /\|(?: .*|(?=\n|$))/y, T.MultilineString],
|
|
71
|
+
[true, /\|[^ \n]/y, T.InvalidMultilineStringMark],
|
|
72
|
+
[true, /.+?(?=[\[\]"`|#{}&/;]|\$\{|$|=>|!!|:\s*$)/my, T.Words],
|
|
73
|
+
];
|
|
74
|
+
export default rules;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Parser, ParserOutput, Token } from 'typescript-parsec';
|
|
2
|
+
import { Action, CodeLiteral, Docstring, ErrorResponse, Label, Response, Section, SetVariable, Step, StringLiteral, Switch, Word } from '../model/model.ts';
|
|
3
|
+
import { T } from './lexer.ts';
|
|
4
|
+
export declare function parse(input: string): Section;
|
|
5
|
+
export declare function parse<T>(input: string, production: Parser<any, T>): T;
|
|
6
|
+
export declare const NEWLINES: Parser<T, Token<T>[]>, WORDS: Parser<T, Word>, DOUBLE_QUOTE_STRING: Parser<T, StringLiteral>, BACKTICK_STRING: Parser<T, CodeLiteral>, DOCSTRING: Parser<T, Docstring>, ERROR_MARK: Parser<T, Token<T>>, VARIABLE: Parser<T, string>, SIMPLE_PART: Parser<T, Word | StringLiteral | Docstring | CodeLiteral>, SWITCH: Parser<T, Switch>, BRACES: Parser<T, Switch>, PART: Parser<T, Word | Switch | StringLiteral | Docstring | CodeLiteral>, PHRASE: Parser<T, (Word | Switch | StringLiteral | Docstring | CodeLiteral)[]>, ARG: Parser<T, StringLiteral | Docstring | CodeLiteral>, SET_VARIABLE: Parser<T, SetVariable>, ACTION: Parser<T, Action | SetVariable>, RESPONSE: Parser<T, Response>, ERROR_RESPONSE: Parser<T, ErrorResponse>, SAVE_TO_VARIABLE: Parser<T, Response>, ARROW: Parser<T, Token<T>>, RESPONSE_ITEM: Parser<T, Response | ErrorResponse>, STEP: Parser<T, Step>, LABEL: Parser<T, Label>, SECTION: Parser<T, Section>, BRANCH: Parser<T, Section | Step>, // section first, to make sure there is no colon after step
|
|
7
|
+
DENTS: Parser<T, {
|
|
8
|
+
dent: number;
|
|
9
|
+
isFork: boolean;
|
|
10
|
+
}>, NODE: Parser<T, {
|
|
11
|
+
dent: number;
|
|
12
|
+
branch: Section | Step;
|
|
13
|
+
}>, ANYTHING_BUT_NEWLINE: {
|
|
14
|
+
parse(token: Token<T> | undefined): ParserOutput<T, Token<T>>;
|
|
15
|
+
}, TEXT: Parser<T, undefined>, LINE: Parser<T, {
|
|
16
|
+
dent: number;
|
|
17
|
+
branch: Section | Step;
|
|
18
|
+
} | undefined>, TEST_DESIGN: Parser<T, Section>;
|
package/parser/parser.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { alt_sc, apply, expectEOF, expectSingleResult, fail, kleft, kmid, kright, list_sc, nil, opt_sc, rep_sc, seq, tok, unableToConsumeToken, } from 'typescript-parsec';
|
|
2
|
+
import { Action, CodeLiteral, Docstring, ErrorResponse, Label, Response, SaveToVariable, Section, SetVariable, Step, StringLiteral, Switch, Word, } from "../model/model.js";
|
|
3
|
+
import { T, lexer } from "./lexer.js";
|
|
4
|
+
export function parse(input, production = TEST_DESIGN) {
|
|
5
|
+
const tokens = lexer.parse(input);
|
|
6
|
+
return expectSingleResult(expectEOF(production.parse(tokens)));
|
|
7
|
+
}
|
|
8
|
+
function anythingBut(kind) {
|
|
9
|
+
return {
|
|
10
|
+
parse(token) {
|
|
11
|
+
if (token === undefined)
|
|
12
|
+
return { successful: false, error: unableToConsumeToken(token) };
|
|
13
|
+
if (token.kind === kind)
|
|
14
|
+
return { successful: false, error: unableToConsumeToken(token) };
|
|
15
|
+
return {
|
|
16
|
+
candidates: [
|
|
17
|
+
{
|
|
18
|
+
firstToken: token,
|
|
19
|
+
nextToken: token.next,
|
|
20
|
+
result: token,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
successful: true,
|
|
24
|
+
error: undefined,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const NEWLINES = list_sc(tok(T.Newline), nil()), WORDS = apply(tok(T.Words), ({ text }) => new Word(text.trimEnd().split(/\s+/).join(' '))), DOUBLE_QUOTE_STRING = alt_sc(apply(tok(T.DoubleQuoteString), ({ text }) => new StringLiteral(JSON.parse(text))), seq(tok(T.UnclosedDoubleQuoteString), fail('unclosed double-quote string'))), BACKTICK_STRING = apply(tok(T.BacktickString), ({ text }) => new CodeLiteral(text.slice(1, -1))), DOCSTRING = kright(opt_sc(NEWLINES), apply(list_sc(tok(T.MultilineString), tok(T.Newline)), (lines) => new Docstring(lines.map(({ text }) => text.slice(2)).join('\n')))), ERROR_MARK = tok(T.ErrorMark), VARIABLE = apply(tok(T.Variable), ({ text }) => text.slice(2, -1)), SIMPLE_PART = alt_sc(WORDS, DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING),
|
|
30
|
+
//REPEATER = apply(
|
|
31
|
+
//list_sc(rep_sc(SIMPLE_PART), tok(T.And)),
|
|
32
|
+
//(cs) => new Repeater(cs)
|
|
33
|
+
//),
|
|
34
|
+
SWITCH = apply(list_sc(SIMPLE_PART, tok(T.Slash)), (cs) => new Switch(cs)),
|
|
35
|
+
//ROUTER = apply(list_sc(REPEATER, tok(T.Semicolon)), (cs) => new Router(cs)),
|
|
36
|
+
BRACES = kmid(tok(T.OpeningBrace), SWITCH, tok(T.ClosingBrace)), PART = alt_sc(SIMPLE_PART, BRACES), PHRASE = rep_sc(PART), ARG = alt_sc(DOUBLE_QUOTE_STRING, BACKTICK_STRING, DOCSTRING), SET_VARIABLE = apply(seq(VARIABLE, ARG), ([variable, value]) => new SetVariable(variable, value)), ACTION = alt_sc(SET_VARIABLE, apply(PHRASE, (parts, range) => new Action(parts).at(range))), RESPONSE = apply(seq(PHRASE, opt_sc(VARIABLE)), ([parts, variable], range) => new Response(parts, variable !== undefined ? new SaveToVariable(variable) : undefined).at(range)), ERROR_RESPONSE = apply(seq(ERROR_MARK, opt_sc(alt_sc(DOUBLE_QUOTE_STRING, DOCSTRING))), ([, parts]) => new ErrorResponse(parts)), SAVE_TO_VARIABLE = apply(VARIABLE, (variable) => new Response([], new SaveToVariable(variable))), ARROW = kright(opt_sc(NEWLINES), tok(T.ResponseArrow)), RESPONSE_ITEM = kright(ARROW, alt_sc(SAVE_TO_VARIABLE, ERROR_RESPONSE, RESPONSE)), STEP = apply(seq(ACTION, rep_sc(RESPONSE_ITEM)), ([action, responses]) => new Step(action, responses).setFork(true)), LABEL = apply(kleft(list_sc(PART, nil()), tok(T.Colon)), (words, range) => new Label(words.map((w) => w.toString()).join(' ')).at(range)), SECTION = apply(LABEL, (text) => new Section(text)), BRANCH = apply(alt_sc(SECTION, STEP), (branch, range) => branch.at(range)), // section first, to make sure there is no colon after step
|
|
37
|
+
DENTS = apply(alt_sc(tok(T.Plus), tok(T.Minus)), (seqOrFork) => {
|
|
38
|
+
return {
|
|
39
|
+
dent: (seqOrFork.text.length - 2) / 2,
|
|
40
|
+
isFork: seqOrFork.kind === T.Plus,
|
|
41
|
+
};
|
|
42
|
+
}), NODE = apply(seq(DENTS, BRANCH), ([{ dent, isFork }, branch], [start, end]) => ({
|
|
43
|
+
dent,
|
|
44
|
+
branch: branch.setFork(isFork),
|
|
45
|
+
})), ANYTHING_BUT_NEWLINE = anythingBut(T.Newline), TEXT = apply(seq(tok(T.Words), rep_sc(ANYTHING_BUT_NEWLINE)), () => undefined), LINE = alt_sc(NODE, TEXT), TEST_DESIGN = kmid(rep_sc(NEWLINES), apply(opt_sc(list_sc(apply(LINE, (line, [start, end]) => ({ line, start, end })), NEWLINES)), (lines) => {
|
|
46
|
+
let dent;
|
|
47
|
+
const root = new Section(new Label(''));
|
|
48
|
+
let parent = root;
|
|
49
|
+
for (const { line, start } of lines !== null && lines !== void 0 ? lines : []) {
|
|
50
|
+
if (line === undefined)
|
|
51
|
+
continue;
|
|
52
|
+
const { dent: d, branch } = line;
|
|
53
|
+
if (dent === undefined) {
|
|
54
|
+
if (d !== 0)
|
|
55
|
+
throw new Error(`invalid indent ${d} at line ${start.pos.rowBegin}: first step must not be indented`);
|
|
56
|
+
dent = 0;
|
|
57
|
+
}
|
|
58
|
+
else if (Math.round(d) !== d) {
|
|
59
|
+
throw new Error(`invalid odd indent of ${d * 2} at line ${start.pos.rowBegin}`);
|
|
60
|
+
}
|
|
61
|
+
else if (d > dent + 1) {
|
|
62
|
+
throw new Error(`invalid indent ${d} at line ${start.pos.rowBegin}`);
|
|
63
|
+
}
|
|
64
|
+
else if (d === dent + 1) {
|
|
65
|
+
parent = parent.children[parent.children.length - 1];
|
|
66
|
+
++dent;
|
|
67
|
+
}
|
|
68
|
+
else
|
|
69
|
+
while (d < dent) {
|
|
70
|
+
parent = parent.parent;
|
|
71
|
+
--dent;
|
|
72
|
+
}
|
|
73
|
+
parent.addChild(branch);
|
|
74
|
+
}
|
|
75
|
+
return root;
|
|
76
|
+
}), rep_sc(NEWLINES));
|
package/util/indent.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const Indent: (n: number) => (lines: Iterable<string>) => Generator<string, void, unknown>;
|
package/util/indent.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function flatMap<T, R>(iterable: Iterable<T>, callback: (value: T) => Iterable<R>): Generator<R, void, undefined>;
|
package/util/xmur3.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function xmur3(str: string): () => number;
|
package/util/xmur3.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// https://stackoverflow.com/revisions/47593316/25 by bryc (github.com/bryc)
|
|
2
|
+
export function xmur3(str) {
|
|
3
|
+
let h = 1779033703 ^ str.length;
|
|
4
|
+
for (let i = 0; i < str.length; i++)
|
|
5
|
+
(h = Math.imul(h ^ str.charCodeAt(i), 3432918353)),
|
|
6
|
+
(h = (h << 13) | (h >>> 19));
|
|
7
|
+
return function () {
|
|
8
|
+
h = Math.imul(h ^ (h >>> 16), 2246822507);
|
|
9
|
+
h = Math.imul(h ^ (h >>> 13), 3266489909);
|
|
10
|
+
return (h ^= h >>> 16) >>> 0;
|
|
11
|
+
};
|
|
12
|
+
}
|
package/vitest/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import c from 'tinyrainbow';
|
|
2
|
+
import { compileFeature } from "../compiler/compile.js";
|
|
3
|
+
import { preprocess } from "../compiler/compiler.js";
|
|
4
|
+
export default function harmonyPlugin({} = {}) {
|
|
5
|
+
return {
|
|
6
|
+
name: 'harmony',
|
|
7
|
+
resolveId(id) {
|
|
8
|
+
if (id.endsWith('.harmony')) {
|
|
9
|
+
return id;
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
transform(code, id, options) {
|
|
13
|
+
if (!id.endsWith('.harmony'))
|
|
14
|
+
return null;
|
|
15
|
+
code = preprocess(code);
|
|
16
|
+
const { outFile } = compileFeature(id, code);
|
|
17
|
+
return {
|
|
18
|
+
code: outFile.valueWithoutSourceMap,
|
|
19
|
+
map: outFile.sourceMap,
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
config(config) {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
var _c;
|
|
25
|
+
(_a = config.test) !== null && _a !== void 0 ? _a : (config.test = {});
|
|
26
|
+
(_b = (_c = config.test).reporters) !== null && _b !== void 0 ? _b : (_c.reporters = ['default']);
|
|
27
|
+
if (!Array.isArray(config.test.reporters)) {
|
|
28
|
+
config.test.reporters = [config.test.reporters];
|
|
29
|
+
}
|
|
30
|
+
config.test.reporters.splice(0, 0, new HarmonyReporter());
|
|
31
|
+
},
|
|
32
|
+
// This has been removed in favor of using transform, so no need to generate an actual file
|
|
33
|
+
// async configureServer(server) {
|
|
34
|
+
// const isWatchMode = server.config.server.watch !== null
|
|
35
|
+
// const patterns = [`${watchDir}/**/*.harmony`]
|
|
36
|
+
// if (isWatchMode) {
|
|
37
|
+
// await watchFiles(patterns)
|
|
38
|
+
// } else {
|
|
39
|
+
// await compileFiles(patterns)
|
|
40
|
+
// }
|
|
41
|
+
// },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
class HarmonyReporter {
|
|
45
|
+
onInit(ctx) {
|
|
46
|
+
this.ctx = ctx;
|
|
47
|
+
}
|
|
48
|
+
onCollected(files) {
|
|
49
|
+
this.files = files;
|
|
50
|
+
}
|
|
51
|
+
onTaskUpdate(packs) {
|
|
52
|
+
if (this.files)
|
|
53
|
+
for (const file of this.files)
|
|
54
|
+
addPhrases(file);
|
|
55
|
+
}
|
|
56
|
+
onFinished(files, errors) {
|
|
57
|
+
for (const file of files)
|
|
58
|
+
addPhrases(file);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function addPhrases(task, depth = 2) {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
if ('tasks' in task) {
|
|
64
|
+
for (const child of task.tasks)
|
|
65
|
+
addPhrases(child, depth + 1);
|
|
66
|
+
}
|
|
67
|
+
else if (task.type === 'test' &&
|
|
68
|
+
((_a = task.result) === null || _a === void 0 ? void 0 : _a.state) === 'fail' &&
|
|
69
|
+
((_b = task.meta) === null || _b === void 0 ? void 0 : _b.hasOwnProperty('phrases')) &&
|
|
70
|
+
task.meta.phrases.length > 0) {
|
|
71
|
+
const x = task;
|
|
72
|
+
x.name +=
|
|
73
|
+
'\n' +
|
|
74
|
+
task.meta
|
|
75
|
+
.phrases.map((step, i, a) => {
|
|
76
|
+
const indent = ' '.repeat(depth);
|
|
77
|
+
const failed = i === a.length - 1;
|
|
78
|
+
const figure = failed ? c.red('×') : c.green('✓');
|
|
79
|
+
return `${indent}${figure} ${step}`;
|
|
80
|
+
})
|
|
81
|
+
.join('\n');
|
|
82
|
+
delete task.meta.phrases; // to make sure not to add them again
|
|
83
|
+
}
|
|
84
|
+
}
|