perttool 0.1.0 → 0.3.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/CHANGELOG.md +112 -33
- package/README.md +206 -111
- package/dist/analysis/temporal-deadline.d.ts +59 -0
- package/dist/analysis/temporal-deadline.d.ts.map +1 -0
- package/dist/analysis/temporal-deadline.js +387 -0
- package/dist/analysis/temporal-deadline.js.map +1 -0
- package/dist/analysis/temporal-precedence.d.ts +45 -0
- package/dist/analysis/temporal-precedence.d.ts.map +1 -0
- package/dist/analysis/temporal-precedence.js +123 -0
- package/dist/analysis/temporal-precedence.js.map +1 -0
- package/dist/analysis/temporal-resource.d.ts +66 -0
- package/dist/analysis/temporal-resource.d.ts.map +1 -0
- package/dist/analysis/temporal-resource.js +292 -0
- package/dist/analysis/temporal-resource.js.map +1 -0
- package/dist/application/analyze.js +5 -5
- package/dist/application/analyze.js.map +1 -1
- package/dist/application/check.d.ts +16 -0
- package/dist/application/check.d.ts.map +1 -1
- package/dist/application/check.js +51 -0
- package/dist/application/check.js.map +1 -1
- package/dist/application/contract4.d.ts +17 -0
- package/dist/application/contract4.d.ts.map +1 -0
- package/dist/application/contract4.js +27 -0
- package/dist/application/contract4.js.map +1 -0
- package/dist/application/init.d.ts +43 -0
- package/dist/application/init.d.ts.map +1 -0
- package/dist/application/init.js +226 -0
- package/dist/application/init.js.map +1 -0
- package/dist/application/mutate.d.ts +19 -0
- package/dist/application/mutate.d.ts.map +1 -1
- package/dist/application/mutate.js +47 -25
- package/dist/application/mutate.js.map +1 -1
- package/dist/application/next.d.ts +2 -0
- package/dist/application/next.d.ts.map +1 -1
- package/dist/application/next.js +7 -1
- package/dist/application/next.js.map +1 -1
- package/dist/application/project.d.ts +3 -1
- package/dist/application/project.d.ts.map +1 -1
- package/dist/application/project.js +2 -1
- package/dist/application/project.js.map +1 -1
- package/dist/application/target-check.d.ts +34 -0
- package/dist/application/target-check.d.ts.map +1 -0
- package/dist/application/target-check.js +90 -0
- package/dist/application/target-check.js.map +1 -0
- package/dist/application/target-grammar-boundary.d.ts +22 -0
- package/dist/application/target-grammar-boundary.d.ts.map +1 -0
- package/dist/application/target-grammar-boundary.js +91 -0
- package/dist/application/target-grammar-boundary.js.map +1 -0
- package/dist/application/target-mutate.d.ts +8 -0
- package/dist/application/target-mutate.d.ts.map +1 -0
- package/dist/application/target-mutate.js +56 -0
- package/dist/application/target-mutate.js.map +1 -0
- package/dist/application/target-project.d.ts +28 -0
- package/dist/application/target-project.d.ts.map +1 -0
- package/dist/application/target-project.js +60 -0
- package/dist/application/target-project.js.map +1 -0
- package/dist/application/target-temporal-analysis.d.ts +138 -0
- package/dist/application/target-temporal-analysis.d.ts.map +1 -0
- package/dist/application/target-temporal-analysis.js +409 -0
- package/dist/application/target-temporal-analysis.js.map +1 -0
- package/dist/application/target-temporal-input.d.ts +86 -0
- package/dist/application/target-temporal-input.d.ts.map +1 -0
- package/dist/application/target-temporal-input.js +238 -0
- package/dist/application/target-temporal-input.js.map +1 -0
- package/dist/application/target-unit-migration-candidate.d.ts +60 -0
- package/dist/application/target-unit-migration-candidate.d.ts.map +1 -0
- package/dist/application/target-unit-migration-candidate.js +290 -0
- package/dist/application/target-unit-migration-candidate.js.map +1 -0
- package/dist/application/target-unit-migration-request.d.ts +35 -0
- package/dist/application/target-unit-migration-request.d.ts.map +1 -0
- package/dist/application/target-unit-migration-request.js +68 -0
- package/dist/application/target-unit-migration-request.js.map +1 -0
- package/dist/application/target-unit-migration-result.d.ts +66 -0
- package/dist/application/target-unit-migration-result.d.ts.map +1 -0
- package/dist/application/target-unit-migration-result.js +86 -0
- package/dist/application/target-unit-migration-result.js.map +1 -0
- package/dist/application/unit-migration.d.ts +13 -0
- package/dist/application/unit-migration.d.ts.map +1 -0
- package/dist/application/unit-migration.js +9 -0
- package/dist/application/unit-migration.js.map +1 -0
- package/dist/cli.js +494 -383
- package/dist/cli.js.map +1 -1
- package/dist/command/discovery.d.ts +30 -0
- package/dist/command/discovery.d.ts.map +1 -0
- package/dist/command/discovery.js +752 -0
- package/dist/command/discovery.js.map +1 -0
- package/dist/command/registry.d.ts +84 -0
- package/dist/command/registry.d.ts.map +1 -0
- package/dist/command/registry.js +964 -0
- package/dist/command/registry.js.map +1 -0
- package/dist/command/usage.d.ts +42 -0
- package/dist/command/usage.d.ts.map +1 -0
- package/dist/command/usage.js +337 -0
- package/dist/command/usage.js.map +1 -0
- package/dist/conversion/mermaid-import.js +56 -56
- package/dist/conversion/mermaid-import.js.map +1 -1
- package/dist/conversion/mermaid.js +1 -1
- package/dist/conversion/mermaid.js.map +1 -1
- package/dist/formatter/source-formatter.d.ts +12 -0
- package/dist/formatter/source-formatter.d.ts.map +1 -1
- package/dist/formatter/source-formatter.js +48 -6
- package/dist/formatter/source-formatter.js.map +1 -1
- package/dist/formatter/target-source-formatter.d.ts +8 -0
- package/dist/formatter/target-source-formatter.d.ts.map +1 -0
- package/dist/formatter/target-source-formatter.js +36 -0
- package/dist/formatter/target-source-formatter.js.map +1 -0
- package/dist/help/guide.d.ts +12 -0
- package/dist/help/guide.d.ts.map +1 -0
- package/dist/help/guide.js +103 -0
- package/dist/help/guide.js.map +1 -0
- package/dist/help/registry.d.ts +0 -6
- package/dist/help/registry.d.ts.map +1 -1
- package/dist/help/registry.js +186 -86
- package/dist/help/registry.js.map +1 -1
- package/dist/index.d.ts +20 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/io/safe-write.d.ts +7 -0
- package/dist/io/safe-write.d.ts.map +1 -1
- package/dist/io/safe-write.js +36 -27
- package/dist/io/safe-write.js.map +1 -1
- package/dist/io/target-safe-write.d.ts +7 -0
- package/dist/io/target-safe-write.d.ts.map +1 -0
- package/dist/io/target-safe-write.js +33 -0
- package/dist/io/target-safe-write.js.map +1 -0
- package/dist/migration/conversion.d.ts +40 -0
- package/dist/migration/conversion.d.ts.map +1 -0
- package/dist/migration/conversion.js +106 -0
- package/dist/migration/conversion.js.map +1 -0
- package/dist/migration/grammar-boundary.d.ts +20 -0
- package/dist/migration/grammar-boundary.d.ts.map +1 -0
- package/dist/migration/grammar-boundary.js +92 -0
- package/dist/migration/grammar-boundary.js.map +1 -0
- package/dist/migration/request.d.ts +79 -0
- package/dist/migration/request.d.ts.map +1 -0
- package/dist/migration/request.js +308 -0
- package/dist/migration/request.js.map +1 -0
- package/dist/model/calendar-arithmetic.d.ts +46 -0
- package/dist/model/calendar-arithmetic.d.ts.map +1 -0
- package/dist/model/calendar-arithmetic.js +236 -0
- package/dist/model/calendar-arithmetic.js.map +1 -0
- package/dist/model/calendar.d.ts +22 -0
- package/dist/model/calendar.d.ts.map +1 -0
- package/dist/model/calendar.js +83 -0
- package/dist/model/calendar.js.map +1 -0
- package/dist/model/declaration-fields.d.ts +15 -0
- package/dist/model/declaration-fields.d.ts.map +1 -0
- package/dist/model/declaration-fields.js +54 -0
- package/dist/model/declaration-fields.js.map +1 -0
- package/dist/model/exact-duration-source.d.ts +10 -0
- package/dist/model/exact-duration-source.d.ts.map +1 -0
- package/dist/model/exact-duration-source.js +69 -0
- package/dist/model/exact-duration-source.js.map +1 -0
- package/dist/model/rational.d.ts +2 -2
- package/dist/model/rational.d.ts.map +1 -1
- package/dist/model/rational.js +3 -1
- package/dist/model/rational.js.map +1 -1
- package/dist/model/syntax.d.ts +9 -2
- package/dist/model/syntax.d.ts.map +1 -1
- package/dist/model/syntax.js +20 -4
- package/dist/model/syntax.js.map +1 -1
- package/dist/model/target-calendar.d.ts +25 -0
- package/dist/model/target-calendar.d.ts.map +1 -0
- package/dist/model/target-calendar.js +56 -0
- package/dist/model/target-calendar.js.map +1 -0
- package/dist/mutation/advance.d.ts.map +1 -1
- package/dist/mutation/advance.js +21 -4
- package/dist/mutation/advance.js.map +1 -1
- package/dist/mutation/gate.d.ts +5 -0
- package/dist/mutation/gate.d.ts.map +1 -0
- package/dist/mutation/gate.js +156 -0
- package/dist/mutation/gate.js.map +1 -0
- package/dist/mutation/milestone.d.ts +10 -1
- package/dist/mutation/milestone.d.ts.map +1 -1
- package/dist/mutation/milestone.js +104 -48
- package/dist/mutation/milestone.js.map +1 -1
- package/dist/mutation/project.d.ts +7 -1
- package/dist/mutation/project.d.ts.map +1 -1
- package/dist/mutation/project.js +32 -19
- package/dist/mutation/project.js.map +1 -1
- package/dist/mutation/resource.js +22 -22
- package/dist/mutation/resource.js.map +1 -1
- package/dist/mutation/target-types.d.ts +42 -0
- package/dist/mutation/target-types.d.ts.map +1 -0
- package/dist/mutation/target-types.js +2 -0
- package/dist/mutation/target-types.js.map +1 -0
- package/dist/mutation/task.d.ts +12 -1
- package/dist/mutation/task.d.ts.map +1 -1
- package/dist/mutation/task.js +148 -89
- package/dist/mutation/task.js.map +1 -1
- package/dist/mutation/types.d.ts +34 -3
- package/dist/mutation/types.d.ts.map +1 -1
- package/dist/parser/document-parser.d.ts +14 -0
- package/dist/parser/document-parser.d.ts.map +1 -1
- package/dist/parser/document-parser.js +159 -55
- package/dist/parser/document-parser.js.map +1 -1
- package/dist/recommendation/override-types.d.ts +2 -2
- package/dist/recommendation/override.d.ts +2 -2
- package/dist/recommendation/override.d.ts.map +1 -1
- package/dist/recommendation/override.js +49 -12
- package/dist/recommendation/override.js.map +1 -1
- package/dist/semantic/target-validator.d.ts +36 -0
- package/dist/semantic/target-validator.d.ts.map +1 -0
- package/dist/semantic/target-validator.js +87 -0
- package/dist/semantic/target-validator.js.map +1 -0
- package/dist/semantic/validator.d.ts +2 -0
- package/dist/semantic/validator.d.ts.map +1 -1
- package/dist/semantic/validator.js +90 -45
- package/dist/semantic/validator.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/dist/mutation/task.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { fieldNamed } from "../model/syntax.js";
|
|
2
|
+
import { GRAMMAR_1_DECLARATION_FIELD_ORDER, TARGET_GRAMMAR_2_DECLARATION_FIELD_ORDER, } from "../model/declaration-fields.js";
|
|
3
|
+
import { canonicalizeExactDurationSourceToken, } from "../model/exact-duration-source.js";
|
|
2
4
|
import { mutationDiagnostic } from "./diagnostics.js";
|
|
3
5
|
import { appendDeclarationEdit, contentEndOffset, contentTextEndOffset, deleteDeclarationEdit, deleteFieldEdit, fieldInsertionOffset, insertionText, leadingCommentStart, lineIndexAt, majorLineEnding, serializeTags, serializeTextField, splitPhysicalLines, } from "./source.js";
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
export const ACTIVE_TASK_MUTATION_PROFILE = Object.freeze({
|
|
7
|
+
fieldOrder: GRAMMAR_1_DECLARATION_FIELD_ORDER.task,
|
|
8
|
+
temporalFields: false,
|
|
9
|
+
exactDurations: false,
|
|
10
|
+
});
|
|
11
|
+
export const TARGET_GRAMMAR_2_TASK_MUTATION_PROFILE = Object.freeze({
|
|
12
|
+
fieldOrder: TARGET_GRAMMAR_2_DECLARATION_FIELD_ORDER.task,
|
|
13
|
+
temporalFields: true,
|
|
14
|
+
exactDurations: false,
|
|
15
|
+
});
|
|
16
|
+
export const TARGET_GRAMMAR_3_TASK_MUTATION_PROFILE = Object.freeze({
|
|
17
|
+
fieldOrder: TARGET_GRAMMAR_2_DECLARATION_FIELD_ORDER.task,
|
|
18
|
+
temporalFields: true,
|
|
19
|
+
exactDurations: true,
|
|
20
|
+
});
|
|
19
21
|
const taskStatuses = new Set(["planned", "active", "blocked", "done"]);
|
|
20
|
-
const
|
|
22
|
+
const activeClearableFields = [
|
|
21
23
|
"description",
|
|
22
24
|
"status",
|
|
23
25
|
"priority",
|
|
@@ -26,7 +28,10 @@ const clearableFields = new Set([
|
|
|
26
28
|
"source",
|
|
27
29
|
"tags",
|
|
28
30
|
"requires",
|
|
29
|
-
]
|
|
31
|
+
];
|
|
32
|
+
function fieldRank(profile) {
|
|
33
|
+
return new Map(profile.fieldOrder.map((name, index) => [name, index]));
|
|
34
|
+
}
|
|
30
35
|
function validEstimate(value) {
|
|
31
36
|
if (value === null || typeof value !== "object")
|
|
32
37
|
return false;
|
|
@@ -45,15 +50,16 @@ function validRequirement(value) {
|
|
|
45
50
|
function validStringArray(value) {
|
|
46
51
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
47
52
|
}
|
|
48
|
-
function taskDefinitionError(value) {
|
|
53
|
+
function taskDefinitionError(value, profile) {
|
|
49
54
|
if (value === null || typeof value !== "object")
|
|
50
|
-
return "task definition
|
|
55
|
+
return "task definition is not an object";
|
|
51
56
|
const task = value;
|
|
52
57
|
const knownFields = new Set([
|
|
53
58
|
"title",
|
|
54
59
|
"description",
|
|
55
60
|
"duration",
|
|
56
61
|
"estimate",
|
|
62
|
+
...(profile.temporalFields ? ["notBefore", "deadline"] : []),
|
|
57
63
|
"status",
|
|
58
64
|
"priority",
|
|
59
65
|
"requirements",
|
|
@@ -63,42 +69,48 @@ function taskDefinitionError(value) {
|
|
|
63
69
|
"source",
|
|
64
70
|
]);
|
|
65
71
|
if (Object.keys(task).some((name) => !knownFields.has(name))) {
|
|
66
|
-
return "task definition
|
|
72
|
+
return "task definition contains unsupported fields";
|
|
67
73
|
}
|
|
68
74
|
if (typeof task["title"] !== "string")
|
|
69
|
-
return "task title
|
|
75
|
+
return "task title is not a string";
|
|
70
76
|
if ((task["duration"] === undefined) === (task["estimate"] === undefined)) {
|
|
71
|
-
return "duration
|
|
77
|
+
return "exactly one of duration or estimate is required";
|
|
72
78
|
}
|
|
73
79
|
if (task["duration"] !== undefined && typeof task["duration"] !== "string") {
|
|
74
|
-
return "duration
|
|
80
|
+
return "duration is not a string";
|
|
75
81
|
}
|
|
76
82
|
if (task["estimate"] !== undefined && !validEstimate(task["estimate"])) {
|
|
77
|
-
return "estimate
|
|
83
|
+
return "estimate requires optimistic, mostLikely, and pessimistic string fields";
|
|
78
84
|
}
|
|
79
|
-
for (const name of [
|
|
85
|
+
for (const name of [
|
|
86
|
+
"description",
|
|
87
|
+
"owner",
|
|
88
|
+
"blockedReason",
|
|
89
|
+
"source",
|
|
90
|
+
...(profile.temporalFields ? ["notBefore", "deadline"] : []),
|
|
91
|
+
]) {
|
|
80
92
|
if (task[name] !== undefined && typeof task[name] !== "string") {
|
|
81
|
-
return `${name}
|
|
93
|
+
return `${name} is not a string`;
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
if (task["status"] !== undefined && !taskStatuses.has(task["status"])) {
|
|
85
|
-
return "status
|
|
97
|
+
return "status is not a task status";
|
|
86
98
|
}
|
|
87
99
|
if (task["priority"] !== undefined && !Number.isSafeInteger(task["priority"])) {
|
|
88
|
-
return "priority
|
|
100
|
+
return "priority is not a safe integer";
|
|
89
101
|
}
|
|
90
102
|
if (task["tags"] !== undefined && !validStringArray(task["tags"])) {
|
|
91
|
-
return "tags
|
|
103
|
+
return "tags are not a string array";
|
|
92
104
|
}
|
|
93
105
|
if (task["requirements"] !== undefined &&
|
|
94
106
|
(!Array.isArray(task["requirements"]) || !task["requirements"].every(validRequirement))) {
|
|
95
|
-
return "requirements
|
|
107
|
+
return "requirements must be an array of entries with resourceId and safe integer units";
|
|
96
108
|
}
|
|
97
109
|
return undefined;
|
|
98
110
|
}
|
|
99
111
|
function taskMutationRequestError(value) {
|
|
100
112
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
101
|
-
return "mutation request
|
|
113
|
+
return "mutation request is not an object";
|
|
102
114
|
}
|
|
103
115
|
const request = value;
|
|
104
116
|
const kind = request["kind"];
|
|
@@ -106,10 +118,10 @@ function taskMutationRequestError(value) {
|
|
|
106
118
|
kind !== "task.set" &&
|
|
107
119
|
kind !== "task.remove" &&
|
|
108
120
|
kind !== "task.finish") {
|
|
109
|
-
return "mutation kind
|
|
121
|
+
return "mutation kind is unsupported";
|
|
110
122
|
}
|
|
111
123
|
if (typeof request["id"] !== "string")
|
|
112
|
-
return "mutation id
|
|
124
|
+
return "mutation id is not a string";
|
|
113
125
|
const fieldsByKind = {
|
|
114
126
|
"task.add": new Set(["kind", "id", "from", "to", "task"]),
|
|
115
127
|
"task.set": new Set([
|
|
@@ -128,11 +140,14 @@ function taskMutationRequestError(value) {
|
|
|
128
140
|
"task.finish": new Set(["kind", "id"]),
|
|
129
141
|
};
|
|
130
142
|
if (Object.keys(request).some((name) => !fieldsByKind[kind].has(name))) {
|
|
131
|
-
return `${kind} request
|
|
143
|
+
return `${kind} request contains unsupported fields`;
|
|
132
144
|
}
|
|
133
145
|
return undefined;
|
|
134
146
|
}
|
|
135
|
-
function canonicalDuration(value) {
|
|
147
|
+
function canonicalDuration(value, profile) {
|
|
148
|
+
if (profile.exactDurations) {
|
|
149
|
+
return canonicalizeExactDurationSourceToken(value)?.token ?? value;
|
|
150
|
+
}
|
|
136
151
|
const match = /^(\d+)(?:\.(\d+))?([dhp])$/.exec(value);
|
|
137
152
|
if (match === null)
|
|
138
153
|
return value;
|
|
@@ -140,12 +155,12 @@ function canonicalDuration(value) {
|
|
|
140
155
|
const fraction = (match[2] ?? "").replace(/0+$/, "");
|
|
141
156
|
return `${whole}${fraction === "" ? "" : `.${fraction}`}${match[3]}`;
|
|
142
157
|
}
|
|
143
|
-
function serializeEstimate(estimate, lineEnding) {
|
|
158
|
+
function serializeEstimate(estimate, lineEnding, profile) {
|
|
144
159
|
return [
|
|
145
160
|
" estimate:",
|
|
146
|
-
` optimistic ${canonicalDuration(estimate.optimistic)}`,
|
|
147
|
-
` most_likely ${canonicalDuration(estimate.mostLikely)}`,
|
|
148
|
-
` pessimistic ${canonicalDuration(estimate.pessimistic)}`,
|
|
161
|
+
` optimistic ${canonicalDuration(estimate.optimistic, profile)}`,
|
|
162
|
+
` most_likely ${canonicalDuration(estimate.mostLikely, profile)}`,
|
|
163
|
+
` pessimistic ${canonicalDuration(estimate.pessimistic, profile)}`,
|
|
149
164
|
].join(lineEnding);
|
|
150
165
|
}
|
|
151
166
|
function serializeRequirements(requirements, lineEnding) {
|
|
@@ -154,7 +169,7 @@ function serializeRequirements(requirements, lineEnding) {
|
|
|
154
169
|
...requirements.map(({ resourceId, units }) => ` ${resourceId} ${units}`),
|
|
155
170
|
].join(lineEnding);
|
|
156
171
|
}
|
|
157
|
-
function serializeField(name, value, lineEnding) {
|
|
172
|
+
function serializeField(name, value, lineEnding, profile) {
|
|
158
173
|
switch (name) {
|
|
159
174
|
case "title":
|
|
160
175
|
case "owner":
|
|
@@ -164,9 +179,12 @@ function serializeField(name, value, lineEnding) {
|
|
|
164
179
|
case "blocked_reason":
|
|
165
180
|
return serializeTextField(name, value, lineEnding);
|
|
166
181
|
case "duration":
|
|
167
|
-
return ` duration ${canonicalDuration(value)}`;
|
|
182
|
+
return ` duration ${canonicalDuration(value, profile)}`;
|
|
168
183
|
case "estimate":
|
|
169
|
-
return serializeEstimate(value, lineEnding);
|
|
184
|
+
return serializeEstimate(value, lineEnding, profile);
|
|
185
|
+
case "not_before":
|
|
186
|
+
case "deadline":
|
|
187
|
+
return ` ${name} ${value}`;
|
|
170
188
|
case "status":
|
|
171
189
|
case "priority":
|
|
172
190
|
return ` ${name} ${value}`;
|
|
@@ -186,6 +204,12 @@ function taskFields(task) {
|
|
|
186
204
|
fields.set("duration", task.duration);
|
|
187
205
|
if (task.estimate !== undefined)
|
|
188
206
|
fields.set("estimate", task.estimate);
|
|
207
|
+
if ("notBefore" in task && task.notBefore !== undefined) {
|
|
208
|
+
fields.set("not_before", task.notBefore);
|
|
209
|
+
}
|
|
210
|
+
if ("deadline" in task && task.deadline !== undefined) {
|
|
211
|
+
fields.set("deadline", task.deadline);
|
|
212
|
+
}
|
|
189
213
|
if (task.status !== undefined)
|
|
190
214
|
fields.set("status", task.status);
|
|
191
215
|
if (task.priority !== undefined)
|
|
@@ -202,37 +226,44 @@ function taskFields(task) {
|
|
|
202
226
|
fields.set("source", task.source);
|
|
203
227
|
return fields;
|
|
204
228
|
}
|
|
205
|
-
function serializeTask(mutation, lineEnding) {
|
|
229
|
+
function serializeTask(mutation, lineEnding, profile) {
|
|
230
|
+
if (mutation.kind !== "task.add") {
|
|
231
|
+
throw new Error("task serializer requires task.add");
|
|
232
|
+
}
|
|
206
233
|
const fields = taskFields(mutation.task);
|
|
207
234
|
return [
|
|
208
235
|
`task ${mutation.id} ${mutation.from} -> ${mutation.to}:`,
|
|
209
|
-
...fieldOrder.flatMap((name) => {
|
|
236
|
+
...profile.fieldOrder.flatMap((name) => {
|
|
210
237
|
if (name === "duration" && fields.has("estimate"))
|
|
211
238
|
return [];
|
|
212
239
|
if (name === "estimate" && fields.has("duration"))
|
|
213
240
|
return [];
|
|
214
|
-
return fields.has(name)
|
|
241
|
+
return fields.has(name)
|
|
242
|
+
? [serializeField(name, fields.get(name), lineEnding, profile)]
|
|
243
|
+
: [];
|
|
215
244
|
}),
|
|
216
245
|
].join(lineEnding);
|
|
217
246
|
}
|
|
218
|
-
function addTaskPlan(text, mutation) {
|
|
247
|
+
function addTaskPlan(text, mutation, profile) {
|
|
219
248
|
const task = mutation.task;
|
|
220
|
-
const taskError = taskDefinitionError(task);
|
|
249
|
+
const taskError = taskDefinitionError(task, profile);
|
|
221
250
|
if (typeof mutation.id !== "string" ||
|
|
222
251
|
typeof mutation.from !== "string" ||
|
|
223
252
|
typeof mutation.to !== "string" ||
|
|
224
253
|
taskError !== undefined) {
|
|
225
254
|
return {
|
|
226
255
|
edits: [],
|
|
227
|
-
diagnostic: mutationDiagnostic("PTMUT-301", `task.add request
|
|
256
|
+
diagnostic: mutationDiagnostic("PTMUT-301", `task.add request is invalid${taskError === undefined ? "" : `: ${taskError}`}`),
|
|
228
257
|
};
|
|
229
258
|
}
|
|
230
259
|
const lineEnding = majorLineEnding(text);
|
|
231
260
|
return {
|
|
232
|
-
edits: [
|
|
261
|
+
edits: [
|
|
262
|
+
appendDeclarationEdit(text, serializeTask(mutation, lineEnding, profile), lineEnding),
|
|
263
|
+
],
|
|
233
264
|
};
|
|
234
265
|
}
|
|
235
|
-
function setRequestError(mutation) {
|
|
266
|
+
function setRequestError(mutation, profile) {
|
|
236
267
|
const rawSet = mutation.set;
|
|
237
268
|
const rawClear = mutation.clear;
|
|
238
269
|
const rawAddTags = mutation.addTags;
|
|
@@ -241,7 +272,7 @@ function setRequestError(mutation) {
|
|
|
241
272
|
const rawRemoveRequirements = mutation.removeRequirements;
|
|
242
273
|
if (rawSet !== undefined &&
|
|
243
274
|
(rawSet === null || typeof rawSet !== "object" || Array.isArray(rawSet))) {
|
|
244
|
-
return "set
|
|
275
|
+
return "set is not an object";
|
|
245
276
|
}
|
|
246
277
|
for (const [name, value] of [
|
|
247
278
|
["clear", rawClear],
|
|
@@ -251,7 +282,7 @@ function setRequestError(mutation) {
|
|
|
251
282
|
["removeRequirements", rawRemoveRequirements],
|
|
252
283
|
]) {
|
|
253
284
|
if (value !== undefined && !Array.isArray(value))
|
|
254
|
-
return `${name}
|
|
285
|
+
return `${name} is not an array`;
|
|
255
286
|
}
|
|
256
287
|
const set = (rawSet ?? {});
|
|
257
288
|
const setEntries = Object.entries(set).filter(([, value]) => value !== undefined);
|
|
@@ -268,22 +299,23 @@ function setRequestError(mutation) {
|
|
|
268
299
|
removeTags.length === 0 &&
|
|
269
300
|
upsertRequirements.length === 0 &&
|
|
270
301
|
removeRequirements.length === 0) {
|
|
271
|
-
return "task.set
|
|
302
|
+
return "task.set requires at least one change specification";
|
|
272
303
|
}
|
|
273
304
|
if (set.duration !== undefined && set.estimate !== undefined) {
|
|
274
|
-
return "duration
|
|
305
|
+
return "duration and estimate cannot both be specified in set";
|
|
275
306
|
}
|
|
276
307
|
if (mutation.from !== undefined && typeof mutation.from !== "string") {
|
|
277
|
-
return "from
|
|
308
|
+
return "from is not a string";
|
|
278
309
|
}
|
|
279
310
|
if (mutation.to !== undefined && typeof mutation.to !== "string") {
|
|
280
|
-
return "to
|
|
311
|
+
return "to is not a string";
|
|
281
312
|
}
|
|
282
313
|
const knownSetFields = new Set([
|
|
283
314
|
"title",
|
|
284
315
|
"description",
|
|
285
316
|
"duration",
|
|
286
317
|
"estimate",
|
|
318
|
+
...(profile.temporalFields ? ["notBefore", "deadline"] : []),
|
|
287
319
|
"status",
|
|
288
320
|
"priority",
|
|
289
321
|
"owner",
|
|
@@ -291,41 +323,57 @@ function setRequestError(mutation) {
|
|
|
291
323
|
"source",
|
|
292
324
|
]);
|
|
293
325
|
if (Object.keys(set).some((name) => !knownSetFields.has(name))) {
|
|
294
|
-
return "set
|
|
326
|
+
return "set contains unsupported fields";
|
|
295
327
|
}
|
|
296
|
-
for (const name of [
|
|
297
|
-
|
|
298
|
-
|
|
328
|
+
for (const name of [
|
|
329
|
+
"title",
|
|
330
|
+
"description",
|
|
331
|
+
"duration",
|
|
332
|
+
"owner",
|
|
333
|
+
"blockedReason",
|
|
334
|
+
"source",
|
|
335
|
+
...(profile.temporalFields ? ["notBefore", "deadline"] : []),
|
|
336
|
+
]) {
|
|
337
|
+
if (set[name] !== undefined &&
|
|
338
|
+
typeof set[name] !== "string") {
|
|
339
|
+
return `${name} is not a string`;
|
|
299
340
|
}
|
|
300
341
|
}
|
|
301
342
|
if (set.estimate !== undefined && !validEstimate(set.estimate)) {
|
|
302
|
-
return "estimate
|
|
343
|
+
return "estimate requires optimistic, mostLikely, and pessimistic string fields";
|
|
303
344
|
}
|
|
304
345
|
if (set.status !== undefined && !taskStatuses.has(set.status)) {
|
|
305
|
-
return "status
|
|
346
|
+
return "status is not a task status";
|
|
306
347
|
}
|
|
307
348
|
if (set.priority !== undefined && !Number.isSafeInteger(set.priority)) {
|
|
308
|
-
return "priority
|
|
349
|
+
return "priority is not a safe integer";
|
|
309
350
|
}
|
|
351
|
+
const clearableFields = new Set([
|
|
352
|
+
...activeClearableFields,
|
|
353
|
+
...(profile.temporalFields ? ["not_before", "deadline"] : []),
|
|
354
|
+
]);
|
|
310
355
|
if (!Array.isArray(clear) || clear.some((name) => !clearableFields.has(name))) {
|
|
311
|
-
return "clear
|
|
356
|
+
return "clear contains unsupported fields";
|
|
312
357
|
}
|
|
313
358
|
if (!validStringArray(addTags) || !validStringArray(removeTags)) {
|
|
314
|
-
return "addTags
|
|
359
|
+
return "addTags and removeTags must be string arrays";
|
|
315
360
|
}
|
|
316
361
|
if (!Array.isArray(upsertRequirements) ||
|
|
317
362
|
!upsertRequirements.every(validRequirement) ||
|
|
318
363
|
!validStringArray(removeRequirements)) {
|
|
319
|
-
return "requirement
|
|
364
|
+
return "requirement change specification is invalid";
|
|
320
365
|
}
|
|
321
366
|
if (new Set(clear).size !== clear.length)
|
|
322
|
-
return "clear
|
|
367
|
+
return "clear contains duplicate fields";
|
|
323
368
|
const clearNames = new Set(clear);
|
|
324
369
|
const setToClear = {
|
|
325
370
|
title: "title",
|
|
326
371
|
description: "description",
|
|
327
372
|
duration: "duration",
|
|
328
373
|
estimate: "estimate",
|
|
374
|
+
...(profile.temporalFields
|
|
375
|
+
? { notBefore: "not_before", deadline: "deadline" }
|
|
376
|
+
: {}),
|
|
329
377
|
status: "status",
|
|
330
378
|
priority: "priority",
|
|
331
379
|
owner: "owner",
|
|
@@ -334,27 +382,27 @@ function setRequestError(mutation) {
|
|
|
334
382
|
};
|
|
335
383
|
for (const [name] of setEntries) {
|
|
336
384
|
if (clearNames.has(setToClear[name])) {
|
|
337
|
-
return `${setToClear[name]}
|
|
385
|
+
return `${setToClear[name]} cannot be specified in both set and clear`;
|
|
338
386
|
}
|
|
339
387
|
}
|
|
340
388
|
if (clearNames.has("tags") && (addTags.length > 0 || removeTags.length > 0)) {
|
|
341
|
-
return "clear tags
|
|
389
|
+
return "clear tags cannot be combined with add/remove tags";
|
|
342
390
|
}
|
|
343
391
|
if (clearNames.has("requires") &&
|
|
344
392
|
(upsertRequirements.length > 0 || removeRequirements.length > 0)) {
|
|
345
|
-
return "clear requires
|
|
393
|
+
return "clear requires cannot be combined with upsert/remove requirements";
|
|
346
394
|
}
|
|
347
395
|
const removedTags = new Set(removeTags);
|
|
348
396
|
if (addTags.some((tag) => removedTags.has(tag))) {
|
|
349
|
-
return "
|
|
397
|
+
return "the same tag cannot be specified in both add and remove";
|
|
350
398
|
}
|
|
351
399
|
const removedResources = new Set(removeRequirements);
|
|
352
400
|
if (upsertRequirements.some(({ resourceId }) => removedResources.has(resourceId))) {
|
|
353
|
-
return "
|
|
401
|
+
return "the same resource cannot be specified in both upsert and remove";
|
|
354
402
|
}
|
|
355
403
|
const upsertIds = upsertRequirements.map(({ resourceId }) => resourceId);
|
|
356
404
|
if (new Set(upsertIds).size !== upsertIds.length) {
|
|
357
|
-
return "upsert
|
|
405
|
+
return "upsert requirements contain duplicate resources";
|
|
358
406
|
}
|
|
359
407
|
return undefined;
|
|
360
408
|
}
|
|
@@ -370,8 +418,8 @@ function currentRequirements(task) {
|
|
|
370
418
|
? value.map(({ resourceId, units }) => ({ resourceId, units }))
|
|
371
419
|
: [];
|
|
372
420
|
}
|
|
373
|
-
function planSetTask(text, task, mutation) {
|
|
374
|
-
const error = setRequestError(mutation);
|
|
421
|
+
function planSetTask(text, task, mutation, profile) {
|
|
422
|
+
const error = setRequestError(mutation, profile);
|
|
375
423
|
if (error !== undefined) {
|
|
376
424
|
return { edits: [], diagnostic: mutationDiagnostic("PTMUT-301", error, task) };
|
|
377
425
|
}
|
|
@@ -380,10 +428,14 @@ function planSetTask(text, task, mutation) {
|
|
|
380
428
|
const edits = [];
|
|
381
429
|
const deleted = new Set(mutation.clear ?? []);
|
|
382
430
|
const additions = new Map();
|
|
431
|
+
const rank = fieldRank(profile);
|
|
383
432
|
const queueAddition = (name, value) => {
|
|
384
|
-
const offset = fieldInsertionOffset(task, name, deleted, lines,
|
|
433
|
+
const offset = fieldInsertionOffset(task, name, deleted, lines, rank);
|
|
385
434
|
const entries = additions.get(offset) ?? [];
|
|
386
|
-
entries.push({
|
|
435
|
+
entries.push({
|
|
436
|
+
name,
|
|
437
|
+
serialized: serializeField(name, value, lineEnding, profile),
|
|
438
|
+
});
|
|
387
439
|
additions.set(offset, entries);
|
|
388
440
|
};
|
|
389
441
|
const setScalar = (name, value, valueText) => {
|
|
@@ -432,6 +484,7 @@ function planSetTask(text, task, mutation) {
|
|
|
432
484
|
});
|
|
433
485
|
}
|
|
434
486
|
const set = mutation.set ?? {};
|
|
487
|
+
const targetSet = set;
|
|
435
488
|
if (set.title !== undefined)
|
|
436
489
|
setScalar("title", set.title, JSON.stringify(set.title));
|
|
437
490
|
if (set.description !== undefined)
|
|
@@ -440,13 +493,13 @@ function planSetTask(text, task, mutation) {
|
|
|
440
493
|
const duration = fieldNamed(task, "duration");
|
|
441
494
|
const estimate = fieldNamed(task, "estimate");
|
|
442
495
|
if (duration !== undefined) {
|
|
443
|
-
setScalar("duration", set.duration, canonicalDuration(set.duration));
|
|
496
|
+
setScalar("duration", set.duration, canonicalDuration(set.duration, profile));
|
|
444
497
|
}
|
|
445
498
|
else if (estimate !== undefined) {
|
|
446
499
|
edits.push({
|
|
447
500
|
startOffset: estimate.span.start.offset,
|
|
448
501
|
endOffset: contentTextEndOffset(estimate, lines),
|
|
449
|
-
replacement: serializeField("duration", set.duration, lineEnding),
|
|
502
|
+
replacement: serializeField("duration", set.duration, lineEnding, profile),
|
|
450
503
|
});
|
|
451
504
|
}
|
|
452
505
|
}
|
|
@@ -463,7 +516,7 @@ function planSetTask(text, task, mutation) {
|
|
|
463
516
|
edits.push({
|
|
464
517
|
startOffset: child.valueSpan.start.offset,
|
|
465
518
|
endOffset: child.valueSpan.end.offset,
|
|
466
|
-
replacement: canonicalDuration(value),
|
|
519
|
+
replacement: canonicalDuration(value, profile),
|
|
467
520
|
});
|
|
468
521
|
}
|
|
469
522
|
}
|
|
@@ -471,10 +524,16 @@ function planSetTask(text, task, mutation) {
|
|
|
471
524
|
edits.push({
|
|
472
525
|
startOffset: duration.span.start.offset,
|
|
473
526
|
endOffset: contentTextEndOffset(duration, lines),
|
|
474
|
-
replacement: serializeEstimate(set.estimate, lineEnding),
|
|
527
|
+
replacement: serializeEstimate(set.estimate, lineEnding, profile),
|
|
475
528
|
});
|
|
476
529
|
}
|
|
477
530
|
}
|
|
531
|
+
if (profile.temporalFields && targetSet.notBefore !== undefined) {
|
|
532
|
+
setScalar("not_before", targetSet.notBefore, targetSet.notBefore);
|
|
533
|
+
}
|
|
534
|
+
if (profile.temporalFields && targetSet.deadline !== undefined) {
|
|
535
|
+
setScalar("deadline", targetSet.deadline, targetSet.deadline);
|
|
536
|
+
}
|
|
478
537
|
if (set.status !== undefined)
|
|
479
538
|
setScalar("status", set.status, set.status);
|
|
480
539
|
if (set.priority !== undefined)
|
|
@@ -586,7 +645,7 @@ function planSetTask(text, task, mutation) {
|
|
|
586
645
|
edits.push(deleteFieldEdit(field, lines));
|
|
587
646
|
}
|
|
588
647
|
for (const [offset, entries] of additions) {
|
|
589
|
-
entries.sort((left, right) => (
|
|
648
|
+
entries.sort((left, right) => (rank.get(left.name) ?? 99) - (rank.get(right.name) ?? 99));
|
|
590
649
|
edits.push({
|
|
591
650
|
startOffset: offset,
|
|
592
651
|
endOffset: offset,
|
|
@@ -595,7 +654,7 @@ function planSetTask(text, task, mutation) {
|
|
|
595
654
|
}
|
|
596
655
|
return { edits };
|
|
597
656
|
}
|
|
598
|
-
export function planTaskMutationEdits(text, document, mutation) {
|
|
657
|
+
export function planTaskMutationEdits(text, document, mutation, profile = ACTIVE_TASK_MUTATION_PROFILE) {
|
|
599
658
|
const requestError = taskMutationRequestError(mutation);
|
|
600
659
|
if (requestError !== undefined) {
|
|
601
660
|
return {
|
|
@@ -608,21 +667,21 @@ export function planTaskMutationEdits(text, document, mutation) {
|
|
|
608
667
|
if (entity !== undefined) {
|
|
609
668
|
return {
|
|
610
669
|
edits: [],
|
|
611
|
-
diagnostic: mutationDiagnostic("PTMUT-304", `entity ID ${mutation.id}
|
|
670
|
+
diagnostic: mutationDiagnostic("PTMUT-304", `entity ID ${mutation.id} is already in use`, entity),
|
|
612
671
|
};
|
|
613
672
|
}
|
|
614
|
-
return addTaskPlan(text, mutation);
|
|
673
|
+
return addTaskPlan(text, mutation, profile);
|
|
615
674
|
}
|
|
616
675
|
if (entity === undefined) {
|
|
617
676
|
return {
|
|
618
677
|
edits: [],
|
|
619
|
-
diagnostic: mutationDiagnostic("PTMUT-302", `task ${mutation.id}
|
|
678
|
+
diagnostic: mutationDiagnostic("PTMUT-302", `task ${mutation.id} does not exist`),
|
|
620
679
|
};
|
|
621
680
|
}
|
|
622
681
|
if (entity.kind !== "task") {
|
|
623
682
|
return {
|
|
624
683
|
edits: [],
|
|
625
|
-
diagnostic: mutationDiagnostic("PTMUT-303", `entity ${mutation.id}
|
|
684
|
+
diagnostic: mutationDiagnostic("PTMUT-303", `entity ${mutation.id} is not a task`, entity),
|
|
626
685
|
};
|
|
627
686
|
}
|
|
628
687
|
const lines = splitPhysicalLines(text);
|
|
@@ -637,8 +696,8 @@ export function planTaskMutationEdits(text, document, mutation) {
|
|
|
637
696
|
...(fieldNamed(entity, "blocked_reason") === undefined
|
|
638
697
|
? {}
|
|
639
698
|
: { clear: ["blocked_reason"] }),
|
|
640
|
-
});
|
|
699
|
+
}, profile);
|
|
641
700
|
}
|
|
642
|
-
return planSetTask(text, entity, mutation);
|
|
701
|
+
return planSetTask(text, entity, mutation, profile);
|
|
643
702
|
}
|
|
644
703
|
//# sourceMappingURL=task.js.map
|