Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn main() {
for lang in languages.values() {
for ext in &lang.extensions {
let ext = ext.trim_start_matches('.');
code.push_str(&format!(" set.insert(\"{}\");\n", ext));
code.push_str(&format!(" set.insert(\"{ext}\");\n"));
}
}

Expand All @@ -54,7 +54,7 @@ fn main() {

for lang in languages.values() {
for filename in &lang.filenames {
code.push_str(&format!(" set.insert(\"{}\");\n", filename));
code.push_str(&format!(" set.insert(\"{filename}\");\n"));
}
}

Expand All @@ -67,7 +67,7 @@ fn main() {

for lang in languages.values() {
for interpreter in &lang.interpreters {
code.push_str(&format!(" set.insert(\"{}\");\n", interpreter));
code.push_str(&format!(" set.insert(\"{interpreter}\");\n"));
}
}

Expand Down
36 changes: 15 additions & 21 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
for pattern in includes {
// Include patterns are positive patterns (no ! prefix)
if let Err(e) = override_builder.add(pattern) {
eprintln!("Warning: Invalid include pattern '{}': {}", pattern, e);
eprintln!("Warning: Invalid include pattern '{pattern}': {e}");
}
}
}
Expand All @@ -113,16 +113,13 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
// Add a '!' prefix if it doesn't already have one
// This makes it a negative pattern (exclude)
let exclude_pattern = if !pattern.starts_with('!') {
format!("!{}", pattern)
format!("!{pattern}")
} else {
pattern.clone()
};

if let Err(e) = override_builder.add(&exclude_pattern) {
eprintln!(
"Warning: Invalid exclude pattern '{}': {}",
pattern, e
);
eprintln!("Warning: Invalid exclude pattern '{pattern}': {e}");
}
}
Exclude::File(file_path) => {
Expand All @@ -149,8 +146,7 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
let pattern = format!("!{}", file_path.display());
if let Err(e) = override_builder.add(&pattern) {
eprintln!(
"Warning: Could not add file exclude pattern '{}': {}",
pattern, e
"Warning: Could not add file exclude pattern '{pattern}': {e}"
);
}
}
Expand Down Expand Up @@ -195,7 +191,7 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
for pattern in includes {
// Include patterns are positive patterns (no ! prefix)
if let Err(e) = override_builder.add(pattern) {
eprintln!("Warning: Invalid include pattern '{}': {}", pattern, e);
eprintln!("Warning: Invalid include pattern '{pattern}': {e}");
}
}
}
Expand All @@ -208,14 +204,13 @@ pub fn process_entries(args: &Cli) -> Result<Vec<FileEntry>> {
// Add a '!' prefix if it doesn't already have one
// This makes it a negative pattern (exclude)
let exclude_pattern = if !pattern.starts_with('!') {
format!("!{}", pattern)
format!("!{pattern}")
} else {
pattern.clone()
};
if let Err(e) = override_builder.add(&exclude_pattern) {
eprintln!(
"Warning: Invalid exclude pattern '{}': {}",
pattern, e
"Warning: Invalid exclude pattern '{pattern}': {e}"
);
}
}
Expand Down Expand Up @@ -336,7 +331,7 @@ mod tests {
fs::create_dir_all(parent)?;
}
let mut file = File::create(&full_path)?;
writeln!(file, "{}", content)?;
writeln!(file, "{content}")?;
created_files.push(full_path);
}

