cargo-insert-docs 1.6.0

Inserts feature docs into crate docs, and crate docs into README.
use std::{collections::HashMap, fmt};

use expect_test::expect;
use rustdoc_types::Id;

use crate::tests::TreeFormatter;

use super::{Tree, Value};

macro_rules! paths {
    ($(
        $id:literal: $kind:ident {
            $($path:ident)*
        }
    )*) => {
        super::Paths::from_iter(vec![
            $((
                rustdoc_types::Id($id),
                rustdoc_types::ItemSummary {
                    crate_id: 0,
                    path: vec![$(stringify!($path).to_string()),*],
                    kind: rustdoc_types::ItemKind::$kind,
                }
            ),)*
        ])
    };
}

#[test]
fn test_simple() {
    let paths = paths! {
        0: Function { std io Write write }
        1: Trait { std io Write }
        2: Module { std io }
        2: Module { std }
    };

    let tree = Tree::new_simple(&paths);
    let path = tree.path_to(Id(0)).unwrap();

    expect![[r#"
        [
            PathItem {
                name: "write",
                kind: Function,
            },
            PathItem {
                name: "Write",
                kind: Trait,
            },
            PathItem {
                name: "io",
                kind: Module,
            },
            PathItem {
                name: "std",
                kind: Module,
            },
        ]
    "#]]
    .assert_debug_eq(&path);
}

#[test]
fn test_partially_dangling() {
    let paths = paths! {
        0: Trait { std io Write }
    };

    let tree = Tree::new_simple(&paths);
    let path = tree.path_to(Id(0)).unwrap();

    expect![[r#"
        [
            PathItem {
                name: "Write",
                kind: Trait,
            },
            PathItem {
                name: "io",
                kind: Module,
            },
            PathItem {
                name: "std",
                kind: Module,
            },
        ]
    "#]]
    .assert_debug_eq(&path);
}

impl fmt::Display for Tree<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&format_tree(self))
    }
}

fn format_tree(tree: &Tree) -> String {
    let mut branches: HashMap<Id, Branch> = HashMap::new();

    for (&id, value) in &tree.inv_tree {
        branches.entry(id).or_insert(Branch { value, children: vec![] });

        if let Some(parent_id) = value.parent {
            let value = &tree.inv_tree[&parent_id];
            let branch = branches.entry(parent_id).or_insert(Branch { value, children: vec![] });
            branch.children.push(id);
        }
    }

    for branch in branches.values_mut() {
        branch.children.sort_by_key(|id| tree.inv_tree[id].name);
    }

    let mut roots = tree
        .inv_tree
        .iter()
        .filter_map(|(&i, v)| v.parent.is_none().then_some(i))
        .collect::<Vec<_>>();

    roots.sort_by_key(|id| tree.inv_tree[id].name);

    let mut out = String::new();

    for root in roots {
        out.push_str(&format_branch(&branches, root));
    }

    out
}

fn format_branch(branches: &HashMap<Id, Branch>, id: Id) -> String {
    let mut fmt = TreeFormatter::default();
    let Branch { value: Value { kind, name, .. }, children } = &branches[&id];

    let space = if name.is_empty() { "" } else { " " };
    fmt.label(format_args!("{name}{space}{kind:?}"));
    fmt.children(children.iter().map(|&id| format_branch(branches, id)));
    fmt.finish()
}

struct Branch<'a> {
    value: &'a Value<'a>,
    children: Vec<Id>,
}