#![forbid(unsafe_code)]
#[cfg(not(any(feature = "encode", feature = "decode")))]
compile_error!("Building bin target requires that at least one encoder or decoder is enabled");
use std::fs::{File, OpenOptions};
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use anyhow::Result;
use clap::{Parser, ValueEnum};
#[cfg(any(feature = "decode_pgp", feature = "decode_eff"))]
use utf8_chars::BufReadCharsExt;
#[cfg(feature = "encode_eff")]
use base256::EffEncode;
#[cfg(any(feature = "encode_pgp", feature = "encode_eff"))]
use base256::Encode;
#[cfg(feature = "encode_pgp")]
use base256::PgpEncode;
#[cfg(any(feature = "decode_pgp", feature = "decode_eff"))]
use base256::Decode;
#[cfg(feature = "decode_eff")]
use base256::EffDecode;
#[cfg(feature = "decode_pgp")]
use base256::PgpDecode;
#[derive(Parser)]
#[command(author, version, about, long_about = None, name = "lastresort")]
struct Cli {
#[cfg(feature = "decode")]
#[command(flatten)]
decoding: CliDecoding,
#[cfg(feature = "encode")]
#[command(flatten)]
encoding: CliEncoding,
#[arg(short, long, value_name = "INPUT_FILE")]
input: Option<String>,
#[arg(short, long, value_name = "OUTPUT_FILE")]
output: Option<String>,
}
#[cfg(all(feature = "decode", feature = "encode"))]
#[derive(clap::Args)]
struct CliDecoding {
#[cfg(feature = "decode_pgp")]
#[arg(short, long, value_name = "DECODER", conflicts_with("encoder"))]
decode: Option<Option<Decoder>>,
#[cfg(all(feature = "decode_eff", not(feature = "decode_pgp")))]
#[arg(short, long, value_name = "DECODER", conflicts_with("encoder"))]
#[arg(required = false)]
decode: Option<Decoder>,
}
#[cfg(all(feature = "decode", not(feature = "encode")))]
#[derive(clap::Args)]
struct CliDecoding {
#[cfg(feature = "decode_pgp")]
#[arg(short, long, value_name = "DECODER", required = true)]
decode: Option<Option<Decoder>>,
#[cfg(all(feature = "decode_eff", not(feature = "decode_pgp")))]
#[arg(short, long, value_name = "DECODER")]
decode: Decoder,
}
#[cfg(all(feature = "encode", feature = "decode"))]
#[derive(clap::Args)]
struct CliEncoding {
#[cfg(feature = "encode_pgp")]
#[arg(short, long, conflicts_with("decode"))]
encoder: Option<Encoder>,
#[cfg(all(feature = "encode_eff", not(feature = "encode_pgp")))]
#[arg(
short,
long,
conflicts_with("decode"),
required_unless_present("decode")
)]
encoder: Option<Encoder>,
}
#[cfg(all(feature = "encode", not(feature = "decode")))]
#[derive(clap::Args)]
struct CliEncoding {
#[cfg(feature = "encode_pgp")]
#[arg(short, long)]
encoder: Option<Encoder>,
#[cfg(all(feature = "encode_eff", not(feature = "encode_pgp")))]
#[arg(short, long)]
encoder: Encoder,
}
#[cfg(feature = "encode")]
#[derive(ValueEnum, Clone)]
enum Encoder {
#[cfg(feature = "encode_pgp")]
Pgp,
#[cfg(feature = "encode_eff")]
Eff,
}
#[cfg(feature = "decode")]
#[derive(ValueEnum, Clone)]
enum Decoder {
#[cfg(feature = "decode_pgp")]
Pgp,
#[cfg(feature = "decode_eff")]
Eff,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let mut input: Box<dyn BufRead> = match cli.input {
None => Box::new(stdin().lock()),
Some(path) => {
if path == "-" {
Box::new(stdin().lock())
} else {
Box::new(BufReader::new(File::open(path)?))
}
}
};
let mut output: Box<dyn Write> = match cli.output {
None => Box::new(stdout()),
Some(path) => {
if path == "-" {
Box::new(stdout())
} else {
let file = OpenOptions::new().create(true).write(true).open(path)?;
Box::new(BufWriter::new(file))
}
}
};
#[cfg(feature = "decode")]
{
#[cfg(not(any(feature = "decode_pgp", feature = "decode_eff")))]
compile_error!("Building bin target with decoding feature enabled requires that at least one decoder is enabled");
let decoder = cli.decoding.decode;
#[cfg(all(
feature = "decode_eff",
not(feature = "decode_pgp"),
not(feature = "encode")
))]
let decoder = Some(Some(decoder));
if let Some(decoder) = decoder {
let input_chars = input.chars();
#[cfg(feature = "decode_pgp")]
let decoder = decoder.unwrap_or(Decoder::Pgp);
#[cfg(not(any(feature = "decode_pgp", feature = "encode")))]
let decoder = match decoder {
Some(decoder) => decoder,
None => {
unreachable!("This match arm should never be reached due to clap parse rules.");
}
};
match decoder {
#[cfg(feature = "decode_pgp")]
Decoder::Pgp => {
for byte in Decode::<_, PgpDecode<_>>::decode(input_chars) {
output.write_all(&[byte?])?;
}
}
#[cfg(feature = "decode_eff")]
Decoder::Eff => {
for byte in Decode::<_, EffDecode<_>>::decode(input_chars) {
output.write_all(&[byte?])?;
}
}
}
return Ok(());
}
}
#[cfg(feature = "encode")]
{
let input_bytes = input.bytes();
#[cfg(not(any(feature = "encode_pgp", feature = "encode_eff")))]
compile_error!("Building bin target with encoding feature enabled requires that at least one encoder is enabled");
let encoder = cli.encoding.encoder;
#[cfg(feature = "encode_pgp")]
let encoder = encoder.unwrap_or(Encoder::Pgp);
#[cfg(all(feature = "decode", not(feature = "encode_pgp")))]
let encoder = match encoder {
Some(encoder) => encoder,
None => {
unreachable!("This match arm should never be reached due to clap parse rules.");
}
};
#[cfg(not(any(feature = "decode", feature = "encode_pgp")))]
let encoder = encoder;
let mut did_write_any_words = false;
match encoder {
#[cfg(feature = "encode_pgp")]
Encoder::Pgp => {
let mut encoded = Encode::<_, PgpEncode<_>>::encode(input_bytes);
if let Some(word) = encoded.next() {
did_write_any_words = true;
write!(output, "{}", word?)?;
}
for word in encoded {
write!(output, " {}", word?)?
}
}
#[cfg(feature = "encode_eff")]
Encoder::Eff => {
let mut encoded = Encode::<_, EffEncode<_>>::encode(input_bytes);
if let Some(word) = encoded.next() {
did_write_any_words = true;
write!(output, "{}", word?)?;
}
for word in encoded {
write!(output, " {}", word?)?
}
}
}
if did_write_any_words {
writeln!(output)?;
}
}
Ok(())
}