| 209 | } |
| 210 | |
| 211 | static int parse_workspace_kv(CBMArena* a, const char* s, int len, int from, |
| 212 | CBMCargoManifest* out) { |
| 213 | from = skip_ws_and_comment(s, len, from); |
| 214 | if (from >= len || s[from] == '[') return from; |
| 215 | out->is_workspace_root = true; |
| 216 | const char* key = NULL; |
| 217 | from = parse_key(a, s, len, from, &key); |
| 218 | from = skip_ws_and_comment(s, len, from); |
| 219 | if (from < len && s[from] == '=') { |
| 220 | from++; |
| 221 | from = skip_ws_and_comment(s, len, from); |
| 222 | } |
| 223 | if (key && strcmp(key, "members") == 0 && from < len && s[from] == '[') { |
| 224 | from++; |
| 225 | while (from < len && s[from] != ']') { |
| 226 | from = skip_ws_and_comment(s, len, from); |
| 227 | if (from < len && (s[from] == '"' || s[from] == '\'')) { |
| 228 | const char* mem = NULL; |
| 229 | from = parse_string(a, s, len, from, &mem); |
| 230 | if (mem && out->member_count < CBM_CARGO_MAX_MEMBERS) { |
| 231 | /* Derive a member NAME from the path's last segment. */ |
| 232 | const char* last = mem; |
| 233 | for (const char* p = mem; *p; p++) { |
| 234 | if (*p == '/') last = p + 1; |
| 235 | } |
| 236 | out->members[out->member_count].member_name = last; |
| 237 | out->members[out->member_count].member_path = mem; |
| 238 | out->member_count++; |
| 239 | } |
| 240 | } |
| 241 | from = skip_ws_and_comment(s, len, from); |
| 242 | if (from < len && s[from] == ',') from++; |
| 243 | from = skip_ws_and_comment(s, len, from); |
| 244 | } |
| 245 | if (from < len) from++; /* consume `]` */ |
| 246 | } else { |
| 247 | from = skip_value(s, len, from); |
| 248 | } |
| 249 | return from; |
| 250 | } |
| 251 | |
| 252 | void cbm_cargo_parse(CBMArena* arena, const char* src, int src_len, |
| 253 | CBMCargoManifest* out) { |
no test coverage detected