rustloclib/lib.rs
1//! # rustloclib
2//!
3//! A language-aware lines of code counter library with a simple, flat data model.
4//!
5//! ## Overview
6//!
7//! Unlike generic LOC counters (tokei, cloc, scc), this library has semantic
8//! backends for languages where tests can live alongside production code. Rust is
9//! enabled by default; Python, TypeScript, and generic source backends can be
10//! selected through [`FilterConfig`]. It categorizes lines into one of 6 types:
11//!
12//! - **code**: Production code logic lines
13//! - **tests**: Test code logic lines (same-file test constructs or test paths)
14//! - **examples**: Example code logic lines (examples/)
15//! - **docs**: Documentation comments (///, //!, /** */, /*! */)
16//! - **comments**: Regular comments (//, /* */)
17//! - **blanks**: Blank/whitespace-only lines
18//!
19//! The key insight: only actual code lines need context (code/tests/examples).
20//! A blank is a blank, a comment is a comment - where they appear doesn't matter.
21//!
22//! ## Data Pipeline
23//!
24//! The library is organized into four stages that form a clear data pipeline:
25//!
26//! ```text
27//! ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
28//! │ source │ -> │ data │ -> │ query │ -> │ output │
29//! └──────────┘ └──────────┘ └──────────┘ └──────────┘
30//! Discover Parse & Filter, Format
31//! files collect sort strings
32//! ```
33//!
34//! ### Stage 1: Source Discovery ([`source`])
35//!
36//! Find what files to analyze:
37//! - [`WorkspaceInfo`]: Discover Cargo workspace structure
38//! - [`FilterConfig`]: Include/exclude files with glob patterns
39//!
40//! ### Stage 2: Data Collection ([`data`])
41//!
42//! Parse files and collect statistics:
43//! - [`gather_stats`]: Parse a single file into [`Locs`]
44//! - [`count_workspace`]: Count all files, returns [`CountResult`]
45//! - [`diff_revspec`]: Compare commits via a git revspec string, returns [`DiffResult`]
46//!
47//! ### Stage 3: Query Processing ([`query`])
48//!
49//! Filter, aggregate, sort, and slice the collected data:
50//! - [`CountQuerySet`] / [`DiffQuerySet`]: Processed data ready for display
51//! - [`Aggregation`]: Total, ByCrate, ByModule, ByFile
52//! - [`LineTypes`]: Which line types to include in output
53//! - [`Ordering`]: How to sort results
54//! - [`Predicate`] (built from [`Field`] + [`Op`]): Threshold filters,
55//! chained via `CountQuerySet::filter(&[Predicate])`
56//! - `CountQuerySet::top(N)`: Truncate to the first N rows after sorting
57//!
58//! ### Stage 4: Output Formatting ([`output`])
59//!
60//! Format data for presentation:
61//! - [`LOCTable`]: Table with headers, rows, footer (all strings)
62//!
63//! ## Example
64//!
65//! ```rust
66//! use rustloclib::{count_file, count_workspace, CountOptions, FilterConfig};
67//! use std::fs;
68//! use tempfile::tempdir;
69//!
70//! // Set up a temporary project
71//! let dir = tempdir().unwrap();
72//! fs::write(dir.path().join("Cargo.toml"), r#"
73//! [package]
74//! name = "my-lib"
75//! version = "0.1.0"
76//! edition = "2021"
77//! "#).unwrap();
78//! fs::create_dir(dir.path().join("src")).unwrap();
79//! let file_path = dir.path().join("src/lib.rs");
80//! fs::write(&file_path, "pub fn hello() {\n println!(\"Hi\");\n}\n").unwrap();
81//!
82//! // Count a single file
83//! let stats = count_file(&file_path).unwrap();
84//! assert_eq!(stats.code, 3); // 3 lines of production code
85//!
86//! // Count an entire workspace
87//! let result = count_workspace(dir.path(), CountOptions::new()).unwrap();
88//! assert!(result.total.code >= 1);
89//!
90//! // Count with filtering
91//! let filter = FilterConfig::new().exclude("**/generated/**").unwrap();
92//! let result = count_workspace(dir.path(), CountOptions::new().filter(filter)).unwrap();
93//! ```
94//!
95//! ## Full Pipeline Example
96//!
97//! ```rust,ignore
98//! use rustloclib::{
99//! count_workspace, CountOptions, CountQuerySet, LOCTable,
100//! Aggregation, Field, LineTypes, Op, Ordering, Predicate,
101//! };
102//!
103//! // Stage 1-2: Discover and collect
104//! let result = count_workspace(".", CountOptions::new())?;
105//!
106//! // Stage 3: Query — aggregate, sort, then filter and slice. Chain
107//! // `.filter(...)` and `.top(...)` for the equivalent of the CLI's
108//! // `--code-gte 1000 --top 10`.
109//! let queryset = CountQuerySet::from_result(
110//! &result,
111//! Aggregation::ByFile,
112//! LineTypes::everything(),
113//! Ordering::by_code(),
114//! )
115//! .filter(&[Predicate::new(Field::Code, Op::Gte, 1000)])
116//! .top(10);
117//!
118//! // Stage 4: Format for output
119//! let table = LOCTable::from_count_queryset(&queryset);
120//! ```
121//!
122//! [`DiffQuerySet`] mirrors [`CountQuerySet`] for the diff side; both
123//! support the same `.filter()` / `.top()` chain. Diff filters operate
124//! on the net change (added − removed) per row.
125//!
126//! ## Origins
127//!
128//! The parsing logic is adapted from [cargo-warloc](https://github.com/Maximkaaa/cargo-warloc)
129//! by Maxim Gritsenko. We thank the original author for the excellent parsing implementation.
130//! cargo-warloc is MIT licensed.
131
132// Pipeline modules (in order)
133pub mod data;
134pub mod output;
135pub mod query;
136pub mod source;
137
138// Infrastructure
139pub mod error;
140
141// Re-export all public types at crate root for convenience
142pub use data::{
143 available_languages, count_directory, count_directory_with_options, count_file,
144 count_file_with_filter, count_workspace, default_languages, diff_revspec, diff_workdir,
145 gather_stats, gather_stats_for_path, CountOptions, CountResult, CrateDiffStats, CrateStats,
146 DiffOptions, DiffResult, FileChangeType, FileDiffStats, FileStats, LanguageName,
147 LanguageSelection, Locs, LocsDiff, ModuleStats, VisitorContext, WorkdirDiffMode,
148};
149pub use error::RustlocError;
150pub use output::{LOCTable, TableRow};
151pub use query::{
152 Aggregation, CountQuerySet, DiffQuerySet, Field, LineTypes, Op, OrderBy, OrderDirection,
153 Ordering, Predicate, QueryItem,
154};
155pub use source::{CrateInfo, FilterConfig, WorkspaceInfo};
156
157/// Result type for rustloclib operations
158pub type Result<T> = std::result::Result<T, RustlocError>;