Skip to content

Commit 9f723fe

Browse files
committed
*
1 parent 3070d4b commit 9f723fe

8 files changed

Lines changed: 516 additions & 116 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ image = "0.25"
5959
# EPUB generation
6060
epub-builder = "0.7"
6161
zip = "2.1"
62+
regex = "1.11.1"
6263

6364
[dev-dependencies]
6465
tempfile = "3.8"

README.md

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Scrollcast 🦀📄
22

3-
A modern Rust CLI tool that converts Git repositories into beautifully formatted documents (PDF, EPUB, HTML, Markdown) with professional syntax highlighting and theming.
3+
A modern Rust CLI tool that converts Git repositories into beautifully formatted documents (PDF, EPUB, HTML, Markdown) with professional syntax highlighting and theming. Pure Rust implementation with no external dependencies.
44

55
## ✨ Features
66

77
- 🚀 **Fast & Efficient**: Built with Rust for maximum performance
8-
- 🎨 **Professional Output**: Powered by Pandoc for publication-quality documents
9-
- 📚 **Multiple Formats**: PDF, EPUB, HTML, and Markdown output
10-
- 🌈 **Syntax Highlighting**: Support for 300+ programming languages via Skylighting
11-
- 🔧 **Solidity Support**: Automatic download and integration of Solidity syntax definitions
8+
- 🦀 **Pure Rust**: No external dependencies - everything built with Rust libraries
9+
- 📚 **Multiple Formats**: PDF, EPUB (experimental), HTML, and Markdown output
10+
- 🌈 **Syntax Highlighting**: Support for 100+ programming languages via Syntect
11+
- 🔧 **Smart Language Detection**: Automatic syntax detection and highlighting
1212
- 📁 **Smart Git Integration**: Respects .gitignore by default with override options
1313
- 🎯 **Binary File Detection**: Intelligent handling of different file types
1414
- 📋 **Table of Contents**: Automatic TOC generation for easy navigation
@@ -19,18 +19,10 @@ A modern Rust CLI tool that converts Git repositories into beautifully formatted
1919

2020
### Prerequisites
2121

