Skip to content

Commit

Permalink
Add manpage support
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Apr 3, 2024
1 parent a408745 commit 20c28e3
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 236 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/bws/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,11 @@ uuid = { version = "^1.7.0", features = ["serde"] }

bitwarden = { workspace = true, features = ["secrets"] }

[build-dependencies]
clap = { version = "4.5.1", features = ["derive", "string"] }
clap_complete = "4.5.0"
clap_mangen = "0.2.20"
uuid = { version = "^1.7.0" }

[dev-dependencies]
tempfile = "3.10.0"
16 changes: 16 additions & 0 deletions crates/bws/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
include!("src/cli.rs");

fn main() -> Result<(), std::io::Error> {
use std::{env, fs, path::Path};

let out_dir = env::var_os("OUT_DIR").unwrap();
let path = Path::new(&out_dir).join("manpages");
fs::create_dir_all(&path).unwrap();

let cmd = <Cli as clap::CommandFactory>::command();
clap_mangen::generate_to(cmd, &path)?;

println!("cargo:warning=man files generated: {path:?}");

Ok(())
}
234 changes: 234 additions & 0 deletions crates/bws/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
use std::path::PathBuf;

use clap::{ArgGroup, Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use uuid::Uuid;

pub(crate) const ACCESS_TOKEN_KEY_VAR_NAME: &str = "BWS_ACCESS_TOKEN";
pub(crate) const CONFIG_FILE_KEY_VAR_NAME: &str = "BWS_CONFIG_FILE";
pub(crate) const PROFILE_KEY_VAR_NAME: &str = "BWS_PROFILE";
pub(crate) const SERVER_URL_KEY_VAR_NAME: &str = "BWS_SERVER_URL";

pub(crate) const DEFAULT_CONFIG_FILENAME: &str = "config";
pub(crate) const DEFAULT_CONFIG_DIRECTORY: &str = ".bws";

#[allow(non_camel_case_types)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub(crate) enum ProfileKey {
server_base,
server_api,
server_identity,
state_file_dir,
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum Output {
JSON,
YAML,
Env,
Table,
TSV,
None,
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub(crate) enum Color {
No,
Yes,
Auto,
}

#[derive(Parser, Debug)]
#[command(name = "bws", version, about = "Bitwarden Secrets CLI", long_about = None)]
pub(crate) struct Cli {
// Optional as a workaround for https://github.com/clap-rs/clap/issues/3572
#[command(subcommand)]
pub(crate) command: Option<Commands>,

#[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON, help="Output format")]
pub(crate) output: Output,

Check warning on line 50 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L49-L50

Added lines #L49 - L50 were not covered by tests

#[arg(short = 'c', long, global = true, value_enum, default_value_t = Color::Auto, help="Use colors in the output")]
pub(crate) color: Color,

Check warning on line 53 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L52-L53

Added lines #L52 - L53 were not covered by tests

#[arg(short = 't', long, global = true, env = ACCESS_TOKEN_KEY_VAR_NAME, hide_env_values = true, help="Specify access token for the service account")]
pub(crate) access_token: Option<String>,

#[arg(
short = 'f',
long,
global = true,
env = CONFIG_FILE_KEY_VAR_NAME,
help = format!("[default: ~/{}/{}] Config file to use", DEFAULT_CONFIG_DIRECTORY, DEFAULT_CONFIG_FILENAME)
)]
pub(crate) config_file: Option<PathBuf>,

#[arg(short = 'p', long, global = true, env = PROFILE_KEY_VAR_NAME, help="Profile to use from the config file")]
pub(crate) profile: Option<String>,

#[arg(short = 'u', long, global = true, env = SERVER_URL_KEY_VAR_NAME, help="Override the server URL from the config file")]
pub(crate) server_url: Option<String>,
}

#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
#[command(long_about = "Configure the CLI", arg_required_else_help(true))]
Config {
name: Option<ProfileKey>,
value: Option<String>,

#[arg(short = 'd', long)]
delete: bool,

Check warning on line 82 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L82

Added line #L82 was not covered by tests
},

#[command(long_about = "Generate shell completion files")]
Completions { shell: Option<Shell> },

