rust-code-analysis-cli/src/formats.rs (186 lines of code) (raw):
use std::fs::{File, create_dir_all};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use serde::Serialize;
#[derive(Debug, Clone)]
pub enum Format {
Cbor,
Json,
Toml,
Yaml,
}
impl Format {
pub const fn all() -> &'static [&'static str] {
&["cbor", "json", "toml", "yaml"]
}
pub fn dump_formats<T: Serialize>(
&self,
space: T,
path: PathBuf,
output_path: Option<&PathBuf>,
pretty: bool,
) {
if let Some(output_path) = output_path {
match self {
Self::Cbor => Cbor::with_writer(space, path, output_path),
Self::Json => Json::with_pretty_writer(space, path, output_path, pretty),
Self::Toml => Toml::with_pretty_writer(space, path, output_path, pretty),
Self::Yaml => Yaml::with_writer(space, path, output_path),
}
} else {
match self {
Self::Json => Json::write_on_stdout_pretty(space, pretty),
Self::Toml => Toml::write_on_stdout_pretty(space, pretty),
Self::Yaml => Yaml::write_on_stdout(space),
Self::Cbor => panic!("Cbor format cannot be printed to stdout"),
}
}
}
}
impl FromStr for Format {
type Err = String;
fn from_str(format: &str) -> Result<Self, Self::Err> {
match format {
"cbor" => Ok(Self::Cbor),
"json" => Ok(Self::Json),
"toml" => Ok(Self::Toml),
"yaml" => Ok(Self::Yaml),
format => Err(format!("{format:?} is not a supported format")),
}
}
}
#[inline(always)]
fn print_on_stdout(content: String) {
writeln!(std::io::stdout().lock(), "{content}").unwrap();
}
trait WriteOnStdout {
#[inline(always)]
fn write_on_stdout<T: Serialize>(content: T) {
print_on_stdout(Self::format(content));
}
fn format<T: Serialize>(content: T) -> String;
}
trait WritePrettyOnStdout: WriteOnStdout {
fn write_on_stdout_pretty<T: Serialize>(content: T, pretty: bool) {
print_on_stdout(if pretty {
Self::format_pretty(content)
} else {
Self::format(content)
});
}
fn format_pretty<T: Serialize>(content: T) -> String;
}
fn handle_path(path: PathBuf, output_path: &Path, extension: &str) -> PathBuf {
// Remove root /
let path = path.as_path().strip_prefix("/").unwrap_or(path.as_path());
// Remove root ./
let path = path.strip_prefix("./").unwrap_or(path);
// Replace .. with . to keep files inside the output folder
let cleaned_path: Vec<&str> = path
.iter()
.map(|os_str| {
let s_str = os_str.to_str().unwrap();
if s_str == ".." { "." } else { s_str }
})
.collect();
// Create the filename
let filename = cleaned_path.join("/") + extension;
// Build the file path
output_path.join(filename)
}
trait WriteFile {
const EXTENSION: &'static str;
fn open_file(path: PathBuf, output_path: &Path) -> File {
// Handle output path
let format_path = handle_path(path, output_path, Self::EXTENSION);
// Create directories
create_dir_all(format_path.parent().unwrap()).unwrap();
File::create(format_path).unwrap()
}
fn with_writer<T: Serialize>(content: T, path: PathBuf, output_path: &Path);
}
trait WritePrettyFile: WriteFile {
fn with_pretty_writer<T: Serialize>(
content: T,
path: PathBuf,
output_path: &Path,
pretty: bool,
);
}
struct Json;
impl WriteOnStdout for Json {
fn format<T: Serialize>(content: T) -> String {
serde_json::to_string(&content).unwrap()
}
}
impl WritePrettyOnStdout for Json {
fn format_pretty<T: Serialize>(content: T) -> String {
serde_json::to_string_pretty(&content).unwrap()
}
}
impl WriteFile for Json {
const EXTENSION: &'static str = ".json";
fn with_writer<T: Serialize>(content: T, path: PathBuf, output_path: &Path) {
serde_json::to_writer(Self::open_file(path, output_path), &content).unwrap()
}
}
impl WritePrettyFile for Json {
fn with_pretty_writer<T: Serialize>(
content: T,
path: PathBuf,
output_path: &Path,
pretty: bool,
) {
if pretty {
serde_json::to_writer_pretty(Self::open_file(path, output_path), &content).unwrap();
} else {
Self::with_writer(content, path, output_path);
}
}
}
struct Toml;
impl WriteOnStdout for Toml {
fn format<T: Serialize>(content: T) -> String {
toml::to_string(&content).unwrap()
}
}
impl WritePrettyOnStdout for Toml {
fn format_pretty<T: Serialize>(content: T) -> String {
toml::to_string_pretty(&content).unwrap()
}
}
impl WriteFile for Toml {
const EXTENSION: &'static str = ".toml";
fn with_writer<T: Serialize>(content: T, path: PathBuf, output_path: &Path) {
Self::open_file(path, output_path)
.write_all(Self::format(content).as_bytes())
.unwrap();
}
}
impl WritePrettyFile for Toml {
fn with_pretty_writer<T: Serialize>(
content: T,
path: PathBuf,
output_path: &Path,
pretty: bool,
) {
if pretty {
Self::open_file(path, output_path)
.write_all(Self::format_pretty(&content).as_bytes())
.unwrap();
} else {
Self::with_writer(content, path, output_path);
}
}
}
struct Yaml;
impl WriteOnStdout for Yaml {
fn format<T: Serialize>(content: T) -> String {
serde_yaml::to_string(&content).unwrap()
}
}
impl WriteFile for Yaml {
const EXTENSION: &'static str = ".yml";
fn with_writer<T: Serialize>(content: T, path: PathBuf, output_path: &Path) {
serde_yaml::to_writer(Self::open_file(path, output_path), &content).unwrap()
}
}
struct Cbor;
impl WriteFile for Cbor {
const EXTENSION: &'static str = ".cbor";
fn with_writer<T: Serialize>(content: T, path: PathBuf, output_path: &Path) {
serde_cbor::to_writer(Self::open_file(path, output_path), &content).unwrap()
}
}