-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Perform revamp of rbx_util to make it easier to add thing to (#412)
- Loading branch information
Showing
5 changed files
with
209 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Changelog | ||
|
||
## Version 0.2.0 | ||
|
||
- Refactor commands into seperate files | ||
- Add `verbosity` and `color` global flags to control logging and terminal color | ||
|
||
# Version 0.1.0 | ||
|
||
- Initial implementation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
use std::{ | ||
io::{BufReader, BufWriter}, | ||
path::PathBuf, | ||
}; | ||
|
||
use anyhow::Context; | ||
use clap::Parser; | ||
use fs_err::File; | ||
|
||
use crate::ModelKind; | ||
|
||
#[derive(Debug, Parser)] | ||
pub struct ConvertCommand { | ||
/// A path to the file to convert. | ||
input_path: PathBuf, | ||
/// A path to the desired output for the conversion. The output format is | ||
/// deteremined by the file extension of this path. | ||
output_path: PathBuf, | ||
} | ||
|
||
impl ConvertCommand { | ||
pub fn run(&self) -> anyhow::Result<()> { | ||
let input_kind = ModelKind::from_path(&self.input_path)?; | ||
let output_kind = ModelKind::from_path(&self.output_path)?; | ||
|
||
let input_file = BufReader::new(File::open(&self.input_path)?); | ||
|
||
log::debug!("Reading file into WeakDom"); | ||
let dom = match input_kind { | ||
ModelKind::Xml => { | ||
let options = rbx_xml::DecodeOptions::new() | ||
.property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown); | ||
|
||
rbx_xml::from_reader(input_file, options) | ||
.with_context(|| format!("Failed to read {}", self.input_path.display()))? | ||
} | ||
|
||
ModelKind::Binary => rbx_binary::from_reader(input_file) | ||
.with_context(|| format!("Failed to read {}", self.input_path.display()))?, | ||
}; | ||
|
||
let root_ids = dom.root().children(); | ||
|
||
let output_file = BufWriter::new(File::create(&self.output_path)?); | ||
|
||
log::debug!("Writing into new file at {}", self.output_path.display()); | ||
match output_kind { | ||
ModelKind::Xml => { | ||
let options = rbx_xml::EncodeOptions::new() | ||
.property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown); | ||
|
||
rbx_xml::to_writer(output_file, &dom, root_ids, options) | ||
.with_context(|| format!("Failed to write {}", self.output_path.display()))?; | ||
} | ||
|
||
ModelKind::Binary => { | ||
rbx_binary::to_writer(output_file, &dom, root_ids) | ||
.with_context(|| format!("Failed to write {}", self.output_path.display()))?; | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,119 +1,130 @@ | ||
use std::io::{self, BufReader, BufWriter}; | ||
use std::path::{Path, PathBuf}; | ||
mod convert; | ||
mod view_binary; | ||
|
||
use std::process; | ||
use std::{path::Path, str::FromStr}; | ||
|
||
use clap::Parser; | ||
use convert::ConvertCommand; | ||
|
||
use anyhow::{anyhow, bail, Context}; | ||
use fs_err::File; | ||
use structopt::StructOpt; | ||
use view_binary::ViewBinaryCommand; | ||
|
||
#[derive(Debug, StructOpt)] | ||
#[derive(Debug, Parser)] | ||
#[clap(name = "rbx_util", about)] | ||
struct Options { | ||
#[structopt(subcommand)] | ||
#[clap(flatten)] | ||
global: GlobalOptions, | ||
#[clap(subcommand)] | ||
subcommand: Subcommand, | ||
} | ||
|
||
#[derive(Debug, StructOpt)] | ||
impl Options { | ||
fn run(self) -> anyhow::Result<()> { | ||
match self.subcommand { | ||
Subcommand::ViewBinary(command) => command.run(), | ||
Subcommand::Convert(command) => command.run(), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Parser)] | ||
enum Subcommand { | ||
/// Convert a model or place file in one format to another. | ||
Convert { input: PathBuf, output: PathBuf }, | ||
/// Displays a binary file in a text format. | ||
ViewBinary(ViewBinaryCommand), | ||
/// Convert between the XML and binary formats for places and models. | ||
Convert(ConvertCommand), | ||
} | ||
|
||
/// View a binary file as an undefined text representation. | ||
ViewBinary { input: PathBuf }, | ||
#[derive(Debug, Parser, Clone, Copy)] | ||
struct GlobalOptions { | ||
/// Sets verbosity level. Can be specified multiple times. | ||
#[clap(long, short, global(true), action = clap::ArgAction::Count)] | ||
verbosity: u8, | ||
/// Set color behavior. Valid values are auto, always, and never. | ||
#[clap(long, global(true), default_value = "auto")] | ||
color: ColorChoice, | ||
} | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
enum ModelKind { | ||
pub enum ModelKind { | ||
Binary, | ||
Xml, | ||
} | ||
|
||
impl ModelKind { | ||
fn from_path(path: &Path) -> anyhow::Result<ModelKind> { | ||
pub fn from_path(path: &Path) -> anyhow::Result<ModelKind> { | ||
log::trace!("Resolving type of file for path {}", path.display()); | ||
match path.extension().and_then(|ext| ext.to_str()) { | ||
Some("rbxm") | Some("rbxl") => Ok(ModelKind::Binary), | ||
Some("rbxmx") | Some("rbxlx") => Ok(ModelKind::Xml), | ||
|
||
_ => Err(anyhow!( | ||
"not a Roblox model or place file: {}", | ||
path.display() | ||
)), | ||
_ => anyhow::bail!("not a Roblox model or place file: {}", path.display()), | ||
} | ||
} | ||
} | ||
|
||
fn run(options: Options) -> anyhow::Result<()> { | ||
match options.subcommand { | ||
Subcommand::Convert { input, output } => convert(&input, &output)?, | ||
Subcommand::ViewBinary { input } => view_binary(&input)?, | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn convert(input_path: &Path, output_path: &Path) -> anyhow::Result<()> { | ||
let input_kind = ModelKind::from_path(input_path)?; | ||
let output_kind = ModelKind::from_path(output_path)?; | ||
|
||
let input_file = BufReader::new(File::open(input_path)?); | ||
|
||
let dom = match input_kind { | ||
ModelKind::Xml => { | ||
let options = rbx_xml::DecodeOptions::new() | ||
.property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown); | ||
|
||
rbx_xml::from_reader(input_file, options) | ||
.with_context(|| format!("Failed to read {}", input_path.display()))? | ||
} | ||
fn main() { | ||
let options = Options::parse(); | ||
|
||
ModelKind::Binary => rbx_binary::from_reader(input_file) | ||
.with_context(|| format!("Failed to read {}", input_path.display()))?, | ||
let log_filter = match options.global.verbosity { | ||
0 => "info", | ||
1 => "info,rbx_binary=debug,rbx_xml=debug,rbx_util=debug", | ||
2 => "debug,rbx_binary=trace,rbx_xml=trace,rbx_util=trace", | ||
_ => "trace", | ||
}; | ||
|
||
let root_ids = dom.root().children(); | ||
let log_env = env_logger::Env::default().default_filter_or(log_filter); | ||
env_logger::Builder::from_env(log_env) | ||
.format_module_path(false) | ||
.format_timestamp(None) | ||
.format_indent(Some(8)) | ||
.write_style(options.global.color.into()) | ||
.init(); | ||
|
||
let output_file = BufWriter::new(File::create(output_path)?); | ||
|
||
match output_kind { | ||
ModelKind::Xml => { | ||
let options = rbx_xml::EncodeOptions::new() | ||
.property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown); | ||
if let Err(err) = options.run() { | ||
eprintln!("{:?}", err); | ||
process::exit(1); | ||
} | ||
} | ||
|
||
rbx_xml::to_writer(output_file, &dom, root_ids, options) | ||
.with_context(|| format!("Failed to write {}", output_path.display()))?; | ||
} | ||
#[derive(Debug, Clone, Copy)] | ||
pub enum ColorChoice { | ||
Auto, | ||
Always, | ||
Never, | ||
} | ||
|
||
ModelKind::Binary => { | ||
rbx_binary::to_writer(output_file, &dom, root_ids) | ||
.with_context(|| format!("Failed to write {}", output_path.display()))?; | ||
impl FromStr for ColorChoice { | ||
type Err = anyhow::Error; | ||
|
||
fn from_str(source: &str) -> Result<Self, Self::Err> { | ||
match source { | ||
"auto" => Ok(ColorChoice::Auto), | ||
"always" => Ok(ColorChoice::Always), | ||
"never" => Ok(ColorChoice::Never), | ||
_ => anyhow::bail!( | ||
"Invalid color choice '{source}'. Valid values are: auto, always, never" | ||
), | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn view_binary(input_path: &Path) -> anyhow::Result<()> { | ||
let input_kind = ModelKind::from_path(input_path)?; | ||
|
||
if input_kind != ModelKind::Binary { | ||
bail!("not a binary model or place file: {}", input_path.display()); | ||
impl From<ColorChoice> for clap::ColorChoice { | ||
fn from(value: ColorChoice) -> Self { | ||
match value { | ||
ColorChoice::Auto => clap::ColorChoice::Auto, | ||
ColorChoice::Always => clap::ColorChoice::Always, | ||
ColorChoice::Never => clap::ColorChoice::Never, | ||
} | ||
} | ||
|
||
let input_file = BufReader::new(File::open(input_path)?); | ||
|
||
let model = rbx_binary::text_format::DecodedModel::from_reader(input_file); | ||
|
||
let stdout = io::stdout(); | ||
let output = BufWriter::new(stdout.lock()); | ||
serde_yaml::to_writer(output, &model)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn main() { | ||
let options = Options::from_args(); | ||
|
||
if let Err(err) = run(options) { | ||
eprintln!("{:?}", err); | ||
process::exit(1); | ||
impl From<ColorChoice> for env_logger::WriteStyle { | ||
fn from(value: ColorChoice) -> Self { | ||
match value { | ||
ColorChoice::Auto => env_logger::WriteStyle::Auto, | ||
ColorChoice::Always => env_logger::WriteStyle::Always, | ||
ColorChoice::Never => env_logger::WriteStyle::Never, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
use std::{ | ||
io::{self, BufReader, BufWriter}, | ||
path::PathBuf, | ||
}; | ||
|
||
use clap::Parser; | ||
use fs_err::File; | ||
|
||
use crate::ModelKind; | ||
|
||
#[derive(Debug, Parser)] | ||
pub struct ViewBinaryCommand { | ||
/// The file to emit the contents of. | ||
input: PathBuf, | ||
} | ||
|
||
impl ViewBinaryCommand { | ||
pub fn run(&self) -> anyhow::Result<()> { | ||
let input_kind = ModelKind::from_path(&self.input)?; | ||
|
||
if input_kind != ModelKind::Binary { | ||
anyhow::bail!("not a binary model or place file: {}", self.input.display()); | ||
} | ||
|
||
let input_file = BufReader::new(File::open(&self.input)?); | ||
|
||
log::debug!("Decoding file into text format"); | ||
let model = rbx_binary::text_format::DecodedModel::from_reader(input_file); | ||
|
||
log::debug!("Writing to stdout"); | ||
let stdout = io::stdout(); | ||
let output = BufWriter::new(stdout.lock()); | ||
serde_yaml::to_writer(output, &model)?; | ||
|
||
Ok(()) | ||
} | ||
} |