Skip to content

Commit

Permalink
Merge pull request #27 from donovanglover/feat/man-pages-shell-comple…
Browse files Browse the repository at this point in the history
…tions

feat: add man pages and shell completions
  • Loading branch information
sts10 committed Jul 30, 2024
2 parents 97cf6dc + 61324fd commit f5d0e84
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 114 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ clap = { version = "4.5.4", features = ["derive"] }
unicode-normalization = "0.1.23"
include-lines = "1.1.2"

[build-dependencies]
clap = { version = "4.5.4", features = ["derive"] }
clap_complete = "4.5.2"
clap_mangen = "0.2.20"

[dev-dependencies]
criterion = "0.5.1"

Expand Down
1 change: 1 addition & 0 deletions benches/generate_passphrase.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use criterion::{criterion_group, criterion_main, Criterion};
use phraze::cli::ListChoice;
use phraze::*;

fn criterion_benchmark(c: &mut Criterion) {
Expand Down
40 changes: 40 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#[path = "src/cli.rs"]
mod cli;

use clap::Command;
use clap::CommandFactory;
use clap_complete::generate_to;
use clap_complete::Shell::{Bash, Fish, Zsh};
use clap_mangen::Man;
use cli::Args;
use std::fs;
use std::path::PathBuf;

static NAME: &str = "phraze";

fn generate_man_pages(cmd: Command) {
let man_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target/man");
let mut buffer = Vec::default();

Man::new(cmd.clone()).render(&mut buffer).unwrap();
fs::create_dir_all(&man_dir).unwrap();
fs::write(man_dir.join(NAME.to_owned() + ".1"), buffer).unwrap();
}

fn generate_shell_completions(mut cmd: Command) {
let comp_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target/completions");

fs::create_dir_all(&comp_dir).unwrap();

for shell in [Bash, Fish, Zsh] {
generate_to(shell, &mut cmd, NAME, &comp_dir).unwrap();
}
}

fn main() {
let mut cmd = Args::command();
cmd.set_bin_name(NAME);

generate_man_pages(cmd.clone());
generate_shell_completions(cmd);
}
115 changes: 115 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use clap::Parser;
use std::path::PathBuf;

/// This enum, `ListChoice`, represents all of the "built-in" word lists that Phraze can use.
#[derive(Clone, Debug, Copy)]
pub enum ListChoice {
Long,
Medium,
Eff,
Mnemonicode,
Effshort,
Qwerty,
Alpha,
}

/// Generate random passphrases
#[derive(Parser, Debug)]
#[clap(version, name = "phraze")]
pub struct Args {
/// Strengthen your passphrase the easy way: Each -S flag increases minimum entropy by 20 bits (above the default of
/// 80 bits).
#[clap(short = 'S', long = "strength", conflicts_with = "number_of_words", conflicts_with = "minimum_entropy", action = clap::ArgAction::Count)]
pub strength_count: u8,

/// Set minimum amount of entropy in bits for generated passphrase. If neither minimum_entropy or
/// number_of_words is specified, Phraze will default to an 80-bit minimum.
#[clap(
short = 'e',
long = "minimum-entropy",
conflicts_with = "number_of_words",
conflicts_with = "strength_count"
)]
pub minimum_entropy: Option<usize>,

/// Set exactly how many words to use in generated passphrase. If neither number_of_words or
/// minimum_entropy is specified, Phraze will default to an 80-bit minimum.
#[clap(
short = 'w',
long = "words",
conflicts_with = "minimum_entropy",
conflicts_with = "strength_count"
)]
pub number_of_words: Option<usize>,

/// Number of passphrases to generate
#[clap(short = 'n', long = "passphrases", default_value = "1")]
pub n_passphrases: usize,

/// Word separator. Can accept single quotes around the separator. To not use a separator,
/// use empty single quotes: ''.
///
/// There are special values that will trigger generated separators:
///
/// _n: separators will be random numbers
///
/// _s: separators will be random symbols
///
/// _b: separators will be a mix of random numbers and symbols
#[clap(short = 's', long = "sep", default_value = "-")]
pub separator: String,

/// Choose a word list to use.
///
/// Options:
///
/// m: Orchard Street Medium List (8,192 words) [DEFAULT]
///
/// l: Orchard Street Long List (17,576 words)
///
/// e: EFF long list (7,776 words)
///
/// n: Mnemonicode list (1,633 words). Good if you know you're going to be speaking
/// passphrases out loud.
///
/// s: EFF short list (1,296 words)
///
/// q: Orchard Street QWERTY list (1,296 words). Optimized to minimize travel
/// distance on QWERTY keyboard layout.
///
/// a: Orchard Street Alpha list (1,296 words). Optimized to minimize travel
/// distance on alphabetical keyboard layout.
#[clap(short = 'l', long = "list", value_parser=parse_list_choice, default_value="m")]
pub list_choice: ListChoice,

/// Provide a text file with a list of words to randomly generate passphrase
/// from. Should be a text file with one word per line.
#[clap(short = 'c', long = "custom-list", conflicts_with = "list_choice")]
pub custom_list_file_path: Option<PathBuf>,

/// Use Title Case for words in generated passphrase
#[clap(short = 't', long = "title-case")]
pub title_case: bool,

/// Print estimated entropy of generated passphrase, in bits, along with
/// the passphrase itself
#[clap(short = 'v', long = "verbose")]
pub verbose: bool,
}

