use std::io::Write;
use std::process::{Command, Stdio};
#[derive(Debug, Clone)]
pub struct SystemPager {
command: String,
}
impl SystemPager {
pub fn new() -> Self {
Self {
command: std::env::var("PAGER").unwrap_or_else(|_| "less".into()),
}
}
pub fn show(&self, content: &str) -> std::io::Result<()> {
let mut child = Command::new(&self.command)
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(content.as_bytes())?;
}
drop(child.stdin.take());
child.wait()?;
Ok(())
}
}
impl Default for SystemPager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Pager {
enabled: bool,
command: String,
color: bool,
}
impl Pager {
pub fn new() -> Self {
Self {
enabled: true,
command: std::env::var("PAGER").unwrap_or_else(|_| "less".into()),
color: true,
}
}
pub fn enabled(mut self, value: bool) -> Self {
self.enabled = value;
self
}
pub fn command(mut self, cmd: impl Into<String>) -> Self {
self.command = cmd.into();
self
}
pub fn color(mut self, value: bool) -> Self {
self.color = value;
self
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn command_str(&self) -> &str {
&self.command
}
pub fn is_color(&self) -> bool {
self.color
}
pub fn show(&self, content: &str) -> std::io::Result<()> {
if !self.enabled {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_all(content.as_bytes())?;
handle.flush()?;
return Ok(());
}
let display = if !self.color {
strip_ansi_escapes(content)
} else {
content.to_string()
};
let pager = SystemPager {
command: self.command.clone(),
};
pager.show(&display)
}
}
impl Default for Pager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct PagerContext {
pager: Pager,
content: String,
enabled: bool,
}
impl PagerContext {
pub fn new(pager: Pager) -> Self {
let enabled = pager.enabled;
Self {
pager,
content: String::new(),
enabled,
}
}
pub fn feed(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn flush(&mut self) -> std::io::Result<()> {
if !self.content.is_empty() {
let result = self.pager.show(&self.content);
self.content.clear();
result
} else {
Ok(())
}
}
pub fn content(&self) -> &str {
&self.content
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
impl Write for PagerContext {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let s = String::from_utf8_lossy(buf);
self.feed(&s);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Drop for PagerContext {
fn drop(&mut self) {
if self.enabled && !self.content.is_empty() {
let _ = self.pager.show(&self.content);
}
}
}
fn strip_ansi_escapes(s: &str) -> String {
use regex::Regex;
let re = Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap();
re.replace_all(s, "").to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_pager_creation() {
let pager = SystemPager::new();
assert!(!pager.command.is_empty());
}
#[test]
fn test_pager_defaults() {
let pager = Pager::new();
assert!(pager.is_enabled());
assert!(pager.is_color());
assert!(!pager.command_str().is_empty());
}
#[test]
fn test_pager_builder() {
let pager = Pager::new()
.enabled(false)
.command("more")
.color(false);
assert!(!pager.is_enabled());
assert!(!pager.is_color());
assert_eq!(pager.command_str(), "more");
}
#[test]
fn test_pager_disabled_show() {
let pager = Pager::new().enabled(false);
assert!(pager.show("test").is_ok());
}
#[test]
fn test_pager_context_feed() {
let pager = Pager::new().enabled(false);
let mut ctx = PagerContext::new(pager);
ctx.feed("Hello, ");
ctx.feed("World!");
assert_eq!(ctx.content(), "Hello, World!");
}
#[test]
fn test_pager_context_write_trait() {
use std::io::Write;
let pager = Pager::new().enabled(false);
let mut ctx = PagerContext::new(pager);
write!(ctx, "Hello {}!", "World").unwrap();
assert!(ctx.content().contains("Hello"));
assert!(ctx.content().contains("World"));
}
#[test]
fn test_strip_ansi_escapes_basic() {
let input = "\x1b[31mhello\x1b[0m world";
let result = strip_ansi_escapes(input);
assert_eq!(result, "hello world");
}
#[test]
fn test_strip_ansi_escapes_no_ansi() {
let input = "hello world";
let result = strip_ansi_escapes(input);
assert_eq!(result, "hello world");
}
#[test]
fn test_pager_context_flush() {
let pager = Pager::new().enabled(false);
let mut ctx = PagerContext::new(pager);
ctx.feed("test");
assert!(ctx.flush().is_ok());
assert!(ctx.content().is_empty());
}
}