fn display_items_table()

in src/data.rs [1135:1205]


fn display_items_table(
    items: Vec<HashMap<String, AttributeValue>>,
    ts: &app::TableSchema,
    selected_attributes: &Option<String>,
    keys_only: bool,
) {
    // Print no item message and return if items length is 0.
    if items.is_empty() {
        println!("No item to show in the table '{}'", ts.name.to_string());
        return;
    };

    // build header - first, primary key(s). Even index, key(s) are always projected.
    // ref: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Projections
    let mut header: Vec<&str> = vec![ts.pk.name.as_str()];
    if let Some(sk) = &ts.sk {
        header.push(sk.name.as_str())
    };

    // build header - next, attribute names or aggregated "attributes" header, unless --keys-only flag is set.
    if !keys_only {
        if let Some(attrs) = selected_attributes {
            header.extend(attrs.split(',').collect::<Vec<&str>>());
        } else {
            header.push("attributes")
        };
    };
    debug!("built header elements: {:?}", header);

    let mut tw = TabWriter::new(io::stdout());
    tw.write_all((header.join("\t") + "\n").as_bytes()).unwrap();

    // `cells` is sth like: ["item1-pk\titem1-attr1\titem1-attr2", "item2-pk\titem2-attr1\titem2-attr2"]
    let mut cells: Vec<String> = vec![]; // may be able to use with_capacity to initialize the vec.
    for mut item in items {
        let mut item_attributes = vec![];
        // First, take primary key(s) of each item.
        let x: Option<AttributeValue> = item.remove(&ts.pk.name);
        if let Some(sk) = &ts.sk {
            let y: Option<AttributeValue> = item.remove(&sk.name);
            item_attributes.extend(vec![attrval_to_cell_print(x), attrval_to_cell_print(y)]);
        } else {
            item_attributes.extend(vec![attrval_to_cell_print(x)]);
        };

        if !item.is_empty() {
            if let Some(_attributes) = selected_attributes {
                let attrs: Vec<&str> = _attributes.split(',').map(|x| x.trim()).collect();
                for attr in attrs {
                    let attrval: Option<AttributeValue> = item.get(attr).cloned();
                    item_attributes.push(attrval_to_cell_print(attrval));
                }
            } else if !keys_only {
                // print rest aggreated "attributes" column in JSON format.
                let full = serde_json::to_string(&convert_to_json(&item)).unwrap();
                let threshold: usize = 50;
                if full.chars().count() > threshold {
                    // NOTE: counting bytes slice doesn't work for multi-bytes strings
                    let st: &String = &full.chars().take(threshold).collect();
                    item_attributes.push(String::from(st) + "...");
                } else {
                    item_attributes.push(full);
                }
            }
        }
        cells.push(item_attributes.join("\t"));
    }

    tw.write_all((cells.join("\n") + "\n").as_bytes()).unwrap();
    tw.flush().unwrap();
}