gistcafe/
inspect.rs

1use serde_json::*;
2use std::*;
3use std::fs::File;
4use std::io::prelude::*;
5use std::path::Path;
6use std::collections::BTreeMap;
7
8pub fn vars<T>(args : &T)
9where
10    T: ?Sized + serde::ser::Serialize,
11{
12    if let Ok(inspect_vars_path) = env::var("INSPECT_VARS") {
13        if let Ok(json) = to_string(&args) {
14            let vars_path = inspect_vars_path.replace("\\","/");
15            if vars_path.contains("/") {
16                let dir = Path::new(&vars_path).parent().unwrap().to_str().unwrap();
17                let _ = fs::create_dir_all(dir);
18            }
19
20            let _ = match File::create(&vars_path) {
21                Ok(mut file) => file.write_all(json.as_bytes()),
22                Err(err) => Err(err),
23            };
24        }
25    }
26}
27
28pub fn dump<T>(args : &T) -> Option<String>
29where
30    T: ?Sized + serde::ser::Serialize,
31{
32    match to_string_pretty(&args) {
33        Ok(json) => {
34            Some(json.replace("\"",""))
35        },
36        _ => None
37    }
38}
39
40pub fn print_dump<T>(args : &T)
41where
42    T: ?Sized + serde::ser::Serialize,
43{
44    match dump(args) {
45        Some(s) => println!("{}", s),
46        _ => (),
47    }
48}
49
50pub fn to_list_map<T: Sized>(rows : &Vec<T>) -> Vec<Map<String,Value>>
51where
52    T: ?Sized + serde::ser::Serialize,
53{
54    let mut to: Vec<Map<String,Value>> = Vec::new();
55    for row in rows {
56        let map = from_str(&to_string(row).unwrap());
57        to.push(map.unwrap());
58    }
59    return to;
60}
61
62pub fn all_keys(rows : &Vec<Map<String,Value>>) -> Vec<String>
63{
64    let mut to: Vec<String> = Vec::new();
65    for row in rows {
66        for key in row.keys() {
67            if !to.contains(key) {
68                to.push(key.to_string());
69            }
70        }
71    }
72    return to;
73}
74
75pub fn dump_table<T: Sized>(rows : &Vec<T>) -> Option<String>
76where
77    T: ?Sized + serde::ser::Serialize,
78{
79    return dump_table_columns(rows, Vec::new());
80}
81
82pub fn dump_table_columns<T: Sized>(rows: &Vec<T>, columns: Vec<&str>) -> Option<String>
83where
84    T: ?Sized + serde::ser::Serialize,
85{
86    if rows.is_empty() {
87        return None;
88    }
89    let map_rows = to_list_map(rows);
90    let keys = if columns.is_empty() {
91        all_keys(&map_rows) 
92    } else { 
93        columns.iter().map(|x| x.to_string()).collect()
94    };
95    let mut col_sizes: BTreeMap<String,usize> = BTreeMap::new();
96
97    for k in &keys {
98        let mut max = k.len();
99        for row in &map_rows {
100            if let Some(col) = &row.get(k) {
101                max = cmp::max(format!("{}", col).len(), max);
102            }
103        }
104        col_sizes.insert(k.to_string(), max);
105    }
106
107    let col_sizes_length = col_sizes.len();
108    let row_width: usize = col_sizes.values().sum::<usize>()
109        + (col_sizes_length * 2)
110        + (col_sizes_length + 1);
111
112    let dashes = "-".repeat(row_width - 2);
113    let mut sb: Vec<String> = Vec::new();
114    sb.push(format!("+{}+", dashes));
115    let mut head = "|".to_string();
116    for k in &keys {
117        head = format!("{}{}|", &head, &align_center(k, col_sizes[k], " "));
118    }
119    sb.push(head.to_string());
120    sb.push(format!("|{}|", dashes));
121
122    for row in &map_rows {
123        let mut to = "|".to_string();
124        for k in &keys {
125            to = format!("{}{}!", &to, &align_auto(&row[k], col_sizes[k], " "));
126        }
127        sb.push(to);
128    }
129    sb.push(format!("+{}+", dashes));
130
131    return Some(sb.join("\n"));
132}
133
134pub fn print_dump_table<T: Sized>(rows : &Vec<T>)
135where
136    T: ?Sized + serde::ser::Serialize,
137{
138    match dump_table(rows) {
139        Some(s) => println!("{}", s),
140        _ => (),
141    }
142}
143
144pub fn print_dump_table_columns<T: Sized>(rows : &Vec<T>, columns: Vec<&str>)
145where
146    T: ?Sized + serde::ser::Serialize,
147{
148    match dump_table_columns(rows, columns) {
149        Some(s) => println!("{}", s),
150        _ => (),
151    }
152}
153
154pub fn align_left(str: &str, len: usize, pad: &str) -> String {
155    if len > 0 { 
156        let a_len = len + 1 - str.len();
157        if a_len > 0 {
158            return format!("{}{}{}", pad, str, pad.repeat(a_len));
159        }
160    }
161    return "".into();
162}
163
164pub fn align_center(str: &str, len: usize, pad: &str) -> String {
165    if len > 0 {
166        let n_len = str.len();
167        let half = (len as f64 / 2.0 - n_len as f64 / 2.0).floor() as usize;
168        let odds = ((n_len % 2) as i64 - (len % 2) as i64).abs() as usize;
169        return format!("{}{}{}", pad.repeat(half + 1), str, pad.repeat(half + 1 + odds));
170    }
171    return "".into();
172}
173
174pub fn align_right(str: &str, len: usize, pad: &str) -> String {
175    if len > 0 { 
176        let a_len = len + 1 - str.len();
177        if a_len > 0 {
178            return format!("{}{}{}", pad.repeat(a_len), str, pad);
179        }
180    }
181    return "".into();
182}
183
184pub fn align_auto(obj: &Value, len: usize, pad: &str) -> String {
185    let str = match obj {
186        Value::String(s) => format!("{}", s),
187        _ => format!("{}", obj),
188    };
189    if str.len() <= len {
190        return match obj {
191            Value::Number(_) => align_right(&str, len, &pad),
192            _ => align_left(&str, len, &pad),
193        }
194    }
195    return str;
196}
197
198
199#[cfg(test)]
200mod test {
201    use super::*;
202
203    #[test]
204    fn it_works() {
205        assert_eq!(2 + 2, 4);
206    }
207
208    fn args() -> Value {
209        return json!([
210            { "a": 1, "b":"foo" },
211            { "a": 2.1, "b":"barbar" },
212            { "a": 3.21, "b":"bazbazbaz" }
213        ]);
214    }
215
216    #[test]
217    fn inspect_vars() {
218        vars(&args());
219    }
220
221    #[test]
222    fn inspect_print_dump() {
223        print_dump(&args());
224    }
225
226    #[test]
227    fn inspect_print_dump_table() {
228        print_dump_table(&args().as_array().unwrap());
229    }
230
231    #[tokio::test]
232    async fn inspect_print_dump_table_json_api() {
233
234        let org_name = "rust-lang";
235
236        let res = reqwest::Client::new()
237            .get(&format!("https://api.github.com/orgs/{}/repos", org_name))
238            .header(reqwest::header::USER_AGENT, "gist.cafe")
239            .send()
240            .await.unwrap();
241
242        let json: Vec<Map<String,Value>> = res.json().await.unwrap();
243        let mut org_repos: Vec<Map<String,Value>> = Vec::new();
244        for x in json.iter() {
245            org_repos.push(json!({
246                "name":        x["name"],
247                "description": x["description"],
248                "lang":        x["language"],
249                "watchers":    x["watchers"],
250                "forks":       x["forks"],
251            }).as_object().unwrap().clone());
252        }
253        org_repos.sort_by(|a, b| b["watchers"].as_i64().cmp(&a["watchers"].as_i64()));
254        
255        println!("Top 3 {} GitHub Repos:", org_name);
256        print_dump(&org_repos[1..=3]);
257
258        println!("\nTop 10 {} GitHub Repos:", org_name);
259        print_dump_table(&org_repos[1..=10].iter().map(|x| json!({
260            "name":        x["name"],
261            "lang":        x["lang"],
262            "watchers":    x["watchers"],
263            "forks":       x["forks"],
264        }).as_object().unwrap().clone()).collect());
265
266        println!("\nTop 10 {} GitHub Repos:", org_name);
267        print_dump_table_columns(&org_repos[1..=10].to_vec(), 
268            vec!["name", "lang", "watchers", "forks"]);
269    }    
270}