#![deny(warnings)] use chrono::Utc; use clap::Parser; use near_syn::{ contract::Contract, md::{md_footer, md_items, md_methods_table, md_prelude}, ts::{ts_contract_methods, ts_extend_traits, ts_items, ts_prelude}, }; use std::{ env, fs::{self, File}, io::{self, stdout, Read, Write}, path::Path, }; /// Analyzes Rust source files to generate either TypeScript bindings or Markdown documentation #[derive(Parser)] #[clap(version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"))] struct Args { #[clap(subcommand)] cmd: Cmd, } #[derive(Parser)] enum Cmd { /// Emits TypeScript bindings #[clap(version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"))] TS(EmitArgs), /// Emits Markdown documentation #[clap(version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"))] MD(MDEmitArgs), } #[derive(Parser)] struct EmitArgs { /// Does not emit date/time information, /// otherwise emits current time #[clap(long)] no_now: bool, /// Rust source files (*.rs) to analize #[clap()] files: Vec, } #[derive(Parser)] struct MDEmitArgs { /// If provided, the output will be embedded in between markers inside the README #[clap(long)] readme: Option, #[clap(flatten)] emit_args: EmitArgs, } impl EmitArgs { fn now(&self) -> String { if self.no_now { "".to_string() } else { format!(" on {}", Utc::now()) } } fn contract(&self) -> Contract { let asts = self.files.iter().map(|file| parse_rust(file)).collect(); let mut contract = Contract::new(); contract.push_asts(asts); contract } } fn main() -> io::Result<()> { let args = Args::parse(); match args.cmd { Cmd::TS(args) => emit_ts(&mut stdout(), args).unwrap(), Cmd::MD(args) => { if let Some(readme) = args.readme { let content = fs::read_to_string(&readme)?; let mut buf = Vec::new(); emit_md_table(&mut buf, &args.emit_args, content)?; let mut file = File::create(readme)?; file.write_all(&buf)?; } else { emit_md(&mut stdout(), &args.emit_args)? } } } Ok(()) } fn emit_ts(buf: &mut W, args: EmitArgs) -> io::Result<()> { let contract = args.contract(); ts_prelude(buf, args.now(), env!("CARGO_BIN_NAME"))?; ts_items(buf, &contract)?; ts_extend_traits(buf, &contract)?; ts_contract_methods(buf, &contract)?; Ok(()) } fn emit_md(buf: &mut W, args: &EmitArgs) -> io::Result<()> { let now = args.now(); let contract = args.contract(); md_prelude(buf, now.clone())?; md_methods_table(buf, &contract)?; md_items(buf, &contract)?; md_footer(buf, env!("CARGO_BIN_NAME"), now)?; Ok(()) } fn emit_md_table(buf: &mut W, args: &EmitArgs, content: String) -> io::Result<()> { let contract = args.contract(); let mut is_open = false; for line in content.lines() { let line = line.trim_end().to_string(); if line == "" { is_open = true; } else if line == "" && is_open { writeln!( buf, "" )?; md_methods_table(buf, &contract).unwrap(); writeln!( buf, "" )?; is_open = false; } else if !is_open { writeln!(buf, "{}", line)?; } } Ok(()) } /// Returns the Rust syntax tree for the given `file_name` path. /// Panics if the file cannot be open or the file has syntax errors. /// /// ## Example /// /// ```no_run /// let mut ts = near_syn::ts::TS::new(std::io::stdout()); /// let ast = near_syn::parse_rust("path/to/file.rs"); /// ts.ts_items(&ast.items); /// ``` fn parse_rust>(file_name: S) -> syn::File { let mut file = File::open(file_name).expect("Unable to open file"); let mut src = String::new(); file.read_to_string(&mut src).expect("Unable to read file"); syn::parse_file(&src).expect("Unable to parse file") }