22-
You need to have [Pandoc](https://pandoc.org/installing.html) installed on your system:
22+
**No external dependencies required!** Scrollcast is a pure Rust implementation that works out of the box.
2323

24-
```bash
25-
# Ubuntu/Debian
26-
sudo apt-get install pandoc
27-
28-
# macOS
29-
brew install pandoc
30-
31-
# Windows
32-
# Download from https://pandoc.org/installing.html
33-
```
24+
- Rust 1.70+ (for building from source)
25+
- Git (for processing git repositories)
3426

3527
### Installation
3628

@@ -50,15 +42,18 @@ cargo build --release
5042
# Convert to PDF (default)
5143
scrollcast /path/to/repo -o output.pdf
5244

53-
# Convert to EPUB
54-
scrollcast /path/to/repo -o output.epub -f epub
45+
# Convert to EPUB (experimental)
46+
scrollcast /path/to/repo -o output.epub -f epub --include-experimental
5547

5648
# Convert to HTML
5749
scrollcast /path/to/repo -o output.html -f html
5850

5951
# Convert to Markdown
6052
scrollcast /path/to/repo -o output.md -f markdown
6153

54+
# Test project generation
55+
scrollcast --test-project
56+
6257
# Use different theme
6358
scrollcast /path/to/repo -o output.pdf -t zenburn
6459

@@ -89,6 +84,8 @@ OPTIONS:
8984
--no-gitignore Ignore .gitignore files and process all files
9085
--no-toc Don't include table of contents
9186
--ignore <DIR> Ignore specific directories (can be used multiple times)
87+
--test-project Generate test project documentation in testfiles/output_test
88+
--include-experimental Include experimental features like EPUB
9289
-y, --yes Skip confirmation prompts
9390
--list-themes List available syntax highlighting themes
9491
--list-languages List supported programming languages
@@ -99,13 +96,14 @@ OPTIONS:
9996
## 🎨 Output Formats
10097
10198
### PDF
102-
- **Engine**: XeLaTeX for superior Unicode support
103-
- **Features**: Vector graphics, professional typography, bookmarks
99+
- **Engine**: printpdf for pure Rust implementation
100+
- **Features**: Vector graphics, professional typography
104101
- **Best for**: Printing, sharing, archival
105102
106-
### EPUB
107-
- **Features**: Reflowable text, TOC navigation, metadata
103+
### EPUB (Experimental)
104+
- **Features**: Reflowable text, TOC navigation, inline CSS styling
108105
- **Best for**: E-readers, mobile devices, accessibility
106+
- **Note**: Include with `--include-experimental` flag
109107
110108
### HTML
111109
- **Features**: Standalone HTML with embedded CSS, responsive design
@@ -248,21 +246,27 @@ cargo install --path .
248246
```
249247
src/
250248
├── main.rs # CLI interface and orchestration
251-
├── config.rs # Configuration management (legacy)
249+
├── config.rs # Configuration management
252250
├── file_processor.rs # File discovery and filtering
253251
├── markdown_generator.rs # Markdown generation from file tree
254-
├── pandoc.rs # Pandoc integration and format conversion
255-
└── theme.rs # Theme definitions (legacy)
252+
├── theme.rs # Theme definitions
253+
└── renderer/
254+
├── mod.rs # Document renderer trait and core logic
255+
├── html.rs # HTML renderer with syntax highlighting
256+
├── epub.rs # EPUB renderer with inline CSS
257+
└── pdf.rs # PDF renderer (printpdf-based)
256258
```
257259
258260
### Dependencies
259261
260-
- **Pandoc**: Document conversion engine
261-
- **Tokio**: Async runtime for HTTP requests
262-
- **Clap**: Command-line argument parsing
263-
- **Git2**: Git repository integration
264-
- **Colorful**: Terminal output styling
265-
- **Reqwest**: HTTP client for downloading syntax definitions
262+
- **pulldown-cmark**: Markdown parsing and processing
263+
- **syntect**: Syntax highlighting engine
264+
- **printpdf**: PDF generation
265+
- **epub-builder**: EPUB document creation
266+
- **clap**: Command-line argument parsing
267+
- **git2**: Git repository integration
268+
- **regex**: Pattern matching for CSS conversion
269+
- **tokio**: Async runtime
266270
267271
## 📋 Examples
268272
@@ -272,8 +276,8 @@ src/
272276
# Generate project documentation
273277
scrollcast ./my-project -o docs/codebase.pdf -t kate
274278
275-
# Create EPUB for mobile reading
276-
scrollcast ./my-project -o docs/codebase.epub -f epub -t breezedark
279+
# Create EPUB for mobile reading (experimental)
280+
scrollcast ./my-project -o docs/codebase.epub -f epub -t breezedark --include-experimental
277281
```
278282

279283
### Code Review
@@ -320,10 +324,11 @@ MIT License
320324

321325
## 🙏 Acknowledgments
322326

323-
- **Pandoc**: Universal document converter
324-
- **Skylighting**: Syntax highlighting engine
327+
- **pulldown-cmark**: Fast CommonMark parser
328+
- **syntect**: Pure Rust syntax highlighting
329+
- **printpdf**: Rust PDF generation library
330+
- **epub-builder**: EPUB creation toolkit
325331
- **KDE Syntax Highlighting**: Comprehensive language definitions
326-
- **XeLaTeX**: Superior Unicode and typography support
327332

328333
---
329334

src/main.rs

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,16 @@ async fn main() -> Result<()> {
4343
Arg::new("format")
4444
.short('f')
4545
.long("format")
46-
.help("Output format")
46+
.help("Output format (epub is experimental)")
4747
.value_parser(["pdf", "epub", "html", "markdown"])
4848
.default_value("pdf")
4949
)
50+
.arg(
51+
Arg::new("include-experimental")
52+
.long("include-experimental")
53+
.help("Include experimental formats (EPUB) when using --test-project")
54+
.action(ArgAction::SetTrue)
55+
)
5056
.arg(
5157
Arg::new("theme")
5258
.short('t')
@@ -140,7 +146,8 @@ async fn main() -> Result<()> {
140146
}
141147

142148
if matches.get_flag("test-project") {
143-
run_test_project().await?;
149+
let include_experimental = matches.get_flag("include-experimental");
150+
run_test_project(include_experimental).await?;
144151
return Ok(());
145152
}
146153

@@ -632,50 +639,60 @@ fn list_languages() -> Result<()> {
632639
Ok(())
633640
}
634641

635-
async fn run_test_project() -> Result<()> {
642+
async fn run_test_project(include_experimental: bool) -> Result<()> {
636643
use std::process::Command;
637644

638645
println!("{}", "🧪 Running Test Project Generation".color(Color::Blue).bold());
639646

647+
if include_experimental {
648+
println!("{}", "🧪 Including experimental formats (EPUB)".color(Color::Yellow));
649+
} else {
650+
println!("{}", "📝 Skipping experimental formats (use --include-experimental to include EPUB)".color(Color::Yellow));
651+
}
652+
640653
// Clean output_test folder
641-
println!("{}", "🧹 Cleaning output_test folder...".color(Color::Cyan));
642-
if Path::new("output_test").exists() {
643-
fs::remove_dir_all("output_test")
644-
.context("Failed to remove output_test directory")?;
654+
println!("{}", "🧹 Cleaning testfiles/output_test folder...".color(Color::Cyan));
655+
if Path::new("testfiles/output_test").exists() {
656+
fs::remove_dir_all("testfiles/output_test")
657+
.context("Failed to remove testfiles/output_test directory")?;
645658
}
646-
fs::create_dir_all("output_test")
647-
.context("Failed to create output_test directory")?;
659+
fs::create_dir_all("testfiles/output_test")
660+
.context("Failed to create testfiles/output_test directory")?;
648661

649662
// Check test_project directory exists
650-
println!("{}", "📁 Checking test_project...".color(Color::Cyan));
651-
if !Path::new("test_project").exists() {
652-
println!("❌ test_project directory not found!");
653-
println!("Please create a test_project directory with some files to test with.");
663+
println!("{}", "📁 Checking testfiles/test_project...".color(Color::Cyan));
664+
if !Path::new("testfiles/test_project").exists() {
665+
println!("❌ testfiles/test_project directory not found!");
666+
println!("Please create a testfiles/test_project directory with some files to test with.");
654667
return Ok(());
655668
}
656669

657670
// Check if directory has files
658-
let test_files = fs::read_dir("test_project")
659-
.context("Failed to read test_project directory")?
671+
let test_files = fs::read_dir("testfiles/test_project")
672+
.context("Failed to read testfiles/test_project directory")?
660673
.count();
661674

662675
if test_files == 0 {
663-
println!("❌ test_project directory is empty!");
664-
println!("Please add some files to the test_project directory to test with.");
676+
println!("❌ testfiles/test_project directory is empty!");
677+
println!("Please add some files to the testfiles/test_project directory to test with.");
665678
return Ok(());
666679
}
667680

668-
println!("✅ Found test_project directory with {} files", test_files);
681+
println!("✅ Found testfiles/test_project directory with {} files", test_files);
669682

670-
// Generate all formats
671-
let formats = ["markdown", "html", "epub", "pdf"];
683+
// Generate all formats (exclude EPUB unless experimental flag is set)
684+
let formats = if include_experimental {
685+
vec!["markdown", "html", "epub", "pdf"]
686+
} else {
687+
vec!["markdown", "html", "pdf"]
688+
};
672689
let mut success_count = 0;
673690
let mut failed_formats = Vec::new();
674691

675692
for format in &formats {
676693
println!("{}", format!("📄 Generating {} format...", format).color(Color::Cyan));
677694

678-
let output_file = format!("output_test/test_project.{}",
695+
let output_file = format!("testfiles/output_test/test_project.{}",
679696
match *format {
680697
"markdown" => "md",
681698
other => other,
@@ -686,7 +703,7 @@ async fn run_test_project() -> Result<()> {
686703
let result = Command::new("cargo")
687704
.args(&[
688705
"run", "--",
689-
"test_project",
706+
"testfiles/test_project",
690707
"--output", &output_file,
691708
"--format", format,
692709
"--yes"
@@ -731,10 +748,10 @@ async fn run_test_project() -> Result<()> {
731748
println!("❌ Failed formats: {}", failed_list.join(", "));
732749
}
733750

734-
println!("📁 Output directory: {}", "output_test/".color(Color::Blue));
751+
println!("📁 Output directory: {}", "testfiles/output_test/".color(Color::Blue));
735752

736753
// List generated files
737-
if let Ok(entries) = fs::read_dir("output_test") {
754+
if let Ok(entries) = fs::read_dir("testfiles/output_test") {
738755
println!("\n{}", "📄 Generated files:".color(Color::Cyan));
739756
for entry in entries {
740757
if let Ok(entry) = entry {

0 commit comments

Comments
 (0)