1use crate::errors::*;
2use crate::lockfile::Lockfile;
3use crate::manifest::Manifest;
4use clap::{ArgAction, CommandFactory, Parser, Subcommand};
5use clap_complete::Shell;
6use std::collections::HashSet;
7use std::env;
8use std::io;
9use std::path::Path;
10use std::path::PathBuf;
11
12#[derive(Debug, Parser)]
13#[command(version)]
14pub struct Args {
15 #[arg(short, long, global = true, action(ArgAction::Count))]
17 pub verbose: u8,
18 #[arg(short = 'C', long)]
20 pub context: Option<PathBuf>,
21 #[command(subcommand)]
22 pub subcommand: SubCommand,
23}
24
25#[derive(Debug, Subcommand)]
26pub enum SubCommand {
27 Build(Build),
28 Update(Update),
29 Fetch(Fetch),
30 Completions(Completions),
31}
32
33#[derive(Debug, Parser)]
35pub struct Build {
36 #[arg(short, long)]
38 pub file: Option<PathBuf>,
39 #[arg(short, long)]
41 pub keep: bool,
42 #[arg(short, long)]
44 pub env: Vec<String>,
45 #[arg(required = true)]
47 pub cmd: Vec<String>,
48}
49
50impl Build {
51 pub fn validate(&self) -> Result<()> {
52 let mut env_keys = HashSet::new();
53 for env in &self.env {
54 let key = if let Some((key, _value)) = env.split_once('=') {
55 key
56 } else if env::var(env).is_ok() {
57 env
58 } else {
59 bail!("Referenced environment variables does not exist: {env:?}");
60 };
61
62 if !env_keys.insert(key) {
63 bail!("Can not set environment multiple times: {key:?}");
64 }
65 }
66 Ok(())
67 }
68
69 pub async fn load_files(&self) -> Result<(Option<Manifest>, Lockfile)> {
70 let path = self.file.as_deref().unwrap_or(Path::new("repro-env.lock"));
71 let lockfile = Lockfile::read_from_file(path).await?;
72
73 let manifest = if self.file.is_none() {
74 Some(Manifest::read_from_file("repro-env.toml").await?)
75 } else {
76 None
77 };
78
79 Ok((manifest, lockfile))
80 }
81}
82
83#[derive(Debug, Parser)]
85pub struct Update {
86 #[arg(long)]
88 pub no_pull: bool,
89 #[arg(short, long)]
91 pub keep: bool,
92}
93
94#[derive(Debug, Parser)]
96pub struct Fetch {
97 #[arg(short, long)]
99 pub file: Option<PathBuf>,
100 #[arg(long)]
102 pub no_pull: bool,
103}
104
105#[derive(Debug, Parser)]
107pub struct Completions {
108 pub shell: Shell,
109}
110
111impl Completions {
112 pub fn generate<W: io::Write>(&self, mut w: W) -> Result<()> {
113 clap_complete::generate(self.shell, &mut Args::command(), "repro-env", &mut w);
114 Ok(())
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_zsh_completions() {
124 Completions { shell: Shell::Zsh }
125 .generate(io::sink())
126 .unwrap();
127 }
128}