cargo-equip 0.5.3

A Cargo subcommand to bundle your code into one `.rs` file for competitive programming.
Documentation
use std::collections::HashSet;
use std::{
    collections::{BTreeMap, BTreeSet, VecDeque},
    fmt,
    str::FromStr,
};

use cargo_metadata as cm;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{de::Error as _, Deserialize, Deserializer};

type PackageModulePath<'cm> = (&'cm cm::PackageId, String);

pub(crate) fn assign_packages<'cm>(
    module_deps: &BTreeMap<ModulePath, BTreeSet<ModulePath>>,
    package_id: &'cm cm::PackageId,
    mut extern_crate_from_name: impl FnMut(&str) -> anyhow::Result<&'cm cm::PackageId>,
) -> anyhow::Result<BTreeMap<PackageModulePath<'cm>, BTreeSet<PackageModulePath<'cm>>>> {
    let mut translate = |node: &_| -> anyhow::Result<_> {
        Ok(match node {
            ModulePath::Crate(CrateModulePath { module_name }) => (package_id, module_name.clone()),
            ModulePath::ExternCrate(ExternCrateModulePath {
                extern_crate_name,
                module_name,
            }) => (
                extern_crate_from_name(extern_crate_name)?,
                module_name.clone(),
            ),
        })
    };

    let mut ret = BTreeMap::<_, BTreeSet<_>>::new();

    for (from, to) in module_deps {
        let ret = ret.entry(translate(from)?).or_default();
        for to in to {
            ret.insert(translate(to)?);
        }
    }

    Ok(ret)
}

pub(crate) fn connect<'cm>(
    graph: &BTreeMap<PackageModulePath<'cm>, BTreeSet<PackageModulePath<'cm>>>,
    start: &BTreeMap<&'cm cm::PackageId, HashSet<String>>,
) -> Option<BTreeSet<PackageModulePath<'cm>>> {
    let mut ret = start
        .iter()
        .flat_map(|(k, v)| v.iter().map(move |v| (*k, v.clone())))
        .collect::<BTreeSet<_>>();
    let mut queue = ret.iter().cloned().collect::<VecDeque<_>>();
    loop {
        if let Some(from) = queue.pop_front() {
            if let Some(to) = graph.get(&from) {
                for to in to {
                    if ret.insert(to.clone()) {
                        queue.push_back(to.clone());
                    }
                }
            } else {
                break None;
            }
        } else {
            break Some(ret);
        }
    }
}

#[derive(Debug, Clone, Ord, Eq, PartialOrd, PartialEq, Hash)]
pub(crate) enum ModulePath {
    Crate(CrateModulePath),
    ExternCrate(ExternCrateModulePath),
}

impl<'de> Deserialize<'de> for ModulePath {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        String::deserialize(deserializer)?
            .parse()
            .map_err(D::Error::custom)
    }
}

impl FromStr for ModulePath {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, String> {
        static CRATE: Lazy<Regex> =
            Lazy::new(|| Regex::new(r"\Acrate::([a-zA-Z0-9_]+)\z").unwrap());
        static EXTERN_CRATE: Lazy<Regex> =
            Lazy::new(|| Regex::new(r"\A::([a-zA-Z0-9_]+)::([a-zA-Z0-9_]+)\z").unwrap());

        if let Some(caps) = CRATE.captures(s) {
            Ok(Self::Crate(CrateModulePath {
                module_name: caps[1].to_owned(),
            }))
        } else if let Some(caps) = EXTERN_CRATE.captures(s) {
            Ok(Self::ExternCrate(ExternCrateModulePath {
                extern_crate_name: caps[1].to_owned(),
                module_name: caps[2].to_owned(),
            }))
        } else {
            Err(format!(
                "expected `{}` or `{}`",
                CRATE.as_str(),
                EXTERN_CRATE.as_str(),
            ))
        }
    }
}

impl fmt::Display for ModulePath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Crate(CrateModulePath { module_name }) => write!(f, "\"crate::{}\"", module_name),
            Self::ExternCrate(ExternCrateModulePath {
                extern_crate_name,
                module_name,
            }) => write!(f, "\"::{}::{}\"", extern_crate_name, module_name),
        }
    }
}

#[derive(Debug, Clone, Ord, Eq, PartialOrd, PartialEq, Hash)]
pub(crate) struct CrateModulePath {
    module_name: String,
}

impl<'de> Deserialize<'de> for CrateModulePath {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        String::deserialize(deserializer)?
            .parse()
            .map_err(D::Error::custom)
    }
}

impl FromStr for CrateModulePath {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, String> {
        static CRATE: Lazy<Regex> =
            Lazy::new(|| Regex::new(r"\Acrate::([a-zA-Z0-9_]+)\z").unwrap());

        if let Some(caps) = CRATE.captures(s) {
            Ok(Self {
                module_name: caps[1].to_owned(),
            })
        } else {
            Err(format!("expected `{}`", CRATE.as_str()))
        }
    }
}

#[derive(Debug, Clone, Ord, Eq, PartialOrd, PartialEq, Hash)]
pub(crate) struct ExternCrateModulePath {
    extern_crate_name: String,
    module_name: String,
}

#[cfg(test)]
mod tests {
    use crate::mod_dep::ModulePath;

    #[test]
    fn parse_module_path() {
        fn parse(s: &str) -> Result<(), ()> {
            s.parse::<ModulePath>().map(|_| ()).map_err(|_| ())
        }

        assert!(parse("::library::module").is_ok());
        assert!(parse("::library::module::module").is_err());
        assert!(parse("library::module").is_err());
    }
}