crap4rust/invocation/entry_point.rs
1// Copyright 2026 Umberto Gotti <umberto.gotti@umbertogotti.dev>
2// Licensed under the MIT License
3// SPDX-License-Identifier: MIT
4
5use crate::invocation::app::run_from_args;
6use std::process::ExitCode;
7
8// Everything `main` used to do, moved somewhere a test can reach it.
9//
10// A binary entry point is not reachable from an integration test, so logic
11// living in `main.rs` is logic nothing can check. What was sitting there is not
12// glue: it is the argv fixup every cargo subcommand needs, and it has two
13// properties that are easy to get wrong and impossible to notice.
14//
15// Cargo runs `cargo crap4rust ...` as `cargo-crap4rust crap4rust ...`, so the
16// name arrives twice. Running the binary directly does not repeat it, which is
17// why the strip is **conditional**. And it is **positional** -- only argv[1] is
18// dropped -- because removing every occurrence would swallow
19// `--package crap4rust`.
20pub struct EntryPoint;
21
22impl EntryPoint {
23 pub const SUBCOMMAND: &'static str = "crap4rust";
24
25 // Conditional and positional: only a repeated name in argv[1] goes.
26 pub fn without_cargo_subcommand(args: Vec<String>) -> Vec<String> {
27 if args.get(1).map(String::as_str) != Some(Self::SUBCOMMAND) {
28 return args;
29 }
30 let mut forwarded = Vec::with_capacity(args.len().saturating_sub(1));
31 let mut rest = args.into_iter();
32 if let Some(binary) = rest.next() {
33 forwarded.push(binary);
34 }
35 forwarded.extend(rest.skip(1));
36 forwarded
37 }
38
39 // An error is exit 2, the same code a broken rule uses, because a run that
40 // could not finish must never read as a clean project.
41 pub fn run(args: Vec<String>) -> ExitCode {
42 match run_from_args(Self::without_cargo_subcommand(args)) {
43 Ok(code) => code,
44 Err(error) => {
45 println!("error: {error:#}");
46 ExitCode::from(2)
47 }
48 }
49 }
50}