From 248e646137d2451fd6e937d65a019a417682738f Mon Sep 17 00:00:00 2001 From: mgifos Date: Sun, 25 Mar 2018 15:50:27 +0200 Subject: [PATCH] Readme update and logging improved --- README.md | 94 ++++++++++++++++--- .../GarminConnect.scala | 6 +- .../com.github.mgifos.workouts/Main.scala | 8 +- src/test/resources/ultra-80k-runnersworld.csv | 78 ++++++++++----- 4 files changed, 140 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index f35258a..2274933 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,92 @@ -# Workouts -The objectives of this project are to: - - enable easier notation for workouts (textual form) - - be able to specify weekly based training plans in e.g. spreadsheets that can be exported to CSV - - be able to import workouts from CSV file to Garmin Connect workout list - - be able to schedule imported workouts in Garmin Connect calendar automaticaly based on start/end date +# Quick plan +is a command line tool to define, import, schedule and share GarminConnect workouts i.e. weekly based training plans. An example of a workout definition notation: ```sh -workout: run-fast -- warmup: 10:00 @ z2 +workout: 15k, 3x3.2k @HMP +- warmup: 2km @z2 - repeat: 3 - - run: 1.5km @ 5:10-4:40 - - recover: 500m @ z2 -- cooldown: 05:00 + - run: 3200m @ 5:05-4:50 + - recover: 800m @z2 +- run: 1km @z2 +- cooldown: lap-button ``` -In CSV 1st row is reserved for heading, can be anything and the 1st column is reserved for a week number. The cells represent days and they are populated with workouts (definitions and references), so there's a limitation: a single work out per day/cell, for now. In case of the references, a workout needs to be defined first in one of the previous cells and then it can be referenced by name in the following cells. +and the tool's job is actually to translate it to this: +![15k workout](https://i.imgur.com/vxXNV7w.png) + +## File format + +The file format is a spreadsheet exported to csv format. The 1st row is reserved for heading, can be anything (you define your first day of a week etc.). The 1st column is reserved for a week number. The cells represent days and they are populated with workouts (definitions and references), so there's a limitation: a single workout per day/cell, for now. In case of the references, a workout needs to be defined first in one of the previous cells and then it can be referenced by name in the following cells. An example of 2-weeks training plan, containing 2 workout definitions, 4 references and 6 training days in total: | Week | Mon | Tue | Wed | Thu | Fri | Sat | Sun | | ----:| --- | --- | --- | --- | --- | --- | --- | | 1 | ``workout: run-fast``
``- warmup: 10:00 @ z2``
``- repeat: 3``
  ``- run: 1.5km @ 5:10-4:40``
  ``- recover: 500m @ z2``
