cleanlib-cli 0.1.0

Terminal interface to CleanLibrary — query dependency verdicts and scan package manifests for ALLOW / DENY / WARN signals from the terminal or CI pipelines.
//! `go get` arg parser.
//!
//! Go modules use `module@version` syntax (Go's own; canonical). Examples:
//! - `go get github.com/sirupsen/logrus@v1.9.0` — pinned
//! - `go get github.com/sirupsen/logrus` — latest minor/patch
//! - `go get github.com/sirupsen/logrus@latest` — explicit latest
//! - `go get -u ./...` — relative path, skip

use super::WrappedPackage;

const ECO: &str = "go";
const VERB: &str = "get";

pub fn parse(argv: &[&str]) -> Vec<WrappedPackage> {
    let Some(verb_pos) = argv.iter().position(|a| *a == VERB) else {
        return Vec::new();
    };
    let mut packages = Vec::new();
    for arg in argv.iter().skip(verb_pos + 1) {
        if arg.starts_with('-') {
            continue;
        }
        if arg.is_empty() {
            continue;
        }
        // Relative-path patterns like `./...` are local; skip.
        if arg.starts_with('.') || *arg == "all" {
            continue;
        }
        let (name, version) = split_pkg_spec(arg);
        packages.push(WrappedPackage::new(ECO, name, version));
    }
    packages
}

pub fn split_pkg_spec(spec: &str) -> (String, String) {
    match spec.rsplit_once('@') {
        Some((name, ver)) if !name.is_empty() && !ver.is_empty() => {
            (name.to_string(), ver.to_string())
        }
        _ => (spec.to_string(), "latest".to_string()),
    }
}

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

    fn argv<'a>(args: &[&'a str]) -> Vec<&'a str> {
        let mut v: Vec<&'a str> = vec!["go"];
        v.extend_from_slice(args);
        v
    }

    #[test]
    fn parses_get_pinned() {
        let a = argv(&["get", "github.com/sirupsen/logrus@v1.9.0"]);
        let pkgs = parse(&a);
        assert_eq!(
            pkgs[0],
            WrappedPackage::new("go", "github.com/sirupsen/logrus", "v1.9.0")
        );
    }

    #[test]
    fn parses_get_unpinned() {
        let a = argv(&["get", "github.com/sirupsen/logrus"]);
        let pkgs = parse(&a);
        assert_eq!(pkgs[0].version, "latest");
    }

    #[test]
    fn parses_get_at_latest() {
        let a = argv(&["get", "github.com/sirupsen/logrus@latest"]);
        let pkgs = parse(&a);
        assert_eq!(pkgs[0].version, "latest");
    }

    #[test]
    fn skips_relative_path() {
        let a = argv(&["get", "./..."]);
        assert!(parse(&a).is_empty());
    }

    #[test]
    fn skips_all_target() {
        let a = argv(&["get", "all"]);
        assert!(parse(&a).is_empty());
    }

    #[test]
    fn skips_update_flag() {
        let a = argv(&["get", "-u", "github.com/sirupsen/logrus"]);
        let pkgs = parse(&a);
        assert_eq!(pkgs.len(), 1);
        assert_eq!(pkgs[0].name, "github.com/sirupsen/logrus");
    }

    #[test]
    fn no_get_verb_returns_empty() {
        let a = argv(&["build"]);
        assert!(parse(&a).is_empty());
    }
}