#[ cfg( feature = "std" ) ]
use std::
{
string::{ String, ToString },
};
#[ cfg( all( feature = "use_alloc", not( feature = "std" ) ) ) ]
use alloc::
{
string::{ String, ToString },
};
#[ cfg( all( feature = "use_alloc", not( feature = "std" ) ) ) ]
extern crate alloc;
use strs_tools::ansi::{ truncate_lines, TruncateOptions };
#[ cfg( feature = "ansi_unicode" ) ]
use strs_tools::ansi::truncate_lines_unicode;
#[ cfg( all( feature = "string_split", feature = "std" ) ) ]
use strs_tools::string::lines::{ head, tail, head_and_tail };
#[ derive( Clone, Debug ) ]
pub struct OutputConfig
{
pub head : Option< usize >,
pub tail : Option< usize >,
pub width : Option< usize >,
pub width_suffix : String,
pub stream_filter : StreamFilter,
pub unicode_aware : bool,
}
#[ derive( Clone, Debug, Default, PartialEq, Eq ) ]
pub enum StreamFilter
{
#[ default ]
Both,
Stdout,
Stderr,
}
#[ derive( Clone, Debug ) ]
pub struct ProcessedOutput
{
pub content : String,
pub lines_omitted : usize,
pub width_truncated : bool,
}
impl Default for OutputConfig
{
fn default() -> Self
{
Self
{
head : None,
tail : None,
width : None,
width_suffix : "→".to_string(),
stream_filter : StreamFilter::Both,
unicode_aware : false,
}
}
}
impl OutputConfig
{
#[ must_use ]
pub fn new() -> Self
{
Self::default()
}
#[ must_use ]
pub fn with_head( mut self, count : usize ) -> Self
{
self.head = Some( count );
self
}
#[ must_use ]
pub fn with_tail( mut self, count : usize ) -> Self
{
self.tail = Some( count );
self
}
#[ must_use ]
pub fn with_width( mut self, width : usize ) -> Self
{
self.width = Some( width );
self
}
#[ must_use ]
pub fn with_suffix( mut self, suffix : impl Into< String > ) -> Self
{
self.width_suffix = suffix.into();
self
}
#[ must_use ]
pub fn with_stream_filter( mut self, filter : StreamFilter ) -> Self
{
self.stream_filter = filter;
self
}
#[ must_use ]
pub fn with_unicode_aware( mut self, enabled : bool ) -> Self
{
self.unicode_aware = enabled;
self
}
#[ must_use ]
pub fn has_processing( &self ) -> bool
{
self.head.is_some() || self.tail.is_some() || self.width.is_some()
}
#[ must_use ]
pub fn is_default( &self ) -> bool
{
self.head.is_none()
&& self.tail.is_none()
&& self.width.is_none()
&& self.stream_filter == StreamFilter::Both
&& self.width_suffix == "→"
&& !self.unicode_aware
}
}
#[ must_use ]
pub fn process_output(
stdout : &str,
stderr : &str,
config : &OutputConfig
) -> ProcessedOutput
{
let content = merge_streams( stdout, stderr, &config.stream_filter );
let ( lines_result, omitted ) = apply_line_filtering( &content, config.head, config.tail );
let ( final_content, width_truncated ) = apply_width_filtering( &lines_result, config );
ProcessedOutput
{
content : final_content,
lines_omitted : omitted,
width_truncated,
}
}
#[ must_use ]
pub fn merge_streams( stdout : &str, stderr : &str, filter : &StreamFilter ) -> String
{
match filter
{
StreamFilter::Stdout => stdout.to_string(),
StreamFilter::Stderr => stderr.to_string(),
StreamFilter::Both =>
{
let mut merged = String::with_capacity( stdout.len() + stderr.len() );
if !stderr.is_empty()
{
merged.push_str( stderr );
if !stderr.ends_with( '\n' ) && !stdout.is_empty()
{
merged.push( '\n' );
}
}
if !stdout.is_empty()
{
merged.push_str( stdout );
}
merged
}
}
}
#[ cfg( feature = "string_split" ) ]
fn apply_line_filtering(
content : &str,
head_opt : Option< usize >,
tail_opt : Option< usize >
) -> ( String, usize )
{
if content.is_empty()
{
return ( String::new(), 0 );
}
match ( head_opt, tail_opt )
{
( None, None ) => ( content.to_string(), 0 ),
( Some( h ), None ) =>
{
let total = content.lines().count();
if h >= total
{
( content.to_string(), 0 )
}
else
{
( head( content, h ), total - h )
}
}
( None, Some( t ) ) =>
{
let total = content.lines().count();
if t >= total
{
( content.to_string(), 0 )
}
else
{
( tail( content, t ), total - t )
}
}
( Some( h ), Some( t ) ) =>
{
head_and_tail( content, h, t )
}
}
}
#[ cfg( not( all( feature = "string_split", feature = "std" ) ) ) ]
fn apply_line_filtering(
content : &str,
_head_opt : Option< usize >,
_tail_opt : Option< usize >
) -> ( String, usize )
{
( content.to_string(), 0 )
}
fn apply_width_filtering( content : &str, config : &OutputConfig ) -> ( String, bool )
{
let Some( max_width ) = config.width else
{
return ( content.to_string(), false );
};
if max_width == 0
{
return ( content.to_string(), false );
}
let opts = TruncateOptions::new( max_width )
.with_suffix( &config.width_suffix )
.with_reset( true );
if config.unicode_aware
{
#[ cfg( feature = "ansi_unicode" ) ]
{ truncate_lines_unicode( content, max_width, &opts ) }
#[ cfg( not( feature = "ansi_unicode" ) ) ]
{ truncate_lines( content, max_width, &opts ) }
}
else
{
truncate_lines( content, max_width, &opts )
}
}
#[ doc( inline ) ]
#[ allow( unused_imports ) ]
pub use own::*;
#[ allow( unused_imports ) ]
pub mod own
{
#[ allow( unused_imports ) ]
use super::*;
pub use orphan::*;
}
#[ allow( unused_imports ) ]
pub mod orphan
{
#[ allow( unused_imports ) ]
use super::*;
pub use exposed::*;
}
#[ allow( unused_imports ) ]
pub mod exposed
{
#[ allow( unused_imports ) ]
use super::*;
pub use prelude::*;
}
#[ allow( unused_imports ) ]
pub mod prelude
{
#[ allow( unused_imports ) ]
use super::*;
pub use super::
{
OutputConfig,
StreamFilter,
ProcessedOutput,
process_output,
merge_streams,
};
}