Positive comment offset syntax: +01:00

This commit is contained in:
Rene Saarsoo 2021-03-02 21:58:34 +02:00
parent ba92dd50d1
commit 574272602f
3 changed files with 163 additions and 5 deletions

View File

@ -643,6 +643,143 @@ Rest: 5:00 50%
`); `);
}); });
it("parses intervals with positive comment offsets", () => {
expect(
parse(`
Name: My Workout
Interval: 10:00 90%
@ 0:50 First comment
@ +0:10 Comment #2 10 seconds later
@ +0:10 Comment #3 another 10 seconds later
@ 5:00 Half way!
@ +0:10 Comment #5 10 seconds later
@ +0:10 Comment #6 another 10 seconds later
`),
).toMatchInlineSnapshot(`
Object {
"author": "",
"description": "",
"intervals": Array [
Object {
"cadence": undefined,
"comments": Array [
Object {
"loc": Object {
"col": 4,
"row": 3,
},
"offset": Duration {
"seconds": 50,
},
"text": "First comment",
},
Object {
"loc": Object {
"col": 4,
"row": 4,
},
"offset": Duration {
"seconds": 60,
},
"text": "Comment #2 10 seconds later",
},
Object {
"loc": Object {
"col": 4,
"row": 5,
},
"offset": Duration {
"seconds": 70,
},
"text": "Comment #3 another 10 seconds later",
},
Object {
"loc": Object {
"col": 4,
"row": 6,
},
"offset": Duration {
"seconds": 300,
},
"text": "Half way!",
},
Object {
"loc": Object {
"col": 4,
"row": 7,
},
"offset": Duration {
"seconds": 310,
},
"text": "Comment #5 10 seconds later",
},
Object {
"loc": Object {
"col": 4,
"row": 8,
},
"offset": Duration {
"seconds": 320,
},
"text": "Comment #6 another 10 seconds later",
},
],
"duration": Duration {
"seconds": 600,
},
"intensity": ConstantIntensity {
"_value": 0.9,
},
"type": "Interval",
},
],
"name": "My Workout",
"tags": Array [],
}
`);
});
it("treats positive comment offset as relative to interval start when there's no previous comment", () => {
expect(
parse(`
Name: My Workout
Interval: 10:00 90%
@ +1:00 First comment
`),
).toMatchInlineSnapshot(`
Object {
"author": "",
"description": "",
"intervals": Array [
Object {
"cadence": undefined,
"comments": Array [
Object {
"loc": Object {
"col": 4,
"row": 3,
},
"offset": Duration {
"seconds": 60,
},
"text": "First comment",
},
],
"duration": Duration {
"seconds": 600,
},
"intensity": ConstantIntensity {
"_value": 0.9,
},
"type": "Interval",
},
],
"name": "My Workout",
"tags": Array [],
}
`);
});
it("throws error when comment offset is outside of interval length", () => { it("throws error when comment offset is outside of interval length", () => {
expect(() => expect(() =>
parse(` parse(`

View File

@ -1,8 +1,9 @@
import { last } from "ramda";
import { Interval, Workout, Comment } from "../ast"; import { Interval, Workout, Comment } from "../ast";
import { Duration } from "../Duration"; import { Duration } from "../Duration";
import { ConstantIntensity, FreeIntensity, RangeIntensity } from "../Intensity"; import { ConstantIntensity, FreeIntensity, RangeIntensity } from "../Intensity";
import { ParseError } from "./ParseError"; import { ParseError } from "./ParseError";
import { IntervalType, SourceLocation, Token } from "./tokenizer"; import { IntervalType, OffsetToken, SourceLocation, Token } from "./tokenizer";
type Header = Partial<Omit<Workout, "intervals">>; type Header = Partial<Omit<Workout, "intervals">>;
@ -72,8 +73,7 @@ const parseIntervalComments = (tokens: Token[], intervalDuration: Duration): [Co
throw new ParseError(`Expected [comment text] instead got ${tokenToString(text)}`, text?.loc || offset.loc); throw new ParseError(`Expected [comment text] instead got ${tokenToString(text)}`, text?.loc || offset.loc);
} }
comments.push({ comments.push({
// when offset is negative, recalculate it based on interval length offset: absoluteOffset(offset, intervalDuration, last(comments)),
offset: new Duration(offset.kind === "absolute" ? offset.value : intervalDuration.seconds - offset.value),
text: text.value, text: text.value,
loc: offset.loc, loc: offset.loc,
}); });
@ -85,6 +85,16 @@ const parseIntervalComments = (tokens: Token[], intervalDuration: Duration): [Co
return [comments, tokens]; return [comments, tokens];
}; };
const absoluteOffset = (offset: OffsetToken, intervalDuration: Duration, previousComment?: Comment): Duration => {
if (offset.kind === "relative-minus") {
return new Duration(intervalDuration.seconds - offset.value);
} else if (offset.kind === "relative-plus" && previousComment) {
return new Duration(previousComment.offset.seconds + offset.value);
} else {
return new Duration(offset.value);
}
};
const parseIntervalParams = (type: IntervalType, tokens: Token[], loc: SourceLocation): [Interval, Token[]] => { const parseIntervalParams = (type: IntervalType, tokens: Token[], loc: SourceLocation): [Interval, Token[]] => {
let duration; let duration;
let cadence; let cadence;

View File

@ -102,7 +102,7 @@ const tokenizeParams = (text: string, loc: SourceLocation): Token[] => {
}; };
const tokenizeComment = (line: string, row: number): Token[] | undefined => { const tokenizeComment = (line: string, row: number): Token[] | undefined => {
const [, commentHead, minus, offset, commentText] = line.match(/^(\s*@\s*)(-?)([0-9:]+)(.*?)$/) || []; const [, commentHead, sign, offset, commentText] = line.match(/^(\s*@\s*)([-+]?)([0-9:]+)(.*?)$/) || [];
if (!commentHead) { if (!commentHead) {
return undefined; return undefined;
} }
@ -113,7 +113,7 @@ const tokenizeComment = (line: string, row: number): Token[] | undefined => {
{ type: "comment-start", loc: { row, col: line.indexOf("@") } }, { type: "comment-start", loc: { row, col: line.indexOf("@") } },
{ {
type: "offset", type: "offset",
kind: minus ? "relative-minus" : "absolute", kind: signToKind(sign),
value: toSeconds(offset), value: toSeconds(offset),
loc: { row, col: commentHead.length }, loc: { row, col: commentHead.length },
}, },
@ -121,6 +121,17 @@ const tokenizeComment = (line: string, row: number): Token[] | undefined => {
]; ];
}; };
const signToKind = (sign: string) => {
switch (sign) {
case "-":
return "relative-minus";
case "+":
return "relative-plus";
default:
return "absolute";
}
};
const tokenizeHeader = (label: HeaderType, separator: string, paramString: string, row: number): Token[] => { const tokenizeHeader = (label: HeaderType, separator: string, paramString: string, row: number): Token[] => {
const token: HeaderToken = { const token: HeaderToken = {
type: "header", type: "header",