refac: move core to estring crate
This commit is contained in:
parent
a8c124aabc
commit
2171d6b6a7
22 changed files with 846 additions and 401 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -1,7 +1,5 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -181,6 +179,7 @@ version = "1.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"enve_mod",
|
"enve_mod",
|
||||||
|
"estring",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -195,6 +194,10 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "estring"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "1.8.2"
|
version = "1.8.2"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"estring",
|
||||||
"enve_mod",
|
"enve_mod",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -21,13 +22,14 @@ readme = "../README.md"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
number = []
|
number = ["estring/number"]
|
||||||
bool = []
|
bool = ["estring/bool"]
|
||||||
vec = []
|
vec = ["estring/vec"]
|
||||||
|
|
||||||
macro = ["enve_mod"]
|
macro = ["enve_mod"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
estring = { version = "0", path = "./estring" }
|
||||||
enve_mod = { version = "1.1", path = "./enve_mod", optional = true }
|
enve_mod = { version = "1.1", path = "./enve_mod", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 pleshevskiy
|
Copyright (c) 2019-2022 pleshevskiy
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
28
estring/Cargo.toml
Normal file
28
estring/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "estring"
|
||||||
|
description = "A simple way to parse a string using type annotations"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Dmitriy Pleshevskiy <dmitriy@ideascup.me>"]
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/pleshevskiy/itconfig-rs/tree/redesign/estring"
|
||||||
|
license = "MIT"
|
||||||
|
keywords = ["parsing", "type", "annotations", "customizable"]
|
||||||
|
categories = ["data-structures", "parsing"]
|
||||||
|
|
||||||
|
# rust-version = "1.51.0" # The first version of Cargo that supports this field is 1.56.0
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
msrv = "1.51.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
number = []
|
||||||
|
bool = []
|
||||||
|
vec = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
maintenance = { status = "actively-developed" }
|
21
estring/LICENSE
Normal file
21
estring/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 pleshevskiy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
57
estring/README.md
Normal file
57
estring/README.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# EString
|
||||||
|
|
||||||
|
A simple way to parse a string using type annotations.
|
||||||
|
|
||||||
|
This package was originally designed for [enve]
|
||||||
|
|
||||||
|
[enve]: https://github.com/pleshevskiy/itconfig-rs/tree/redesign
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use estring::{SepVec, EString};
|
||||||
|
|
||||||
|
type PlusVec<T> = SepVec<T, '+'>;
|
||||||
|
type MulVec<T> = SepVec<T, '*'>;
|
||||||
|
|
||||||
|
fn main() -> Result<(), estring::ParseError> {
|
||||||
|
let res = EString::from("10+5*2+3")
|
||||||
|
.parse::<PlusVec<MulVec<f32>>>()?
|
||||||
|
.iter()
|
||||||
|
.map(|m| m.iter().product::<f32>())
|
||||||
|
.sum::<f32>();
|
||||||
|
|
||||||
|
assert_eq!(res, 23.0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use custom types as annotations! Just implement `TryFrom<EString>`!
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
**The MSRV is 1.51.0**
|
||||||
|
|
||||||
|
Add `estring = { version = "0.1", features = ["vec", "number"] }` as a
|
||||||
|
dependency in `Cargo.toml`.
|
||||||
|
|
||||||
|
`Cargo.toml` example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "my-crate"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Me <user@rust-lang.org>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
estring = { version = "0.1", features = ["vec", "number"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
**MIT**. See [LICENSE](./LICENSE) to see the full text.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
[pleshevskiy](https://github.com/pleshevskiy) (Dmitriy Pleshevskiy) – creator,
|
||||||
|
maintainer.
|
99
estring/src/core.rs
Normal file
99
estring/src/core.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
//! Contains the ``EString`` type, as well as the basic implementation of conversions to
|
||||||
|
//! string types
|
||||||
|
//!
|
||||||
|
#[cfg(any(feature = "number", feature = "bool"))]
|
||||||
|
pub mod prim;
|
||||||
|
#[cfg(any(feature = "number", feature = "bool"))]
|
||||||
|
pub use prim::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "vec")]
|
||||||
|
pub mod vec;
|
||||||
|
#[cfg(feature = "vec")]
|
||||||
|
pub use vec::*;
|
||||||
|
|
||||||
|
use crate::ParseError;
|
||||||
|
use std::convert::{Infallible, TryFrom};
|
||||||
|
|
||||||
|
/// Wrapper under String type.
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||||
|
pub struct EString(pub String);
|
||||||
|
|
||||||
|
impl EString {
|
||||||
|
/// Parses inner string by type annotations and returns result.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Will return `Err` if estring cannot parse inner fragment
|
||||||
|
///
|
||||||
|
#[inline]
|
||||||
|
pub fn parse<T: TryFrom<EString>>(self) -> Result<T, ParseError> {
|
||||||
|
let orig = self.0.clone();
|
||||||
|
<T as TryFrom<EString>>::try_from(self).map_err(|_| ParseError(orig))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for EString
|
||||||
|
where
|
||||||
|
T: std::fmt::Display,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn from(val: T) -> Self {
|
||||||
|
Self(val.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for EString {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<EString> for String {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
||||||
|
Ok(s.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<EString> for &'static str {
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Box::leak(s.0.into_boxed_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_deref_to_string() {
|
||||||
|
let estr = EString::from("hello");
|
||||||
|
assert_eq!(*estr, String::from("hello"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_into_itself() {
|
||||||
|
let estr = EString::from("hello");
|
||||||
|
match estr.parse::<EString>() {
|
||||||
|
Ok(res) => assert_eq!(res, EString::from("hello")),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_into_string() {
|
||||||
|
let estr = EString::from("hello");
|
||||||
|
match estr.parse::<String>() {
|
||||||
|
Ok(res) => assert_eq!(res, String::from("hello")),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
estring/src/core/prim.rs
Normal file
14
estring/src/core/prim.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
//! Contains the implementations to primitive types (number, boolean)
|
||||||
|
//!
|
||||||
|
//! **NOTE**: Require the enabling of the same-name features
|
||||||
|
//!
|
||||||
|
|
||||||
|
#[cfg(feature = "bool")]
|
||||||
|
mod bool;
|
||||||
|
#[cfg(feature = "bool")]
|
||||||
|
pub use self::bool::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "number")]
|
||||||
|
mod number;
|
||||||
|
#[cfg(feature = "number")]
|
||||||
|
pub use self::number::*;
|
57
estring/src/core/prim/bool.rs
Normal file
57
estring/src/core/prim/bool.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::core::EString;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
impl TryFrom<EString> for bool {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
||||||
|
match s.to_lowercase().as_str() {
|
||||||
|
"true" | "t" | "yes" | "y" | "on" | "1" => Ok(true),
|
||||||
|
"false" | "f" | "no" | "n" | "off" | "0" | "" => Ok(false),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ParseError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_bool_variable() {
|
||||||
|
let test_cases = [
|
||||||
|
("1", true),
|
||||||
|
("0", false),
|
||||||
|
("y", true),
|
||||||
|
("f", false),
|
||||||
|
("yes", true),
|
||||||
|
("true", true),
|
||||||
|
("false", false),
|
||||||
|
("t", true),
|
||||||
|
("f", false),
|
||||||
|
("on", true),
|
||||||
|
("off", false),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (val, expected) in test_cases {
|
||||||
|
let estr = EString::from(val);
|
||||||
|
match estr.parse::<bool>() {
|
||||||
|
Ok(res) => assert_eq!(res, expected),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_throw_parse_error() {
|
||||||
|
let estr = EString::from("something");
|
||||||
|
match estr.parse::<bool>() {
|
||||||
|
Err(ParseError(orig)) => {
|
||||||
|
assert_eq!(orig, String::from("something"));
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
62
estring/src/core/prim/number.rs
Normal file
62
estring/src/core/prim/number.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use crate::core::EString;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
macro_rules! from_env_string_numbers_impl {
|
||||||
|
($($ty:ty),+$(,)?) => {
|
||||||
|
$(
|
||||||
|
#[cfg(feature = "number")]
|
||||||
|
impl TryFrom<EString> for $ty {
|
||||||
|
type Error = <$ty as std::str::FromStr>::Err;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
||||||
|
s.0.parse::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
from_env_string_numbers_impl![
|
||||||
|
i8, i16, i32, i64, i128, isize,
|
||||||
|
u8, u16, u32, u64, u128, usize,
|
||||||
|
f32, f64
|
||||||
|
];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ParseError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_number() {
|
||||||
|
let estr = EString::from("-10");
|
||||||
|
match estr.parse::<i32>() {
|
||||||
|
Ok(res) => assert_eq!(res, -10),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_float_number() {
|
||||||
|
let estr = EString::from("-0.15");
|
||||||
|
match estr.parse::<f32>() {
|
||||||
|
#[allow(clippy::float_cmp)]
|
||||||
|
Ok(res) => assert_eq!(res, -0.15),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_throw_parse_error() {
|
||||||
|
let estr = EString::from("-10");
|
||||||
|
match estr.parse::<u32>() {
|
||||||
|
Err(ParseError(orig)) => {
|
||||||
|
assert_eq!(orig, String::from("-10"));
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
153
estring/src/core/vec.rs
Normal file
153
estring/src/core/vec.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
//! Contains the implementations to vec type
|
||||||
|
//!
|
||||||
|
//! **NOTE**: Require the enabling of the `vec` features
|
||||||
|
//!
|
||||||
|
|
||||||
|
use crate::core::EString;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
/// Wrapper for ``Vec`` to split string by a separator (`SEP`).
|
||||||
|
///
|
||||||
|
/// **NOTE**: Required the enabling of the `vec` feature.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use estring::{SepVec, EString};
|
||||||
|
///
|
||||||
|
/// type CommaVec<T> = SepVec<T, ','>;
|
||||||
|
///
|
||||||
|
/// fn main() -> Result<(), estring::ParseError> {
|
||||||
|
/// let res = EString::from("1,2,3").parse::<CommaVec<u8>>()?;
|
||||||
|
/// assert_eq!(*res, vec![1, 2, 3]);
|
||||||
|
///
|
||||||
|
/// Ok(())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct SepVec<T, const SEP: char>(pub Vec<T>);
|
||||||
|
|
||||||
|
impl<T, const SEP: char> std::ops::Deref for SepVec<T, SEP> {
|
||||||
|
type Target = Vec<T>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const SEP: char> From<Vec<T>> for SepVec<T, SEP> {
|
||||||
|
#[inline]
|
||||||
|
fn from(vec: Vec<T>) -> Self {
|
||||||
|
Self(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const SEP: char> std::fmt::Display for SepVec<T, SEP>
|
||||||
|
where
|
||||||
|
T: std::fmt::Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.iter().enumerate().try_for_each(|(i, part)| {
|
||||||
|
if i != 0 {
|
||||||
|
f.write_char(SEP)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_str(&part.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const SEP: char> TryFrom<EString> for SepVec<T, SEP>
|
||||||
|
where
|
||||||
|
T: TryFrom<EString> + std::fmt::Display,
|
||||||
|
{
|
||||||
|
type Error = T::Error;
|
||||||
|
|
||||||
|
fn try_from(value: EString) -> Result<Self, Self::Error> {
|
||||||
|
let inner = value
|
||||||
|
.split(SEP)
|
||||||
|
.map(str::trim)
|
||||||
|
.map(EString::from)
|
||||||
|
.map(T::try_from)
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
Ok(Self(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const COMMA: char = ',';
|
||||||
|
const SEMI: char = ';';
|
||||||
|
|
||||||
|
type CommaVec<T> = SepVec<T, COMMA>;
|
||||||
|
type SemiVec<T> = SepVec<T, SEMI>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_into_vec() {
|
||||||
|
let estr = EString::from("a,b,c,d,e");
|
||||||
|
match estr.parse::<CommaVec<&str>>() {
|
||||||
|
Ok(res) => assert_eq!(*res, vec!["a", "b", "c", "d", "e"]),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_trim_identations_before_parsing() {
|
||||||
|
let input = "
|
||||||
|
a , b, c,
|
||||||
|
d,e";
|
||||||
|
let estr = EString::from(input);
|
||||||
|
match estr.parse::<CommaVec<&str>>() {
|
||||||
|
Ok(res) => assert_eq!(*res, vec!["a", "b", "c", "d", "e"]),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_into_vector_of_vectors() {
|
||||||
|
let estr = EString::from("a,b; c,d,e; f,g");
|
||||||
|
match estr.parse::<SemiVec<CommaVec<&str>>>() {
|
||||||
|
Ok(res) => assert_eq!(
|
||||||
|
res,
|
||||||
|
SemiVec::from(vec![
|
||||||
|
CommaVec::from(vec!["a", "b"]),
|
||||||
|
CommaVec::from(vec!["c", "d", "e"]),
|
||||||
|
CommaVec::from(vec!["f", "g"])
|
||||||
|
])
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "number")]
|
||||||
|
mod numbers {
|
||||||
|
use super::*;
|
||||||
|
use crate::ParseError;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_into_num_vec() {
|
||||||
|
let estr = EString::from("1,2,3,4,5");
|
||||||
|
match estr.parse::<CommaVec<i32>>() {
|
||||||
|
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_throw_parse_vec_error() {
|
||||||
|
let estr = EString::from("1,2,3,4,5");
|
||||||
|
match estr.parse::<SemiVec<i32>>() {
|
||||||
|
Err(ParseError(orig)) => {
|
||||||
|
assert_eq!(orig, String::from("1,2,3,4,5"));
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
estring/src/error.rs
Normal file
19
estring/src/error.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/// Failed to parse the specified string.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParseError(pub String);
|
||||||
|
|
||||||
|
impl std::fmt::Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, r#"Failed to parse: "{}""#, self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ParseError {}
|
||||||
|
|
||||||
|
impl std::ops::Deref for ParseError {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
72
estring/src/lib.rs
Normal file
72
estring/src/lib.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
//!
|
||||||
|
//! # ``EString``
|
||||||
|
//!
|
||||||
|
//! A simple way to parse a string using type annotations.
|
||||||
|
//!
|
||||||
|
//! This package was originally designed for [enve]
|
||||||
|
//!
|
||||||
|
//! [enve]: https://github.com/pleshevskiy/itconfig-rs
|
||||||
|
//!
|
||||||
|
//! ## Getting started
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use estring::{SepVec, EString};
|
||||||
|
//!
|
||||||
|
//! type PlusVec<T> = SepVec<T, '+'>;
|
||||||
|
//! type MulVec<T> = SepVec<T, '*'>;
|
||||||
|
//!
|
||||||
|
//! fn main() -> Result<(), estring::ParseError> {
|
||||||
|
//! let res = EString::from("10+5*2+3")
|
||||||
|
//! .parse::<PlusVec<MulVec<f32>>>()?
|
||||||
|
//! .iter()
|
||||||
|
//! .map(|m| m.iter().product::<f32>())
|
||||||
|
//! .sum::<f32>();
|
||||||
|
//!
|
||||||
|
//! assert_eq!(res, 23.0);
|
||||||
|
//! Ok(())
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Installation
|
||||||
|
//!
|
||||||
|
//! **The MSRV is 1.51.0**
|
||||||
|
//!
|
||||||
|
//! Add `estring = { version = "0.1", features = ["vec", "number"] }` as a
|
||||||
|
//! dependency in `Cargo.toml`.
|
||||||
|
//!
|
||||||
|
//! `Cargo.toml` example:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [package]
|
||||||
|
//! name = "my-crate"
|
||||||
|
//! version = "0.1.0"
|
||||||
|
//! authors = ["Me <user@rust-lang.org>"]
|
||||||
|
//!
|
||||||
|
//! [dependencies]
|
||||||
|
//! estring = { version = "0.1", features = ["vec", "number"] }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## License
|
||||||
|
//!
|
||||||
|
//! **MIT**.
|
||||||
|
//!
|
||||||
|
//! See [LICENSE](./LICENSE) to see the full text.
|
||||||
|
//!
|
||||||
|
#![deny(clippy::pedantic)]
|
||||||
|
#![allow(clippy::module_name_repetitions)]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
pub mod core;
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
pub use crate::core::*;
|
||||||
|
pub use crate::error::ParseError;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = 2 + 2;
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use enve::core::SepVec;
|
use enve::estr::SepVec;
|
||||||
|
|
||||||
type MinusVec<T> = SepVec<T, '-'>;
|
type MinusVec<T> = SepVec<T, '-'>;
|
||||||
type PlusVec<T> = SepVec<T, '+'>;
|
type PlusVec<T> = SepVec<T, '+'>;
|
||||||
|
|
269
src/core.rs
269
src/core.rs
|
@ -1,65 +1,238 @@
|
||||||
#[cfg(any(feature = "number", feature = "bool"))]
|
|
||||||
pub mod prim;
|
|
||||||
#[cfg(any(feature = "number", feature = "bool"))]
|
|
||||||
pub use prim::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "vec")]
|
|
||||||
pub mod vec;
|
|
||||||
#[cfg(feature = "vec")]
|
|
||||||
pub use vec::*;
|
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use std::convert::{Infallible, TryFrom};
|
use estring::EString;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
/// Wrapper under String type.
|
pub fn get_or_set_default<R>(env_name: &str, default: R) -> Result<R, Error>
|
||||||
///
|
|
||||||
/// When we read the environment variable, we automatically convert the value
|
|
||||||
/// to EnvString and then convert it to your expected type.
|
|
||||||
///
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
|
||||||
pub struct EString(String);
|
|
||||||
|
|
||||||
impl EString {
|
|
||||||
#[inline]
|
|
||||||
pub fn parse<T: TryFrom<EString>>(self) -> Result<T, Error> {
|
|
||||||
let orig = self.0.clone();
|
|
||||||
<T as TryFrom<EString>>::try_from(self).map_err(|_| Error::Parse(orig))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> From<T> for EString
|
|
||||||
where
|
where
|
||||||
T: std::fmt::Display,
|
R: TryFrom<EString> + std::fmt::Display,
|
||||||
{
|
{
|
||||||
#[inline]
|
get::<R>(env_name).or_else(|err| match err {
|
||||||
fn from(val: T) -> Self {
|
Error::NotPresent => sset(env_name, &default).parse().map_err(Error::from),
|
||||||
Self(val.to_string())
|
_ => Err(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<R>(env_name: &str) -> Result<R, Error>
|
||||||
|
where
|
||||||
|
R: TryFrom<EString>,
|
||||||
|
{
|
||||||
|
sget(env_name).and_then(|v| v.parse().map_err(Error::from))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sget(env_name: &str) -> Result<EString, Error> {
|
||||||
|
std::env::var(env_name)
|
||||||
|
.map_err(Error::from)
|
||||||
|
.map(EString::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sset<V>(env_name: &str, value: V) -> EString
|
||||||
|
where
|
||||||
|
V: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let val = value.to_string();
|
||||||
|
std::env::set_var(env_name, &val);
|
||||||
|
val.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct TestCase<const N: u8>;
|
||||||
|
|
||||||
|
impl<const N: u8> std::fmt::Display for TestCase<N> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "test_case_{}", N)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for EString {
|
#[test]
|
||||||
type Target = String;
|
fn should_add_env_variable_to_process() {
|
||||||
|
let en = TestCase::<0>.to_string();
|
||||||
#[inline]
|
sset(&en, "hello");
|
||||||
fn deref(&self) -> &Self::Target {
|
match std::env::var(&en) {
|
||||||
&self.0
|
Ok(var) => assert_eq!(&var, "hello"),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<EString> for String {
|
#[test]
|
||||||
type Error = Infallible;
|
fn should_return_variable() {
|
||||||
|
let en = TestCase::<1>.to_string();
|
||||||
|
std::env::set_var(&en, "hello");
|
||||||
|
match get::<&str>(&en) {
|
||||||
|
Ok(res) => assert_eq!(res, "hello"),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[test]
|
||||||
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
fn should_throw_no_present_error() {
|
||||||
Ok(s.0)
|
let en = TestCase::<2>.to_string();
|
||||||
|
match get::<&str>(&en) {
|
||||||
|
Err(Error::NotPresent) => {}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_set_default_if_var_is_no_present() {
|
||||||
|
let en = TestCase::<3>.to_string();
|
||||||
|
let orig = "hello";
|
||||||
|
match get_or_set_default(&en, orig) {
|
||||||
|
Ok(res) => {
|
||||||
|
assert_eq!(res, orig);
|
||||||
|
assert_eq!(std::env::var(&en).unwrap(), orig);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "number")]
|
||||||
|
mod numbers {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_parsed_num() {
|
||||||
|
let en = TestCase::<4>.to_string();
|
||||||
|
std::env::set_var(&en, "-10");
|
||||||
|
match get::<i32>(&en) {
|
||||||
|
Ok(res) => assert_eq!(res, -10),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_throw_parse_error() {
|
||||||
|
let en = TestCase::<5>.to_string();
|
||||||
|
std::env::set_var(&en, "-10");
|
||||||
|
match get::<u32>(&en) {
|
||||||
|
Err(Error::Parse(orig)) => {
|
||||||
|
assert_eq!(orig, String::from("-10"))
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_set_default_num_if_var_is_no_present() {
|
||||||
|
let en = TestCase::<6>.to_string();
|
||||||
|
let orig = 10;
|
||||||
|
match get_or_set_default(&en, orig) {
|
||||||
|
Ok(res) => {
|
||||||
|
assert_eq!(res, orig);
|
||||||
|
assert_eq!(std::env::var(&en).unwrap(), "10");
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<EString> for &'static str {
|
#[cfg(feature = "bool")]
|
||||||
type Error = Infallible;
|
mod boolean {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[inline]
|
#[test]
|
||||||
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
fn should_parse_bool_variable() {
|
||||||
Ok(Box::leak(s.0.into_boxed_str()))
|
let en = TestCase::<7>.to_string();
|
||||||
|
|
||||||
|
[
|
||||||
|
("1", true),
|
||||||
|
("y", true),
|
||||||
|
("yes", true),
|
||||||
|
("true", true),
|
||||||
|
("t", true),
|
||||||
|
("on", true),
|
||||||
|
("false", false),
|
||||||
|
("f", false),
|
||||||
|
("0", false),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.for_each(|(val, expected)| {
|
||||||
|
let mut en = en.clone();
|
||||||
|
en.push_str(val.as_ref());
|
||||||
|
|
||||||
|
std::env::set_var(&en, val);
|
||||||
|
match get::<bool>(&en) {
|
||||||
|
Ok(res) => assert_eq!(res, *expected),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vec")]
|
||||||
|
mod vector {
|
||||||
|
use super::*;
|
||||||
|
use crate::estr::{CommaVec, SemiVec, SepVec};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_var_as_vector() {
|
||||||
|
let en = TestCase::<8>.to_string();
|
||||||
|
|
||||||
|
std::env::set_var(&en, "1,2,3,4,5");
|
||||||
|
match get::<CommaVec<i32>>(&en) {
|
||||||
|
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_trim_identations_before_parsing() {
|
||||||
|
let en = TestCase::<9>.to_string();
|
||||||
|
|
||||||
|
let input = "
|
||||||
|
1 , 2, 3,
|
||||||
|
4,5";
|
||||||
|
|
||||||
|
std::env::set_var(&en, input);
|
||||||
|
match get::<CommaVec<i32>>(&en) {
|
||||||
|
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_vector_of_vectors() {
|
||||||
|
let en = TestCase::<10>.to_string();
|
||||||
|
|
||||||
|
std::env::set_var(&en, "1,2; 3,4,5; 6,7");
|
||||||
|
match get::<SemiVec<CommaVec<i32>>>(&en) {
|
||||||
|
Ok(res) => assert_eq!(
|
||||||
|
res,
|
||||||
|
SemiVec::from(vec![
|
||||||
|
CommaVec::from(vec![1, 2]),
|
||||||
|
CommaVec::from(vec![3, 4, 5]),
|
||||||
|
CommaVec::from(vec![6, 7])
|
||||||
|
])
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_throw_parse_vec_error() {
|
||||||
|
let en = TestCase::<11>.to_string();
|
||||||
|
std::env::set_var(&en, "1,2,3,4,5");
|
||||||
|
match get::<SepVec<i32, '+'>>(&en) {
|
||||||
|
Err(Error::Parse(orig)) => {
|
||||||
|
assert_eq!(orig, String::from("1,2,3,4,5"))
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_set_default_vector_if_var_is_no_present() {
|
||||||
|
let en = TestCase::<12>.to_string();
|
||||||
|
let orig = CommaVec::from(vec![1, 2, 3, 4]);
|
||||||
|
match get_or_set_default(&en, orig.clone()) {
|
||||||
|
Ok(res) => {
|
||||||
|
assert_eq!(res, orig);
|
||||||
|
assert_eq!(std::env::var(&en).unwrap(), "1,2,3,4");
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
use crate::core::EString;
|
|
||||||
use std::convert::{Infallible, TryFrom};
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
macro_rules! from_env_string_numbers_impl {
|
|
||||||
($($ty:ty),+$(,)?) => {
|
|
||||||
$(
|
|
||||||
#[cfg(feature = "number")]
|
|
||||||
impl TryFrom<EString> for $ty {
|
|
||||||
type Error = <$ty as std::str::FromStr>::Err;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
|
||||||
s.0.parse::<Self>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
from_env_string_numbers_impl![
|
|
||||||
i8, i16, i32, i64, i128, isize,
|
|
||||||
u8, u16, u32, u64, u128, usize,
|
|
||||||
f32, f64
|
|
||||||
];
|
|
||||||
|
|
||||||
#[cfg(feature = "bool")]
|
|
||||||
impl TryFrom<EString> for bool {
|
|
||||||
type Error = Infallible;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_from(s: EString) -> Result<Self, Self::Error> {
|
|
||||||
Ok(matches!(
|
|
||||||
s.to_lowercase().as_str(),
|
|
||||||
"true" | "t" | "yes" | "y" | "on" | "1"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use crate::core::EString;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
pub const COMMA: char = ',';
|
|
||||||
pub const SEMI: char = ';';
|
|
||||||
|
|
||||||
pub type CommaVec<T> = SepVec<T, COMMA>;
|
|
||||||
pub type SemiVec<T> = SepVec<T, SEMI>;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub struct SepVec<T, const SEP: char>(pub Vec<T>);
|
|
||||||
|
|
||||||
impl<T, const SEP: char> std::ops::Deref for SepVec<T, SEP> {
|
|
||||||
type Target = Vec<T>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const SEP: char> From<Vec<T>> for SepVec<T, SEP> {
|
|
||||||
#[inline]
|
|
||||||
fn from(vec: Vec<T>) -> Self {
|
|
||||||
Self(vec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const SEP: char> std::fmt::Display for SepVec<T, SEP>
|
|
||||||
where
|
|
||||||
T: std::fmt::Display,
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.iter().enumerate().try_for_each(|(i, part)| {
|
|
||||||
if i != 0 {
|
|
||||||
f.write_char(SEP)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_str(&part.to_string())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const SEP: char> TryFrom<EString> for SepVec<T, SEP>
|
|
||||||
where
|
|
||||||
T: TryFrom<EString> + std::fmt::Display,
|
|
||||||
{
|
|
||||||
type Error = T::Error;
|
|
||||||
|
|
||||||
fn try_from(value: EString) -> Result<Self, Self::Error> {
|
|
||||||
let inner = value
|
|
||||||
.split(SEP)
|
|
||||||
.map(|p| p.trim())
|
|
||||||
.map(EString::from)
|
|
||||||
.map(T::try_from)
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
Ok(Self(inner))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ use std::ffi::OsString;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// The error type for operations interacting with environment variables
|
/// The error type for operations interacting with environment variables
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// The specified environment variable was not present in the current process's environment.
|
/// The specified environment variable was not present in the current process's environment.
|
||||||
NotPresent,
|
NotPresent,
|
||||||
|
@ -45,3 +45,9 @@ impl From<VarError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<estring::ParseError> for Error {
|
||||||
|
fn from(err: estring::ParseError) -> Self {
|
||||||
|
Error::Parse(err.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
6
src/estr.rs
Normal file
6
src/estr.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#[cfg(feature = "vec")]
|
||||||
|
pub mod vec;
|
||||||
|
#[cfg(feature = "vec")]
|
||||||
|
pub use vec::{CommaVec, SemiVec};
|
||||||
|
|
||||||
|
pub use estring::core::*;
|
7
src/estr/vec.rs
Normal file
7
src/estr/vec.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use estring::SepVec;
|
||||||
|
|
||||||
|
const COMMA: char = ',';
|
||||||
|
const SEMI: char = ';';
|
||||||
|
|
||||||
|
pub type CommaVec<T> = SepVec<T, COMMA>;
|
||||||
|
pub type SemiVec<T> = SepVec<T, SEMI>;
|
11
src/lib.rs
11
src/lib.rs
|
@ -137,24 +137,25 @@
|
||||||
#![forbid(non_ascii_idents)]
|
#![forbid(non_ascii_idents)]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
// missing_docs,
|
missing_docs,
|
||||||
unstable_features,
|
unstable_features,
|
||||||
unused_imports,
|
unused_imports,
|
||||||
unused_qualifications
|
unused_qualifications
|
||||||
)]
|
)]
|
||||||
|
#![warn(missing_docs)]
|
||||||
// Clippy lints
|
// Clippy lints
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![allow(clippy::needless_doctest_main)]
|
#![allow(clippy::needless_doctest_main)]
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
pub mod core;
|
mod core;
|
||||||
mod error;
|
mod error;
|
||||||
mod utils;
|
pub mod estr;
|
||||||
|
|
||||||
pub use self::core::*;
|
pub use self::core::*;
|
||||||
pub use self::error::*;
|
pub use self::core::{get, get_or_set_default, sget, sset};
|
||||||
pub use self::utils::*;
|
pub use self::error::Error;
|
||||||
|
|
||||||
#[cfg(feature = "macro")]
|
#[cfg(feature = "macro")]
|
||||||
extern crate enve_mod;
|
extern crate enve_mod;
|
||||||
|
|
236
src/utils.rs
236
src/utils.rs
|
@ -1,236 +0,0 @@
|
||||||
use crate::core::EString;
|
|
||||||
use crate::error::Error;
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
pub fn get_or_set_default<R>(env_name: &str, default: R) -> Result<R, Error>
|
|
||||||
where
|
|
||||||
R: TryFrom<EString> + std::fmt::Display,
|
|
||||||
{
|
|
||||||
get::<R>(env_name).or_else(|err| match err {
|
|
||||||
Error::NotPresent => set(env_name, &default).parse(),
|
|
||||||
_ => Err(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<R>(env_name: &str) -> Result<R, Error>
|
|
||||||
where
|
|
||||||
R: TryFrom<EString>,
|
|
||||||
{
|
|
||||||
env::var(env_name)
|
|
||||||
.map_err(From::from)
|
|
||||||
.map(EString::from)
|
|
||||||
.and_then(EString::parse)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set<V>(env_name: &str, value: V) -> EString
|
|
||||||
where
|
|
||||||
V: std::fmt::Display,
|
|
||||||
{
|
|
||||||
let val = value.to_string();
|
|
||||||
env::set_var(env_name, &val);
|
|
||||||
val.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
struct TestCase<const N: u8>;
|
|
||||||
|
|
||||||
impl<const N: u8> std::fmt::Display for TestCase<N> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "test_case_{}", N)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_add_env_variable_to_process() {
|
|
||||||
let en = TestCase::<0>.to_string();
|
|
||||||
set(&en, "hello");
|
|
||||||
match env::var(&en) {
|
|
||||||
Ok(var) => assert_eq!(&var, "hello"),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_return_variable() {
|
|
||||||
let en = TestCase::<1>.to_string();
|
|
||||||
env::set_var(&en, "hello");
|
|
||||||
match get::<&str>(&en) {
|
|
||||||
Ok(res) => assert_eq!(res, "hello"),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_throw_no_present_error() {
|
|
||||||
let en = TestCase::<2>.to_string();
|
|
||||||
match get::<&str>(&en) {
|
|
||||||
Err(Error::NotPresent) => {}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_set_default_if_var_is_no_present() {
|
|
||||||
let en = TestCase::<3>.to_string();
|
|
||||||
let orig = "hello";
|
|
||||||
match get_or_set_default(&en, orig) {
|
|
||||||
Ok(res) => {
|
|
||||||
assert_eq!(res, orig);
|
|
||||||
assert_eq!(env::var(&en).unwrap(), orig);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "number")]
|
|
||||||
mod numbers {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_return_parsed_num() {
|
|
||||||
let en = TestCase::<4>.to_string();
|
|
||||||
env::set_var(&en, "-10");
|
|
||||||
match get::<i32>(&en) {
|
|
||||||
Ok(res) => assert_eq!(res, -10),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_throw_parse_error() {
|
|
||||||
let en = TestCase::<5>.to_string();
|
|
||||||
env::set_var(&en, "-10");
|
|
||||||
match get::<u32>(&en) {
|
|
||||||
Err(Error::Parse(orig)) => {
|
|
||||||
assert_eq!(orig, String::from("-10"))
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_set_default_num_if_var_is_no_present() {
|
|
||||||
let en = TestCase::<6>.to_string();
|
|
||||||
let orig = 10;
|
|
||||||
match get_or_set_default(&en, orig) {
|
|
||||||
Ok(res) => {
|
|
||||||
assert_eq!(res, orig);
|
|
||||||
assert_eq!(env::var(&en).unwrap(), "10");
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bool")]
|
|
||||||
mod boolean {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_parse_bool_variable() {
|
|
||||||
let en = TestCase::<7>.to_string();
|
|
||||||
|
|
||||||
[
|
|
||||||
("1", true),
|
|
||||||
("y", true),
|
|
||||||
("yes", true),
|
|
||||||
("true", true),
|
|
||||||
("t", true),
|
|
||||||
("on", true),
|
|
||||||
("false", false),
|
|
||||||
("f", false),
|
|
||||||
("0", false),
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.for_each(|(val, expected)| {
|
|
||||||
let mut en = en.clone();
|
|
||||||
en.push_str(val.as_ref());
|
|
||||||
|
|
||||||
env::set_var(&en, val);
|
|
||||||
match get::<bool>(&en) {
|
|
||||||
Ok(res) => assert_eq!(res, *expected),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "vec")]
|
|
||||||
mod vector {
|
|
||||||
use super::*;
|
|
||||||
use crate::core::vec::{CommaVec, SemiVec, SepVec};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_return_var_as_vector() {
|
|
||||||
let en = TestCase::<8>.to_string();
|
|
||||||
|
|
||||||
env::set_var(&en, "1,2,3,4,5");
|
|
||||||
match get::<CommaVec<i32>>(&en) {
|
|
||||||
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_trim_identations_before_parsing() {
|
|
||||||
let en = TestCase::<9>.to_string();
|
|
||||||
|
|
||||||
let input = "
|
|
||||||
1 , 2, 3,
|
|
||||||
4,5";
|
|
||||||
|
|
||||||
env::set_var(&en, input);
|
|
||||||
match get::<CommaVec<i32>>(&en) {
|
|
||||||
Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_return_vector_of_vectors() {
|
|
||||||
let en = TestCase::<10>.to_string();
|
|
||||||
|
|
||||||
env::set_var(&en, "1,2; 3,4,5; 6,7");
|
|
||||||
match get::<SemiVec<CommaVec<i32>>>(&en) {
|
|
||||||
Ok(res) => assert_eq!(
|
|
||||||
res,
|
|
||||||
SemiVec::from(vec![
|
|
||||||
CommaVec::from(vec![1, 2]),
|
|
||||||
CommaVec::from(vec![3, 4, 5]),
|
|
||||||
CommaVec::from(vec![6, 7])
|
|
||||||
])
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_throw_parse_vec_error() {
|
|
||||||
let en = TestCase::<11>.to_string();
|
|
||||||
env::set_var(&en, "1,2,3,4,5");
|
|
||||||
match get::<SepVec<i32, '+'>>(&en) {
|
|
||||||
Err(Error::Parse(orig)) => {
|
|
||||||
assert_eq!(orig, String::from("1,2,3,4,5"))
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_set_default_vector_if_var_is_no_present() {
|
|
||||||
let en = TestCase::<12>.to_string();
|
|
||||||
let orig = CommaVec::from(vec![1, 2, 3, 4]);
|
|
||||||
match get_or_set_default(&en, orig.clone()) {
|
|
||||||
Ok(res) => {
|
|
||||||
assert_eq!(res, orig);
|
|
||||||
assert_eq!(env::var(&en).unwrap(), "1,2,3,4");
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue