Package: zwolang
ZWO MinilangPython toolkit for the ZWO minilang.InstallationInstall from PyPi with your favoritepipinvocation:$pipinstallzwolangYou can confirm proper installation via thezwomCLI:$ zwom --help
                                                                               
 Usage: zwom [OPTIONS] COMMAND [ARGS]...                                       
                                                                               
+- Options -------------------------------------------------------------------+
| --help          Show this message and exit.                                 |
+-----------------------------------------------------------------------------+
+- Commands ------------------------------------------------------------------+
| batch    Discover and convert all `*.zwom` files in the given directory.    |
| single   Convert the specified `*.zwom` file to Zwift's `*.zwo`.            |
+-----------------------------------------------------------------------------+The ZWOM File SpecificationThe primary purpose of this package is to provide a simple, human-readable format for constructing Zwift workouts that can be used to generate the actual workout XML. Let's call it a*.zwomfile, or ZWOM.ZWOM files are parsed using aParsimoniousgrammar, as specified below:workout   = ((comment / block) elws*)+ / elws
block     = tag ws "{" ((comment / params) / elws)+ "}"
params    = (message / value) ","?
value     = tag ws (string / range / rangeval)

message   = "@" ws duration ws string
range     = rangeval ws "->" ws rangeval
rangeval  = duration / numeric / zone
duration  = number ":" number
percent   = number "%"
zone      = ("Z" number) / "SS"
numeric   = percent / number
elws      = ws / emptyline

comment   = ~r"\;[^\r\n]*"
tag       = ~"[A-Z_]+"
string    = ~'"[^\"]+"'
number    = ~"\d+"
ws        = ~"\s*"
emptyline = ws+Syntax & KeywordsLike Zwift's built-in workout builder, the ZWO minilang is a block-based system. Blocks are specified using a<tag> {<block contents>}format supporting arbitrary whitespace.Inline comments are also supported, denoted by a leading;.Workout MetadataEach ZWO file must begin with aMETAblock containing comma-separated parameters:KeywordDescriptionAccepted InputsOptional?NAMEDisplayed workout namestrNoAUTHORWorkout authorstrNoDESCRIPTIONWorkout descriptionstr1NoFTPRider's FTPintMaybe2TAGSWorkout tagsString of hashtags3YesMultiline strings are supportedZwift's workouts are generated using FTP percentages rather than absolute watts, so your FTP is required if you want to use absolute watts in your ZWOMTags are capped at 31 total characters, including spaces and hashtags. Zwift also provides 4 built-in tags (#RECOVERY,#INTERVALS,#FTP, and#TT) that may also be added and do not count against this total.Workout BlocksFollowing theMETAblock are your workout blocks:KeywordDescriptionFREEFree rideCOOLDOWNCooldownINTERVALSIntervalsRAMPRampSEGMENTSteady segmentWARMUPWarmupNOTE:While there is no specific Ramp block in the workout building UI, some experimental observations have been made:If a ramp is at the very beginning of the workout, Zwift serializes it as a Warmup blockIf there are multiple blocks in a workout and a ramp is at the end, Zwift serializes it as a Cooldown blockIf there are multiple blocks in a workout and a ramp is not at the beginning or the end, Zwift serializes it as a Ramp blockWhen writing your*.zwomfile, these 3 blocks can be used interchangably, and ZWOM will try to match this behavior when outputting its*.zwofile. Zwift may do its own normalization if edits are made in the workout UI.Workout Block MetadataWorkout blocks can contain the following (optionally) comma-separated parameters:KeywordDescriptionAccepted InputsOptional?DURATIONBlock durationMM:SS, Range1NoCADENCETarget cadenceint, Range1,2YesREPEATNumber of intervalsintOnly valid for intervalsPOWERTarget powerint,int%, Zone3, Range1Mostly no4@Display a message@ MM:SS str5YesFor Interval & Ramp segments, the range syntax can be used to set values for the<left> -> <right>segments (e.g.65% -> 120%orZ2 -> Z6)Cadence ranges are only valid for Interval segmentsZones may be specified asZ1-7orSSPower is ignored for Free segmentsMessage timestamps are relative to their containing blockRepeating a Chunk of BlocksTheSTART_REPEATandEND_REPEATmeta blocks are provided to specify an arbitrary chunk of blocks to repeat. TheSTART_REPEATblock must specify aREPEATparameter;END_REPEATaccepts no parameters. Nested repeats are not currently supported.For example:SEGMENT {DURATION 2:00, POWER 65%}
RAMP {
    DURATION 2:00,
    POWER 120% -> 140%,
    @ 0:00 "Here goes the ramp!",
    @ 1:50 "10 seconds left!",
}
SEGMENT {DURATION 2:00, POWER 65%}
RAMP {
    DURATION 2:00,
    POWER 120% -> 140%,
    @ 0:00 "Here goes the ramp!",
    @ 1:50 "10 seconds left!",
}Becomes:START_REPEAT {REPEAT 2}
SEGMENT {DURATION 2:00, POWER 65%}
RAMP {
    DURATION 2:00,
    POWER 120% -> 140%,
    @ 0:00 "Here goes the ramp!",
    @ 1:50 "10 seconds left!",
}
END_REPEAT {}Sample Workout; Here is a workout-level comment!
META {
    NAME "Sample Workout",
    AUTHOR "sco1",
    DESCRIPTION "Here's a description!

    Descriptions may be on more than one line too!",
    TAGS "#RECOVERY #super #sweet #workout",
    FTP 270,
}
FREE {DURATION 10:00}
INTERVALS {
    ; Here is a block-level comment!
    REPEAT 3,
    DURATION 1:00 -> 0:30,
    POWER 55% -> 78%,
    CADENCE 85 -> 110,
}
SEGMENT {DURATION 2:00, POWER 65%}
RAMP {
    DURATION 2:00,
    POWER 120% -> 140%,
    @ 0:00 "Here goes the ramp!",
    @ 1:50 "10 seconds left!",
}
FREE {DURATION 10:00}