| 🛟 Updated. 28d5985 k33g 20h ago | 1 | package syntax |
| 2 | |
| 3 | // Block scalars: the one construct in YAML whose extent is decided by |
| 4 | // indentation rather than by a delimiter, and the one every CI file uses. |
| 5 | |
| 6 | // yamlCarry is what a line leaves open for the next. |
| 7 | // |
| 8 | // Only a block scalar does. It is the one construct in YAML whose extent is |
| 9 | // decided by *indentation* rather than by a delimiter: everything indented |
| 10 | // further than the key that opened it belongs to it, and the first line that is |
| 11 | // not ends it. So the carry holds that key's indentation, and whether one is |
| 12 | // open at all — a depth of zero is a real indentation, not an absence. |
| 13 | type yamlCarry struct { |
| 14 | inBlock bool |
| 15 | // keyIndent is the column the key that opened the block starts at. A line |
| 16 | // indented further is inside the block. |
| 17 | keyIndent int |
| 18 | // seenBody is false until the block's first content line, whose indentation |
| 19 | // is not yet known. YAML lets a block be written at any deeper indent, so |
| 20 | // the first line establishes it rather than a fixed offset doing so. |
| 21 | seenBody bool |
| 22 | } |
| 23 | |
| 24 | // continueBlockScalar colours a line belonging to an open block scalar, and |
| 25 | // reports whether the block swallowed it. |
| 26 | // |
| 27 | // A blank line inside a block belongs to the block whatever its indentation: |
| 28 | // YAML keeps empty lines in a literal scalar, and treating one as the end would |
| 29 | // cut a shell script in a CI file in half at its first paragraph break. |
| 30 | func continueBlockScalar(s *LineScanner, carry yamlCarry) (bool, yamlCarry) { |
| 31 | if s.Len() == 0 { |
| 32 | return true, carry |
| 33 | } |
| 34 | |
| 35 | indent := leadingSpaces(s.line) |
| 36 | if indent == s.Len() { // a line of nothing but spaces |
| 37 | return true, carry |
| 38 | } |
| 39 | |
| 40 | if !carry.seenBody { |
| 41 | // The first content line fixes the block's own indentation. It only |
| 42 | // counts as the body if it is indented past the key. |
| 43 | if indent <= carry.keyIndent { |
| 44 | return false, yamlCarry{} |
| 45 | } |
| 46 | carry.seenBody = true |
| 47 | carry.keyIndent = indent - 1 // anything at or past `indent` is inside |
| 48 | } |
| 49 | if indent <= carry.keyIndent { |
| 50 | return false, yamlCarry{} |
| 51 | } |
| 52 | |
| 53 | s.TakeRest(ClassString) |
| 54 | return true, carry |
| 55 | } |
| 56 | |
| 57 | // opensBlockScalar reports whether what is left of the line is a `|` or `>` |
| 58 | // header, with its optional chomping and indentation indicators. |
| 59 | func opensBlockScalar(s *LineScanner) bool { |
| 60 | if r := s.Peek(0); r != '|' && r != '>' { |
| 61 | return false |
| 62 | } |
| 63 | for at := 1; s.Pos()+at < s.Len(); at++ { |
| 64 | switch s.Peek(at) { |
| 65 | case '-', '+', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ': |
| 66 | continue |
| 67 | case '#': |
| 68 | return true // a trailing comment still leaves the block open |
| 69 | default: |
| 70 | return false |
| 71 | } |
| 72 | } |
| 73 | return true |
| 74 | } |
| 75 | |
| 76 | // leadingSpaces returns how many spaces a line starts with. |
| 77 | func leadingSpaces(line []rune) int { |
| 78 | for at, r := range line { |
| 79 | if r != ' ' { |
| 80 | return at |
| 81 | } |
| 82 | } |
| 83 | return len(line) |
| 84 | } |