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}