palc 0.0.2

WIP: Command Line Argument Parser with several opposite design goal from Clap
Documentation
//! Runtime reflection of arguments, subcommands and help strings.
//! Required by precise error messages and help generations.
//!
//! Most of `&str` here can be changed to thin `&CStr`, which is blocked by extern types.
//! WAIT: <https://github.com/rust-lang/rust/issues/43467>
//!
//! TODO(low): Decide whether to expose these API.
#![cfg_attr(not(feature = "default"), allow(unused))]

use std::fmt;
#[cfg(not(feature = "help"))]
use std::marker::PhantomData;

use crate::util::{split_once, split_sep_many, split_terminator};

/// Runtime information of a enum of subcommands.
#[derive(Debug)]
pub struct RawSubcommandInfo<A: ?Sized = [&'static str]> {
    /// Zero or more NUL-terminated subcommand names.
    #[cfg(feature = "help")]
    subcommands: &'static str,

    /// `RawArgsInfo::cmd_doc` of each subcommand.
    #[cfg(feature = "help")]
    cmd_docs: A,

    #[cfg(not(feature = "help"))]
    _marker: PhantomData<A>,
}

impl RawSubcommandInfo {
    pub(crate) const EMPTY_REF: &Self = &Self::new("", []);

    // Used by proc-macro.
    #[cfg(feature = "help")]
    pub const fn new<const N: usize>(
        subcommands: &'static str,
        cmd_docs: [&'static str; N],
    ) -> RawSubcommandInfo<[&'static str; N]> {
        RawSubcommandInfo { subcommands, cmd_docs }
    }

    #[cfg(not(feature = "help"))]
    pub const fn new<const N: usize>(
        _subcommands: &'static str,
        _cmd_docs: [&'static str; N],
    ) -> Self {
        Self { _marker: PhantomData }
    }
}

/// - `w` will always be a `&mut String` but type-erased to avoid aggressive
///   inlining (reserve, fail handling, inlined memcpy).
/// - `what` indicates what to format, see constants below.
type FmtWriter = fn(w: &mut dyn fmt::Write, what: u8);

const fn fmt_noop(_w: &mut dyn fmt::Write, _what: u8) {}

// This should be an enum but we use numbers to simplify proc-macro codegen.
pub(crate) const FMT_UNNAMED: u8 = 0;
pub(crate) const FMT_NAMED: u8 = 1;
pub(crate) const FMT_USAGE_UNNAMED: u8 = 2;
pub(crate) const FMT_USAGE_NAMED: u8 = 3;

// Break the type cycle.
#[derive(Debug, Clone, Copy)]
pub struct RawArgsInfoRef(pub &'static RawArgsInfo);

#[derive(Debug)]
pub struct RawArgsInfo<A: ?Sized = [RawArgsInfoRef]> {
    /// Zero or more '\0'-terminated argument descriptions, either:
    /// `-s`, `--long`, `-s, --long=<VALUE>`, `<REQUIRED>`, or `[OPTIONAL]`.
    descriptions: &'static str,

    /// Is the child subcommand optional or required? Only useful if there are subcommands.
    #[cfg(feature = "help")]
    subcmd_optional: bool,

    /// If there is any optional named args, so that "[OPTIONS]" should be shown?
    #[cfg(feature = "help")]
    has_optional_named: bool,

    /// Child subcommands.
    #[cfg(feature = "help")]
    subcmd_info: Option<&'static RawSubcommandInfo>,

    /// The documentation about this command applet.
    ///
    /// This consists of '\0'-separated following elements:
    /// - long_about
    /// - after_long_help
    #[cfg(feature = "help")]
    cmd_doc: &'static str,

    /// Help string formatter.
    #[cfg(feature = "help")]
    fmt_help: FmtWriter,

    flattened: A,
}

impl RawArgsInfo {
    pub(crate) const EMPTY_REF: &'static Self =
        &RawArgsInfo::new(false, false, None, "", "", fmt_noop, []);

    // Used by proc-macro.
    pub const fn new<const N: usize>(
        subcmd_optional: bool,
        mut has_optional_named: bool,
        subcmd_info: Option<&'static RawSubcommandInfo>,
        cmd_doc: &'static str,
        descriptions: &'static str,
        fmt_help: FmtWriter,
        flattened: [RawArgsInfoRef; N],
    ) -> RawArgsInfo<[RawArgsInfoRef; N]> {
        #[cfg(feature = "help")]
        {
            let len = flattened.len();
            let mut i = 0usize;
            while i < len {
                let inner = flattened[i];
                has_optional_named |= inner.0.has_optional_named;
                i += 1;
            }
        }

        RawArgsInfo {
            descriptions,

            #[cfg(feature = "help")]
            subcmd_optional,
            #[cfg(feature = "help")]
            has_optional_named,
            #[cfg(feature = "help")]
            subcmd_info,
            #[cfg(feature = "help")]
            cmd_doc,
            #[cfg(feature = "help")]
            fmt_help,

            flattened,
        }
    }

    // Used by proc-macro for construction of `RawSubcommandInfo`.
    pub const fn raw_cmd_docs(&self) -> &str {
        #[cfg(feature = "help")]
        {
            self.cmd_doc
        }
        #[cfg(not(feature = "help"))]
        {
            ""
        }
    }

    // FIXME: This is quite complex. Can we directly codegen byte offset of
    // description string, instead of using `idx`?
    pub(crate) fn get_description(&self, mut idx: u8) -> Option<&'static str> {
        // See `RawArgsInfo`.
        let mut desc = self.descriptions;
        while let Some((lhs, rhs)) = split_once(desc, b'\0') {
            if idx == 0 {
                return Some(lhs);
            }
            idx -= 1;
            desc = rhs;
        }
        for child in &self.flattened {
            if let Some(s) = child.0.get_description(idx) {
                return Some(s);
            }
        }
        None
    }

    #[cfg(feature = "help")]
    pub(crate) fn has_optional_named(&self) -> bool {
        self.has_optional_named
    }

    #[cfg(feature = "help")]
    pub(crate) fn fmt_help(&self, w: &mut dyn fmt::Write, what: u8) {
        (self.fmt_help)(w, what);
        for f in &self.flattened {
            f.0.fmt_help(w, what);
        }
    }

    #[cfg(feature = "help")]
    pub(crate) fn doc(&self) -> CommandDoc {
        let [long_about, after_long_help] = split_sep_many(self.cmd_doc, b'\0').unwrap_or([""; 2]);
        CommandDoc { long_about, after_long_help }
    }

    /// Iterate over subcommands and short descriptions.
    #[cfg(feature = "help")]
    pub(crate) fn subcommands(
        &self,
    ) -> Option<impl Iterator<Item = (&'static str, &'static str)> + Clone> {
        let subcmd = self.subcmd_info?;
        Some(split_terminator(subcmd.subcommands, b'\0').zip(
            subcmd.cmd_docs.iter().map(|raw_doc| split_once(raw_doc, b'\0').unwrap_or(("", "")).0),
        ))
    }

    #[cfg(feature = "help")]
    pub(crate) fn subcommand_optional(&self) -> bool {
        self.subcmd_optional
    }
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct CommandDoc {
    pub(crate) long_about: &'static str,
    pub(crate) after_long_help: &'static str,
}