#[command(long_about = "Commands available on Projects")]
Project {
#[command(subcommand)]
cmd: ProjectCommand,
},
#[command(long_about = "Commands available on Secrets")]
Secret {
#[command(subcommand)]
cmd: SecretCommand,
},
#[command(long_about = "Create a single item (deprecated)", hide(true))]
Create {
#[command(subcommand)]
cmd: CreateCommand,
},
#[command(long_about = "Delete one or more items (deprecated)", hide(true))]
Delete {
#[command(subcommand)]
cmd: DeleteCommand,
},
#[command(long_about = "Edit a single item (deprecated)", hide(true))]
Edit {
#[command(subcommand)]
cmd: EditCommand,
},
#[command(long_about = "Retrieve a single item (deprecated)", hide(true))]
Get {
#[command(subcommand)]
cmd: GetCommand,
},
#[command(long_about = "List items (deprecated)", hide(true))]
List {
#[command(subcommand)]
cmd: ListCommand,
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum SecretCommand {
Create {
key: String,
value: String,

Check warning on line 129 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L128-L129

Added lines #L128 - L129 were not covered by tests

#[arg(help = "The ID of the project this secret will be added to")]
project_id: Uuid,

Check warning on line 132 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L132

Added line #L132 was not covered by tests

#[arg(long, help = "An optional note to add to the secret")]
note: Option<String>,
},
Delete {
secret_ids: Vec<Uuid>,

Check warning on line 138 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L138

Added line #L138 was not covered by tests
},
#[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))]
Edit {
secret_id: Uuid,

Check warning on line 142 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L142

Added line #L142 was not covered by tests
#[arg(long, group = "edit_field")]
key: Option<String>,
#[arg(long, group = "edit_field")]
value: Option<String>,
#[arg(long, group = "edit_field")]
note: Option<String>,
#[arg(long, group = "edit_field")]
project_id: Option<Uuid>,
},
Get {
secret_id: Uuid,

Check warning on line 153 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L153

Added line #L153 was not covered by tests
},
List {
project_id: Option<Uuid>,
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum ProjectCommand {
Create {
name: String,

Check warning on line 163 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L163

Added line #L163 was not covered by tests
},
Delete {
project_ids: Vec<Uuid>,

Check warning on line 166 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L166

Added line #L166 was not covered by tests
},
Edit {
project_id: Uuid,

Check warning on line 169 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L169

Added line #L169 was not covered by tests
#[arg(long, group = "edit_field")]
name: String,

Check warning on line 171 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L171

Added line #L171 was not covered by tests
},
Get {
project_id: Uuid,

Check warning on line 174 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L174

Added line #L174 was not covered by tests
},
List,
}

#[derive(Subcommand, Debug)]
pub(crate) enum ListCommand {
Projects,
Secrets { project_id: Option<Uuid> },
}

#[derive(Subcommand, Debug)]
pub(crate) enum GetCommand {
Project { project_id: Uuid },
Secret { secret_id: Uuid },

Check warning on line 188 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L187-L188

Added lines #L187 - L188 were not covered by tests
}

#[derive(Subcommand, Debug)]
pub(crate) enum CreateCommand {
Project {
name: String,

Check warning on line 194 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L194

Added line #L194 was not covered by tests
},
Secret {
key: String,
value: String,

Check warning on line 198 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L197-L198

Added lines #L197 - L198 were not covered by tests

#[arg(long, help = "An optional note to add to the secret")]
note: Option<String>,

#[arg(long, help = "The ID of the project this secret will be added to")]
project_id: Uuid,

Check warning on line 204 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L204

Added line #L204 was not covered by tests
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum EditCommand {
#[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))]
Project {
project_id: Uuid,

Check warning on line 212 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L212

Added line #L212 was not covered by tests
#[arg(long, group = "edit_field")]
name: String,

Check warning on line 214 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L214

Added line #L214 was not covered by tests
},
#[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))]
Secret {
secret_id: Uuid,

Check warning on line 218 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L218

Added line #L218 was not covered by tests
#[arg(long, group = "edit_field")]
key: Option<String>,
#[arg(long, group = "edit_field")]
value: Option<String>,
#[arg(long, group = "edit_field")]
note: Option<String>,
#[arg(long, group = "edit_field")]
project_id: Option<Uuid>,
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum DeleteCommand {
Project { project_ids: Vec<Uuid> },
Secret { secret_ids: Vec<Uuid> },

Check warning on line 233 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L232-L233

Added lines #L232 - L233 were not covered by tests
}
21 changes: 6 additions & 15 deletions crates/bws/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use std::{
path::{Path, PathBuf},
};

use clap::ValueEnum;
use color_eyre::eyre::{bail, Result};
use directories::BaseDirs;
use serde::{Deserialize, Serialize};

use crate::cli::{ProfileKey, DEFAULT_CONFIG_DIRECTORY, DEFAULT_CONFIG_FILENAME};

#[derive(Debug, Serialize, Deserialize, Default)]
pub(crate) struct Config {
pub profiles: HashMap<String, Profile>,
Expand All @@ -22,16 +23,6 @@ pub(crate) struct Profile {
pub state_file_dir: Option<String>,
}

// TODO: This could probably be derived with a macro if we start adding more fields
#[allow(non_camel_case_types)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub(crate) enum ProfileKey {
server_base,
server_api,
server_identity,
state_file_dir,
}

impl ProfileKey {
fn update_profile_value(&self, p: &mut Profile, value: String) {
match self {
Expand All @@ -43,13 +34,13 @@ impl ProfileKey {
}
}

pub(crate) const FILENAME: &str = "config";
pub(crate) const DIRECTORY: &str = ".bws";

pub(crate) fn get_config_path(config_file: Option<&Path>, ensure_folder_exists: bool) -> PathBuf {
let config_file = config_file.map(ToOwned::to_owned).unwrap_or_else(|| {
let base_dirs = BaseDirs::new().unwrap();
base_dirs.home_dir().join(DIRECTORY).join(FILENAME)
base_dirs
.home_dir()
.join(DEFAULT_CONFIG_DIRECTORY)
.join(DEFAULT_CONFIG_FILENAME)
});

if ensure_folder_exists {
Expand Down
Loading

0 comments on commit 20c28e3

Please sign in to comment.