#![deny(unsafe_code)]
#![warn(missing_docs)]
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(rustdoc::private_intra_doc_links)]
#![warn(clippy::doc_markdown)]
#![doc(html_root_url = "https://docs.rs/atomwrite/0.1.2")]
rust_i18n::i18n!("locales", fallback = "en");
pub mod atomic;
pub mod binary_detect;
pub mod checksum;
pub mod cli;
pub mod cli_args;
pub mod commands;
pub mod constants;
pub mod error;
pub mod file_io;
pub mod lang_utils;
pub mod line_endings;
pub mod ndjson_types;
pub mod output;
pub mod path_safety;
pub mod platform;
pub mod signal;
use std::io::{Read, Write};
use anyhow::Result;
use crate::cli::{Cli, Commands};
use crate::output::NdjsonWriter;
fn emit_json_schema(command: &Commands, mut out: impl Write) -> Result<()> {
let schema = match command {
Commands::Read(_) => schemars::schema_for!(ndjson_types::ReadOutput),
Commands::Write(_) => schemars::schema_for!(ndjson_types::WriteOutput),
Commands::Edit(_) => schemars::schema_for!(ndjson_types::EditOutput),
Commands::Search(_) => schemars::schema_for!(ndjson_types::SearchMatch),
Commands::Replace(_) => schemars::schema_for!(ndjson_types::ReplaceResult),
Commands::Hash(_) => schemars::schema_for!(ndjson_types::WriteOutput),
Commands::Delete(_) => schemars::schema_for!(ndjson_types::WriteOutput),
Commands::Count(_) => schemars::schema_for!(ndjson_types::Summary),
Commands::Diff(_) => schemars::schema_for!(ndjson_types::DryRunPlan),
Commands::Move(_) => schemars::schema_for!(ndjson_types::WriteOutput),
Commands::Copy(_) => schemars::schema_for!(ndjson_types::WriteOutput),
Commands::List(_) => schemars::schema_for!(ndjson_types::ListEntry),
Commands::Extract(_) => schemars::schema_for!(ndjson_types::CalcOutput),
Commands::Calc(_) => schemars::schema_for!(ndjson_types::CalcOutput),
Commands::Regex(_) => schemars::schema_for!(ndjson_types::RegexOutput),
Commands::Transform(_) => schemars::schema_for!(ndjson_types::TransformResult),
Commands::Batch(_) => schemars::schema_for!(ndjson_types::BatchSummary),
Commands::Scope(_) => schemars::schema_for!(ndjson_types::ScopeResult),
Commands::Backup(_) => schemars::schema_for!(ndjson_types::BackupResult),
Commands::Rollback(_) => schemars::schema_for!(ndjson_types::RollbackResult),
Commands::Apply(_) => schemars::schema_for!(ndjson_types::ApplyResult),
Commands::Completions(_) => schemars::schema_for!(ndjson_types::CalcOutput),
};
serde_json::to_writer_pretty(&mut out, &schema)?;
out.write_all(b"\n")?;
out.flush()?;
Ok(())
}
pub fn emit_schema_by_name(name: &str, mut out: impl Write) -> Result<bool> {
let schema = match name {
"read" => schemars::schema_for!(ndjson_types::ReadOutput),
"write" => schemars::schema_for!(ndjson_types::WriteOutput),
"edit" => schemars::schema_for!(ndjson_types::EditOutput),
"search" => schemars::schema_for!(ndjson_types::SearchMatch),
"replace" => schemars::schema_for!(ndjson_types::ReplaceResult),
"hash" => schemars::schema_for!(ndjson_types::WriteOutput),
"delete" => schemars::schema_for!(ndjson_types::WriteOutput),
"count" => schemars::schema_for!(ndjson_types::Summary),
"diff" => schemars::schema_for!(ndjson_types::DryRunPlan),
"move" => schemars::schema_for!(ndjson_types::WriteOutput),
"copy" => schemars::schema_for!(ndjson_types::WriteOutput),
"list" => schemars::schema_for!(ndjson_types::ListEntry),
"extract" => schemars::schema_for!(ndjson_types::CalcOutput),
"calc" => schemars::schema_for!(ndjson_types::CalcOutput),
"regex" => schemars::schema_for!(ndjson_types::RegexOutput),
"transform" => schemars::schema_for!(ndjson_types::TransformResult),
"batch" => schemars::schema_for!(ndjson_types::BatchSummary),
"scope" => schemars::schema_for!(ndjson_types::ScopeResult),
"backup" => schemars::schema_for!(ndjson_types::BackupResult),
"rollback" => schemars::schema_for!(ndjson_types::RollbackResult),
"apply" => schemars::schema_for!(ndjson_types::ApplyResult),
"completions" => schemars::schema_for!(ndjson_types::CalcOutput),
_ => return Ok(false),
};
serde_json::to_writer_pretty(&mut out, &schema)?;
out.write_all(b"\n")?;
out.flush()?;
Ok(true)
}
pub fn run(cli: &Cli, stdin: impl Read, stdout: impl Write) -> Result<()> {
if cli.global.json_schema {
return emit_json_schema(&cli.command, stdout);
}
if cli.global.json {
tracing::debug!("--json is a no-op; output is always NDJSON");
}
if let Commands::Completions(args) = &cli.command {
return generate_completions(args, stdout);
}
if let Some(threads) = cli.global.threads {
let n = if threads == 0 { num_cpus() } else { threads };
if let Err(e) = rayon::ThreadPoolBuilder::new()
.num_threads(n)
.build_global()
{
tracing::warn!(error = %e, "failed to configure rayon global pool");
}
}
let mut writer = NdjsonWriter::new(stdout);
let shutdown = signal::get_or_install_handlers()?;
let _workspace = cli.global.resolve_workspace()?;
let result = match &cli.command {
Commands::Read(args) => commands::read::cmd_read(args, &cli.global, &mut writer),
Commands::Write(args) => {
commands::write::cmd_write(args, &cli.global, stdin, &mut writer, &shutdown)
}
Commands::Edit(args) => {
commands::edit::cmd_edit(args, &cli.global, stdin, &mut writer, &_workspace)
}
Commands::Search(args) => {
commands::search::cmd_search(args, &cli.global, &mut writer, &shutdown)
}
Commands::Replace(args) => {
commands::replace::cmd_replace(args, &cli.global, &mut writer, &shutdown)
}
Commands::Hash(args) => commands::hash::cmd_hash(args, &cli.global, stdin, &mut writer),
Commands::Delete(args) => commands::delete::cmd_delete(args, &cli.global, &mut writer),
Commands::Count(args) => commands::count::cmd_count(args, &cli.global, &mut writer),
Commands::Diff(args) => commands::diff::cmd_diff(args, &cli.global, &mut writer),
Commands::Move(args) => commands::r#move::cmd_move(args, &cli.global, &mut writer),
Commands::Copy(args) => commands::copy::cmd_copy(args, &cli.global, &mut writer),
Commands::List(args) => commands::list::cmd_list(args, &cli.global, &mut writer),
Commands::Extract(args) => commands::extract::cmd_extract(args, stdin, &mut writer),
Commands::Calc(args) => commands::calc::cmd_calc(args, stdin, &mut writer),
Commands::Regex(args) => commands::regex_gen::cmd_regex(args, stdin, &mut writer),
Commands::Transform(args) => {
commands::transform::cmd_transform(args, &cli.global, &mut writer, &shutdown)
}
Commands::Batch(args) => {
if args.input_schema {
return commands::batch::emit_input_schema(&mut writer);
}
commands::batch::cmd_batch(
&cli.global,
stdin,
&mut writer,
args.dry_run,
args.transaction,
args.file.as_deref(),
&shutdown,
)
}
Commands::Scope(args) => {
commands::scope::cmd_scope(args, &cli.global, &mut writer, &shutdown)
}
Commands::Backup(args) => commands::backup::cmd_backup(args, &cli.global, &mut writer),
Commands::Rollback(args) => {
commands::rollback::cmd_rollback(args, &cli.global, &mut writer)
}
Commands::Apply(args) => commands::apply::cmd_apply(args, &cli.global, stdin, &mut writer),
Commands::Completions(_) => unreachable!("completions handled in prescan_json_schema"),
};
let _ = writer.flush();
result
}
fn num_cpus() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4)
}
fn generate_completions(args: &cli::CompletionsArgs, mut out: impl Write) -> Result<()> {
use clap::CommandFactory;
let shell = match args.shell {
cli::ShellType::Bash => clap_complete::Shell::Bash,
cli::ShellType::Zsh => clap_complete::Shell::Zsh,
cli::ShellType::Fish => clap_complete::Shell::Fish,
cli::ShellType::PowerShell => clap_complete::Shell::PowerShell,
cli::ShellType::Elvish => clap_complete::Shell::Elvish,
};
if args.install {
let xdg_data = std::env::var_os("XDG_DATA_HOME")
.map(std::path::PathBuf::from)
.or_else(|| {
std::env::var_os("HOME")
.map(|h| std::path::PathBuf::from(h).join(".local").join("share"))
})
.ok_or_else(|| anyhow::anyhow!("cannot determine XDG data directory"))?;
let (subdir, filename) = match args.shell {
cli::ShellType::Bash => ("bash-completion/completions", "atomwrite"),
cli::ShellType::Zsh => ("zsh/site-functions", "_atomwrite"),
cli::ShellType::Fish => ("fish/vendor_completions.d", "atomwrite.fish"),
cli::ShellType::PowerShell => ("powershell/Completions", "_atomwrite.ps1"),
cli::ShellType::Elvish => ("elvish/lib", "atomwrite.elv"),
};
let install_dir = xdg_data.join(subdir);
std::fs::create_dir_all(&install_dir)
.map_err(|e| anyhow::anyhow!("cannot create {}: {e}", install_dir.display()))?;
let install_path = install_dir.join(filename);
let mut file = std::fs::File::create(&install_path)
.map_err(|e| anyhow::anyhow!("cannot create {}: {e}", install_path.display()))?;
clap_complete::generate(shell, &mut cli::Cli::command(), "atomwrite", &mut file);
let path_str = install_path.display().to_string();
writeln!(out, "{{\"type\":\"installed\",\"path\":\"{path_str}\"}}")?;
Ok(())
} else {
clap_complete::generate(shell, &mut cli::Cli::command(), "atomwrite", &mut out);
Ok(())
}
}