Nested repeats #38

master
mgifos 7 years ago
parent c68c1174ad
commit 05eaef7b7d
  1. 49
      src/main/scala/com.github.mgifos.workouts/model/Step.scala
  2. 48
      src/test/scala/com/github/mgifos/workouts/model/WorkoutSpec.scala

@ -50,23 +50,17 @@ case class RepeatStep(count: Int, steps: Seq[Step]) extends Step {
object Step {
private val StepRx = """^(-\s\w*:\s.*)(([\r\n]+\s{1,}-\s.*)*)$""".r
private val StepHeader = """^\s*-\s*(\w*):(.*)$""".r
private val ParamsRx = """^([\w-\.:\s]+)\s*(@(.*))?$""".r
def parse(text: String)(implicit msys: MeasurementSystems.MeasurementSystem): Step = {
def parse(x: String)(implicit msys: MeasurementSystems.MeasurementSystem): Step = x match {
case StepRx(header, subSteps, _) if subSteps.nonEmpty =>
header match {
case StepHeader(name, params) =>
if (name != "repeat") throw new IllegalArgumentException(s"'$name' cannot contain sub-steps, it must be 'repeat'")
RepeatStep(params.trim.toInt, subSteps.trim.lines.toList.map(parseDurationStep))
case _ => throw new IllegalArgumentException(s"Cannot parse repeat step $header")
}
case StepRx(header, "", null) => parseDurationStep(header)
case _ => throw new IllegalArgumentException(s"Cannot parse step:$x")
}
def loop(depth: Int)(x: String): Step = {
val indent = depth * 2
private def parseDurationStep(x: String)(implicit msys: MeasurementSystems.MeasurementSystem): DurationStep = x match {
val StepRx = raw"""^(\s{$indent}-\s\w*:\s.*)(([\r\n]+\s{1,}-\s.*)*)$$""".r
val StepHeader = raw"""^\s{$indent}-\s*(\w*):(.*)$$""".r
val ParamsRx = """^([\w-\.:\s]+)\s*(@(.*))?$""".r
def parseDurationStep(x: String)(implicit msys: MeasurementSystems.MeasurementSystem): DurationStep = x match {
case StepHeader(name, params) =>
name match {
case "warmup" => WarmupStep.tupled(expect(params))
@ -78,10 +72,33 @@ object Step {
case _ => throw new IllegalArgumentException(s"Cannot parse duration step: $x")
}
private def expect(x: String)(implicit msys: MeasurementSystems.MeasurementSystem): (Duration, Option[Target]) = x.trim match {
def expect(x: String)(implicit msys: MeasurementSystems.MeasurementSystem): (Duration, Option[Target]) = x.trim match {
case ParamsRx(duration, _, target) =>
val maybeTarget = Option(target).filter(_.trim.nonEmpty).map(Target.parse)
(Duration.parse(duration.trim), maybeTarget)
case raw => throw new IllegalArgumentException(s"Cannot parse step parameters $raw")
}
x match {
case StepRx(header, subdef, _) if subdef.nonEmpty =>
header match {
case StepHeader(name, params) =>
if (name != "repeat") throw new IllegalArgumentException(s"'$name' cannot contain sub-steps, it must be 'repeat'")
val next = subdef.replaceFirst("[\n\r]*", "")
val nextIndent = indent + 2
val steps = next.split(raw"""[\n\r]{1,2}\s{$nextIndent}-""").toList match {
case head :: tail => head :: tail.map(" " * nextIndent + "-" + _)
case original => original
}
RepeatStep(params.trim.toInt, steps.map(loop(depth + 1)))
case _ => throw new IllegalArgumentException(s"Cannot parse repeat step $header")
}
case StepRx(header, "", null) => parseDurationStep(header)
case _ => throw new IllegalArgumentException(s"Cannot parse step:$x")
}
}
loop(0)(text)
}
}

@ -28,12 +28,21 @@ class WorkoutSpec extends WordSpec with Matchers {
}
"parse a workout definition correctly" in {
Workout.parse(testWO) shouldBe WorkoutDef("running", "run-fast", Seq(
Workout.parse(testWO) shouldBe WorkoutDef(
"running",
"run-fast",
Seq(
WarmupStep(TimeDuration(minutes = 10)),
RepeatStep(2, Seq(
RepeatStep(
2,
Seq(
IntervalStep(DistanceDuration(1500, m), Some(PaceTarget(Pace(msys.distance, "4:30"), Pace(msys.distance, "5:00")))),
RecoverStep(TimeDuration(1, 30), Some(HrZoneTarget(2))))),
CooldownStep(LapButtonPressed)))
RecoverStep(TimeDuration(1, 30), Some(HrZoneTarget(2)))
)
),
CooldownStep(LapButtonPressed)
)
)
}
"parse various printable workout-names correctly" in {
@ -49,10 +58,15 @@ class WorkoutSpec extends WordSpec with Matchers {
"parse cycling workouts" in {
val testBike = "cycling: cycle-test\r\n- warmup: 5:00\n- bike: 20km @ 20.0-100kph\r- cooldown: lap-button"
Workout.parse(testBike) shouldBe WorkoutDef("cycling", "cycle-test", Seq(
Workout.parse(testBike) shouldBe WorkoutDef(
"cycling",
"cycle-test",
Seq(
WarmupStep(TimeDuration(minutes = 5)),
IntervalStep(DistanceDuration(20, km), Some(SpeedTarget(Speed(km, "20.0"), Speed(km, "100")))),
CooldownStep(LapButtonPressed)))
CooldownStep(LapButtonPressed)
)
)
}
"validate on a workout definition level" in {
@ -75,6 +89,28 @@ class WorkoutSpec extends WordSpec with Matchers {
case WorkoutStepFailure(_, "'20x' is not a valid target specification") =>
}
}
"parse nested repeats" in {
val testNested =
"running: nested repeats\n- warmup: 20:00\n- repeat: 2\n - repeat: 4\n - run: 0.6km @ 4:25-4:15\n - recover: 1:00\n - recover: 05:00\n- cooldown: lap-button"
Workout.parse(testNested) shouldBe WorkoutDef(
"running",
"nested repeats",
Seq(
WarmupStep(TimeDuration(20)),
RepeatStep(
count = 2,
Seq(
RepeatStep(count = 4,
Seq(IntervalStep(DistanceDuration(0.6f, km), Some(PaceTarget(Pace(km, "4:25"), Pace(km, "4:15")))),
RecoverStep(TimeDuration(1, 0)))),
RecoverStep(TimeDuration(5, 0))
)
),
CooldownStep(LapButtonPressed)
)
)
}
}
"Workout should" should {

Loading…
Cancel
Save