rucksack 0.9.0

A terminal-based secrets manager, generator, and importer/exporter (Firefox, Chrome) backed with a concurrent hashmap
Documentation
use std::fmt;

use prettytable::color::BLUE;
use prettytable::{Attr, Cell};

use super::Opts;

#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Column {
    Category,
    Count,
    Created,
    DupeInfo,
    Hash,
    HistoryCount,
    Id,
    Imported,
    Key,
    Kind,
    LastAccessed,
    LastUpdated,
    Name,
    Password,
    Permissions,
    Score,
    Status,
    Synced,
    Tags,
    Url,
}

impl fmt::Display for Column {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}

impl Column {
    pub fn name(&self) -> String {
        match self {
            Column::Count => "Access Count".to_string(),
            Column::DupeInfo => "Duplicate Info".to_string(),
            Column::HistoryCount => "History Count".to_string(),
            Column::Kind => "Type".to_string(),
            Column::LastUpdated => "Last Updated".to_string(),
            Column::Score => "Score / Strength".to_string(),
            Column::Url => "URL".to_string(),
            _ => format!("{self}"),
        }
    }

    pub fn header(&self) -> Cell {
        Cell::new(self.name().as_str())
            .with_style(Attr::Bold)
            .with_style(Attr::ForegroundColor(BLUE))
    }
}

pub trait Columns {
    fn gen(&self, opts: &Opts) -> Vec<Column> {
        let mut cols = self.pre(opts);
        cols = self.passwd(opts, cols);
        cols = self.status(opts, cols);
        cols = self.post(opts, cols);
        cols.clone()
    }

    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![]
    }

    fn status(&self, opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        if opts.with_status {
            cols.push(Column::Status);
        }
        cols
    }

    fn passwd(&self, opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        if opts.with_passwd {
            cols.push(Column::Password);
            if opts.reveal && opts.decrypted {
                cols.push(Column::Score);
            }
        }
        cols
    }

    fn post(&self, _opts: &Opts, cols: Vec<Column>) -> Vec<Column> {
        cols
    }
}

pub struct ColsOnlyKey;

impl Columns for ColsOnlyKey {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Key]
    }
}

pub struct ColsOnlyKind;

impl Columns for ColsOnlyKind {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Kind]
    }
}

pub struct ColsOnlyTags;

impl Columns for ColsOnlyTags {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Tags]
    }
}

pub struct ColsOnlyCat;

impl Columns for ColsOnlyCat {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Category]
    }
}

pub struct ColsBackupFiles;

impl Columns for ColsBackupFiles {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Name, Column::Permissions]
    }
}

pub struct ColsGroupByName;

impl Columns for ColsGroupByName {
    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![Column::Count, Column::Url]);
        cols
    }
}

#[derive(Eq, PartialEq, PartialOrd)]
pub struct ColsGroupByHash;

impl Columns for ColsGroupByHash {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Kind, Column::Category, Column::DupeInfo]
    }

    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![
            Column::Count,
            Column::LastUpdated,
            Column::HistoryCount,
        ]);
        cols
    }
}

pub struct ColsGroupByPasswd;

impl Columns for ColsGroupByPasswd {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Name, Column::Kind, Column::Category]
    }

    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![Column::Count, Column::Url]);
        cols
    }
}

pub struct ColsGroupByKind;

impl Columns for ColsGroupByKind {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Name, Column::Category]
    }

    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![Column::Count, Column::Url]);
        cols
    }
}

pub struct ColsGroupByCat;

impl Columns for ColsGroupByCat {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Name, Column::Kind]
    }

    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![Column::Count, Column::Url]);
        cols
    }
}

pub struct ColsDefault;

impl Columns for ColsDefault {
    fn pre(&self, _opts: &Opts) -> Vec<Column> {
        vec![Column::Name, Column::Kind, Column::Category]
    }

    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![Column::Count, Column::Url]);
        cols
    }
}

pub struct ColsPasswdHist;

