bpaf_cauwugo/
add.rs

1use crate::shared::parse_package;
2use bpaf::*;
3use std::{path::PathBuf, process::Command};
4
5#[derive(Debug, Clone, Bpaf)]
6#[bpaf(command, generate(parse_add))]
7/// Add dependencies to a Cargo.toml manifest file
8pub struct Add {
9    #[bpaf(external)]
10    pub package: Option<&'static str>,
11
12    /// Add as dev dependency
13    pub dev: bool,
14
15    /// Add as build dependency
16    pub build: bool,
17
18    #[bpaf(external)]
19    pub source: Source,
20}
21
22#[derive(Debug, Clone, Bpaf)]
23pub enum Source {
24    Git {
25        #[bpaf(long, argument("URI"))]
26        /// Git repository location
27        git: String,
28
29        #[bpaf(long, argument("BRANCH"))]
30        /// Git branch to download the crate from
31        branch: Option<String>,
32
33        #[bpaf(long, argument("TAG"))]
34        /// Git tag to download the crate from
35        tag: Option<String>,
36
37        #[bpaf(long, argument("TAG"))]
38        /// Git reference to download the crate from
39        rev: Option<String>,
40    },
41    Local {
42        /// Filesystem path to local crate to add
43        path: PathBuf,
44    },
45    Crates {
46        /// Package registry to use instead of crates.io
47        registry: Option<String>,
48
49        /// Package name
50        #[bpaf(positional("DEP"), complete(complete_available_package))]
51        name: String,
52    },
53}
54
55/// ask cargo search for all the available packages matching the name
56///
57/// TODO: do some filtering - squatted names and such don't belong here
58fn complete_available_package(name: &String) -> Vec<(String, Option<String>)> {
59    if name.is_empty() {
60        return vec![("<PACKAGE>".to_string(), None)];
61    }
62    let mut cmd = Command::new("cargo");
63    cmd.args(["search", "--limit", "100"]); // greedy cargo will print only 100 or so at most :(
64    cmd.arg(name);
65    let output = cmd.output().expect("Couldn't run cargo search?");
66
67    if output.status.code() == Some(0) {
68        String::from_utf8_lossy(&output.stdout)
69            .lines()
70            .filter_map(|line| {
71                if !line.starts_with(name) {
72                    return None;
73                }
74                let (full_package, descr) = line.split_once(" # ")?;
75                let package = full_package.split(' ').next()?;
76                Some((package.to_owned(), Some(descr.to_owned())))
77            })
78            .collect::<Vec<_>>()
79    } else {
80        panic!("{:?}", output);
81    }
82}
83
84impl Add {
85    pub fn pass_to_cmd(&self, cmd: &mut Command) {
86        cmd.arg("add");
87        pass_arg!(cmd, self.package, "--package");
88        pass_flag!(cmd, self.dev, "--dev");
89        pass_flag!(cmd, self.build, "--build");
90        match &self.source {
91            Source::Git {
92                git,
93                branch,
94                tag,
95                rev,
96            } => {
97                pass_req_arg!(cmd, git, "--git");
98                pass_arg!(cmd, branch, "--branch");
99                pass_arg!(cmd, tag, "--tag");
100                pass_arg!(cmd, rev, "--rev");
101            }
102            Source::Local { path } => {
103                pass_req_arg!(cmd, path, "--path");
104            }
105            Source::Crates { registry, name } => {
106                cmd.arg(name);
107                pass_arg!(cmd, registry, "--registry");
108            }
109        }
110    }
111}
112
113fn package() -> impl Parser<Option<&'static str>> {
114    parse_package("Package to add a new dependency to")
115}