use fitparser::de::{from_reader_with_options, DecodeOption};
use serde::Serialize;
use std::collections::{BTreeMap, HashSet};
use std::error::Error;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "fit_to_json")]
struct Cli {
#[structopt(name = "FILE", parse(from_os_str))]
files: Vec<PathBuf>,
#[structopt(short, long, parse(from_os_str))]
output: Option<PathBuf>,
#[structopt(long)]
drop_unknown: bool,
#[structopt(long)]
numeric_enums: bool,
#[structopt(long)]
keep_generic_names: bool,
#[structopt(long)]
keep_composite_fields: bool,
#[structopt(long)]
no_crc_check: bool,
}
#[derive(Clone, Debug, Serialize)]
struct FitDataMap {
kind: fitparser::profile::MesgNum,
fields: BTreeMap<String, fitparser::ValueWithUnits>,
}
impl FitDataMap {
fn new(record: fitparser::FitDataRecord) -> Self {
FitDataMap {
kind: record.kind(),
fields: record
.into_vec()
.into_iter()
.map(|f| (f.name().to_owned(), fitparser::ValueWithUnits::from(f)))
.collect(),
}
}
}
#[derive(Clone, Debug)]
enum OutputLocation {
Inplace,
LocalDirectory(PathBuf),
LocalFile(PathBuf),
Stdout,
}
impl OutputLocation {
fn new(location: PathBuf) -> Self {
if location.is_dir() {
OutputLocation::LocalDirectory(location)
} else if location.as_os_str() == "-" {
OutputLocation::Stdout
} else {
OutputLocation::LocalFile(location)
}
}
fn write_json_file(
&self,
filename: &Path,
data: Vec<fitparser::FitDataRecord>,
) -> Result<(), Box<dyn Error>> {
let data: Vec<FitDataMap> = data.into_iter().map(FitDataMap::new).collect();
let json = serde_json::to_string(&data)?;
let outname = match self {
Self::Inplace => filename.with_extension("json"),
Self::LocalDirectory(dest) => dest
.clone()
.join(filename.file_name().unwrap())
.with_extension("json"),
Self::LocalFile(dest) => dest.clone(),
Self::Stdout => {
println!("{}", json);
return Ok(());
}
};
let mut fp = File::create(outname)?;
match fp.write_all(json.as_bytes()) {
Ok(_) => Ok(()),
Err(e) => Err(Box::new(e)),
}
}
}
fn run() -> Result<(), Box<dyn Error>> {
let opt = Cli::from_args();
let mut decode_opts = HashSet::new();
if opt.drop_unknown {
decode_opts.insert(DecodeOption::DropUnknownFields);
decode_opts.insert(DecodeOption::DropUnknownMessages);
}
if opt.keep_generic_names {
decode_opts.insert(DecodeOption::UseGenericSubFieldName);
}
if opt.keep_composite_fields {
decode_opts.insert(DecodeOption::KeepCompositeFields);
}
if opt.numeric_enums {
decode_opts.insert(DecodeOption::ReturnNumericEnumValues);
}
if opt.no_crc_check {
decode_opts.insert(DecodeOption::SkipHeaderCrcValidation);
decode_opts.insert(DecodeOption::SkipDataCrcValidation);
}
let output_loc = opt
.output
.map_or(OutputLocation::Inplace, OutputLocation::new);
let collect_all = matches!(output_loc, OutputLocation::LocalFile(_));
if opt.files.is_empty() {
let mut stdin = io::stdin();
let data = from_reader_with_options(&mut stdin, &decode_opts)?;
output_loc.write_json_file(&PathBuf::from("<stdin>"), data)?;
return Ok(());
}
let mut all_fit_data: Vec<fitparser::FitDataRecord> = Vec::new();
for file in opt.files {
let mut fp = File::open(&file)?;
let mut data = from_reader_with_options(&mut fp, &decode_opts)?;
if collect_all {
all_fit_data.append(&mut data);
} else {
output_loc.write_json_file(&file, data)?;
}
}
if collect_all {
output_loc.write_json_file(&PathBuf::new(), all_fit_data)?;
}
Ok(())
}
fn main() {
std::process::exit(match run() {
Ok(_) => 0,
Err(err) => {
eprintln!("{}", err);
1
}
});
}