Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ subtle = { version = "2", default-features = false }
swagger-ui-redist = { version = "0.1" }
syn = { version = "3", default-features = false }
sync_wrapper = "1"
tempfile = "3"
tempfile = "3.11"
thiserror = "2"
time = { version = "0.3.55", default-features = false }
tokio = { version = "1.53", default-features = false }
Expand Down
6 changes: 4 additions & 2 deletions cot-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
tempfile = { workspace = true, optional = true }
wait-timeout.workspace = true

[dev-dependencies]
cot-cli = { path = ".", features = ["test_utils"] }
assert_cmd.workspace = true
insta.workspace = true
insta-cmd.workspace = true
tempfile.workspace = true
trybuild.workspace = true

[features]
test_utils = []
test_utils = [
"dep:tempfile"
]
105 changes: 105 additions & 0 deletions cot-cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::ffi::OsString;
use std::path::PathBuf;

use clap::{Args, Parser, Subcommand};
use clap_verbosity_flag::Verbosity;

pub const PACKAGE_LONG_FLAG: &str = "--package";
pub const PACKAGE_SHORT_FLAG: &str = "-p";
pub const RELEASE_FLAG: &str = "--release";
pub const BINARY_FLAG: &str = "--bin";
pub const BUILD_FLAG: &str = "--build";

#[derive(Debug, Parser)]
#[command(
Expand All @@ -15,6 +18,16 @@ pub const BINARY_FLAG: &str = "--bin";
long_about = None
)]
pub struct Cli {
/// Use target/release instead of target/debug when looking for the project
/// binary
#[arg(long, global = true)]
release: bool,
/// Build the binary if it does not exist
#[arg(long, global = true)]
build: bool,
/// Package to use, in case you're running this in a workspace
#[arg(short = 'p', long, global = true, value_name = "PACKAGE")]
pub package: Option<String>,
#[command(flatten)]
pub verbose: Verbosity,
#[command(subcommand)]
Expand All @@ -33,6 +46,9 @@ pub enum Commands {
/// Manage Cot CLI
#[command(subcommand)]
Cli(CliCommands),

#[command(external_subcommand)]
External(Vec<OsString>),
}

#[derive(Debug, Args)]
Expand All @@ -54,6 +70,9 @@ pub enum MigrationCommands {
Make(MigrationMakeArgs),
/// Create a new empty migration
New(MigrationNewArgs),
/// External migration subcommands shipped with the cot binary
#[command(external_subcommand)]
External(Vec<OsString>),
}

#[derive(Debug, Args)]
Expand Down Expand Up @@ -123,3 +142,89 @@ pub struct CompletionsArgs {
/// Shell to generate completions for
pub shell: clap_complete::Shell,
}

/// Pulls `-p <name>` / `--package <name>` / `--package=<name>` out of raw
/// argv, before clap has parsed anything. Needed because `project::load`
/// must run before `Cli::parse` for the `--help` interception path.
#[must_use]
pub fn extract_package_arg(raw: &[String]) -> Option<String> {
let mut iter = raw.iter();
while let Some(arg) = iter.next() {
if arg == "--" {
// all args before the double dash delimeter is used internally per convention
// and any arg after the delimeter is forwarded to the binary, so we
// stop here
return None;
}
if let Some(value) = arg.strip_prefix(&format!("{PACKAGE_LONG_FLAG}=")) {
return Some(value.to_string());
}
if arg == PACKAGE_LONG_FLAG || arg == PACKAGE_SHORT_FLAG {
return iter.next().cloned();
}
}
None
}

#[cfg(test)]
mod tests {
use super::*;

fn args(raw: &[&str]) -> Vec<String> {
raw.iter().map(|arg| (*arg).to_string()).collect()
}

#[test]
fn extract_package_arg_long_with_separate_value() {
let raw = args(&["cot", "--release", "--package", "blog", "check"]);

assert_eq!(extract_package_arg(&raw), Some("blog".to_string()));
}

#[test]
fn extract_package_arg_long_with_equals_value() {
let raw = args(&["cot", "--package=blog", "check"]);

assert_eq!(extract_package_arg(&raw), Some("blog".to_string()));
}

#[test]
fn extract_package_arg_short_with_value() {
let raw = args(&["cot", "-p", "blog", "check"]);

assert_eq!(extract_package_arg(&raw), Some("blog".to_string()));
}

#[test]
fn extract_package_arg_returns_first_package_flag() {
let raw = args(&["cot", "-p", "first", "--package", "second", "check"]);

assert_eq!(extract_package_arg(&raw), Some("first".to_string()));
}

#[test]
fn extract_package_arg_missing_value_returns_none() {
let raw = args(&["cot", "check", "-p"]);

assert_eq!(extract_package_arg(&raw), None);
}

#[test]
fn extract_package_arg_absent_returns_none() {
let raw = args(&["cot", "--release", "check"]);

assert_eq!(extract_package_arg(&raw), None);
}

#[test]
fn extract_package_arg_stops_scanning_at_double_dash() {
let raw = args(&["cot", "check", "--", "-p", "package"]);
assert_eq!(extract_package_arg(&raw), None);
}

#[test]
fn extract_package_arg_found_before_double_dash_ignores_forwarded_content() {
let raw = args(&["cot", "-p", "real", "check", "--", "-p", "forwarded"]);
assert_eq!(extract_package_arg(&raw), Some("real".to_string()));
}
}
Loading
Loading