| 135 | } |
| 136 | |
| 137 | fn parse_builtin(line_reader: &mut LineReader) -> Result<BuiltinCommand, PosError> { |
| 138 | let (pos, line) = line_reader.next().unwrap(); |
| 139 | let mut builtin_reader = BuiltinReader::new(&line, pos); |
| 140 | let name = match builtin_reader.next() { |
| 141 | Some(Ok((_, s))) => s, |
| 142 | Some(Err(e)) => return Err(e), |
| 143 | None => { |
| 144 | return Err(PosError { |
| 145 | source: anyhow!("command line is missing command name"), |
| 146 | pos: Some(pos), |
| 147 | }); |
| 148 | } |
| 149 | }; |
| 150 | let mut args = BTreeMap::new(); |
| 151 | for el in builtin_reader { |
| 152 | let (pos, token) = el?; |
| 153 | let pieces: Vec<_> = token.splitn(2, '=').collect(); |
| 154 | let pieces = match pieces.as_slice() { |
| 155 | [key, value] => vec![*key, *value], |
| 156 | [key] => vec![*key, ""], |
| 157 | _ => { |
| 158 | return Err(PosError { |
| 159 | source: anyhow!("command argument is not in required key=value format"), |
| 160 | pos: Some(pos), |
| 161 | }); |
| 162 | } |
| 163 | }; |
| 164 | validate_ident(pieces[0]).map_err(|e| PosError::new(e, pos))?; |
| 165 | |
| 166 | if let Some(original) = args.insert(pieces[0].to_owned(), pieces[1].to_owned()) { |
| 167 | return Err(PosError { |
| 168 | source: anyhow!( |
| 169 | "argument '{}' specified twice: {} & {}", |
| 170 | pieces[0], |
| 171 | original, |
| 172 | pieces[1] |
| 173 | ), |
| 174 | pos: Some(pos), |
| 175 | }); |
| 176 | }; |
| 177 | } |
| 178 | Ok(BuiltinCommand { |
| 179 | name, |
| 180 | args: ArgMap(args), |
| 181 | input: slurp_all(line_reader), |
| 182 | }) |
| 183 | } |
| 184 | |
| 185 | /// Validate that the string is an allowed variable name (lowercase letters, numbers and dashes) |
| 186 | pub fn validate_ident(name: &str) -> Result<(), anyhow::Error> { |