use std::io::{self, IsTerminal};
use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use clap_complete::Shell;
use lindisfarner::{magnifica, Border, Config, DropCap, Font, Theme, MIN_WIDTH};
#[derive(Parser, Debug)]
#[command(
name = "lindisfarner",
version,
about = "A digital scriptorium: illuminate plain text with ASCII-art initials, rubrics, and ornate borders."
)]
pub(crate) struct Args {
pub(crate) file: Option<PathBuf>,
#[arg(short, long)]
pub(crate) output: Option<PathBuf>,
#[arg(short, long)]
pub(crate) width: Option<usize>,
#[arg(short, long, value_enum, default_value_t = BorderArg::Ornate)]
pub(crate) border: BorderArg,
#[arg(short = 't', long, value_enum, default_value_t = ThemeArg::Gold)]
pub(crate) theme: ThemeArg,
#[arg(short, long, value_enum, default_value_t = ColorArg::Auto)]
pub(crate) color: ColorArg,
#[arg(short = 'd', long, value_enum, default_value_t = DropCapArg::First)]
pub(crate) drop_cap: DropCapArg,
#[arg(short = 'f', long, value_enum, default_value_t = FontArg::Blackletter)]
pub(crate) font: FontArg,
#[arg(short, long, value_delimiter = ',')]
pub(crate) rubricate: Vec<String>,
#[arg(long)]
pub(crate) drolleries: bool,
#[arg(long, default_value_t = 0)]
pub(crate) seed: u64,
#[arg(short, long)]
pub(crate) pilcrows: bool,
#[arg(short = 'j', long)]
pub(crate) justify: bool,
#[arg(long)]
pub(crate) hyphenate: bool,
#[arg(long)]
pub(crate) fillers: bool,
#[arg(long)]
pub(crate) incipit: bool,
#[arg(long, default_value_t = 1)]
pub(crate) columns: usize,
#[arg(long)]
pub(crate) code: bool,
#[arg(long, conflicts_with = "code")]
pub(crate) prose: bool,
#[arg(long, value_name = "LANG")]
pub(crate) language: Option<String>,
#[arg(long)]
pub(crate) corrupt: bool,
#[arg(long, value_name = "PATTERN")]
pub(crate) find: Option<String>,
#[arg(long, value_name = "RULES", conflicts_with = "find")]
pub(crate) scan: Option<PathBuf>,
#[arg(long, value_enum, value_name = "MODE")]
pub(crate) magnifica: Option<magnifica::Mode>,
#[arg(long, value_name = "FILE")]
pub(crate) quotes: Option<PathBuf>,
#[arg(long, value_enum, value_name = "SHELL")]
pub(crate) completions: Option<Shell>,
#[arg(long)]
pub(crate) man: bool,
}
impl Args {
pub(crate) fn colored(&self) -> bool {
match self.color {
ColorArg::Always => true,
ColorArg::Never => false,
ColorArg::Auto => self.output.is_none() && io::stdout().is_terminal(),
}
}
pub(crate) fn base_config(&self) -> Config {
Config {
width: self.width.unwrap_or_else(default_width),
border: self.border.into(),
theme: self.theme.into(),
colored: self.colored(),
drolleries: self.drolleries,
seed: self.seed,
..Config::default()
}
}
pub(crate) fn prose_config(&self) -> Config {
let rubrics = self
.rubricate
.iter()
.map(|w| w.trim().to_lowercase())
.filter(|w| !w.is_empty())
.collect();
let detected = self
.file
.as_deref()
.and_then(|p| p.to_str())
.and_then(lindisfarner::detect_language);
let code = !self.prose && (self.code || self.language.is_some() || detected.is_some());
let language = self.language.clone().or(detected);
Config {
drop_cap: self.drop_cap.into(),
font: self.font.into(),
rubrics,
pilcrows: self.pilcrows,
justify: self.justify,
hyphenate: self.hyphenate,
fillers: self.fillers,
incipit: self.incipit,
columns: self.columns,
code,
language,
corrupt: self.corrupt,
..self.base_config()
}
}
}
fn default_width() -> usize {
use terminal_size::{terminal_size, Width};
if io::stdout().is_terminal() {
if let Some((Width(cols), _)) = terminal_size() {
return (cols as usize).saturating_sub(4).max(MIN_WIDTH);
}
}
60
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub(crate) enum BorderArg {
None,
Simple,
Double,
Ornate,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub(crate) enum ThemeArg {
Gold,
Crimson,
Mono,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub(crate) enum ColorArg {
Auto,
Always,
Never,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub(crate) enum DropCapArg {
First,
All,
None,
}
#[derive(Copy, Clone, Debug, ValueEnum)]
pub(crate) enum FontArg {
Blackletter,
Standard,
}
impl From<BorderArg> for Border {
fn from(b: BorderArg) -> Self {
match b {
BorderArg::None => Border::None,
BorderArg::Simple => Border::Simple,
BorderArg::Double => Border::Double,
BorderArg::Ornate => Border::Ornate,
}
}
}
impl From<ThemeArg> for Theme {
fn from(t: ThemeArg) -> Self {
match t {
ThemeArg::Gold => Theme::Gold,
ThemeArg::Crimson => Theme::Crimson,
ThemeArg::Mono => Theme::Mono,
}
}
}
impl From<DropCapArg> for DropCap {
fn from(d: DropCapArg) -> Self {
match d {
DropCapArg::First => DropCap::First,
DropCapArg::All => DropCap::All,
DropCapArg::None => DropCap::None,
}
}
}
impl From<FontArg> for Font {
fn from(f: FontArg) -> Self {
match f {
FontArg::Blackletter => Font::Blackletter,
FontArg::Standard => Font::Standard,
}
}
}