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. 50
      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 { object Step {
private val StepRx = """^(-\s\w*:\s.*)(([\r\n]+\s{1,}-\s.*)*)$""".r def parse(text: String)(implicit msys: MeasurementSystems.MeasurementSystem): Step = {
private val StepHeader = """^\s*-\s*(\w*):(.*)$""".r
private val ParamsRx = """^([\w-\.:\s]+)\s*(@(.*))?$""".r
def parse(x: String)(implicit msys: MeasurementSystems.MeasurementSystem): Step = x match { def loop(depth: Int)(x: String): Step = {
case StepRx(header, subSteps, _) if subSteps.nonEmpty =>
header match { val indent = depth * 2
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")
}
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) => case StepHeader(name, params) =>
name match { name match {
case "warmup" => WarmupStep.tupled(expect(params)) case "warmup" => WarmupStep.tupled(expect(params))
@ -78,10 +72,33 @@ object Step {
case _ => throw new IllegalArgumentException(s"Cannot parse duration step: $x") 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) => case ParamsRx(duration, _, target) =>
val maybeTarget = Option(target).filter(_.trim.nonEmpty).map(Target.parse) val maybeTarget = Option(target).filter(_.trim.nonEmpty).map(Target.parse)
(Duration.parse(duration.trim), maybeTarget) (Duration.parse(duration.trim), maybeTarget)
case raw => throw new IllegalArgumentException(s"Cannot parse step parameters $raw") 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)
}
} }

@ -1,7 +1,7 @@
package com.github.mgifos.workouts.model package com.github.mgifos.workouts.model
import com.github.mgifos.workouts.model.DistanceUnits._ import com.github.mgifos.workouts.model.DistanceUnits._
import org.scalatest.{ Matchers, WordSpec } import org.scalatest.{Matchers, WordSpec}
import play.api.libs.json.Json import play.api.libs.json.Json
class WorkoutSpec extends WordSpec with Matchers { class WorkoutSpec extends WordSpec with Matchers {
@ -28,12 +28,21 @@ class WorkoutSpec extends WordSpec with Matchers {
} }
"parse a workout definition correctly" in { "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)), 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")))), IntervalStep(DistanceDuration(1500, m), Some(PaceTarget(Pace(msys.distance, "4:30"), Pace(msys.distance, "5:00")))),
RecoverStep(TimeDuration(1, 30), Some(HrZoneTarget(2))))), RecoverStep(TimeDuration(1, 30), Some(HrZoneTarget(2)))
CooldownStep(LapButtonPressed))) )
),
CooldownStep(LapButtonPressed)
)
)
} }
"parse various printable workout-names correctly" in { "parse various printable workout-names correctly" in {
@ -49,10 +58,15 @@ class WorkoutSpec extends WordSpec with Matchers {
"parse cycling workouts" in { "parse cycling workouts" in {
val testBike = "cycling: cycle-test\r\n- warmup: 5:00\n- bike: 20km @ 20.0-100kph\r- cooldown: lap-button" 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)), WarmupStep(TimeDuration(minutes = 5)),
IntervalStep(DistanceDuration(20, km), Some(SpeedTarget(Speed(km, "20.0"), Speed(km, "100")))), IntervalStep(DistanceDuration(20, km), Some(SpeedTarget(Speed(km, "20.0"), Speed(km, "100")))),
CooldownStep(LapButtonPressed))) CooldownStep(LapButtonPressed)
)
)
} }
"validate on a workout definition level" in { "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") => 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 { "Workout should" should {

Loading…
Cancel
Save