stegtext 0.1.1

Text steganography command-line tool and library.
Documentation
//! Command-line text steganography tool.
//!
//! # Example
//!
//! ```sh
//! # view contents of `input.txt`
//! $ cat input.txt
//! The Hitchhiker's Guide to the Galaxy is a 1979 science fiction comedy
//! novel by English author Douglas Adams, adapted from the first four parts
//! of his radio comedy series of the same name. It centres on the
//! misadventures of Arthur Dent, the only man to survive the destruction of
//! Earth, as he roams the cosmos and learns the truth behind his very
//! existence. The novel's namesake is an in-universe electronic travel
//! guide written in the form of an encyclopaedia, through which the story
//! is framed.
//!
//! # check capacity
//! $ cargo run -q --bin stegtext capacity < input.txt
//! 23
//!
//! # embed secret, write results to `encoded.txt`
//! $ cargo run -q --bin stegtext encode 'secret message' < input.txt > encoded.txt
//!
//! # view contents of `encoded.txt`
//! $ cat encoded.txt
//! Тhe Нitchhіkеr'ѕ Guide to the Galaxy іѕ a 1979 sсіеncе fiсtion соmedу
//! nоvel by Englіѕh author Dоuglas Аdаmѕ, adаptеd from the fіrѕt four partѕ
//! of hіѕ rаdio comedy ѕeriеs оf thе sаmе namе. It сentreѕ оn the
//! mіѕadventurеѕ оf Arthur Dеnt, thе only mаn tо ѕurvivе the destructіоn of
//! Еаrth, аs he rоаms thе cоsmoѕ аnd learns the truth behind his very
//! existence. The novel's namesake is an in-universe electronic travel
//! guide written in the form of an encyclopaedia, through which the story
//! is framed.
//!
//! # extract secret from `encoded.txt`
//! $ cargo run -q --bin stegtext decode < encoded.txt
//! secret message
//! ```

use clap::{Parser, Subcommand};

#[derive(Clone, Debug, Subcommand)]
enum Command {
  /// Read text from stdin, embed secret, then print result.
  #[command(arg_required_else_help = true)]
  #[command(aliases(["e", "enc"]))]
  Encode {
    /// Secret value to embed.
    secret: String,
  },

  /// Read text from stdin, extract secret, then print secret.
  #[command(aliases(["d", "dec"]))]
  Decode,

  /// Read text from stdin, then print encoding capacity.
  #[command(aliases(["c", "cap"]))]
  Capacity,
}

impl Command {
  /// Run command.
  fn run(&self, text: String) -> Result<String, Box<dyn std::error::Error>> {
    Ok(match self {
      Command::Encode { secret } => stegtext::encode(secret.as_bytes(), &text)?,
      Command::Decode => stegtext::decode(&text)?,
      Command::Capacity => stegtext::capacity(&text).to_string(),
    })
  }
}

/// Text steganography tool.
#[derive(Debug, Parser)]
#[command(name = "stegtext")]
struct Cli {
  #[command(subcommand)]
  command: Command,
}

pub fn main() -> Result<(), Box<dyn std::error::Error>> {
  let cli = Cli::parse(); // parse cli args
  let text = std::io::read_to_string(std::io::stdin())?; // read text
  println!("{}", cli.command.run(text)?); // run command, print result
  Ok(())
}

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

  #[test]
  fn test_command_run_pass() {
    let tests = vec![(
      Command::Capacity, // command
      "aaaaaaaa aaaaaaaa".to_string(), // text
      "0".to_string(), // exp
    ), (
      Command::Decode, // command
      "аaаaаaaa aaaaaaaa аaaaaaаa".to_string(), // text
      "A".to_string(), // exp
    ), (
      Command::Encode { secret: "A".to_string() }, // command
      "aaaaaaaa aaaaaaaa aaaaaaaa".to_string(), // text
      "аaаaаaaa aaaaaaaa аaaaaaаa".to_string(), // exp
    )];

    for (c, text, exp) in tests {
      assert_eq!(c.run(text).unwrap(), exp);
    }
  }
}