//
// This PEG file strives to capture as much of the declarative
// pipeline syntax as possible
//
pipeline = _{ SOI ~ preceeding_junk? ~
pipeline_start ~
opening_brace ~
// I am pretty sure this grammar is wrong and there has to
// a better way to allow these declarations anywhere within
// the block, but still only allow one of each.
// For now the parser code will have to inspect this itself
(
// An agent declaration at the root level is required
agentDecl
// A stages declaration is also required
| stagesDecl
| environmentDecl
| optionsDecl
| parametersDecl
| postDecl
| toolsDecl
| triggersDecl
)*
~
closing_brace ~
ending_junk? ~
EOI }
pipeline_start = _{ "pipeline" }
preceeding_junk = { (!pipeline_start ~ ANY)* }
ending_junk = { ANY* }
agentDecl = { "agent" ~
("any"
| "none"
| agentBlock)
}
agentBlock = {
opening_brace ~
(k8sAgent
| dockerAgent
| dockerfileAgent
| nodeAgent
| ("label" ~ string)) ~
closing_brace
}
credentialProperty = { IDENT ~
"=" ~
"credentials" ~ opening_paren ~ string ~ closing_paren
}
dockerAgent = { "docker" ~
(string
| (
opening_brace ~
("reuseNode" ~ bool)? ~
("image" ~ string)? ~
("label" ~ string)? ~
("args" ~ string)? ~
("registryUrl" ~ string)? ~
("registryCredentialsId" ~ string)? ~
("customWorkspace" ~ string)? ~
closing_brace
)
)
}
dockerfileAgent = { "dockerfile" ~
opening_brace ~
("reuseNode" ~ bool)? ~
("filename" ~ string)? ~
("dir" ~ string)? ~
("label" ~ string)? ~
("additionalBuildArgs" ~ string)? ~
("args" ~ string)? ~
("customWorkspace" ~ string)? ~
closing_brace
}
environmentDecl = { "environment" ~
opening_brace ~
(envProperty)* ~
closing_brace
}
envProperty = {
property
| credentialProperty
}
failFast = { "failFast" ~ bool }
func = { IDENT ~ opening_paren? ~ (kwargs | args | func)? ~ closing_paren? }
inputDecl = { "input" ~
opening_brace ~
("message" ~ string)? ~
("submitter" ~ string)? ~
("id" ~ string)? ~
closing_brace
}
k8sAgent = { "kubernetes" ~
opening_brace ~
("defaultContainer" ~ string)? ~
("yaml" ~ string)? ~
("yamlFile" ~ string)? ~
closing_brace
}
nodeAgent = { "node" ~
opening_brace ~
("label" ~ string)? ~
("customWorkspace" ~ string)? ~
closing_brace
}
optionsDecl = { "options" ~
opening_brace ~
(func)* ~
closing_brace
}
parallelDecl = { "parallel" ~
opening_brace ~
(stage)+ ~
closing_brace
}
parametersDecl = { "parameters" ~
opening_brace ~
// Not exactly a step, but the syntax looks the same to me!
(step)* ~
closing_brace
}
postDecl = { "post" ~
opening_brace ~
(postBlock+) ~
closing_brace
}
postBlock = {
("always"
| "changed"
| "fixed"
| "regression"
| "aborted"
| "failure"
| "success"
| "unstable"
| "unsuccessful"
| "cleanup"
) ~ opening_brace ~ (step)+ ~ closing_brace }
scriptStep = { "script" ~ opening_brace ~ groovy ~ closing_brace }
groovy = {
(
// Handle nested structures
(opening_brace ~ groovy ~ closing_brace)
| (!closing_brace ~ ANY)
)*
}
stagesDecl = { "stages" ~
opening_brace ~
stage+ ~
closing_brace
}
stage = { "stage(" ~ string ~ ")" ~
opening_brace ~
(whenDecl
| failFast
| optionsDecl
| agentDecl
| environmentDecl
| inputDecl
| toolsDecl
| (stepsDecl | parallelDecl | stagesDecl)
| postDecl)* ~
closing_brace
}
stepsDecl = { "steps" ~
opening_brace ~
(step | scriptStep)+ ~
closing_brace
}
step_args = _{ args | kwargs | array_args | map_args }
// A simple step can be invoked with parenthesis or without
simple_step = _{ IDENT ~
((opening_paren ~ step_args? ~ closing_paren) | step_args) ~
// This allows for really gross abusive chaining of Groovy
// functionality off the end of a step invocation
("." ~ simple_step)*
}
// A block step is something like dir('foo') { } that accepts steps within it
// in many cases these will have keyword arguments like withCredentials
block_step = _{ IDENT ~
(opening_paren ~ step_args? ~ closing_paren)? ~
opening_brace ~
(step)+ ~
closing_brace
}
// Hard-coding this until the parser has richer symbol support
checkout_step = { "checkout" ~ "scm" }
cmd_step = { ( "sh" | "bat" | "stash" | "unstash" | "echo" ) ~ string}
sleep_step = { "sleep" ~ number }
step = { checkout_step | cmd_step | sleep_step | block_step | simple_step }
value = _{ string | bool | number | magic_vars }
arg = _{ value | array_args | map_args | step }
args = { (arg ~ comma?)+ }
kwarg = { IDENT ~ ":" ~ arg }
kwargs = _{ (kwarg ~ comma?)+ }
map_args = _{ opening_brack ~ kwargs ~ closing_brack }
// This syntax is some Groovy invocation magic that I'm not sure I fully
// understand at the moment
array_args = _{ opening_brack ~ args ~ closing_brack }
property = { IDENT ~ "=" ~ string }
// These are magic variables that are legitimate to use in "declarative"
// pipeline. Supports concatenation
magic_vars = _{ (envRef | scmRef | paramsRef | hudsonRef | "WORKSPACE") ~ (plus ~ (string | magic_vars))? }
// This is required for some step invocations where ABI objects must be
// referenced
hudsonRef = { "hudson." ~ nestedRef }
envRef = { "env." ~ nestedRef }
scmRef = { "scm." ~ nestedRef }
paramsRef = { "params." ~ nestedRef }
// Just parse anything until the whitespace I guess
nestedRef = _{ (IDENT ~ (opening_brack ~ (number) ~ closing_brack)? ~ "."?)+ }
toolsDecl = { "tools" ~
opening_brace ~
(IDENT ~ string)* ~
closing_brace
}
triggersDecl = { "triggers" ~
opening_brace ~
(func)* ~
closing_brace
}
// when {} directives are quite complex
///////////////////////////////////////
whenAll = { "allOf" ~ opening_brace ~ whenCondition+ ~ closing_brace }
whenAny = { "anyOf" ~ opening_brace ~ whenCondition+ ~ closing_brace }
whenBeforeInput = { "beforeInput" ~ bool }
whenBeforeAgent = { "beforeAgent" ~ bool }
whenBeforeOptions = { "beforeOptions" ~ bool }
whenBranch = { "branch" ~ string }
whenCondition = { whenExpression | whenBranch | whenEnvironment | whenTag | whenTriggered }
whenDecl = { "when" ~
opening_brace ~
(whenQualifiers | whenPredicate | whenCondition)+ ~
closing_brace
}
whenEnvironment = { "environment" ~ kwargs }
whenExpression = { "expression" ~ opening_brace ~ (!closing_brace ~ ANY)* ~ closing_brace }
whenNot = { "not" ~ opening_brace ~ whenCondition+ ~ closing_brace }
whenPredicate = { whenNot | whenAll | whenAny }
whenQualifiers = { whenBeforeInput | whenBeforeOptions | whenBeforeAgent }
whenTag = { "tag" ~ string }
whenTriggered = { "triggeredBy" ~ string }
///////////////////////////////////////
IDENT = @{ (ASCII_ALPHA | "$") ~ (ASCII_ALPHANUMERIC | "_")* }
string = { (triple_single_quoted | single_quoted | triple_double_quoted | double_quoted) ~ (plus ~ (magic_vars | string))* }
single_quoted = ${ single_quote ~ inner_single_str ~ single_quote }
triple_single_quoted = ${ triple_single_quote ~ inner_triple_single_str ~ triple_single_quote }
double_quoted = ${ (quote ~ inner_double_str ~ quote) }
triple_double_quoted = ${ triple_quote ~ inner_triple_double_str ~ triple_quote }
triple_single_quote = ${ single_quote ~ single_quote ~ single_quote }
triple_quote = ${ quote ~ quote ~ quote }
inner_single_str = @{ (!("'" | "\\") ~ ANY)* ~ (escape ~ inner_single_str)? }
inner_triple_single_str = @{ (!("'''" | "\\") ~ ANY)* ~ (escape ~ inner_triple_single_str)? }
inner_double_str = @{ (!("\"" | "\\") ~ ANY)* ~ (escape ~ inner_double_str)? }
inner_triple_double_str = @{ (!("\"\"\"" | "\\") ~ ANY)* ~ (escape ~ inner_triple_double_str)? }
inner_chr = @{ escape | ANY }
escape = @{ "\\" ~ ("\"" | "\\" | "r" | "n" | "t" | "0" | "'" | code | unicode) }
code = @{ "x" ~ hex_digit{2} }
unicode = @{ "u" ~ opening_brace ~ hex_digit{2, 6} ~ closing_brace }
hex_digit = @{ '0'..'9' | 'a'..'f' | 'A'..'F' }
quote = { "\"" }
single_quote = { "'" }
assignment_operator = { "=" }
opening_brace = { "{" }
closing_brace = { "}" }
opening_paren = { "(" }
closing_paren = { ")" }
opening_brack = { "[" }
closing_brack = { "]" }
plus = @{ "+" }
number = @{ '0'..'9'+ }
integer = @{ number | "-" ~ "0"* ~ '1'..'9' ~ number? }
comma = @{ "," }
bool = @{ (TRUE | FALSE) }
TRUE = { "true" }
FALSE = { "false" }
WHITESPACE = _{ " " | "\t" | NEWLINE }
block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" }
COMMENT = _{ block_comment | ("//" ~ (!NEWLINE~ ANY)*) }