Implement OSDInspector offline tool (#85)
Some checks failed
Native code generation / deterministic (push) Successful in 18m30s
Imaging and meshing gate / native (push) Successful in 5m41s
JPEG 2000 feature / linux (push) Successful in 2m53s
Skia feature / linux (push) Has been cancelled
Native Rust workspace compile / compile (push) Has been cancelled
Some checks failed
Native code generation / deterministic (push) Successful in 18m30s
Imaging and meshing gate / native (push) Successful in 5m41s
JPEG 2000 feature / linux (push) Successful in 2m53s
Skia feature / linux (push) Has been cancelled
Native Rust workspace compile / compile (push) Has been cancelled
This commit is contained in:
529
programs/src/osd_inspector.rs
Normal file
529
programs/src/osd_inspector.rs
Normal file
@@ -0,0 +1,529 @@
|
||||
//! Bounded, offline command implementation for the `OSDInspector` tool.
|
||||
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use libremetaverse::structured_data::{OSD, OSDParser, OSDType};
|
||||
use libremetaverse::types::{Material, PCode, PathCurve, ProfileCurve, Quaternion, UUID, Vector3};
|
||||
use libremetaverse::{Primitive, PrimitiveConstructionData, PrimitiveObjectProperties};
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, BufWriter, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
|
||||
/// Successful command completion.
|
||||
pub const EXIT_SUCCESS: u8 = 0;
|
||||
/// Command-line usage errors. Clap also uses this value for invalid arguments.
|
||||
pub const EXIT_USAGE: u8 = 2;
|
||||
/// File or standard-stream I/O failed.
|
||||
pub const EXIT_IO: u8 = 3;
|
||||
/// Input was too large or was not valid OSD.
|
||||
pub const EXIT_INVALID_OSD: u8 = 4;
|
||||
/// Valid OSD could not be interpreted as a primitive.
|
||||
pub const EXIT_INVALID_PRIMITIVE: u8 = 5;
|
||||
|
||||
const DEFAULT_MAX_INPUT_BYTES: u64 = OSD::DEFAULT_MAX_BINARY_BYTES as u64;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
name = "osd-inspector",
|
||||
version,
|
||||
about = "Inspect, convert, and validate LibreMetaverse structured data offline",
|
||||
long_about = None,
|
||||
arg_required_else_help = true
|
||||
)]
|
||||
pub struct Cli {
|
||||
/// Maximum accepted input size. Parsing also enforces the library depth and node limits.
|
||||
#[arg(long, global = true, default_value_t = DEFAULT_MAX_INPUT_BYTES, value_name = "BYTES")]
|
||||
max_input_bytes: u64,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
/// Display an OSD value's type and recursively formatted structure.
|
||||
#[command(alias = "i")]
|
||||
Inspect {
|
||||
/// Input file, or '-' for standard input.
|
||||
input: PathBuf,
|
||||
},
|
||||
/// Convert OSD to JSON, XML, binary LLSD, or notation LLSD.
|
||||
#[command(alias = "c")]
|
||||
Convert {
|
||||
/// Input file, or '-' for standard input.
|
||||
input: PathBuf,
|
||||
/// Output format (json/j, xml/x, binary/bin/b, notation/llsd/n).
|
||||
format: OutputFormat,
|
||||
/// Output file, or '-' for standard output.
|
||||
output: PathBuf,
|
||||
},
|
||||
/// Validate an OSD value and report its root shape.
|
||||
#[command(alias = "v")]
|
||||
Validate {
|
||||
/// Input file, or '-' for standard input.
|
||||
input: PathBuf,
|
||||
},
|
||||
/// Emit a deterministic sample cube as pretty JSON.
|
||||
PrimToOsd,
|
||||
/// Parse an OSD value as a Primitive and summarize it.
|
||||
OsdToPrim {
|
||||
/// Input file, or '-' for standard input.
|
||||
input: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, ValueEnum)]
|
||||
enum OutputFormat {
|
||||
#[value(alias = "j")]
|
||||
Json,
|
||||
#[value(alias = "x")]
|
||||
Xml,
|
||||
#[value(alias = "bin", alias = "b")]
|
||||
Binary,
|
||||
#[value(alias = "llsd", alias = "n")]
|
||||
Notation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum InputFormat {
|
||||
Json,
|
||||
Xml,
|
||||
Binary,
|
||||
Notation,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InspectorError {
|
||||
Io { action: String, source: io::Error },
|
||||
InputTooLarge { limit: u64 },
|
||||
InvalidOsd { input: String },
|
||||
Serialization { format: OutputFormat },
|
||||
InvalidPrimitive,
|
||||
}
|
||||
|
||||
impl InspectorError {
|
||||
const fn exit_code(&self) -> u8 {
|
||||
match self {
|
||||
Self::Io { .. } => EXIT_IO,
|
||||
Self::InputTooLarge { .. } | Self::InvalidOsd { .. } => EXIT_INVALID_OSD,
|
||||
Self::Serialization { .. } | Self::InvalidPrimitive => EXIT_INVALID_PRIMITIVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for InspectorError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Io { action, source } => write!(formatter, "{action}: {source}"),
|
||||
Self::InputTooLarge { limit } => {
|
||||
write!(formatter, "input exceeds the configured {limit}-byte limit")
|
||||
}
|
||||
Self::InvalidOsd { input } => write!(formatter, "invalid OSD input: {input}"),
|
||||
Self::Serialization { format } => {
|
||||
write!(formatter, "could not serialize OSD as {format:?}")
|
||||
}
|
||||
Self::InvalidPrimitive => write!(formatter, "OSD value is not a Primitive map"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses command-line arguments with clap and runs the offline tool.
|
||||
#[must_use]
|
||||
pub fn main_entry() -> ExitCode {
|
||||
let cli = Cli::parse();
|
||||
let stdin = io::stdin();
|
||||
let stdout = io::stdout();
|
||||
let stderr = io::stderr();
|
||||
let mut input = stdin.lock();
|
||||
let mut output = stdout.lock();
|
||||
let mut errors = stderr.lock();
|
||||
match execute(cli, &mut input, &mut output) {
|
||||
Ok(()) => ExitCode::from(EXIT_SUCCESS),
|
||||
Err(error) => {
|
||||
let _ = writeln!(errors, "osd-inspector: {error}");
|
||||
ExitCode::from(error.exit_code())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(cli: Cli, stdin: &mut dyn Read, stdout: &mut dyn Write) -> Result<(), InspectorError> {
|
||||
if cli.max_input_bytes == 0 {
|
||||
return Err(InspectorError::InputTooLarge { limit: 0 });
|
||||
}
|
||||
match cli.command {
|
||||
Command::Inspect { input } => inspect(&input, cli.max_input_bytes, stdin, stdout),
|
||||
Command::Convert {
|
||||
input,
|
||||
format,
|
||||
output,
|
||||
} => convert(&input, format, &output, cli.max_input_bytes, stdin, stdout),
|
||||
Command::Validate { input } => validate(&input, cli.max_input_bytes, stdin, stdout),
|
||||
Command::PrimToOsd => primitive_to_osd(stdout),
|
||||
Command::OsdToPrim { input } => {
|
||||
osd_to_primitive(&input, cli.max_input_bytes, stdin, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect(
|
||||
input: &Path,
|
||||
limit: u64,
|
||||
stdin: &mut dyn Read,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<(), InspectorError> {
|
||||
let bytes = read_input(input, limit, stdin)?;
|
||||
let osd = parse_osd(&bytes, input)?;
|
||||
writeln!(output, "File: {}", display_path(input)).map_err(stdout_error)?;
|
||||
writeln!(output, "Type: {}", type_name(osd.type_())).map_err(stdout_error)?;
|
||||
writeln!(output, "Size: {} bytes", bytes.len()).map_err(stdout_error)?;
|
||||
writeln!(output, "\nStructure:\n{}", "-".repeat(60)).map_err(stdout_error)?;
|
||||
display_osd(&osd, 0, output)?;
|
||||
writeln!(output, "{}", "-".repeat(60)).map_err(stdout_error)
|
||||
}
|
||||
|
||||
fn convert(
|
||||
input: &Path,
|
||||
format: OutputFormat,
|
||||
destination: &Path,
|
||||
limit: u64,
|
||||
stdin: &mut dyn Read,
|
||||
stdout: &mut dyn Write,
|
||||
) -> Result<(), InspectorError> {
|
||||
let bytes = read_input(input, limit, stdin)?;
|
||||
let osd = parse_osd(&bytes, input)?;
|
||||
let encoded = serialize(osd, format)?;
|
||||
if is_standard_stream(destination) {
|
||||
stdout.write_all(&encoded).map_err(stdout_error)?;
|
||||
stdout.flush().map_err(stdout_error)?;
|
||||
} else {
|
||||
write_file(destination, &encoded)?;
|
||||
writeln!(
|
||||
stdout,
|
||||
"Converted {} to {}: {} ({} bytes)",
|
||||
display_path(input),
|
||||
format_name(format),
|
||||
destination.display(),
|
||||
encoded.len()
|
||||
)
|
||||
.map_err(stdout_error)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate(
|
||||
input: &Path,
|
||||
limit: u64,
|
||||
stdin: &mut dyn Read,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<(), InspectorError> {
|
||||
let bytes = read_input(input, limit, stdin)?;
|
||||
let osd = parse_osd(&bytes, input)?;
|
||||
writeln!(output, "Valid OSD file").map_err(stdout_error)?;
|
||||
writeln!(output, " Type: {}", type_name(osd.type_())).map_err(stdout_error)?;
|
||||
match osd {
|
||||
OSD::Map(values) => writeln!(output, " Keys: {}", values.len()).map_err(stdout_error)?,
|
||||
OSD::Array(values) => {
|
||||
writeln!(output, " Elements: {}", values.len()).map_err(stdout_error)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn primitive_to_osd(output: &mut dyn Write) -> Result<(), InspectorError> {
|
||||
let osd = sample_primitive()
|
||||
.and_then(|primitive| primitive.get_osd())
|
||||
.map_err(|_| InspectorError::InvalidPrimitive)?;
|
||||
let json = OSDParser::serialize_json_string(osd, Some(true)).map_err(|_| {
|
||||
InspectorError::Serialization {
|
||||
format: OutputFormat::Json,
|
||||
}
|
||||
})?;
|
||||
writeln!(output, "{json}").map_err(stdout_error)
|
||||
}
|
||||
|
||||
fn osd_to_primitive(
|
||||
input: &Path,
|
||||
limit: u64,
|
||||
stdin: &mut dyn Read,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<(), InspectorError> {
|
||||
let bytes = read_input(input, limit, stdin)?;
|
||||
let osd = parse_osd(&bytes, input)?;
|
||||
if !matches!(osd, OSD::Map(_)) {
|
||||
return Err(InspectorError::InvalidPrimitive);
|
||||
}
|
||||
let primitive = Primitive::from_osd(osd).map_err(|_| InspectorError::InvalidPrimitive)?;
|
||||
let name = primitive
|
||||
.properties
|
||||
.as_ref()
|
||||
.map_or("Unknown", |properties| properties.name.as_str());
|
||||
writeln!(output, "Successfully parsed Primitive:").map_err(stdout_error)?;
|
||||
writeln!(output, " ID: {}", primitive.id).map_err(stdout_error)?;
|
||||
writeln!(output, " Name: {name}").map_err(stdout_error)?;
|
||||
writeln!(output, " Type: {:?}", primitive.type_()).map_err(stdout_error)?;
|
||||
writeln!(output, " Position: {}", primitive.position.to_string()).map_err(stdout_error)?;
|
||||
writeln!(output, " Scale: {}", primitive.scale.to_string()).map_err(stdout_error)?;
|
||||
writeln!(output, " Material: {:?}", primitive.prim_data.material).map_err(stdout_error)?;
|
||||
writeln!(output, " PCode: {:?}", primitive.prim_data.p_code).map_err(stdout_error)
|
||||
}
|
||||
|
||||
fn sample_primitive() -> Result<Primitive, libremetaverse::Error> {
|
||||
let mut primitive = Primitive::new_with_constructor()?;
|
||||
primitive.id = UUID::new_with_string("11111111-2222-3333-4444-555555555555".into())?;
|
||||
primitive.local_id = 12_345;
|
||||
primitive.position = Vector3 {
|
||||
x: 128.0,
|
||||
y: 128.0,
|
||||
z: 25.0,
|
||||
};
|
||||
primitive.rotation = Quaternion::identity();
|
||||
primitive.scale = Vector3 {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0,
|
||||
};
|
||||
let mut construction = PrimitiveConstructionData::new_with_constructor()?;
|
||||
construction.p_code = PCode::Prim;
|
||||
construction.material = Material::Wood;
|
||||
construction.path_curve = PathCurve::Line;
|
||||
construction.path_scale_x = 1.0;
|
||||
construction.path_scale_y = 1.0;
|
||||
construction.set_profile_curve_with_property(ProfileCurve::Square);
|
||||
primitive.prim_data = construction;
|
||||
let mut properties = PrimitiveObjectProperties::new()?;
|
||||
properties.name = "Example Cube".into();
|
||||
properties.description = "A simple cube created with LibreMetaverse".into();
|
||||
primitive.properties = Some(properties);
|
||||
Ok(primitive)
|
||||
}
|
||||
|
||||
fn read_input(path: &Path, limit: u64, stdin: &mut dyn Read) -> Result<Vec<u8>, InspectorError> {
|
||||
if is_standard_stream(path) {
|
||||
return read_bounded(stdin, limit, "reading standard input");
|
||||
}
|
||||
let file = File::open(path).map_err(|source| InspectorError::Io {
|
||||
action: format!("opening {}", path.display()),
|
||||
source,
|
||||
})?;
|
||||
if file.metadata().map_or(0, |value| value.len()) > limit {
|
||||
return Err(InspectorError::InputTooLarge { limit });
|
||||
}
|
||||
read_bounded(
|
||||
&mut BufReader::new(file),
|
||||
limit,
|
||||
&format!("reading {}", path.display()),
|
||||
)
|
||||
}
|
||||
|
||||
fn read_bounded(input: &mut dyn Read, limit: u64, action: &str) -> Result<Vec<u8>, InspectorError> {
|
||||
let capacity = usize::try_from(limit.min(64 * 1024)).unwrap_or(64 * 1024);
|
||||
let mut bytes = Vec::with_capacity(capacity);
|
||||
input
|
||||
.take(limit.saturating_add(1))
|
||||
.read_to_end(&mut bytes)
|
||||
.map_err(|source| InspectorError::Io {
|
||||
action: action.into(),
|
||||
source,
|
||||
})?;
|
||||
if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > limit {
|
||||
return Err(InspectorError::InputTooLarge { limit });
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn write_file(path: &Path, bytes: &[u8]) -> Result<(), InspectorError> {
|
||||
let file = File::create(path).map_err(|source| InspectorError::Io {
|
||||
action: format!("creating {}", path.display()),
|
||||
source,
|
||||
})?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
writer
|
||||
.write_all(bytes)
|
||||
.and_then(|()| writer.flush())
|
||||
.map_err(|source| InspectorError::Io {
|
||||
action: format!("writing {}", path.display()),
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_osd(bytes: &[u8], path: &Path) -> Result<OSD, InspectorError> {
|
||||
if bytes.is_empty() || bytes.iter().all(u8::is_ascii_whitespace) {
|
||||
return Err(InspectorError::InvalidOsd {
|
||||
input: display_path(path),
|
||||
});
|
||||
}
|
||||
let hinted = format_hint(path, bytes);
|
||||
let mut attempts = vec![hinted];
|
||||
for format in [
|
||||
InputFormat::Json,
|
||||
InputFormat::Xml,
|
||||
InputFormat::Binary,
|
||||
InputFormat::Notation,
|
||||
] {
|
||||
if !attempts.contains(&format) {
|
||||
attempts.push(format);
|
||||
}
|
||||
}
|
||||
for format in attempts {
|
||||
if let Ok(value) = parse_as(bytes, format) {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
Err(InspectorError::InvalidOsd {
|
||||
input: display_path(path),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_as(bytes: &[u8], format: InputFormat) -> Result<OSD, libremetaverse::Error> {
|
||||
match format {
|
||||
InputFormat::Json => OSDParser::deserialize_json_with_string(
|
||||
String::from_utf8(bytes.to_vec()).map_err(|_| libremetaverse::Error::Argument)?,
|
||||
),
|
||||
InputFormat::Xml => OSDParser::deserialize_llsd_xml_with_bytes(bytes.to_vec()),
|
||||
InputFormat::Binary => OSDParser::deserialize_llsd_binary_with_bytes(bytes.to_vec()),
|
||||
InputFormat::Notation => OSDParser::deserialize_llsd_notation_with_string(
|
||||
String::from_utf8(bytes.to_vec()).map_err(|_| libremetaverse::Error::Argument)?,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_hint(path: &Path, bytes: &[u8]) -> InputFormat {
|
||||
match path
|
||||
.extension()
|
||||
.and_then(|extension| extension.to_str())
|
||||
.map(str::to_ascii_lowercase)
|
||||
.as_deref()
|
||||
{
|
||||
Some("json") => InputFormat::Json,
|
||||
Some("xml") => InputFormat::Xml,
|
||||
Some("bin" | "binary") => InputFormat::Binary,
|
||||
Some("notation" | "nt") => InputFormat::Notation,
|
||||
_ => sniff_format(bytes),
|
||||
}
|
||||
}
|
||||
|
||||
fn sniff_format(bytes: &[u8]) -> InputFormat {
|
||||
let text = String::from_utf8_lossy(&bytes[..bytes.len().min(64)]);
|
||||
let trimmed = text.trim_start_matches('\u{feff}').trim_start();
|
||||
if trimmed.starts_with("<? llsd/binary ?>") || trimmed.starts_with("<?llsd/binary?>") {
|
||||
InputFormat::Binary
|
||||
} else if trimmed.starts_with('<') {
|
||||
InputFormat::Xml
|
||||
} else if matches!(
|
||||
trimmed.as_bytes().first(),
|
||||
Some(b'{' | b'[' | b'"' | b'-' | b'0'..=b'9')
|
||||
) || trimmed.starts_with("true")
|
||||
|| trimmed.starts_with("false")
|
||||
|| trimmed.starts_with("null")
|
||||
{
|
||||
InputFormat::Json
|
||||
} else {
|
||||
InputFormat::Notation
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize(osd: OSD, format: OutputFormat) -> Result<Vec<u8>, InspectorError> {
|
||||
let result = match format {
|
||||
OutputFormat::Json => {
|
||||
OSDParser::serialize_json_string(osd, Some(true)).map(String::into_bytes)
|
||||
}
|
||||
OutputFormat::Xml => OSDParser::serialize_llsd_xml_bytes(osd),
|
||||
OutputFormat::Binary => OSDParser::serialize_llsd_binary_with_osd(osd),
|
||||
OutputFormat::Notation => OSDParser::serialize_llsd_notation(osd).map(String::into_bytes),
|
||||
};
|
||||
result.map_err(|_| InspectorError::Serialization { format })
|
||||
}
|
||||
|
||||
fn display_osd(osd: &OSD, indent: usize, output: &mut dyn Write) -> Result<(), InspectorError> {
|
||||
let prefix = " ".repeat(indent);
|
||||
match osd {
|
||||
OSD::Map(values) => {
|
||||
writeln!(output, "{prefix}Map ({} keys)", values.len()).map_err(stdout_error)?;
|
||||
let mut entries = values.iter().collect::<Vec<_>>();
|
||||
entries.sort_unstable_by_key(|(key, _)| *key);
|
||||
for (key, value) in entries {
|
||||
write!(output, "{prefix} {key}: ").map_err(stdout_error)?;
|
||||
if matches!(value, OSD::Map(_) | OSD::Array(_)) {
|
||||
writeln!(output).map_err(stdout_error)?;
|
||||
display_osd(value, indent + 2, output)?;
|
||||
} else {
|
||||
display_osd(value, 0, output)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
OSD::Array(values) => {
|
||||
writeln!(output, "{prefix}Array ({} elements)", values.len()).map_err(stdout_error)?;
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
write!(output, "{prefix} [{index}]: ").map_err(stdout_error)?;
|
||||
if matches!(value, OSD::Map(_) | OSD::Array(_)) {
|
||||
writeln!(output).map_err(stdout_error)?;
|
||||
display_osd(value, indent + 2, output)?;
|
||||
} else {
|
||||
display_osd(value, 0, output)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
OSD::String(value) => writeln!(output, "String: {value:?}").map_err(stdout_error)?,
|
||||
OSD::Integer(value) => writeln!(output, "Integer: {value}").map_err(stdout_error)?,
|
||||
OSD::Real(value) => writeln!(output, "Real: {value}").map_err(stdout_error)?,
|
||||
OSD::Boolean(value) => writeln!(output, "Boolean: {value}").map_err(stdout_error)?,
|
||||
OSD::UUID(value) => writeln!(output, "UUID: {value}").map_err(stdout_error)?,
|
||||
OSD::Date(_) => writeln!(output, "Date: {}", osd.as_string().unwrap_or_default())
|
||||
.map_err(stdout_error)?,
|
||||
OSD::Uri(value) => writeln!(output, "URI: {}", value.0).map_err(stdout_error)?,
|
||||
OSD::Binary(value) => {
|
||||
writeln!(output, "Binary: {} bytes", value.len()).map_err(stdout_error)?;
|
||||
}
|
||||
OSD::LlsdXml(value) => writeln!(output, "LLSD XML: {value:?}").map_err(stdout_error)?,
|
||||
OSD::Undefined => writeln!(output, "Unknown (Undefined)").map_err(stdout_error)?,
|
||||
_ => writeln!(output, "Unknown ({:?})", osd.type_()).map_err(stdout_error)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const fn type_name(value: OSDType) -> &'static str {
|
||||
match value {
|
||||
OSDType::Unknown => "Unknown",
|
||||
OSDType::Boolean => "Boolean",
|
||||
OSDType::Integer => "Integer",
|
||||
OSDType::Real => "Real",
|
||||
OSDType::String => "String",
|
||||
OSDType::UUID => "UUID",
|
||||
OSDType::Date => "Date",
|
||||
OSDType::URI => "URI",
|
||||
OSDType::Binary => "Binary",
|
||||
OSDType::Map => "Map",
|
||||
OSDType::Array => "Array",
|
||||
OSDType::LlsdXml => "LlsdXml",
|
||||
}
|
||||
}
|
||||
|
||||
const fn format_name(format: OutputFormat) -> &'static str {
|
||||
match format {
|
||||
OutputFormat::Json => "json",
|
||||
OutputFormat::Xml => "xml",
|
||||
OutputFormat::Binary => "binary",
|
||||
OutputFormat::Notation => "notation",
|
||||
}
|
||||
}
|
||||
|
||||
fn is_standard_stream(path: &Path) -> bool {
|
||||
path.as_os_str() == "-"
|
||||
}
|
||||
|
||||
fn display_path(path: &Path) -> String {
|
||||
if is_standard_stream(path) {
|
||||
"<stdin>".into()
|
||||
} else {
|
||||
path.display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn stdout_error(source: io::Error) -> InspectorError {
|
||||
InspectorError::Io {
|
||||
action: "writing standard output".into(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user