package syntax // Block scalars: the one construct in YAML whose extent is decided by // indentation rather than by a delimiter, and the one every CI file uses. // yamlCarry is what a line leaves open for the next. // // Only a block scalar does. It is the one construct in YAML whose extent is // decided by *indentation* rather than by a delimiter: everything indented // further than the key that opened it belongs to it, and the first line that is // not ends it. So the carry holds that key's indentation, and whether one is // open at all — a depth of zero is a real indentation, not an absence. type yamlCarry struct { inBlock bool // keyIndent is the column the key that opened the block starts at. A line // indented further is inside the block. keyIndent int // seenBody is false until the block's first content line, whose indentation // is not yet known. YAML lets a block be written at any deeper indent, so // the first line establishes it rather than a fixed offset doing so. seenBody bool } // continueBlockScalar colours a line belonging to an open block scalar, and // reports whether the block swallowed it. // // A blank line inside a block belongs to the block whatever its indentation: // YAML keeps empty lines in a literal scalar, and treating one as the end would // cut a shell script in a CI file in half at its first paragraph break. func continueBlockScalar(s *LineScanner, carry yamlCarry) (bool, yamlCarry) { if s.Len() == 0 { return true, carry } indent := leadingSpaces(s.line) if indent == s.Len() { // a line of nothing but spaces return true, carry } if !carry.seenBody { // The first content line fixes the block's own indentation. It only // counts as the body if it is indented past the key. if indent <= carry.keyIndent { return false, yamlCarry{} } carry.seenBody = true carry.keyIndent = indent - 1 // anything at or past `indent` is inside } if indent <= carry.keyIndent { return false, yamlCarry{} } s.TakeRest(ClassString) return true, carry } // opensBlockScalar reports whether what is left of the line is a `|` or `>` // header, with its optional chomping and indentation indicators. func opensBlockScalar(s *LineScanner) bool { if r := s.Peek(0); r != '|' && r != '>' { return false } for at := 1; s.Pos()+at < s.Len(); at++ { switch s.Peek(at) { case '-', '+', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ': continue case '#': return true // a trailing comment still leaves the block open default: return false } } return true } // leadingSpaces returns how many spaces a line starts with. func leadingSpaces(line []rune) int { for at, r := range line { if r != ' ' { return at } } return len(line) }