use nbted::unstable::{data, read, string_read, string_write, write};
use nbted::Result;
use std::env;
use std::fs::File;
use std::io;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use std::process::exit;
use std::process::Command;
use getopts::Options;
use anyhow::{bail, format_err, Context};
fn main() {
match run_cmdline() {
Ok(ret) => {
exit(ret);
}
Err(e) => {
eprintln!("{}", e.backtrace());
eprintln!("Error: {}", e);
for e in e.chain().skip(1) {
eprintln!(" caused by: {}", e);
}
eprintln!("For help, run with --help or read the manpage.");
exit(1);
}
}
}
fn run_cmdline() -> Result<i32> {
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
let _: &Options = opts.optflagopt("e", "edit", "edit a NBT file with your $EDITOR.
If [FILE] is specified, then that file is edited in place, but specifying --input and/or --output will override the input/output.
If no file is specified, default to read from --input and writing to --output.", "FILE");
let _: &Options = opts.optflagopt("p", "print", "print NBT file to text format. Adding an argument to this is the same as specifying --input", "FILE");
let _: &Options = opts.optflagopt("r", "reverse", "reverse a file in text format to NBT format. Adding an argument to this is the same as specifying --input", "FILE");
let _: &Options = opts.optopt(
"i",
"input",
"specify the input file, defaults to stdin",
"FILE",
);
let _: &Options = opts.optopt(
"o",
"output",
"specify the output file, defaults to stdout",
"FILE",
);
let _: &Options = opts.optflag("", "man", "print the nbted man page source and exit");
let _: &Options = opts.optflag("h", "help", "print the help menu and exit");
let _: &Options = opts.optflag("", "version", "print program version and exit");
let matches = opts.parse(&args[1..]).context("error parsing options")?;
if matches.opt_present("h") {
let brief = "Usage: nbted [options] FILE";
print!("{}", opts.usage(brief));
println!("\nThe default action, taken if no action is explicitly selected, is to --edit.");
println!(
"\nFor detailed usage information, read the nbted man page. If the nbted man page\
\nwas not installed on your system, such as if you installed using `cargo install`,\
\nthen you can use `nbted --man | nroff -man | less` to read the nbted man page."
);
return Ok(0);
}
if matches.opt_present("version") {
println!(
"{} {} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
include!(concat!(env!("OUT_DIR"), "/git-revision.txt"))
);
println!("https://github.com/C4K3/nbted");
return Ok(0);
}
if matches.opt_present("man") {
print!(include_str!("../nbted.1"));
return Ok(0);
}
let is_print: bool = matches.opt_present("print");
let is_reverse: bool = matches.opt_present("reverse");
let is_edit: bool = if matches.opt_present("edit") {
true
} else {
!(is_reverse || is_print)
};
let mut action_count = 0;
if is_print {
action_count += 1;
}
if is_reverse {
action_count += 1;
}
if is_edit {
action_count += 1;
}
if action_count > 1 {
bail!("You can only specify one action at a time.");
}
let input = if let Some(x) = matches.opt_str("input") {
x
} else if let Some(x) = matches.opt_str("edit") {
x
} else if let Some(x) = matches.opt_str("print") {
x
} else if let Some(x) = matches.opt_str("reverse") {
x
} else if matches.free.len() == 1 {
matches.free[0].clone()
} else {
"-".to_string()
};
let output = if let Some(x) = matches.opt_str("output") {
x
} else if let Some(x) = matches.opt_str("edit") {
x
} else if is_edit && matches.free.len() == 1 {
matches.free[0].clone()
} else {
"-".to_string()
};
if matches.free.len() > 1 {
bail!("nbted was given multiple arguments, but only supports editing one file at a time.");
}
if is_print {
print(&input, &output)
} else if is_reverse {
reverse(&input, &output)
} else if is_edit {
edit(&input, &output)
} else {
bail!("Internal error: No action selected. (Please report this.)");
}
}
fn edit(input: &str, output: &str) -> Result<i32> {
let nbt = if input == "-" {
let f = io::stdin();
let mut f = f.lock();
read::read_file(&mut f).context("Unable to parse any NBT files from stdin")?
} else {
let path: &Path = Path::new(input);
let f = File::open(path).context(format!("Unable to open file {}", input))?;
let mut f = BufReader::new(f);
read::read_file(&mut f).context(format_err!(
"Unable to parse {}, are you sure it's an NBT file?",
input
))?
};
let tmpdir = tempfile::Builder::new()
.prefix("nbted")
.tempdir()
.context("Unable to create temporary directory")?;
let tmp = match Path::new(input).file_name() {
Some(x) => {
let mut x = x.to_os_string();
x.push(".txt");
x
}
None => bail!("Error reading file name"),
};
let tmp_path = tmpdir.path().join(tmp);
{
let mut f = File::create(&tmp_path).context("Unable to create temporary file")?;
string_write::write_file(&mut f, &nbt).context("Unable to write temporary file")?;
f.sync_all().context("Unable to synchronize file")?;
}
let new_nbt = {
let mut new_nbt = open_editor(&tmp_path);
while let Err(e) = new_nbt {
eprintln!("Unable to parse edited file");
for e in e.chain() {
eprintln!(" caused by: {}", e);
}
eprintln!("Do you want to open the file for editing again? (y/N)");
let mut line = String::new();
let _: usize = io::stdin()
.read_line(&mut line)
.context("Error reading from stdin. Nothing was changed")?;
if line.trim() == "y" {
new_nbt = open_editor(&tmp_path);
} else {
eprintln!("Exiting ... File is unchanged.");
return Ok(0);
}
}
new_nbt.expect("new_nbt was Error")
};
if nbt == new_nbt {
eprintln!("No changes, will do nothing.");
return Ok(0);
}
if output == "-" {
let f = io::stdout();
let mut f = f.lock();
match write::write_file(&mut f, &new_nbt) {
Ok(()) => (),
Err(_) => return Ok(1),
}
} else {
let path: &Path = Path::new(output);
let f = File::create(path).context(format_err!(
"Unable to write to output NBT file {}. Nothing was changed",
output
))?;
let mut f = BufWriter::new(f);
write::write_file(&mut f, &new_nbt).context(
format_err!("Error writing NBT file {}. State of NBT file is unknown, consider restoring it from a backup.",
output))?;
}
eprintln!("File edited successfully.");
Ok(0)
}
fn open_editor(tmp_path: &Path) -> Result<data::NBTFile> {
let editor = match env::var("VISUAL") {
Ok(x) => x,
Err(_) => match env::var("EDITOR") {
Ok(x) => x,
Err(_) => bail!("Unable to find $EDITOR"),
},
};
let mut cmd = Command::new(editor);
let _: &mut Command = cmd.arg(tmp_path.as_os_str());
let mut cmd = cmd.spawn().context("Error opening editor")?;
match cmd.wait().context("error executing editor")? {
x if x.success() => (),
_ => bail!("Editor did not exit correctly"),
}
let mut f = File::open(tmp_path).context(format_err!(
"Unable to read temporary file. Nothing was changed."
))?;
string_read::read_file(&mut f)
}
fn print(input: &str, output: &str) -> Result<i32> {
let nbt = if input == "-" {
let f = io::stdin();
let mut f = f.lock();
read::read_file(&mut f).context(format_err!(
"Unable to parse {}, are you sure it's an NBT file?",
input
))?
} else {
let path: &Path = Path::new(input);
let f = File::open(path).context(format_err!("Unable to open file {}", input))?;
let mut f = BufReader::new(f);
read::read_file(&mut f).context(format_err!(
"Unable to parse {}, are you sure it's an NBT file?",
input
))?
};
if output == "-" {
let f = io::stdout();
let mut f = f.lock();
match string_write::write_file(&mut f, &nbt) {
Ok(()) => (),
Err(_) => return Ok(1),
}
} else {
let path: &Path = Path::new(output);
let f = File::create(path).context(format_err!(
"Unable to write to output NBT file {}. Nothing was changed.",
output
))?;
let mut f = BufWriter::new(f);
string_write::write_file(&mut f, &nbt).context(
format_err!("Error writing NBT file {}. State of NBT file is unknown, consider restoring it from a backup.",
output))?;
}
Ok(0)
}
fn reverse(input: &str, output: &str) -> Result<i32> {
let path: &Path = Path::new(input);
let mut f = File::open(path).context(format_err!("Unable to read text file {}", input))?;
let nbt = string_read::read_file(&mut f)
.context(format_err!("Unable to parse text file {}", input))?;
if output == "-" {
let f = io::stdout();
let mut f = f.lock();
match write::write_file(&mut f, &nbt) {
Ok(()) => (),
Err(_) => return Ok(1),
}
} else {
let path: &Path = Path::new(output);
let f = File::create(path).with_context(|| {
format_err!(
"Unable to write to output NBT file {}. Nothing was changed",
output
)
})?;
let mut f = BufWriter::new(f);
write::write_file(&mut f, &nbt).context(
format_err!("error writing to NBT FILE {}, state of NBT file is unknown, consider restoring it from a backup.",
output))?;
}
Ok(0)
}