Fastdoc
This commit is contained in:
commit
8371167ee3
6 changed files with 1903 additions and 0 deletions
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[registries.crates-io]
|
||||||
|
protocol = "sparse"
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
1684
Cargo.lock
generated
Normal file
1684
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "fastdoc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
async-walkdir = "0.2.0"
|
||||||
|
clap = { version = "4.2.7", features = ["derive"] }
|
||||||
|
console = "0.15.6"
|
||||||
|
futures = "0.3.28"
|
||||||
|
indicatif = { version = "0.17.3", features = ["tokio", "improved_unicode", "vt100"] }
|
||||||
|
swc_common = { version = "0.31.10", features = ["tty-emitter"] }
|
||||||
|
swc_ecma_parser = "0.134.6"
|
||||||
|
tokio = { version = "1.28.1", features = ["full"] }
|
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
max_width = 80
|
||||||
|
tab_spaces = 2
|
||||||
|
wrap_comments = true
|
196
src/main.rs
Normal file
196
src/main.rs
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use anyhow::{Error, Result};
|
||||||
|
use async_walkdir::{Filtering, WalkDir};
|
||||||
|
use clap::Parser;
|
||||||
|
use console::Term;
|
||||||
|
use futures::{stream, StreamExt, TryStreamExt};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use swc_common::errors::ColorConfig;
|
||||||
|
use swc_common::{errors::Handler, sync::Lrc};
|
||||||
|
use swc_common::{FileName, SourceMap};
|
||||||
|
use swc_ecma_parser::EsConfig;
|
||||||
|
use swc_ecma_parser::{
|
||||||
|
lexer::Lexer as EcmaLexer, Parser as EcmaParser, StringInput, Syntax,
|
||||||
|
};
|
||||||
|
use tokio::fs;
|
||||||
|
use tokio::sync::Semaphore;
|
||||||
|
use tokio::{fs::File, io::AsyncReadExt};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct Opt {
|
||||||
|
/// Output directory
|
||||||
|
#[clap(default_value = "generated/doc")]
|
||||||
|
out_dir: PathBuf,
|
||||||
|
|
||||||
|
/// Paths to process
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
|
fs::create_dir_all(&opt.out_dir).await?;
|
||||||
|
|
||||||
|
let paths = {
|
||||||
|
let mut paths = opt.paths;
|
||||||
|
if paths.is_empty() {
|
||||||
|
paths.push(".".into())
|
||||||
|
}
|
||||||
|
paths
|
||||||
|
};
|
||||||
|
|
||||||
|
let bar = ProgressBar::new(0);
|
||||||
|
bar.set_style(
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
// note that bar size is fixed unlike cargo which is dynamic
|
||||||
|
// and also the truncation in cargo uses trailers (`...`)
|
||||||
|
if Term::stdout().size().1 > 80 {
|
||||||
|
"{prefix:>3.cyan.bold} [{bar:57}] {pos}/{len} {wide_msg}"
|
||||||
|
} else {
|
||||||
|
"{prefix:>3.cyan.bold} [{bar:57}] {pos}/{len}"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("=> "),
|
||||||
|
);
|
||||||
|
bar.set_prefix("Documenting");
|
||||||
|
|
||||||
|
enum BarAction {
|
||||||
|
Inc,
|
||||||
|
Finish,
|
||||||
|
SetLen(u64),
|
||||||
|
}
|
||||||
|
let (tx, rx) = mpsc::channel::<BarAction>();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
for msg in rx.into_iter() {
|
||||||
|
match msg {
|
||||||
|
BarAction::Inc => bar.inc(1),
|
||||||
|
BarAction::Finish => bar.finish(),
|
||||||
|
BarAction::SetLen(n) => bar.set_length(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for base_path in paths.into_iter() {
|
||||||
|
let num_files = create_walkdir(&base_path).count().await;
|
||||||
|
tx.send(BarAction::SetLen(num_files as u64));
|
||||||
|
println!("Counted {num_files} files.");
|
||||||
|
|
||||||
|
let entries_stream = create_walkdir(&base_path);
|
||||||
|
|
||||||
|
let semaphore = Semaphore::new(10);
|
||||||
|
let base_path2 = base_path.clone();
|
||||||
|
let processed_stream = entries_stream
|
||||||
|
.map_err(|e| Error::from(e))
|
||||||
|
.and_then(|dir_ent| {
|
||||||
|
let tx = tx.clone();
|
||||||
|
let base_path3 = base_path2.clone();
|
||||||
|
let permit_fut = semaphore.acquire();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let _permit = permit_fut.await?;
|
||||||
|
|
||||||
|
let path = dir_ent.path();
|
||||||
|
let relative_path = path.strip_prefix(base_path3)?;
|
||||||
|
|
||||||
|
// println!("Path: {relative_path:?}");
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
let mut file = File::open(&path).await?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents).await?;
|
||||||
|
contents
|
||||||
|
};
|
||||||
|
|
||||||
|
handle_data(relative_path, data).await?;
|
||||||
|
|
||||||
|
tx.send(BarAction::Inc);
|
||||||
|
|
||||||
|
Ok::<_, Error>(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.try_collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.send(BarAction::Finish);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_data(path: impl AsRef<Path>, data: String) -> Result<()> {
|
||||||
|
let cm: Lrc<SourceMap> = Default::default();
|
||||||
|
|
||||||
|
let handler =
|
||||||
|
Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone()));
|
||||||
|
|
||||||
|
let fm = cm.new_source_file(
|
||||||
|
FileName::Custom(path.as_ref().display().to_string()),
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let syntax = Syntax::Es(EsConfig {
|
||||||
|
jsx: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let lexer = EcmaLexer::new(
|
||||||
|
// We want to parse ecmascript
|
||||||
|
syntax,
|
||||||
|
// EsVersion defaults to es5
|
||||||
|
Default::default(),
|
||||||
|
StringInput::from(&*fm),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut parser = EcmaParser::new_from(lexer);
|
||||||
|
|
||||||
|
for e in parser.take_errors() {
|
||||||
|
e.into_diagnostic(&handler).emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
let module = parser
|
||||||
|
.parse_module()
|
||||||
|
.map_err(|e| {
|
||||||
|
// Unrecoverable fatal error occurred
|
||||||
|
e.into_diagnostic(&handler).emit()
|
||||||
|
})
|
||||||
|
.expect("failed to parser module");
|
||||||
|
|
||||||
|
// println!("Module: {module:?}");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_walkdir(path: impl AsRef<Path>) -> WalkDir {
|
||||||
|
WalkDir::new(path.as_ref()).filter(|entry| async move {
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if let Some(true) = path
|
||||||
|
.file_name()
|
||||||
|
.map(|f| f == "node_modules" || f.to_string_lossy().starts_with('.'))
|
||||||
|
{
|
||||||
|
return Filtering::IgnoreDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.is_file()
|
||||||
|
&& !path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.map_or_else(|| false, |f| f.ends_with(".js"))
|
||||||
|
{
|
||||||
|
return Filtering::Ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
return Filtering::Ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filtering::Continue
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue