compat-no-std 0.4.1-patch1

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

from collections import namedtuple
from dataclasses import dataclass, field
import argparse
import os
import re
import subprocess
import sys

Namespace = namedtuple("Namespace", "name module")

@dataclass
class Module:
    unstable: bool
    cfgs: list = field(default_factory=list)

# 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/library`"
))
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", "library")


# 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):
    """
    Return a dictionary of all modules and whether they appear unstable or not.
    """
    root = os.path.join(args.src, crate, "src")
    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 as e:
            print(e, file=sys.stderr)
            pass

        modules[module] = Module(unstable)
    return modules


def generate(module, *namespaces):
    """
    Generate code for any module, given its name and which namespaces it appears
    under and whether it's unstable or not.
    """
    out = f"pub mod {module} {{\n"

    if module == "prelude":
        return None

    for namespace in namespaces:
        out += "    "

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

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

        out += f"pub use __{namespace.name}::{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"
        )
    elif module == "ffi":
        prefix = (
            "    #[cfg(all("
            "feature = \"alloc\", "
            "feature = \"compat_cstr\""
            "))] pub use cstr_core::"
        )
        out += (
            prefix + "CStr;\n"
        )

    out += "}"
    return out


# Main logic

core = modules("core")
alloc = modules("alloc")

# Module overrides
core["lazy"].unstable = True
core["stream"].unstable = True
alloc["sync"].cfgs.append("not(target_os = \"none\")")
alloc["task"].cfgs.append("not(target_os = \"none\")")

generated = {}

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

# Appearing in both
for module in core_keys & alloc_keys:
    generated[module] = generate(
        module,
        Namespace("core", core[module]),
        Namespace("alloc", alloc[module]),
    )

# Only in core
for module in core_keys - alloc_keys:
    generated[module] = generate(
        module,
        Namespace("core", core[module]),
    )

# Only in alloc
for module in alloc_keys - core_keys:
    generated[module] = generate(
        module,
        Namespace("alloc", alloc[module]),
    )

# Complete module overrides

generated["compat_guard_unwrap"] = """
#[cfg(feature = "compat_guard_unwrap")]
pub mod compat_guard_unwrap {
    pub trait UnwrapExt: Sized {
        fn unwrap(self) -> Self { self }
    }
    #[cfg(all(feature = "alloc", feature = "compat_sync"))] impl<'a, T: ?Sized> UnwrapExt for super::sync::MutexGuard<'a, T> {}
    #[cfg(all(feature = "alloc", feature = "compat_sync"))] impl<'a, T: ?Sized> UnwrapExt for super::sync::RwLockReadGuard<'a, T> {}
    #[cfg(all(feature = "alloc", feature = "compat_sync"))] impl<'a, T: ?Sized> UnwrapExt for super::sync::RwLockWriteGuard<'a, T> {}
}"""

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};
        #[cfg(feature = "compat_guard_unwrap")] pub use crate::compat_guard_unwrap::UnwrapExt as __CompatGuardUnwrapExt;
    }
}"""

generated["os"] = """pub mod os {
    pub mod raw {
        pub use __core::ffi::c_void;
        #[cfg(all(feature = "compat_osraw"))] pub use libc::{c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort};
    }
}"""

generated["path"] = """pub mod path {
    #[cfg(feature = "compat_path")] pub use unix_path::*;
}"""

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])