/// Convert list_choice string slice into a ListChoice enum. Clap calls this function.
fn parse_list_choice(list_choice: &str) -> Result<ListChoice, String> {
match list_choice.to_lowercase().as_ref() {
"l" => Ok(ListChoice::Long),
"m" => Ok(ListChoice::Medium),
"e" => Ok(ListChoice::Eff),
"n" => Ok(ListChoice::Mnemonicode),
"s" => Ok(ListChoice::Effshort),
"q" => Ok(ListChoice::Qwerty),
"a" => Ok(ListChoice::Alpha),
_ => Err(format!(
"Inputted list choice '{}' doesn't correspond to an available word list",
list_choice
)),
}
}
14 changes: 2 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
pub mod cli;
pub mod file_reader;
pub mod separators;
pub mod unicode_normalization_check;

use crate::cli::ListChoice;
use crate::separators::make_separator;
use include_lines::include_lines;
use rand::{seq::SliceRandom, thread_rng, Rng};

/// This enum, `ListChoice`, represents all of the "built-in" word lists that Phraze can use.
#[derive(Clone, Debug, Copy)]
pub enum ListChoice {
Long,
Medium,
Eff,
Mnemonicode,
Effshort,
Qwerty,
Alpha,
}

/// Given user's inputs, figure out how many words the generated passphrase will need. If user
/// specified an exact `number_of_words`, just return that `number_of_words`. If user is using a
/// strength_count, do the necessary math. If user specified a `minimum_entropy`, we need to do
Expand Down
103 changes: 1 addition & 102 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,91 +1,7 @@
use crate::cli::Args;
use crate::file_reader::read_in_custom_list;
use clap::Parser;
use phraze::*;
use std::path::PathBuf;

/// Generate random passphrases
#[derive(Parser, Debug)]
#[clap(version, name = "phraze")]
struct Args {
/// Strengthen your passphrase the easy way: Each -S flag increases minimum entropy by 20 bits (above the default of
/// 80 bits).
#[clap(short = 'S', long = "strength", conflicts_with = "number_of_words", conflicts_with = "minimum_entropy", action = clap::ArgAction::Count)]
strength_count: u8,

/// Set minimum amount of entropy in bits for generated passphrase. If neither minimum_entropy or
/// number_of_words is specified, Phraze will default to an 80-bit minimum.
#[clap(
short = 'e',
long = "minimum-entropy",
conflicts_with = "number_of_words",
conflicts_with = "strength_count"
)]
minimum_entropy: Option<usize>,

/// Set exactly how many words to use in generated passphrase. If neither number_of_words or
/// minimum_entropy is specified, Phraze will default to an 80-bit minimum.
#[clap(
short = 'w',
long = "words",
conflicts_with = "minimum_entropy",
conflicts_with = "strength_count"
)]
number_of_words: Option<usize>,

/// Number of passphrases to generate
#[clap(short = 'n', long = "passphrases", default_value = "1")]
n_passphrases: usize,

/// Word separator. Can accept single quotes around the separator. To not use a separator,
/// use empty single quotes: ''.
///
/// There are special values that will trigger generated separators:
///
/// _n: separators will be random numbers
///
/// _s: separators will be random symbols
///
/// _b: separators will be a mix of random numbers and symbols
#[clap(short = 's', long = "sep", default_value = "-")]
separator: String,

/// Choose a word list to use.
///
/// Options:
///
/// m: Orchard Street Medium List (8,192 words) [DEFAULT]
///
/// l: Orchard Street Long List (17,576 words)
///
/// e: EFF long list (7,776 words)
///
/// n: Mnemonicode list (1,633 words). Good if you know you're going to be speaking
/// passphrases out loud.
///
/// s: EFF short list (1,296 words)
///
/// q: Orchard Street QWERTY list (1,296 words). Optimized to minimize travel
/// distance on QWERTY keyboard layout.
///
/// a: Orchard Street Alpha list (1,296 words). Optimized to minimize travel
/// distance on alphabetical keyboard layout.
#[clap(short = 'l', long = "list", value_parser=parse_list_choice, default_value="m")]
list_choice: ListChoice,

/// Provide a text file with a list of words to randomly generate passphrase
/// from. Should be a text file with one word per line.
#[clap(short = 'c', long = "custom-list", conflicts_with = "list_choice")]
custom_list_file_path: Option<PathBuf>,

/// Use Title Case for words in generated passphrase
#[clap(short = 't', long = "title-case")]
title_case: bool,

/// Print estimated entropy of generated passphrase, in bits, along with
/// the passphrase itself
#[clap(short = 'v', long = "verbose")]
verbose: bool,
}

fn main() -> Result<(), String> {
let opt = Args::parse();
Expand Down Expand Up @@ -139,20 +55,3 @@ fn generate_passphrases<T: AsRef<str> + std::fmt::Display>(opt: &Args, word_list
println!("{}", passphrase);
}
}

/// Convert list_choice string slice into a ListChoice enum. Clap calls this function.
fn parse_list_choice(list_choice: &str) -> Result<ListChoice, String> {
match list_choice.to_lowercase().as_ref() {
"l" => Ok(ListChoice::Long),
"m" => Ok(ListChoice::Medium),
"e" => Ok(ListChoice::Eff),
"n" => Ok(ListChoice::Mnemonicode),
"s" => Ok(ListChoice::Effshort),
"q" => Ok(ListChoice::Qwerty),
"a" => Ok(ListChoice::Alpha),
_ => Err(format!(
"Inputted list choice '{}' doesn't correspond to an available word list",
list_choice
)),
}
}
1 change: 1 addition & 0 deletions tests/list_reading_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod minimum_entropy_tests {
use phraze::cli::ListChoice;
use phraze::*;

#[test]
Expand Down

0 comments on commit f5d0e84

Please sign in to comment.