no-std-compat 0.4.1

A `#![no_std]` compatibility layer that will make porting your crate to no_std *easy*.
Documentation
#!/usr/bin/env python3

import argparse
import os
import re
import subprocess
import sys

# Parse arguments

parser = argparse.ArgumentParser(
    description="Generate a std compatibility module"
)
parser.add_argument("--src", help=(
    "Specify the location of the rust source code. The default is "
    "`$(rustc --print sysroot)/lib/rustlib/src/rust/src`"
))
args = parser.parse_args()

if args.src is None:
    output = subprocess.run(["rustc", "--print", "sysroot"],
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    args.src = os.path.join(output.stdout.decode("utf-8").strip(),
                            "lib", "rustlib", "src", "rust", "src")


# Read files

modules_regex = re.compile(
    r"^(?:\S.*)?pub\s+(?:mod\s+|use\s+(?:[a-zA-Z_][a-zA-Z0-9_]*::)*)"
    r"([a-zA-Z_][a-zA-Z0-9_]*);",
    re.MULTILINE
)


def modules(crate):
    root = os.path.join(args.src, crate)
    lib = os.path.join(root, "lib.rs")
    with open(lib) as f:
        contents = f.read()

    modules = dict()
    for match in modules_regex.finditer(contents):
        module = match.group(1)
        unstable = False

        path = os.path.join(root, module + ".rs")
        if not os.path.isfile(path):
            path = os.path.join(root, module, "mod.rs")
        try:
            with open(path, "r") as f:
                unstable = "#![unstable" in f.read()
                if unstable:
                    print(
                        f"Module '{module}' from '{crate}' appears unstable",
                        file=sys.stderr
                    )
        except OSError:
            pass

        modules[module] = unstable
    return modules


def generate(module, unstable, *namespaces):
    out = f"pub mod {module} {{\n"

    if module == "prelude":
        return None

    for namespace in namespaces:
        out += "    "

        cfgs = []
        if namespace != "core":
            cfgs.append(f"feature = \"{namespace}\"")
        if unstable:
            cfgs.append("feature = \"unstable\"")

        if len(cfgs) == 1:
            out += f"#[cfg({cfgs[0]})] "
        elif len(cfgs) > 1:
            out += "#[cfg(all(" + ", ".join(cfgs) + "))] "

        out += f"pub use __{namespace}::{module}::*;\n"

    if module == "collections":
        prefix = (
            "    #[cfg(all("
            "feature = \"alloc\", "
            "feature = \"compat_hash\""
            "))] pub use hashbrown::"
        )
        out += (
            prefix + "HashMap;\n" +
            prefix + "HashSet;\n"
        )
    elif module == "sync":
        prefix = (
            "    #[cfg(all("
            "feature = \"alloc\", "
            "feature = \"compat_sync\""
            "))] pub use spin::"
        )
        out += (
            prefix + "Mutex;\n" +
            prefix + "MutexGuard;\n" +
            prefix + "Once;\n" +
            prefix + "RwLock;\n" +
            prefix + "RwLockReadGuard;\n" +
            prefix + "RwLockWriteGuard;\n"
        )

    out += "}"
    return out


core = modules("libcore")
alloc = modules("liballoc")

generated = {}

core_keys = set(core.keys())
alloc_keys = set(alloc.keys())

for module in core_keys & alloc_keys:
    # TODO: separate these
    unstable = core[module] or alloc[module]
    generated[module] = generate(module, unstable, "core", "alloc")
for module in core_keys - alloc_keys:
    unstable = core[module]
    generated[module] = generate(module, unstable, "core")
for module in alloc_keys - core_keys:
    unstable = alloc[module]
    generated[module] = generate(module, unstable, "alloc")

generated["prelude"] = """pub mod prelude {
    pub mod v1 {
        // Prelude
        pub use __core::prelude::v1::*;
        #[cfg(all(feature = "alloc", feature = "unstable"))]
        pub use __alloc::prelude::v1::*;
        #[cfg(all(feature = "alloc", not(feature = "unstable")))]
        pub use __alloc::{
            borrow::ToOwned,
            boxed::Box,
            // UNSTABLE: slice::SliceConcatExt,
            string::String,
            string::ToString,
            vec::Vec,
        };

        // Other imports
        #[cfg(feature = "alloc")]
        pub use __alloc::{format, vec};
        #[cfg(feature = "compat_macros")]
        pub use crate::{print, println, eprint, eprintln, dbg};
    }
}"""

print("""//! Generated by generate.py located at the repository root
//! ./generate.py > src/generated.rs""")
for module in sorted(generated.items(), key=lambda i: i[0]):
    print(module[1])