pub mod defaults;
pub mod file;
use std::path::PathBuf;
use crate::cli::{ContentFormat, FilterArgs, GenerateArgs, GlobalArgs, TruncationArgs};
use crate::error::PctxError;
#[derive(Debug, Clone)]
pub struct TruncationConfig {
pub max_lines: usize,
pub head_lines: usize,
pub tail_lines: usize,
pub max_line_length: usize,
pub head_chars: usize,
pub tail_chars: usize,
}
impl Default for TruncationConfig {
fn default() -> Self {
Self {
max_lines: 500,
head_lines: 20,
tail_lines: 10,
max_line_length: 500,
head_chars: 200,
tail_chars: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct Config {
pub paths: Vec<PathBuf>,
pub exclude_patterns: Vec<String>,
pub include_patterns: Vec<String>,
pub include_hidden: bool,
pub use_default_excludes: bool,
pub use_gitignore: bool,
pub max_file_size: u64,
pub max_depth: Option<usize>,
pub truncation: TruncationConfig,
pub output_format: ContentFormat,
pub show_tree: bool,
pub show_stats: bool,
pub absolute_paths: bool,
pub verbose: bool,
pub quiet: bool,
}
impl Config {
pub fn from_args(args: &GenerateArgs, global: &GlobalArgs) -> Result<Self, PctxError> {
let file_config = Self::load_file_config(global)?;
let (exclude_patterns, include_patterns) =
Self::build_patterns(&args.filter, file_config.as_ref());
let truncation = Self::build_truncation(&args.truncation, file_config.as_ref());
Ok(Self {
paths: if args.paths.is_empty() {
vec![PathBuf::from(".")]
} else {
args.paths.clone()
},
exclude_patterns,
include_patterns,
include_hidden: args.filter.hidden,
use_default_excludes: !args.filter.no_default_excludes,
use_gitignore: !args.filter.no_gitignore,
max_file_size: args.filter.max_size * 1024,
max_depth: if args.filter.max_depth == 0 {
None
} else {
Some(args.filter.max_depth)
},
truncation,
output_format: args.output.format.clone(),
show_tree: args.output.tree,
show_stats: args.output.stats,
absolute_paths: args.output.absolute_paths,
verbose: global.verbose,
quiet: global.quiet,
})
}
pub fn from_filter_args(filter: &FilterArgs, global: &GlobalArgs) -> Result<Self, PctxError> {
let file_config = Self::load_file_config(global)?;
let (exclude_patterns, include_patterns) =
Self::build_patterns(filter, file_config.as_ref());
Ok(Self {
paths: vec![PathBuf::from(".")],
exclude_patterns,
include_patterns,
include_hidden: filter.hidden,
use_default_excludes: !filter.no_default_excludes,
use_gitignore: !filter.no_gitignore,
max_file_size: filter.max_size * 1024,
max_depth: if filter.max_depth == 0 {
None
} else {
Some(filter.max_depth)
},
truncation: TruncationConfig::default(),
output_format: ContentFormat::default(),
show_tree: false,
show_stats: false,
absolute_paths: false,
verbose: global.verbose,
quiet: global.quiet,
})
}
fn load_file_config(global: &GlobalArgs) -> Result<Option<file::FileConfig>, PctxError> {
match file::find_and_load() {
Ok(cfg) => Ok(Some(cfg)),
Err(PctxError::FileNotFound(_)) => Ok(None), Err(e) => {
if !global.quiet {
eprintln!("Warning: failed to load config file: {}", e);
}
Ok(None)
}
}
}
fn build_patterns(
filter: &FilterArgs,
file_config: Option<&file::FileConfig>,
) -> (Vec<String>, Vec<String>) {
let mut exclude_patterns = if filter.no_default_excludes {
Vec::new()
} else {
defaults::DEFAULT_EXCLUDES
.iter()
.map(|s| s.to_string())
.collect()
};
if let Some(fc) = file_config {
exclude_patterns.extend(fc.exclude.clone());
}
exclude_patterns.extend(filter.exclude.clone());
let mut include_patterns = Vec::new();
if let Some(fc) = file_config {
include_patterns.extend(fc.include.clone());
}
include_patterns.extend(filter.include.clone());
(exclude_patterns, include_patterns)
}
fn build_truncation(
args: &TruncationArgs,
file_config: Option<&file::FileConfig>,
) -> TruncationConfig {
let defaults = TruncationConfig::default();
if args.no_truncation {
return TruncationConfig {
max_lines: 0,
max_line_length: 0,
..defaults
};
}
let fc = file_config;
TruncationConfig {
max_lines: args
.max_lines
.or(fc.and_then(|f| f.max_lines))
.unwrap_or(defaults.max_lines),
head_lines: args
.head_lines
.or(fc.and_then(|f| f.head_lines))
.unwrap_or(defaults.head_lines),
tail_lines: args
.tail_lines
.or(fc.and_then(|f| f.tail_lines))
.unwrap_or(defaults.tail_lines),
max_line_length: args
.max_line_length
.or(fc.and_then(|f| f.max_line_length))
.unwrap_or(defaults.max_line_length),
head_chars: args
.head_chars
.or(fc.and_then(|f| f.head_chars))
.unwrap_or(defaults.head_chars),
tail_chars: args
.tail_chars
.or(fc.and_then(|f| f.tail_chars))
.unwrap_or(defaults.tail_chars),
}
}
}