Skip to content

Commit 7bb506b

Browse files
committed
allow options synced per repo
1 parent 2e1bc38 commit 7bb506b

File tree

9 files changed

+231
-37
lines changed

9 files changed

+231
-37
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "glimpse"
3-
version = "0.7.4"
3+
version = "0.7.5"
44
edition = "2021"
55
description = "A blazingly fast tool for peeking at codebases. Perfect for loading your codebase into an LLM's context."
66
license = "MIT"

flake.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
{
2626
packages.default = pkgs.rustPlatform.buildRustPackage {
2727
pname = "glimpse";
28-
version = "0.7.4";
28+
version = "0.7.5";
2929

3030
src = ./.;
3131

readme.md

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A blazingly fast tool for peeking at codebases. Perfect for loading your codebas
1212
- 📋 Clipboard support
1313
- 🎨 Customizable file type detection
1414
- 🥷 Respects .gitignore automatically
15+
- 📁 Local per-repo configuration with `.glimpse` file
1516
- 🔗 Web content processing with Markdown conversion
1617
- 📦 Git repository support
1718
- 🌐 URL traversal with configurable depth
@@ -69,6 +70,8 @@ glimpse https://example.com/docs
6970
glimpse https://example.com/docs --traverse-links --link-depth 2
7071
```
7172

73+
On first use in a repository, Glimpse will save a `.glimpse` configuration file locally with your specified options. This file can be referenced on subsequent runs, or overridden by passing options again.
74+
7275
Common options:
7376
```bash
7477
# Show hidden files
@@ -77,16 +80,19 @@ glimpse -H /path/to/project
7780
# Only show tree structure
7881
glimpse -o tree /path/to/project
7982

80-
# Copy output to clipboard
81-
glimpse -c /path/to/project
83+
# Save output to GLIMPSE.md (default if no path given)
84+
glimpse -f /path/to/project
8285

83-
# Save output to file
86+
# Save output to a specific file
8487
glimpse -f output.txt /path/to/project
8588

89+
# Print output to stdout instead of copying to clipboard
90+
glimpse -p /path/to/project
91+
8692
# Include specific file types
8793
glimpse -i "*.rs,*.go" /path/to/project
8894

89-
# Exclude patterns
95+
# Exclude patterns or files
9096
glimpse -e "target/*,dist/*" /path/to/project
9197

9298
# Count tokens using tiktoken (OpenAI's tokenizer)
@@ -100,6 +106,15 @@ glimpse --tokenizer huggingface --tokenizer-file /path/to/tokenizer.json /path/t
100106

101107
# Process a Git repository and save as PDF
102108
glimpse https://github.com/username/repo.git --pdf output.pdf
109+
110+
# Open interactive file picker
111+
glimpse --interactive /path/to/project
112+
113+
# Print the config file path and exit
114+
glimpse --config_path
115+
116+
# Initialize a .glimpse config file in the current directory
117+
glimpse --config
103118
```
104119

105120
## CLI Options
@@ -108,29 +123,31 @@ glimpse https://github.com/username/repo.git --pdf output.pdf
108123
Usage: glimpse [OPTIONS] [PATH]
109124
110125
Arguments:
111-
[PATH] Directory/Files/URLs to analyze [default: .]
126+
[PATH] Files, directories, or URLs to analyze [default: .]
112127
113128
Options:
114-
--interactive Opens interactive file picker (? for help)
115-
-i, --include <PATTERNS> Additional patterns to include (e.g. "*.rs,*.go")
116-
-e, --exclude <PATTERNS> Additional patterns to exclude
117-
-s, --max-size <BYTES> Maximum file size in bytes
118-
--max-depth <DEPTH> Maximum directory depth to traverse
119-
-o, --output <FORMAT> Output format: tree, files, or both
120-
-f, --file <PATH> Save output to specified file
121-
-p, --print Print to stdout instead of clipboard
122-
-t, --threads <COUNT> Number of threads for parallel processing
123-
-H, --hidden Show hidden files and directories
124-
--no-ignore Don't respect .gitignore files
125-
--no-tokens Disable token counting
126-
--tokenizer <TYPE> Tokenizer to use: tiktoken or huggingface
127-
--model <NAME> Model name for HuggingFace tokenizer
128-
--tokenizer-file <PATH> Path to local tokenizer file
129-
--traverse-links Traverse links when processing URLs
130-
--link-depth <DEPTH> Maximum depth to traverse links (default: 1)
131-
--pdf <PATH> Save output as PDF
132-
-h, --help Print help
133-
-V, --version Print version
129+
--config_path Print the config file path and exit
130+
--config Init glimpse config file in current directory
131+
--interactive Opens interactive file picker (? for help)
132+
-i, --include <PATTERNS> Additional patterns to include (e.g. "*.rs,*.go")
133+
-e, --exclude <PATTERNS|PATHS> Additional patterns or files to exclude
134+
-s, --max-size <BYTES> Maximum file size in bytes
135+
--max-depth <DEPTH> Maximum directory depth to traverse
136+
-o, --output <FORMAT> Output format: tree, files, or both
137+
-f, --file [<PATH>] Save output to specified file (default: GLIMPSE.md)
138+
-p, --print Print to stdout instead of copying to clipboard
139+
-t, --threads <COUNT> Number of threads for parallel processing
140+
-H, --hidden Show hidden files and directories
141+
--no-ignore Don't respect .gitignore files
142+
--no-tokens Disable token counting
143+
--tokenizer <TYPE> Tokenizer to use: tiktoken or huggingface
144+
--model <NAME> Model name for HuggingFace tokenizer
145+
--tokenizer-file <PATH> Path to local tokenizer file
146+
--traverse-links Traverse links when processing URLs
147+
--link-depth <DEPTH> Maximum depth to traverse links (default: 1)
148+
--pdf <PATH> Save output as PDF
149+
-h, --help Print help
150+
-V, --version Print version
134151
```
135152

136153
## Configuration

src/analyzer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ mod tests {
309309

310310
fn create_test_cli(dir_path: &Path) -> Cli {
311311
Cli {
312+
config: false,
312313
paths: vec![dir_path.to_string_lossy().to_string()],
313314
config_path: false,
314315
include: None,

src/cli.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ pub struct Cli {
6060
pub output: Option<OutputFormat>,
6161

6262
/// Output file path (optional)
63-
#[arg(short = 'f', long)]
63+
#[arg(short = 'f', long, num_args = 0..=1, default_missing_value = "GLIMPSE.md")]
6464
pub file: Option<PathBuf>,
6565

66+
/// Init glimpse config file
67+
#[arg(long, default_value_t = false)]
68+
pub config: bool,
69+
6670
/// Print to stdout instead
6771
#[arg(short, long)]
6872
pub print: bool,

src/config.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::path::PathBuf;
1+
use std::path::{Path, PathBuf};
22

33
use serde::{Deserialize, Serialize};
44

@@ -174,3 +174,46 @@ pub fn get_config_path() -> anyhow::Result<PathBuf> {
174174
.join("glimpse");
175175
Ok(config_dir.join("config.toml"))
176176
}
177+
178+
#[derive(Debug, Serialize, Deserialize, Clone)]
179+
pub struct RepoConfig {
180+
pub include: Option<Vec<String>>,
181+
pub exclude: Option<Vec<Exclude>>,
182+
pub max_size: Option<u64>,
183+
pub max_depth: Option<usize>,
184+
pub output: Option<BackwardsCompatOutputFormat>,
185+
pub file: Option<PathBuf>,
186+
pub hidden: Option<bool>,
187+
pub no_ignore: Option<bool>,
188+
}
189+
190+
impl Default for RepoConfig {
191+
fn default() -> Self {
192+
Self {
193+
include: None,
194+
exclude: None,
195+
max_size: None,
196+
max_depth: None,
197+
output: None,
198+
file: None,
199+
hidden: None,
200+
no_ignore: None,
201+
}
202+
}
203+
}
204+
205+
pub fn save_repo_config(path: &Path, repo_config: &RepoConfig) -> anyhow::Result<()> {
206+
let config_str = toml::to_string_pretty(repo_config)?;
207+
std::fs::write(path, config_str)?;
208+
Ok(())
209+
}
210+
211+
pub fn load_repo_config(path: &Path) -> anyhow::Result<RepoConfig> {
212+
if path.exists() {
213+
let config_str = std::fs::read_to_string(path)?;
214+
let config: RepoConfig = toml::from_str(&config_str)?;
215+
Ok(config)
216+
} else {
217+
Ok(RepoConfig::default())
218+
}
219+
}

src/main.rs

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,31 @@ mod url_processor;
1010

1111
use crate::analyzer::process_directory;
1212
use crate::cli::Cli;
13-
use crate::config::{get_config_path, load_config};
13+
use crate::config::{get_config_path, load_config, load_repo_config, save_repo_config, RepoConfig};
1414
use crate::git_processor::GitProcessor;
1515
use crate::url_processor::UrlProcessor;
1616
use std::fs;
17+
use std::io::{self, Write};
18+
use std::path::{Path, PathBuf};
19+
20+
fn is_url_or_git(path: &str) -> bool {
21+
GitProcessor::is_git_url(path) || path.starts_with("http://") || path.starts_with("https://")
22+
}
23+
24+
fn has_custom_options(args: &Cli) -> bool {
25+
args.include.is_some()
26+
|| args.exclude.is_some()
27+
|| args.max_size.is_some()
28+
|| args.max_depth.is_some()
29+
|| args.output.is_some()
30+
|| args.file.is_some()
31+
|| args.hidden
32+
|| args.no_ignore
33+
}
1734

1835
fn main() -> anyhow::Result<()> {
1936
let config = load_config()?;
20-
let args = Cli::parse_with_config(&config)?;
37+
let mut args = Cli::parse_with_config(&config)?;
2138

2239
if args.config_path {
2340
let path = get_config_path()?;
@@ -28,14 +45,38 @@ fn main() -> anyhow::Result<()> {
2845
let url_paths: Vec<_> = args
2946
.paths
3047
.iter()
31-
.filter(|path| {
32-
GitProcessor::is_git_url(path)
33-
|| path.starts_with("http://")
34-
|| path.starts_with("https://")
35-
})
48+
.filter(|path| is_url_or_git(path))
3649
.take(1)
50+
.cloned()
3751
.collect();
3852

53+
if url_paths.is_empty() && !args.paths.is_empty() {
54+
let base_path = PathBuf::from(&args.paths[0]);
55+
let root_dir = find_containing_dir_with_glimpse(&base_path)?;
56+
let glimpse_file = root_dir.join(".glimpse");
57+
58+
if args.config {
59+
let repo_config = create_repo_config_from_args(&args);
60+
save_repo_config(&glimpse_file, &repo_config)?;
61+
println!("Configuration saved to {}", glimpse_file.display());
62+
} else if glimpse_file.exists() {
63+
println!("Loading configuration from {}", glimpse_file.display());
64+
let repo_config = load_repo_config(&glimpse_file)?;
65+
apply_repo_config(&mut args, &repo_config);
66+
} else if has_custom_options(&args) {
67+
print!("Would you like to save these options as defaults for this directory? (y/n): ");
68+
io::stdout().flush()?;
69+
let mut response = String::new();
70+
io::stdin().read_line(&mut response)?;
71+
72+
if response.trim().to_lowercase() == "y" {
73+
let repo_config = create_repo_config_from_args(&args);
74+
save_repo_config(&glimpse_file, &repo_config)?;
75+
println!("Configuration saved to {}", glimpse_file.display());
76+
}
77+
}
78+
}
79+
3980
if url_paths.len() > 1 {
4081
return Err(anyhow::anyhow!(
4182
"Only one URL or git repository can be processed at a time"
@@ -88,8 +129,19 @@ fn main() -> anyhow::Result<()> {
88129

89130
if let Some(output_file) = &args.file {
90131
fs::write(output_file, content)?;
132+
println!("Output written to: {}", output_file.display());
91133
} else if args.print {
92134
println!("{}", content);
135+
} else {
136+
// Default behavior for URLs if no -f or --print: copy to clipboard
137+
match arboard::Clipboard::new()
138+
.and_then(|mut clipboard| clipboard.set_text(content))
139+
{
140+
Ok(_) => println!("URL content copied to clipboard"),
141+
Err(_) => {
142+
println!("Failed to copy to clipboard, use -f to save to a file instead")
143+
}
144+
}
93145
}
94146
}
95147
} else {
@@ -99,3 +151,79 @@ fn main() -> anyhow::Result<()> {
99151

100152
Ok(())
101153
}
154+
155+
fn find_containing_dir_with_glimpse(path: &Path) -> anyhow::Result<PathBuf> {
156+
let mut current = if path.is_file() {
157+
path.parent().unwrap_or(Path::new(".")).to_path_buf()
158+
} else {
159+
path.to_path_buf()
160+
};
161+
162+
// Try to find a .glimpse file or go up until we reach the root
163+
loop {
164+
if current.join(".glimpse").exists() {
165+
return Ok(current);
166+
}
167+
168+
if !current.pop() {
169+
// If we can't go up anymore, just use the original path
170+
return Ok(if path.is_file() {
171+
path.parent().unwrap_or(Path::new(".")).to_path_buf()
172+
} else {
173+
path.to_path_buf()
174+
});
175+
}
176+
}
177+
}
178+
179+
fn create_repo_config_from_args(args: &Cli) -> RepoConfig {
180+
use crate::config::BackwardsCompatOutputFormat;
181+
182+
RepoConfig {
183+
include: args.include.clone(),
184+
exclude: args.exclude.clone().map(|v| v.clone()),
185+
max_size: args.max_size,
186+
max_depth: args.max_depth,
187+
output: args
188+
.output
189+
.clone()
190+
.map(|o| BackwardsCompatOutputFormat::from(o)),
191+
file: args.file.clone(),
192+
hidden: Some(args.hidden),
193+
no_ignore: Some(args.no_ignore),
194+
}
195+
}
196+
197+
fn apply_repo_config(args: &mut Cli, repo_config: &RepoConfig) {
198+
if let Some(ref include) = repo_config.include {
199+
args.include = Some(include.clone());
200+
}
201+
202+
if let Some(ref exclude) = repo_config.exclude {
203+
args.exclude = Some(exclude.clone());
204+
}
205+
206+
if let Some(max_size) = repo_config.max_size {
207+
args.max_size = Some(max_size);
208+
}
209+
210+
if let Some(max_depth) = repo_config.max_depth {
211+
args.max_depth = Some(max_depth);
212+
}
213+
214+
if let Some(ref output) = repo_config.output {
215+
args.output = Some((*output).clone().into());
216+
}
217+
218+
if let Some(ref file) = repo_config.file {
219+
args.file = Some(file.clone());
220+
}
221+
222+
if let Some(hidden) = repo_config.hidden {
223+
args.hidden = hidden;
224+
}
225+
226+
if let Some(no_ignore) = repo_config.no_ignore {
227+
args.no_ignore = no_ignore;
228+
}
229+
}

src/output.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ mod tests {
324324

325325
let content = "Test content".to_string();
326326
let args = Cli {
327+
config: false,
327328
paths: vec![".".to_string()],
328329
include: None,
329330
exclude: None,

0 commit comments

Comments
 (0)