Skip to content

Commit

Permalink
support generic csv to json and yaml output
Browse files Browse the repository at this point in the history
  • Loading branch information
tyrchen committed Mar 24, 2024
1 parent 71196b3 commit 76f463f
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/target
output.json
output.*
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ clap = { version = "4.5.3", features = ["derive"] }
csv = "1.3.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
serde_yaml = "0.9.33"
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ use rcli::{process_csv, Opts, SubCommand};
fn main() -> anyhow::Result<()> {
let opts = Opts::parse();
match opts.cmd {
SubCommand::Csv(opts) => process_csv(&opts.input, &opts.output)?,
SubCommand::Csv(opts) => {
let output = if let Some(output) = opts.output {
output.clone()
} else {
format!("output.{}", opts.format)
};
process_csv(&opts.input, output, opts.format)?;
}
}

Ok(())
Expand Down
46 changes: 43 additions & 3 deletions src/opts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Parser;
use std::path::Path;
use std::{fmt, path::Path, str::FromStr};

#[derive(Debug, Parser)]
#[command(name = "rcli", version, author, about, long_about = None)]
Expand All @@ -14,13 +14,22 @@ pub enum SubCommand {
Csv(CsvOpts),
}

#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
Json,
Yaml,
}

#[derive(Debug, Parser)]
pub struct CsvOpts {
#[arg(short, long, value_parser = verify_input_file)]
pub input: String,

#[arg(short, long, default_value = "output.json")] // "output.json".into()
pub output: String,
#[arg(short, long)] // "output.json".into()
pub output: Option<String>,

#[arg(long, value_parser = parse_format, default_value = "json")]
pub format: OutputFormat,

#[arg(short, long, default_value_t = ',')]
pub delimiter: char,
Expand All @@ -36,3 +45,34 @@ fn verify_input_file(filename: &str) -> Result<String, &'static str> {
Err("File does not exist")
}
}

fn parse_format(format: &str) -> Result<OutputFormat, anyhow::Error> {
format.parse()
}

impl From<OutputFormat> for &'static str {
fn from(format: OutputFormat) -> Self {
match format {
OutputFormat::Json => "json",
OutputFormat::Yaml => "yaml",
}
}
}

impl FromStr for OutputFormat {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"json" => Ok(OutputFormat::Json),
"yaml" => Ok(OutputFormat::Yaml),
_ => Err(anyhow::anyhow!("Invalid format")),
}
}
}

impl fmt::Display for OutputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", Into::<&str>::into(*self))
}
}
27 changes: 21 additions & 6 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use csv::Reader;
use serde::{Deserialize, Serialize};
use std::fs;

use crate::opts::OutputFormat;

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
struct Player {
Expand All @@ -15,15 +17,28 @@ struct Player {
kit: u8,
}

pub fn process_csv(input: &str, output: &str) -> Result<()> {
pub fn process_csv(input: &str, output: String, format: OutputFormat) -> Result<()> {
let mut reader = Reader::from_path(input)?;
let mut ret = Vec::with_capacity(128);
for result in reader.deserialize() {
let record: Player = result?;
ret.push(record);
let headers = reader.headers()?.clone();
for result in reader.records() {
let record = result?;
// headers.iter() -> 使用 headers 的迭代器
// record.iter() -> 使用 record 的迭代器
// zip() -> 将两个迭代器合并为一个元组的迭代器 [(header, record), ..]
// collect::<Value>() -> 将元组的迭代器转换为 JSON Value
let json_value = headers
.iter()
.zip(record.iter())
.collect::<serde_json::Value>();

ret.push(json_value);
}

let json = serde_json::to_string_pretty(&ret)?;
fs::write(output, json)?;
let content = match format {
OutputFormat::Json => serde_json::to_string_pretty(&ret)?,
OutputFormat::Yaml => serde_yaml::to_string(&ret)?,
};
fs::write(output, content)?;
Ok(())
}

0 comments on commit 76f463f

Please sign in to comment.