bitmask-cli 0.1.0

A CLI tool for manipulating individual bits using commands like SET, CLEAR, TOGGLE, CHECK.
use clap::{Parser, ValueEnum};

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct BitOperation {
    /// Operation you want to perform: [set, clear, toggle, check]
    #[arg(short, long, value_enum)]
    command: Command,

    /// Number: Byte value being manipulated
    #[arg(short, long, value_parser = clap::value_parser!(u8).range(0..=255))]
    number: u8,

    /// Bit Index: Specific position being targeted (0–7)
    #[arg(short, long, value_parser = clap::value_parser!(u8).range(0..=7))]
    bit_index: u8,
}

#[derive(Debug, Clone, ValueEnum, PartialEq)]
enum Command {
    Set,
    Clear,
    Toggle,
    Check,
}

#[derive(Debug)]
enum ShiftDirection {
    Left,
    Right,
}

#[derive(Debug, Clone)]

enum CommandResult {
    Value(u8),
    State(bool),
}

fn print_usage() {
    println!("Bitmask Manipulation Tool - Usage:");
    println!();
    println!("Commands:");
    println!("  SET <number> <bit_index>     - Set bit at index to 1");
    println!("  CLEAR <number> <bit_index>   - Clear bit at index to 0");
    println!("  TOGGLE <number> <bit_index>  - Flip bit at index");
    println!("  CHECK <number> <bit_index>   - Check if bit at index is 1");
    println!();
    println!("Examples:");
    println!(
        "  bitmask-tool SET 8 3       # Result: 8 | (1 << 3) => 00001000 | 00001000 = 00001000"
    );
    println!("  bitmask-tool TOGGLE 8 3    # Result: 00001000 ^ 00001000 = 00000000");
    println!();
    println!("Notes:");
    println!("  <number> must be an integer between 0 and 255 (u8).");
    println!("  <bit_index> must be between 0 and 7.");
}

fn shift(n: u8, index: u8, direction: ShiftDirection) -> u8 {
    match direction {
        ShiftDirection::Left => n << index,
        ShiftDirection::Right => n >> index,
    }
}
fn set(bin: u8, bit_index: u8) -> u8 {
    bin | shift(1, bit_index, ShiftDirection::Left)
}

fn clear(bin: u8, bit_index: u8) -> u8 {
    bin & !shift(1, bit_index, ShiftDirection::Left)
}

fn toggle(bin: u8, bit_index: u8) -> u8 {
    bin ^ shift(1, bit_index, ShiftDirection::Left)
}

fn check(bin: u8, bit_index: u8) -> bool {
    (bin & shift(1, bit_index, ShiftDirection::Left)) != 0
}

fn display_bin(bin: u8, cmd: Command, bit_index: u8, result: CommandResult) {
    println!("\n=== Bitmask Tool Result ===");
    println!("Input Byte   : {:08b} ({})", bin, bin);
    println!("Command      : {:?} @ Bit Index {}", cmd, bit_index);
    match result {
        CommandResult::Value(v) => println!("Output Byte  : {:08b} ({})", v, v),
        CommandResult::State(b) => println!("Bit Status   : {}", b),
    }
    println!("============================\n");
}

fn main() {
    let args = BitOperation::parse();

    let result: CommandResult = match args.command {
        Command::Set => CommandResult::Value(set(args.number, args.bit_index)),
        Command::Clear => CommandResult::Value(clear(args.number, args.bit_index)),
        Command::Toggle => CommandResult::Value(toggle(args.number, args.bit_index)),
        Command::Check => CommandResult::State(check(args.number, args.bit_index)),
    };

    display_bin(args.number, args.command.clone(), args.bit_index, result);
}

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

    #[test]
    fn test_set() {
        assert_eq!(set(0b00000000, 3), 0b00001000);
        assert_eq!(set(0b00001000, 3), 0b00001000); // idempotent
    }

    #[test]
    fn test_clear() {
        assert_eq!(clear(0b11111111, 3), 0b11110111);
        assert_eq!(clear(0b00000000, 3), 0b00000000);
    }

    #[test]
    fn test_toggle() {
        assert_eq!(toggle(0b00001000, 3), 0b00000000);
        assert_eq!(toggle(0b00000000, 3), 0b00001000);
    }

    #[test]
    fn test_check() {
        assert_eq!(check(0b00001000, 3), true);
        assert_eq!(check(0b00000000, 3), false);
    }

    #[test]
    fn test_shift_left() {
        assert_eq!(shift(1, 3, ShiftDirection::Left), 0b00001000);
    }

    #[test]
    fn test_shift_right() {
        assert_eq!(shift(0b00001000, 3, ShiftDirection::Right), 0b00000001);
    }
}