| 541 | } |
| 542 | |
| 543 | int cbm_parse_dockerfile_source(const char *source, cbm_dockerfile_result_t *out) { |
| 544 | if (!source || !out) { |
| 545 | return CBM_NOT_FOUND; |
| 546 | } |
| 547 | memset(out, 0, sizeof(*out)); |
| 548 | |
| 549 | /* Process line by line */ |
| 550 | const char *p = source; |
| 551 | char line[CBM_SZ_4K]; |
| 552 | |
| 553 | while (*p) { |
| 554 | /* Extract one line */ |
| 555 | const char *eol = strchr(p, '\n'); |
| 556 | size_t line_len = eol ? (size_t)(eol - p) : strlen(p); |
| 557 | if (line_len >= sizeof(line)) { |
| 558 | line_len = sizeof(line) - SKIP_ONE; |
| 559 | } |
| 560 | memcpy(line, p, line_len); |
| 561 | line[line_len] = '\0'; |
| 562 | p = eol ? eol + SKIP_ONE : p + line_len; |
| 563 | |
| 564 | /* Trim */ |
| 565 | char *trimmed = line; |
| 566 | while (*trimmed == ' ' || *trimmed == '\t') { |
| 567 | trimmed++; |
| 568 | } |
| 569 | rtrim(trimmed); |
| 570 | |
| 571 | /* Skip empty lines and comments */ |
| 572 | if (*trimmed == '\0' || *trimmed == '#') { |
| 573 | continue; |
| 574 | } |
| 575 | |
| 576 | df_parse_from(trimmed, out); |
| 577 | df_parse_expose(trimmed, out); |
| 578 | df_parse_env(trimmed, out); |
| 579 | df_parse_arg(trimmed, out); |
| 580 | df_parse_directives(trimmed, out); |
| 581 | } |
| 582 | |
| 583 | /* No stages = empty/invalid Dockerfile */ |
| 584 | if (out->stage_count == 0) { |
| 585 | return CBM_NOT_FOUND; |
| 586 | } |
| 587 | |
| 588 | /* base_image = last stage's image (use intermediate copy to avoid restrict overlap) */ |
| 589 | { |
| 590 | char tmp[sizeof(out->base_image)]; |
| 591 | snprintf(tmp, sizeof(tmp), "%s", out->stage_images[out->stage_count - SKIP_ONE]); |
| 592 | memcpy(out->base_image, tmp, sizeof(out->base_image)); |
| 593 | } |
| 594 | |
| 595 | return 0; |
| 596 | } |
| 597 | |
| 598 | /* ── Dotenv parser ──────────────────────────────────────────────── */ |
| 599 |
no test coverage detected