impl Columns for ColsPasswdHist {
    fn post(&self, _opts: &Opts, mut cols: Vec<Column>) -> Vec<Column> {
        cols.append(&mut vec![
            Column::Created,
            Column::LastUpdated,
            Column::LastAccessed,
        ]);
        cols
    }
}

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

    #[test]
    fn column_display() {
        assert_eq!(format!("{}", Column::Count), "Count");
        assert_eq!(format!("{}", Column::Kind), "Kind");
        assert_eq!(format!("{}", Column::Name), "Name");
        assert_eq!(format!("{}", Column::Score), "Score");
        assert_eq!(format!("{}", Column::Url), "Url");
    }

    #[test]
    fn column_name() {
        assert_eq!(Column::Count.name(), "Access Count");
        assert_eq!(Column::Kind.name(), "Type");
        assert_eq!(Column::Name.name(), "Name");
        assert_eq!(Column::Score.name(), "Score / Strength");
        assert_eq!(Column::Url.name(), "URL");
    }

    #[test]
    fn test_column_header() {
        let _header = Column::Name.header();
        // Just verify it creates a cell without panicking
        assert!(true);
    }

    #[test]
    fn test_cols_only_key() {
        let opts = Opts::default();
        let cols = ColsOnlyKey {}.gen(&opts);
        assert!(cols.contains(&Column::Key));
    }

    #[test]
    fn test_cols_only_kind() {
        let opts = Opts::default();
        let cols = ColsOnlyKind {}.gen(&opts);
        assert!(cols.contains(&Column::Kind));
    }

    #[test]
    fn test_cols_only_tags() {
        let opts = Opts::default();
        let cols = ColsOnlyTags {}.gen(&opts);
        assert!(cols.contains(&Column::Tags));
    }

    #[test]
    fn test_cols_only_cat() {
        let opts = Opts::default();
        let cols = ColsOnlyCat {}.gen(&opts);
        assert!(cols.contains(&Column::Category));
    }

    #[test]
    fn test_cols_backup_files() {
        let opts = Opts::default();
        let cols = ColsBackupFiles {}.gen(&opts);
        assert!(cols.contains(&Column::Name));
        assert!(cols.contains(&Column::Permissions));
    }

    #[test]
    fn test_cols_group_by_name() {
        let opts = Opts::default();
        let cols = ColsGroupByName {}.gen(&opts);
        assert!(cols.contains(&Column::Count));
        assert!(cols.contains(&Column::Url));
    }

    #[test]
    fn test_cols_group_by_hash() {
        let opts = Opts::default();
        let cols = ColsGroupByHash {}.gen(&opts);
        assert!(cols.contains(&Column::Kind));
        assert!(cols.contains(&Column::Category));
        assert!(cols.contains(&Column::DupeInfo));
        assert!(cols.contains(&Column::Count));
    }

    #[test]
    fn test_cols_group_by_passwd() {
        let opts = Opts::default();
        let cols = ColsGroupByPasswd {}.gen(&opts);
        assert!(cols.contains(&Column::Name));
        assert!(cols.contains(&Column::Kind));
        assert!(cols.contains(&Column::Category));
    }

    #[test]
    fn test_cols_group_by_kind() {
        let opts = Opts::default();
        let cols = ColsGroupByKind {}.gen(&opts);
        assert!(cols.contains(&Column::Name));
        assert!(cols.contains(&Column::Category));
    }

    #[test]
    fn test_cols_group_by_cat() {
        let opts = Opts::default();
        let cols = ColsGroupByCat {}.gen(&opts);
        assert!(cols.contains(&Column::Name));
        assert!(cols.contains(&Column::Kind));
    }

    #[test]
    fn test_cols_default() {
        let opts = Opts::default();
        let cols = ColsDefault {}.gen(&opts);
        assert!(cols.contains(&Column::Name));
        assert!(cols.contains(&Column::Kind));
        assert!(cols.contains(&Column::Category));
    }

    #[test]
    fn test_cols_passwd_hist() {
        let opts = Opts::default();
        let cols = ColsPasswdHist {}.gen(&opts);
        assert!(cols.contains(&Column::Created));
        assert!(cols.contains(&Column::LastUpdated));
        assert!(cols.contains(&Column::LastAccessed));
    }

    #[test]
    fn test_with_status_option() {
        let mut opts = Opts::default();
        opts.with_status = true;
        let cols = ColsDefault {}.gen(&opts);
        assert!(cols.contains(&Column::Status));
    }

    #[test]
    fn test_with_passwd_option() {
        let mut opts = Opts::default();
        opts.with_passwd = true;
        let cols = ColsDefault {}.gen(&opts);
        assert!(cols.contains(&Column::Password));
    }

    #[test]
    fn test_with_passwd_and_reveal_decrypted() {
        let mut opts = Opts::default();
        opts.with_passwd = true;
        opts.reveal = true;
        opts.decrypted = true;
        let cols = ColsDefault {}.gen(&opts);
        assert!(cols.contains(&Column::Password));
        assert!(cols.contains(&Column::Score));
    }

    #[test]
    fn test_column_eq() {
        assert_eq!(Column::Name, Column::Name);
        assert_ne!(Column::Name, Column::Url);
    }

    #[test]
    fn test_column_ord() {
        assert!(Column::Category < Column::Count);
    }

    #[test]
    fn test_column_clone() {
        let col1 = Column::Name;
        let col2 = col1.clone();
        assert_eq!(col1, col2);
    }
}