Skip to content

Commit 2f3c50a

Browse files
committed
Add directory ignore functionality and fix syntax highlighting
- Add --ignore flag to exclude specific directories from processing - Add -y flag to skip confirmation prompts - Show directory file counts when warning about large repositories - Fix syntax highlighting by removing CSS interference and using JavaScript highlighting for Solidity - Categorize themes by light/dark backgrounds in documentation - Update README with new command line options and examples These improvements make the tool more flexible for handling large repositories with better user control and working syntax highlighting themes.
1 parent 1c9b42e commit 2f3c50a

5 files changed

Lines changed: 112 additions & 57 deletions

File tree

README.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ scrollcast /path/to/repo -o output.pdf -t zenburn
6464

6565
# Ignore .gitignore files
6666
scrollcast /path/to/repo -o output.pdf --no-gitignore
67+
68+
# Ignore specific directories
69+
scrollcast /path/to/repo -o output.pdf --ignore lib --ignore node_modules
70+
71+
# Skip confirmation prompts
72+
scrollcast /path/to/repo -o output.pdf -y
6773
```
6874

6975
## 📖 Command Line Options
@@ -82,6 +88,8 @@ OPTIONS:
8288
[possible values: pygments, kate, monochrome, breezedark, espresso, zenburn, haddock, tango]
8389
--no-gitignore Ignore .gitignore files and process all files
8490
--no-toc Don't include table of contents
91+
--ignore <DIR> Ignore specific directories (can be used multiple times)
92+
-y, --yes Skip confirmation prompts
8593
--list-themes List available syntax highlighting themes
8694
--list-languages List supported programming languages
8795
-h, --help Print help
@@ -118,7 +126,7 @@ Scrollcast automatically detects and highlights syntax for 300+ programming lang
118126
- SQL, Docker, YAML, TOML, XML
119127
120128
**Blockchain & Smart Contracts:**
121-
- **Solidity** (automatically downloaded and configured)
129+
- **Solidity** (using JavaScript syntax highlighting)
122130
- Vyper, Move, Cairo
123131
124132
**Web Technologies:**
@@ -135,14 +143,17 @@ Use `--list-languages` to see all supported languages.
135143
136144
## 🎨 Available Themes
137145
146+
**Light Background Themes:**
138147
- **kate** (default) - Balanced colors, excellent readability
139148
- **pygments** - Classic Python documentation style
149+
- **tango** - GNOME's colorful theme
150+
- **haddock** - Haskell documentation style
151+
- **monochrome** - Black and white for printing
152+
153+
**Dark Background Themes:**
140154
- **zenburn** - Dark, low-contrast theme
141155
- **breezedark** - KDE's dark theme
142156
- **espresso** - Rich coffee-inspired colors
143-
- **monochrome** - Black and white for printing
144-
- **haddock** - Haskell documentation style
145-
- **tango** - GNOME's colorful theme
146157
147158
Use `--list-themes` to see all available themes.
148159
@@ -203,13 +214,13 @@ scrollcast large-repo/ -o output.pdf
203214

204215
### Solidity Development
205216

206-
Solidity syntax highlighting is automatically configured:
217+
Solidity files are highlighted using JavaScript syntax highlighting for optimal compatibility:
207218

208219
```bash
209-
# First run downloads solidity.xml syntax definition
220+
# Process smart contracts with syntax highlighting
210221
scrollcast my-smart-contracts/ -o contracts.pdf
211222
212-
# Subsequent runs use cached definition
223+
# Generate EPUB documentation
213224
scrollcast my-dapp/ -o dapp-code.epub -f epub
214225
```
215226

src/file_processor.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct FileProcessor {
2626
ignore_config: IgnoreConfig,
2727
universal_excludes: UniversalExcludes,
2828
respect_gitignore: bool,
29+
ignored_directories: Vec<String>,
2930
}
3031

3132
impl FileProcessor {
@@ -34,6 +35,7 @@ impl FileProcessor {
3435
ignore_config: IgnoreConfig::default(),
3536
universal_excludes: UniversalExcludes::new(),
3637
respect_gitignore: true,
38+
ignored_directories: Vec::new(),
3739
}
3840
}
3941

@@ -47,6 +49,11 @@ impl FileProcessor {
4749
self
4850
}
4951

52+
pub fn with_ignored_directories(mut self, dirs: Vec<String>) -> Self {
53+
self.ignored_directories = dirs;
54+
self
55+
}
56+
5057
pub fn load_ignore_config_from_path<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
5158
let ignore_file_path = path.as_ref().join("scrollcast.ignore");
5259
if ignore_file_path.exists() {
@@ -109,6 +116,16 @@ impl FileProcessor {
109116
// Show warning for large file counts
110117
if files.len() > 50 {
111118
eprintln!("⚠️ Warning: Processing {} files. This may take a while and result in a large document.", files.len());
119+
120+
// Show top directories by file count
121+
let dir_counts = self.get_directory_file_counts(&files);
122+
if !dir_counts.is_empty() {
123+
eprintln!(" Top directories by file count:");
124+
for (dir, count) in dir_counts.iter().take(5) {
125+
eprintln!(" {} - {} files", dir, count);
126+
}
127+
}
128+
112129
eprintln!(" Consider using .gitignore or custom ignore rules to reduce the number of files.");
113130
}
114131

@@ -123,6 +140,14 @@ impl FileProcessor {
123140
.context("Failed to get relative path")?;
124141
let relative_path_str = relative_path.to_string_lossy();
125142

143+
// Check user-specified ignored directories
144+
for ignored_dir in &self.ignored_directories {
145+
if relative_path_str.starts_with(ignored_dir) ||
146+
relative_path_str.starts_with(&format!("{}/", ignored_dir)) {
147+
return Ok(false);
148+
}
149+
}
150+
126151
// Check universal excludes
127152
if self.universal_excludes.should_exclude(file_path) {
128153
return Ok(false);
@@ -147,6 +172,33 @@ impl FileProcessor {
147172
Ok(true)
148173
}
149174

175+
fn get_directory_file_counts(&self, files: &[FileInfo]) -> Vec<(String, usize)> {
176+
use std::collections::HashMap;
177+
178+
let mut dir_counts: HashMap<String, usize> = HashMap::new();
179+
180+
for file in files {
181+
let path = Path::new(&file.path);
182+
let dir = if let Some(parent) = path.parent() {
183+
if parent == Path::new("") {
184+
".".to_string()
185+
} else {
186+
parent.to_string_lossy().to_string()
187+
}
188+
} else {
189+
".".to_string()
190+
};
191+
192+
*dir_counts.entry(dir).or_insert(0) += 1;
193+
}
194+
195+
// Sort by count descending
196+
let mut sorted: Vec<(String, usize)> = dir_counts.into_iter().collect();
197+
sorted.sort_by(|a, b| b.1.cmp(&a.1));
198+
199+
sorted
200+
}
201+
150202
fn process_single_file(&self, file_path: &Path, root_path: &Path) -> Result<FileInfo> {
151203
let relative_path = file_path.strip_prefix(root_path)
152204
.context("Failed to get relative path")?;

src/main.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ async fn main() -> Result<()> {
8484
.help("Skip confirmation prompts")
8585
.action(ArgAction::SetTrue)
8686
)
87+
.arg(
88+
Arg::new("ignore")
89+
.long("ignore")
90+
.help("Ignore specific directories (can be used multiple times)")
91+
.action(ArgAction::Append)
92+
.value_name("DIR")
93+
)
8794
.get_matches();
8895

8996
// Handle list commands
@@ -105,6 +112,11 @@ async fn main() -> Result<()> {
105112
let respect_gitignore = !matches.get_flag("no-gitignore");
106113
let include_toc = !matches.get_flag("no-toc");
107114
let skip_confirmation = matches.get_flag("yes");
115+
let ignored_dirs: Vec<String> = matches
116+
.get_many::<String>("ignore")
117+
.unwrap_or_default()
118+
.map(|s| s.to_string())
119+
.collect();
108120

109121
// Parse output format
110122
let output_format = match format.as_str() {
@@ -137,7 +149,8 @@ async fn main() -> Result<()> {
137149
// Process the repository/directory
138150
println!("\n{}", "📖 Processing files...".color(Color::Cyan));
139151
let file_processor = FileProcessor::new()
140-
.with_gitignore_respect(respect_gitignore);
152+
.with_gitignore_respect(respect_gitignore)
153+
.with_ignored_directories(ignored_dirs);
141154

142155
let files = file_processor.process_directory(input_path)
143156
.context("Failed to process input directory")?;
@@ -197,8 +210,9 @@ async fn main() -> Result<()> {
197210
converter.convert_markdown_to_document(&temp_markdown, output_path).await
198211
.context("Failed to convert markdown to final format")?;
199212

200-
// Clean up temporary file
201-
let _ = fs::remove_file(&temp_markdown);
213+
// Keep temporary file for debugging
214+
// let _ = fs::remove_file(&temp_markdown);
215+
println!("📝 Debug: Temporary markdown file: {}", temp_markdown.display());
202216

203217
println!("\n{} Document generated successfully!", "🎉".color(Color::Green));
204218
println!("📄 Output: {}", output_path.display().to_string().color(Color::Blue));

src/markdown_generator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl MarkdownGenerator {
157157
"r" => "r",
158158
"sql" => "sql",
159159
"dockerfile" => "dockerfile",
160-
"sol" => "solidity", // Solidity support
160+
"sol" => "javascript", // Use JavaScript highlighting for Solidity as fallback
161161
"vy" => "python", // Vyper (use python highlighting as fallback)
162162
"move" => "rust", // Move language (use rust as fallback)
163163
_ => return None,

src/pandoc.rs

Lines changed: 24 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ impl PandocConverter {
9696
// Highlight style
9797
cmd.arg("--highlight-style").arg(&self.config.highlight_style);
9898

99-
// Add syntax definitions
100-
cmd.arg("--syntax-definition").arg(&solidity_xml);
99+
// Skip broken Solidity syntax definition for now
100+
// cmd.arg("--syntax-definition").arg(&solidity_xml);
101101
for syntax_def in &self.config.syntax_definitions {
102102
cmd.arg("--syntax-definition").arg(syntax_def);
103103
}
@@ -110,16 +110,16 @@ impl PandocConverter {
110110
cmd.arg("--wrap=preserve");
111111
cmd.arg("-V").arg("geometry:margin=1in");
112112
cmd.arg("-V").arg("fontsize=10pt");
113-
cmd.arg("-V").arg("mainfont=DejaVu Sans Mono");
113+
// Don't force monospace font to allow syntax highlighting themes
114114
if self.config.include_toc {
115115
cmd.arg("--toc");
116116
}
117117
},
118118
OutputFormat::Epub => {
119-
// Add line wrapping for EPUB
119+
// Add line wrapping for EPUB
120120
cmd.arg("--wrap=preserve");
121-
// Create temporary CSS file for EPUB
122-
let temp_css = self.create_epub_css_file()?;
121+
// Only add minimal CSS that doesn't interfere with syntax highlighting
122+
let temp_css = self.create_minimal_epub_css_file()?;
123123
cmd.arg("--css").arg(&temp_css);
124124
if self.config.include_toc {
125125
cmd.arg("--toc");
@@ -128,8 +128,8 @@ impl PandocConverter {
128128
},
129129
OutputFormat::Html => {
130130
cmd.arg("--standalone");
131-
// Create temporary CSS file for HTML
132-
let temp_css = self.create_html_css_file()?;
131+
// Only add minimal CSS that doesn't interfere with syntax highlighting
132+
let temp_css = self.create_minimal_html_css_file()?;
133133
cmd.arg("--css").arg(&temp_css);
134134
if self.config.include_toc {
135135
cmd.arg("--toc");
@@ -188,72 +188,50 @@ impl PandocConverter {
188188
Ok(data_dir.join("scrollcast").join("syntax"))
189189
}
190190

191-
fn create_epub_css_file(&self) -> Result<PathBuf> {
191+
fn create_minimal_epub_css_file(&self) -> Result<PathBuf> {
192192
let temp_dir = std::env::temp_dir();
193-
let css_path = temp_dir.join("scrollcast_epub.css");
193+
let css_path = temp_dir.join("scrollcast_epub_minimal.css");
194194

195195
let css_content = r#"
196-
code {
197-
white-space: pre-wrap;
198-
word-break: break-all;
199-
font-family: 'Courier New', monospace;
200-
font-size: 0.9em;
201-
}
202-
196+
/* Ultra-minimal CSS for EPUB that doesn't interfere with syntax highlighting */
203197
pre {
204198
white-space: pre-wrap;
205199
word-wrap: break-word;
206200
overflow-wrap: break-word;
207-
background-color: #f8f8f8;
208-
padding: 10px;
209-
border-radius: 4px;
210-
border: 1px solid #ddd;
211201
}
212202
"#;
213203

214204
fs::write(&css_path, css_content)
215-
.context("Failed to create EPUB CSS file")?;
205+
.context("Failed to create minimal EPUB CSS file")?;
216206

217207
Ok(css_path)
218208
}
219209

220-
fn create_html_css_file(&self) -> Result<PathBuf> {
210+
fn create_minimal_html_css_file(&self) -> Result<PathBuf> {
221211
let temp_dir = std::env::temp_dir();
222-
let css_path = temp_dir.join("scrollcast_html.css");
212+
let css_path = temp_dir.join("scrollcast_html_minimal.css");
223213

224214
let css_content = r#"
225-
pre {
226-
white-space: pre-wrap;
227-
word-wrap: break-word;
228-
overflow-wrap: break-word;
229-
background-color: #f8f8f8;
230-
padding: 15px;
231-
border-radius: 6px;
232-
border: 1px solid #ddd;
233-
font-family: 'Courier New', Consolas, Monaco, monospace;
234-
font-size: 14px;
235-
line-height: 1.4;
236-
overflow-x: auto;
237-
}
238-
239-
code {
240-
font-family: 'Courier New', Consolas, Monaco, monospace;
241-
background-color: #f1f1f1;
242-
padding: 2px 4px;
243-
border-radius: 3px;
244-
}
245-
215+
/* Ultra-minimal CSS that doesn't interfere with Pandoc syntax highlighting */
246216
body {
247217
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
248218
line-height: 1.6;
249219
max-width: 1200px;
250220
margin: 0 auto;
251221
padding: 20px;
252222
}
223+
224+
/* Only add line wrapping, no colors or backgrounds */
225+
pre {
226+
white-space: pre-wrap;
227+
word-wrap: break-word;
228+
overflow-wrap: break-word;
229+
overflow-x: auto;
230+
}
253231
"#;
254232

255233
fs::write(&css_path, css_content)
256-
.context("Failed to create HTML CSS file")?;
234+
.context("Failed to create minimal HTML CSS file")?;
257235

258236
Ok(css_path)
259237
}

0 commit comments

Comments
 (0)