Expand Down Expand Up @@ -393,7 +388,7 @@ mod tests {
// For patterns that should exclude, we need to add a "!" prefix
// to make them negative patterns (exclusions)
let exclude_pattern = if !pattern.starts_with('!') {
format!("!{}", pattern)
format!("!{pattern}")
} else {
pattern.clone()
};
Expand All @@ -419,8 +414,7 @@ mod tests {

assert_eq!(
is_ignored, should_exclude,
"Failed for exclude: {:?}",
exclude
"Failed for exclude: {exclude:?}"
);
}

Expand Down Expand Up @@ -587,12 +581,12 @@ mod tests {

// Test with depth limit of 1
cli.max_depth = Some(1);
let _ = process_directory(&cli)?;
process_directory(&cli)?;
// Verify only top-level files were processed

// Test with depth limit of 2
cli.max_depth = Some(2);
let _ = process_directory(&cli)?;
process_directory(&cli)?;
// Verify files up to depth 2 were processed

Ok(())
Expand All @@ -605,12 +599,12 @@ mod tests {

// Test without hidden files
cli.hidden = false;
let _ = process_directory(&cli)?;
process_directory(&cli)?;
// Verify hidden files were not processed

// Test with hidden files
cli.hidden = true;
let _ = process_directory(&cli)?;
process_directory(&cli)?;
// Verify hidden files were processed

Ok(())
Expand All @@ -622,7 +616,7 @@ mod tests {
let rust_file = files.iter().find(|f| f.ends_with("main.rs")).unwrap();

let cli = create_test_cli(rust_file);
let _ = process_directory(&cli)?;
process_directory(&cli)?;
// Verify single file was processed correctly

Ok(())
Expand Down
20 changes: 20 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ pub struct Config {

#[serde(default)]
pub traverse_links: bool,

/// List of canonical project directories for which the user has already declined to
/// save a local `.glimpse` configuration file. When a directory is present in this
/// list Glimpse will not prompt the user again.
#[serde(default)]
pub skipped_prompt_repos: Vec<String>,
}

impl Default for Config {
Expand All @@ -95,6 +101,7 @@ impl Default for Config {
default_tokenizer_model: default_tokenizer_model(),
default_link_depth: default_link_depth(),
traverse_links: false,
skipped_prompt_repos: Vec::new(),
}
}
}
Expand Down Expand Up @@ -202,3 +209,16 @@ pub fn load_repo_config(path: &Path) -> anyhow::Result<RepoConfig> {
Ok(RepoConfig::default())
}
}

/// Persist the provided global configuration to disk, overriding any existing config file.
pub fn save_config(config: &Config) -> anyhow::Result<()> {
let config_path = get_config_path()?;

if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}

let config_str = toml::to_string_pretty(config)?;
std::fs::write(config_path, config_str)?;
Ok(())
}
15 changes: 5 additions & 10 deletions src/git_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,13 @@ mod tests {
];

for url in valid_urls {
assert!(
GitProcessor::is_git_url(url),
"URL should be valid: {}",
url
);
assert!(GitProcessor::is_git_url(url), "URL should be valid: {url}");
}

for url in invalid_urls {
assert!(
!GitProcessor::is_git_url(url),
"URL should be invalid: {}",
url
"URL should be invalid: {url}"
);
}
}
Expand Down Expand Up @@ -136,11 +131,11 @@ mod tests {
let parsed_url = Url::parse(url).unwrap();
let repo_name = parsed_url
.path_segments()
.and_then(|segments| segments.last())
.and_then(|mut segments| segments.next_back())
.map(|name| name.trim_end_matches(".git"))
.unwrap_or("repo");

assert_eq!(repo_name, expected_name, "Failed for URL: {}", url);
assert_eq!(repo_name, expected_name, "Failed for URL: {url}");
}
}

Expand All @@ -155,7 +150,7 @@ mod tests {
// Check for some common files that should be present
assert!(path.join("Cargo.toml").exists(), "Cargo.toml should exist");
}
Err(e) => println!("Skipping clone test due to error: {}", e),
Err(e) => println!("Skipping clone test due to error: {e}"),
}
}
}
Expand Down
61 changes: 49 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ mod url_processor;

use crate::analyzer::process_directory;
use crate::cli::Cli;
use crate::config::{get_config_path, load_config, load_repo_config, save_repo_config, RepoConfig};
use crate::config::{
get_config_path, load_config, load_repo_config, save_config, save_repo_config, RepoConfig,
};
use crate::git_processor::GitProcessor;
use crate::url_processor::UrlProcessor;
use std::fs;
Expand All @@ -33,7 +35,7 @@ fn has_custom_options(args: &Cli) -> bool {
}

