Skip to main content

src2md/
lib.rs

1//! # src2md Library
2//!
3//! This crate can be used to:
4//!
5//! - Collect all source/text files under given path and compile them into a Markdown file
6//! - Restore original source files back from a generated Markdown file (requires `restore` feature)
7//! - Clone and process git repositories (requires `git` feature)
8//! - Generate mdbook-compatible output (requires `mdbook` feature)
9//!
10//! ## Features
11//!
12//! - `restore` (default) - Enables restoring files from Markdown via `--restore`
13//! - `git` (default) - Enables git repository cloning support via `--git <url>`
14//! - `mdbook` (default) - Enables mdbook format output via `--mdbook <dir>`
15//!
16//! To use only the core bundling functionality without optional features:
17//! ```toml
18//! src2md = { version = "0.1", default-features = false }
19//! ```
20//!
21//! ## Default Exclusions
22//!
23//! The following are always excluded by default:
24//! - Hidden files and directories (starting with `.`)
25//! - Lock files (package-lock.json, yarn.lock, Cargo.lock, etc.)
26//! - Previous src2md output files
27//!
28//! ## Usage
29//!
30//! ### To generate a Markdown file:
31//!
32//! ```rust,no_run
33//! use src2md::{Config, run_src2md};
34//! use std::collections::HashSet;
35//! use std::path::PathBuf;
36//!
37//! #[tokio::main]
38//! async fn main() -> anyhow::Result<()> {
39//!     let config = Config {
40//!         output_path: PathBuf::from("output.md"),
41//!         ignore_file: None,
42//!         specific_paths: HashSet::new(),
43//!         project_root: std::env::current_dir()?,
44//!         #[cfg(feature = "restore")]
45//!         restore_input: None,
46//!         #[cfg(feature = "restore")]
47//!         restore_path: None,
48//!         verbosity: 0,
49//!         fail_fast: true,
50//!         extensions: HashSet::new(),
51//!         #[cfg(feature = "git")]
52//!         git_url: None,
53//!         #[cfg(feature = "git")]
54//!         git_branch: None,
55//!         #[cfg(feature = "mdbook")]
56//!         mdbook_output: None,
57//!     };
58//!
59//!     run_src2md(config).await
60//! }
61//! ```
62
63pub mod cli;
64#[cfg(feature = "restore")]
65pub mod extractor;
66pub mod filewalker;
67pub mod utils;
68pub mod writer;
69
70#[cfg(feature = "git")]
71pub mod git;
72
73#[cfg(feature = "mdbook")]
74pub mod mdbook;
75
76pub use cli::Config;
77#[cfg(feature = "restore")]
78pub use extractor::extract_from_markdown;
79pub use filewalker::collect_files;
80pub use writer::{MarkdownWriter, OUTPUT_MAGIC_BYTES, OUTPUT_MAGIC_HEADER};
81
82#[cfg(feature = "git")]
83pub use git::{ClonedRepo, clone_repository, repo_name_from_url};
84
85#[cfg(feature = "mdbook")]
86pub use mdbook::generate_mdbook;
87
88use anyhow::Result;
89use log::error;
90use tokio::fs::File;
91use tokio::io::BufWriter;
92
93/// Generate a Markdown file from source/text files
94///
95/// If `fail_fast` is true in the config, stops on first error.
96/// Otherwise, logs errors and continues processing remaining files.
97///
98/// # Output File Handling
99///
100/// The output file and any previous src2md outputs are automatically excluded
101/// from collection to prevent:
102/// - Race conditions (writing while reading the same file)
103/// - Self-inclusion (including previous outputs in new outputs)
104///
105/// # Default Exclusions
106///
107/// Hidden files, lock files, and previous src2md outputs are always excluded.
108/// Use the `extensions` field to filter by file type.
109pub async fn run_src2md(config: Config) -> Result<()> {
110    let file = File::create(&config.output_path).await?;
111    let buf_writer = BufWriter::new(file);
112    let mut md_writer = MarkdownWriter::new(buf_writer);
113
114    let entries = collect_files(
115        &config.project_root,
116        config.ignore_file.as_ref(),
117        &config.specific_paths,
118        Some(&config.output_path),
119        &config.extensions,
120    )?;
121
122    for entry in entries {
123        if let Err(e) = md_writer.write_entry(&entry, &config.project_root).await {
124            if config.fail_fast {
125                return Err(e);
126            }
127            error!("Failed to write {}: {e}", entry.path().display());
128        }
129    }
130
131    md_writer.flush().await?;
132    Ok(())
133}
134
135/// Generate a Markdown file from a specific directory path.
136///
137/// This is a convenience function that creates a Config and runs src2md.
138/// Useful when you have a path (e.g., from a cloned git repo) and want to
139/// process it without constructing a full Config.
140pub async fn run_src2md_on_path(
141    project_root: std::path::PathBuf,
142    output_path: std::path::PathBuf,
143    ignore_file: Option<std::path::PathBuf>,
144    extensions: &std::collections::HashSet<String>,
145    fail_fast: bool,
146) -> Result<()> {
147    let file = File::create(&output_path).await?;
148    let buf_writer = BufWriter::new(file);
149    let mut md_writer = MarkdownWriter::new(buf_writer);
150
151    let entries = collect_files(
152        &project_root,
153        ignore_file.as_ref(),
154        &std::collections::HashSet::new(),
155        Some(&output_path),
156        extensions,
157    )?;
158
159    for entry in entries {
160        if let Err(e) = md_writer.write_entry(&entry, &project_root).await {
161            if fail_fast {
162                return Err(e);
163            }
164            error!("Failed to write {}: {e}", entry.path().display());
165        }
166    }
167
168    md_writer.flush().await?;
169    Ok(())
170}