physical-quantity 0.0.5

dimension and unit system for general physical physical quantities.
use std:: {
    cmp::Ordering,
    error::Error,
    env,
    fs::File,
    io::{Write, Read},
};
use physical_quantity::{ predefined::unit::DEFAULT_UNIT_DEF, Dimension, DynDim };
use syn:: { self, Item, ItemConst, Expr, ExprTuple, Type, Lit };
use proc_macro2::TokenTree;
use float_pretty_print::PrettyPrintFloat;

fn read_default_def(s: &str) -> Result<Vec<(String, String)>, Box<dyn Error>> {
    let mut f_in = File::open(s)?;
    let mut content = String::new();

    f_in.read_to_string(&mut content)?;

    let ast = syn::parse_file(&content)?;

//    let mut f_out = File::create("../target/def.txt")?;
    let mut result = Vec::new();
    // writeln!(&mut f_out, "{ast:?}")?;
    // Ok(Vec::new())

    for item in ast.items.iter() {
        match item {
            Item::Const(ItemConst {
                ty, expr, ..
            }) => match (ty.as_ref(), expr.as_ref()) {
                (&Type::Array(_), &Expr::Array(ref expr)) => {
                    for e in expr.elems.iter() {
                        match e {
                            Expr::Tuple(expr) => match extract_comment(expr) {
                                    Some(pair) => result.push(pair),
                                    _ => (),
                            },
                            _ => (),
                        }
                    }
                },
                _ => (),
            },
            _ => (),
        }
    }
    result.sort_by(|left, right| left.0.cmp(&right.0));
    Ok(result)
}

fn extract_comment(expr: &ExprTuple) -> Option<(String, String)> {
    let key  = match expr.elems.first()? {
        Expr::Lit(expr) => match &expr.lit {
            &Lit::Str(ref name) => name.value(),
            _ => return None,
        },
        _ => return None,
    };
    let remarks = expr.attrs.iter().fold(
        String::new(),
        |mut remarks, attr| match attr.path.segments.first() {
        Some(path) => if path.ident.to_string() == "doc" {
            for tt in attr.tokens.clone() {
                match tt {
                    TokenTree::Literal(lit) => {
                        let line = lit.to_string();
                        let line = if line.starts_with('"') {
                            &line[1..]
                        } else {
                            &line
                        };
                        let line = if line.ends_with('"') {
                            &line[..line.len() - 1]
                        } else {
                            &line
                        };
                        remarks += line;
                    },
                    _ => continue,
                }
            }
            remarks
        } else {
            remarks
        },
        _ => remarks,
    });
    (key, remarks).into()
}

const BAR: &str = "\u{2014}";

fn float2string(float: f64) -> String {
    if float == 0.0 {
        BAR.to_string()
    } else {
        format!("{}", PrettyPrintFloat(float))
    }
}

fn dump_default_def(s: &str, remarks: Vec<(String, String)>) -> Result<(), Box<dyn Error>> {
    let mut f_out = File::create(s)?;
    let mut order = Vec::new();
    for (key, (cnv, dim)) in DEFAULT_UNIT_DEF.iter() {
        order.push((*key, cnv.0.to_f64(), cnv.1.to_f64(), dim.dim_code()));
    }
    order.sort_by(|left, right| match left.3.cmp(&right.3) {
        Ordering::Equal => match left.1.partial_cmp(&right.1) {
            Some(Ordering::Equal) => match left.2.partial_cmp(&right.2) {
                Some(Ordering::Equal) => left.0.cmp(&right.0),
                Some(o) => o,
                None => Ordering::Equal,
            },
            Some(o) => o,
            None => Ordering::Equal,
        },
        o => o,
    });

    writeln!(&mut f_out,
        "| name | a | b | dim | remarks |\n\
         |------|---|---|-----|---------|"
    )?;
    for (key, a, b, code) in order {
        let a = float2string(a);
        let b = float2string(b);
        let dim = DynDim::new(code);
        let remark = match remarks.binary_search_by(|(name, _)| name.as_str().cmp(key)) {
            Ok(idx) => &remarks[idx].1,
            Err(_) => BAR,
        };

        writeln!(&mut f_out, "| {key} | {a} | {b} | {dim} | {remark} |")?;
    }
    Ok(())
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut args = env::args();

    println!("starts: {}", args.next().unwrap());

    let remarks = read_default_def(&args.next().unwrap())?;

    dump_default_def(&args.next().unwrap(), remarks)?;
    Ok(())
}