typenum-consts 0.1.1

Procedural macros that take a literal integer (or the result of an evaluation of simple mathematical expressions or an environment variable whose value is a literal integer) and convert it to a `typenum::Unsigned` / `typenum::Integer` type-level positive/negative/unsigned integer.
Documentation
use std::env;

use crate::debug_eprintln;
use proc_macro2::Span;
use std::path::PathBuf;
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::{LitStr, Macro, Token};

const CARGO_BUILD_TARGET_DIR: &str = "CARGO_BUILD_TARGET_DIR";
const CARGO_MANIFEST_DIR: &str = "CARGO_MANIFEST_DIR";
const CARGO_TARGET_DIR: &str = "CARGO_TARGET_DIR";

const CARGO_ENV_DIRS: [&str; 3] = [CARGO_BUILD_TARGET_DIR, CARGO_MANIFEST_DIR, CARGO_TARGET_DIR];

impl EnvArgs {
    fn check_string_cannot_be_empty(
        &self,
        string: &String,
        error_span: Span,
        name: &str,
    ) -> Result<()> {
        if string.is_empty() {
            return Err(Error::new(error_span, format! {"{name} cannot be empty"}));
        }
        Ok(())
    }

    fn get_env_path(&self, mut env_path: PathBuf) -> core::result::Result<PathBuf, [PathBuf; 3]> {
        let mut err_array: [PathBuf; 3] = [PathBuf::new(), PathBuf::new(), PathBuf::new()];
        for (idx, cargo_env_dir) in CARGO_ENV_DIRS.iter().enumerate() {
            if let Ok(manifest_dir) = env::var(cargo_env_dir) {
                let manifest_path = PathBuf::from(manifest_dir);
                env_path = manifest_path.join(env_path);
                err_array[idx] = env_path.clone();
                debug_eprintln!("`env_path` = {env_path:?}; `manifest_path` = {manifest_path:?}");
                if env_path.exists() {
                    return Ok(env_path);
                }
            };
        }
        Err(err_array)
    }
    pub fn read_env_value(&self) -> Result<String> {
        let litkey = &self.key;
        let litfile_path = &self.file_path;
        let key = litkey.value();
        self.check_string_cannot_be_empty(&key, litkey.span(), "key")?;
        if let Some(litpath) = litfile_path {
            let path = &litpath.value();
            self.check_string_cannot_be_empty(path, litpath.span(), "path")?;
            let env_path = PathBuf::from(path);
            match self.get_env_path(env_path.clone()) {
                Ok(env_path) => {
                    dotenv::from_path(&env_path).map_err(|e| {
                        Error::new(
                            litpath.span(),
                            format!(
                                "failed to read file = `{}`: err: {}",
                                &env_path.display(),
                                e
                            ),
                        )
                    })?;
                    env::var(&key).map_err(|e| {
                        Error::new(
                            litkey.span(),
                            format!(
                                "failed to read `{}` key from `{}` file, err = {e}",
                                key,
                                &env_path.display()
                            ),
                        )
                    })
                }
                Err(err) => {
                    Err(Error::new(
                        litpath.span(),
                        format!("failed to locate the file = `{}`, the 3 file paths which are searched for are `{err:?}`", &env_path.display()),
                    ))
                }
            }
        } else {
            env::var(&key).map_err(|e| {
                Error::new(
                    litkey.span(),
                    format!(
                        "failed to read `{}` key from the environment, err = {e}",
                        key
                    ),
                )
            })
        }
    }
}

pub(crate) enum AllowedMacros {
    Env(EnvArgs),
}

impl AllowedMacros {
    pub(crate) fn which_macro(the_macro: &Macro) -> Result<Self> {
        let macro_ident = the_macro.path.require_ident()?;
        match macro_ident.to_string().as_str() {
            "env" => Ok(Self::Env(the_macro.parse_body::<EnvArgs>()?)),
            other => Err(Error::new_spanned(
                other,
                "currently only support `env`-like macro invocation",
            )),
        }
    }

    pub(crate) fn invoke_macro(&self) -> Result<String> {
        match self {
            Self::Env(env_args) => Ok(env_args.read_env_value()?),
        }
    }
}

pub(crate) struct EnvArgs {
    key: LitStr,
    file_path: Option<LitStr>,
}

impl Parse for EnvArgs {
    fn parse(input: ParseStream) -> Result<Self> {
        let key: LitStr = input.parse()?;
        let file_path = if input.peek(Token![,]) {
            input.parse::<Token![,]>()?;
            Some(input.parse()?)
        } else {
            None
        };

        Ok(EnvArgs { key, file_path })
    }
}