``- cooldown: 05:00``|rest|rest|run-fast|rest|rest|rest| -| 2 | run-fast| ``workout: long-15``
``- run: 15 km @ z2``|rest|run-fast|rest|rest|long-15| \ No newline at end of file +| 2 | run-fast| ``workout: long-15``
``- run: 15 km @ z2``|rest|run-fast|rest|rest|long-15| + +Checkout a [complete training plan for 80K ultra](https://docs.google.com/spreadsheets/d/1b1ZzrAFrjd-kvPq11zlbE2bWn2IQmUy0lBqIOFjqbwk/edit?usp=sharing). It was originally published in an article of Runner's world website - here's [the link](https://www.runnersworld.com/ultrarunning/the-ultimate-ultramarathon-training-plan). +## Command line options + +``` +quick-plan --help + +quick-plan 0.x + +Usage: quick-plan [import|schedule] [options] + + -e, --email E-mail to login to Garmin Connect + -p, --password Password to login to Garmin Connect + -d, --delete Delete all existing workouts with same names as the ones that are going to be imported. + --help prints this usage text + + File with a weekly based plan in CSV format + + +Command: import +Imports all workout definitions from CSV file. If it's omitted, it is will be on by default. +Command: schedule [options] +Schedules your weekly plan defined in CSV in Garmin Connect calendar, starting from the first day of first week or ending on the last day of the last week. Either start or end date must be entered so the scheduling can be done properly. In case both are entered, start date has priority. All dates have to be entered in ISO date format e.g. '2018-03-24'. + + -s, --start Date of the first day of the first week of the plan + -n, --end Date of the last day of the last week of the plan + +EXAMPLES + +Schedules ultra 80k plan targeting 28-4-2018 for a race day + +quick-plan schedule -n 2018-04-29 -d -e your-mail-address@example.com ultra-80k-runnersworld.csv +``` + +## Workout notation +The reserved keywords of the notation are: workout, warmup, cooldown, run, repeat, recover and lap-button. + +**`workout`** := `
+` + +**`
`** := `workout: ` + +**``** := ` - *` + +**``** := ` | ` + +**``** := `repeat: +` + +**``** := `- (warmup | cooldown | run | recover): [@ ]` + +**``** := ` | | lap-button` + +**``** := ` (km | m | mi)` + +**``** := `:` + +**`** := ` | ` + +**`** := `z[1-6]` + +**`** := ` - ` + +**`** := `:` + +**`** := `\d{1,2}` + +**`** := `\d{2}` \ No newline at end of file diff --git a/src/main/scala/com.github.mgifos.workouts/GarminConnect.scala b/src/main/scala/com.github.mgifos.workouts/GarminConnect.scala index baa4030..61ce3e3 100644 --- a/src/main/scala/com.github.mgifos.workouts/GarminConnect.scala +++ b/src/main/scala/com.github.mgifos.workouts/GarminConnect.scala @@ -68,7 +68,7 @@ class GarminConnect(email: String, password: String)(implicit system: ActorSyste if res.status == OK json <- res.entity.toStrict(10.seconds).map(_.data.utf8String) } yield { - log.info(s" '$workout'") + log.info(s" $workout") GarminWorkout(workout, Json.parse(json).\("workoutId").as[Long]) } } @@ -97,7 +97,7 @@ class GarminConnect(email: String, password: String)(implicit system: ActorSyste log.info("\nDeleting workouts:") pairs.map { case (workout, id) => - val label = s"'$workout' -> $id" + val label = s"$workout -> $id" label -> Post(s"https://connect.garmin.com/modern/proxy/workout-service/workout/$id") .withHeaders(session.headers :+ Referer("https://connect.garmin.com/modern/workouts") @@ -126,7 +126,7 @@ class GarminConnect(email: String, password: String)(implicit system: ActorSyste Source(spec).map { case (date, gw) => log.debug(s"Making $date -> $gw") - s"$date -> '${gw.name}'" -> Post(s"https://connect.garmin.com/modern/proxy/workout-service/schedule/${gw.id}") + s"$date -> ${gw.name}" -> Post(s"https://connect.garmin.com/modern/proxy/workout-service/schedule/${gw.id}") .withHeaders(session.headers :+ Referer("https://connect.garmin.com/modern/calendar") :+ RawHeader("NK", "NT")) diff --git a/src/main/scala/com.github.mgifos.workouts/Main.scala b/src/main/scala/com.github.mgifos.workouts/Main.scala index 696be61..c3d37c1 100644 --- a/src/main/scala/com.github.mgifos.workouts/Main.scala +++ b/src/main/scala/com.github.mgifos.workouts/Main.scala @@ -113,9 +113,9 @@ object Main extends App { garminWorkouts <- garmin.createWorkouts(workouts) maybeScheduleMessage <- scheduleTask(garminWorkouts) } yield { - log.info("\n Statistics:") + log.info("\nStatistics:") maybeDeleteMessage.foreach(msg => log.info(" " + msg)) - log.info(s" ${garminWorkouts.length} workouts has been imported to Garmin Connect") + log.info(s" ${garminWorkouts.length} imported") maybeScheduleMessage.foreach(msg => log.info(" " + msg)) } } @@ -125,7 +125,7 @@ object Main extends App { */ private def deleteWorkoutsTask(workouts: Seq[String])(implicit config: Config, garmin: GarminConnect): Future[Option[String]] = { if (config.delete) - garmin.deleteWorkouts(workouts).map(c => Some(s"$c workouts is deleted")) + garmin.deleteWorkouts(workouts).map(c => Some(s"$c deleted")) else Future.successful(None) } @@ -143,7 +143,7 @@ object Main extends App { val spec = plan.get.zipWithIndex.collect { case (Some(ref), day) if !start.plusDays(day).isBefore(LocalDate.now()) => start.plusDays(day) -> woMap(ref.name) }.to[Seq] - garmin.schedule(spec).map(c => Some(s"$c workouts is scheduled")) + garmin.schedule(spec).map(c => Some(s"$c scheduled")) } else Future.successful(None) } diff --git a/src/test/resources/ultra-80k-runnersworld.csv b/src/test/resources/ultra-80k-runnersworld.csv index 2530816..abe23d8 100644 --- a/src/test/resources/ultra-80k-runnersworld.csv +++ b/src/test/resources/ultra-80k-runnersworld.csv @@ -4,87 +4,113 @@ WEEK,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday,Estimated km,,Dura - repeat: 4 - run: 1600m @ 5:00-4:30 - recover: 900m @z2 -- cooldown: 2km","workout: 8k jog -- run: 8km @z2","workout: 11-15k, middle 5k @MP +- run: 2km +- cooldown: lap-button","workout: 8k jog +- run: 8km @z2 +- cooldown: lap-button","workout: 11-15k, middle 5k @MP - warmup: 4km @z2 - run: 5km @ 5:40-5:30 -- cooldown: 6km @z2",,"workout: 1.5h run -- run: 90:00","workout: 3h run -- run: 180:00",78.0,,0:10:00,1.7,1.0 +- run: 6km @z2 +- cooldown: lap-button",,"workout: 1.5h run +- run: 90:00 +- cooldown: lap-button","workout: 3h run +- run: 180:00 +- cooldown: lap-button",78.0,,0:10:00,1.7,1.0 2,,"14k, 4x 1.6k @TMP",8k jog,"11-15k, middle 5k @MP",,1.5h run,3h run,78.0,,0:15:00,2.5,1.6 3,,"workout: 14k, 2x1.6k @HMP - warmup: 4km @z2 - repeat: 2 - run: 1.6km @ 5:05-4:50 - recover: 1.4km @z2 -- cooldown: 4km @z2",8k jog,"11-15k, middle 5k @MP",,"workout: 2h run -- run 120:00","workout: 3.5h run -- run: 210:00",90.0,,0:30:00,5.0,3.1 +- run: 4km @z2 +- cooldown: lap-button",8k jog,"11-15k, middle 5k @MP",,"workout: 2h run +- run 120:00 +- cooldown: lap-button","workout: 3.5h run +- run: 210:00 +- cooldown: lap-button",90.0,,0:30:00,5.0,3.1 4,,"workout: 10k, 3x1.6k @TMP - warmup: 1500m @z2 - repeat: 3 - run: 1600m @ 5:00-4:30 - recover: 900m @z2 -- cooldown: 1km",8k jog,"workout: 10k, middle 3.2k @MP +- run: 1km @z2 +- cooldown: lap-button",8k jog,"workout: 10k, middle 3.2k @MP - warmup: 4km @z2 - run: 3200m @ 5:40-5:30 -- cooldown: 2800m @z2",,1.5h run,2h run,63.0,,0:40:00,6.7,4.1 +- run: 2800m @z2 +- cooldown: lap-button",,1.5h run,2h run,63.0,,0:40:00,6.7,4.1 5,,"workout: 15k, 6x1.6k @TMP - warmup: 2km @z2 - repeat: 6 - run: 1600m @ 5:00-4:30 - recover: 400m @z2 -- cooldown: 1km",8k jog,"workout: 15k, middle 5k @MP +- run: 1km +- cooldown: lap-button",8k jog,"workout: 15k, middle 5k @MP - warmup: 5km @z2 - run: 5km @ 5:40-5:30 -- cooldown: 5km @z2",,"workout: 3.5-4h run -- run 225:00",3h run,104.7,,0:45:00,7.5,4.7 +- run: 5km @z2 +- cooldown: lap-button",,"workout: 3.5-4h run +- run 225:00 +- cooldown: lap-button",3h run,104.7,,0:45:00,7.5,4.7 6,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,3.5-4h run,3h run,104.7,,0:50:00,8.3,5.2 7,,"workout: 15k, 6x1.6k @HMP - warmup: 2km @z2 - repeat: 6 - run: 1600m @ 5:05-4:50 - recover: 400m @z2 -- cooldown: 1km",8k jog,"15k, middle 5k @MP",,3.5-4h run,"workout: 3h run, last @MP +- run: 1km +- cooldown: lap-button",8k jog,"15k, middle 5k @MP",,3.5-4h run,"workout: 3h run, last @MP - run: 120:00 -- run: 60:00 @ 5:40-5:30",104.7,,1:00:00,10.0,6.2 +- run: 60:00 @ 5:40-5:30 +- cooldown: lap-button",104.7,,1:00:00,10.0,6.2 8,,"workout: 15k, 3x3.2k @HMP - warmup: 2km @z2 - repeat: 3 - run: 3200m @ 5:05-4:50 - recover: 800m @z2 -- cooldown: 1km",8k jog,"15k, middle 5k @MP",,2h run,"workout: 2.5h run -- run: 150:00",83.0,,1:20:00,13.3,8.3 +- run: 1km @z2 +- cooldown: lap-button",8k jog,"15k, middle 5k @MP",,2h run,"workout: 2.5h run +- run: 150:00 +- cooldown: lap-button",83.0,,1:20:00,13.3,8.3 9,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,"workout: 4h run -- run: 240:00","workout: 3.5h run, last @MP +- run: 240:00 +- cooldown: lap-button","workout: 3.5h run, last @MP - run: 150:00 -- run: 60:00 @ 5:40-5:30",113.0,,1:30:00,15.0,9.3 +- run: 60:00 @ 5:40-5:30 +- cooldown: lap-button",113.0,,1:30:00,15.0,9.3 10,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4-hour run,"3.5h run, last @MP",113.0,,1:45:00,17.5,10.9 11,,"15k, 3x3.2k @HMP",8k jog,"15k, middle 5k @MP",,2.5h run,3h run,93.0,,2:00:00,20.0,12.4 12,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4h run,"workout: 5h run -- run: 300:00",128.0,,2:30:00,25.0,15.5 +- run: 300:00 +- cooldown: lap-button",128.0,,2:30:00,25.0,15.5 13,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4h run,5h run,128.0,,3:00:00,30.0,18.6 14,,"workout: 15k, 4x1.6k @TMP - warmup: 3km @z2 - repeat: 2 - run: 1600m @ 5:00-4:30 - recover: 1400m @z2 -- cooldown: 1km",8k jog,"15k, middle 5k @MP",,2h run,2h run,78.0,,3:30:00,35.0,21.7 +- run: 1km @z2 +- cooldown: lap-button",8k jog,"15k, middle 5k @MP",,2h run,2h run,78.0,,3:30:00,35.0,21.7 15,,"workout: 11k, 3x1.6k @MP - warmup: 1.5km @z2 - repeat: 3 - run: 1600m @ 5:40-5:30 - recover: 1400m @z2 -- cooldown: 500m",8k jog,"workout: 11k, middle 5k @MP +- run: 500m @z2 +- cooldown: lap-button",8k jog,"workout: 11k, middle 5k @MP - warmup: 3km @z2 - run: 5km @ 5:40-5:30 -- cooldown: 3km @z2",,1.5h run,"workout: easy 1h jog -- run: 60:00",55.0,,4:00:00,40.0,24.8 +- run: 3km @z2 +- cooldown: lap-button",,1.5h run,"workout: easy 1h jog +- run: 60:00 +- cooldown: lap-button",55.0,,4:00:00,40.0,24.8 16,,"workout: 10k, middle 5k @HMP - warmup: 3km @z2 - run: 5km @ 5:05-4:30 -- cooldown: 2km @z2",8k jog,"workout: easy 5k jog -- run: 5km",,80k race,Rest. (Duh.),103.0,,5:00:00,50.0,31.1 +- run: 2km @z2 +- cooldown: lap-button",8k jog,"workout: easy 5k jog +- run: 5km +- cooldown: lap-button",,Race day!,,103.0,,5:00:00,50.0,31.1 ,,,,,,,,"1,517.0",,,, ,TMP,16k pace,,,,,,,,,, ,HMP,Half Marathon Pace,,,,,Avg min/km,0:06:00,,,,