fn main() -> anyhow::Result<()> {
let config = load_config()?;
let mut config = load_config()?;
let mut args = Cli::parse_with_config(&config)?;

if args.config_path {
Expand All @@ -59,20 +61,55 @@ fn main() -> anyhow::Result<()> {
let repo_config = create_repo_config_from_args(&args);
save_repo_config(&glimpse_file, &repo_config)?;
println!("Configuration saved to {}", glimpse_file.display());

// If the user explicitly saved a config, remove this directory from the skipped list
if let Ok(canonical_root) = std::fs::canonicalize(&root_dir) {
let root_str = canonical_root.to_string_lossy().to_string();
if let Some(pos) = config
.skipped_prompt_repos
.iter()
.position(|p| p == &root_str)
{
config.skipped_prompt_repos.remove(pos);
save_config(&config)?;
}
}
} else if glimpse_file.exists() {
println!("Loading configuration from {}", glimpse_file.display());
let repo_config = load_repo_config(&glimpse_file)?;
apply_repo_config(&mut args, &repo_config);
} else if has_custom_options(&args) {
print!("Would you like to save these options as defaults for this directory? (y/n): ");
io::stdout().flush()?;
let mut response = String::new();
io::stdin().read_line(&mut response)?;

if response.trim().to_lowercase() == "y" {
let repo_config = create_repo_config_from_args(&args);
save_repo_config(&glimpse_file, &repo_config)?;
println!("Configuration saved to {}", glimpse_file.display());
// Determine canonical root directory path for consistent tracking
let canonical_root = std::fs::canonicalize(&root_dir).unwrap_or(root_dir.clone());
let root_str = canonical_root.to_string_lossy().to_string();

if !config.skipped_prompt_repos.contains(&root_str) {
print!(
"Would you like to save these options as defaults for this directory? (y/n): "
);
io::stdout().flush()?;
let mut response = String::new();
io::stdin().read_line(&mut response)?;

if response.trim().to_lowercase() == "y" {
let repo_config = create_repo_config_from_args(&args);
save_repo_config(&glimpse_file, &repo_config)?;
println!("Configuration saved to {}", glimpse_file.display());

// In case it was previously skipped, remove from skipped list
if let Some(pos) = config
.skipped_prompt_repos
.iter()
.position(|p| p == &root_str)
{
config.skipped_prompt_repos.remove(pos);
save_config(&config)?;
}
} else {
// Record that user declined for this project
config.skipped_prompt_repos.push(root_str);
save_config(&config)?;
}
}
}
}
Expand Down Expand Up @@ -131,7 +168,7 @@ fn main() -> anyhow::Result<()> {
fs::write(output_file, content)?;
println!("Output written to: {}", output_file.display());
} else if args.print {
println!("{}", content);
println!("{content}");
} else {
// Default behavior for URLs if no -f or --print: copy to clipboard
match arboard::Clipboard::new()
Expand Down
4 changes: 2 additions & 2 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ fn try_copy_with_osc52(content: &str) -> Result<(), Box<dyn std::error::Error>>
pub fn handle_output(content: String, args: &Cli) -> Result<()> {
// Print to stdout if no other output method is specified
if args.print {
println!("{}", content);
println!("{content}");
}

// Copy to clipboard if requested
Expand All @@ -153,7 +153,7 @@ pub fn handle_output(content: String, args: &Cli) -> Result<()> {
Err(_) => {
match try_copy_with_osc52(&content) {
Ok(_) => println!("Context prepared! (using terminal clipboard) Paste into your LLM of choice + Profit."),
Err(e) => eprintln!("Warning: Failed to copy to clipboard: {}. Output will continue with other specified formats.", e)
Err(e) => eprintln!("Warning: Failed to copy to clipboard: {e}. Output will continue with other specified formats.")
}
},
}
Expand Down
5 changes: 2 additions & 3 deletions src/source_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ mod tests {
assert_eq!(
extract_interpreter(input).as_deref(),
expected,
"Failed for input: {}",
input
"Failed for input: {input}"
);
}
}
Expand Down Expand Up @@ -151,7 +150,7 @@ mod tests {
for (name, content, expected) in test_cases {
let path = dir.path().join(name);
let mut file = File::create(&path).unwrap();
writeln!(file, "{}", content).unwrap();
writeln!(file, "{content}").unwrap();

assert_eq!(
is_source_file(&path),
Expand Down
Loading
Loading