├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vim └── coc-settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── calc.rs └── dotenv.rs └── src ├── agg.rs ├── agg ├── product.rs └── sum.rs ├── core.rs ├── error.rs ├── lib.rs ├── low.rs ├── low └── trim.rs ├── std.rs ├── std ├── bool.rs ├── number.rs └── option.rs ├── structs.rs └── structs ├── pair.rs ├── sep_vec.rs └── trio.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | tests: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: 18 | - ubuntu-latest 19 | - macos-latest 20 | - windows-latest 21 | rust: 22 | - 1.59.0 # msrv 23 | - stable 24 | - beta 25 | - nightly 26 | runs-on: ${{ matrix.os }} 27 | steps: 28 | - uses: actions/checkout@v3 29 | 30 | - name: Restore cargo cache 31 | uses: actions/cache@v2.1.7 32 | with: 33 | path: | 34 | ~/.cargo/registry 35 | ~/.cargo/git 36 | target 37 | key: ${{ matrix.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('Cargo.lock') }} 38 | 39 | - name: Toolchain 40 | uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | toolchain: ${{ matrix.rust }} 44 | override: true 45 | 46 | - name: Run tests 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: test 50 | args: --all-features --verbose 51 | 52 | clippy: 53 | name: clippy (ubuntu-latest, stable) 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v3 57 | 58 | - name: Toolchain 59 | uses: actions-rs/toolchain@v1 60 | with: 61 | toolchain: stable 62 | override: true 63 | components: clippy 64 | 65 | - name: Restore cargo cache 66 | uses: actions/cache@v2.1.7 67 | with: 68 | path: | 69 | ~/.cargo/registry 70 | ~/.cargo/git 71 | target 72 | key: ${{ runner.os }}-cargo-stable-${{ hashFiles('Cargo.lock') }} 73 | 74 | - name: Check clippy 75 | uses: actions-rs/cargo@v1 76 | with: 77 | command: clippy 78 | args: -- -D warnings 79 | 80 | fmt: 81 | name: fmt (ubuntu-latest, stable) 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v2 85 | 86 | - name: Toolchain 87 | uses: actions-rs/toolchain@v1 88 | with: 89 | toolchain: stable 90 | override: true 91 | components: rustfmt 92 | 93 | - name: Restore cargo cache 94 | uses: actions/cache@v2.1.7 95 | with: 96 | path: | 97 | ~/.cargo/registry 98 | ~/.cargo/git 99 | target 100 | key: ${{ runner.os }}-cargo-stable-${{ hashFiles('Cargo.lock') }} 101 | 102 | - name: Check format 103 | uses: actions-rs/cargo@v1 104 | with: 105 | command: fmt 106 | args: --all -- --check 107 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": "all" 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "estring" 7 | version = "0.3.0" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "estring" 3 | description = "A simple way to parse a string using type annotations" 4 | version = "0.3.0" 5 | edition = "2021" 6 | authors = ["Dmitriy Pleshevskiy "] 7 | readme = "README.md" 8 | repository = "https://github.com/pleshevskiy/estring" 9 | license = "MIT" 10 | keywords = ["parsing", "type", "annotations", "customizable"] 11 | categories = ["data-structures", "parsing"] 12 | rust-version = "1.59.0" 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [features] 20 | low-level = [] 21 | aggs = [] 22 | structs = [] 23 | 24 | [dependencies] 25 | 26 | [badges] 27 | maintenance = { status = "actively-developed" } 28 | 29 | [[example]] 30 | name = "calc" 31 | required-features = ["structs", "aggs"] 32 | 33 | [[example]] 34 | name = "dotenv" 35 | required-features = ["structs", "low-level"] 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 pleshevskiy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EString 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/estring?style=flat-square)](https://crates.io/crates/estring) 4 | [![docs.rs](https://img.shields.io/docsrs/estring?style=flat-square)](https://docs.rs/estring) 5 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/pleshevskiy/estring/CI?label=tests&logo=github&style=flat-square)](https://github.com/pleshevskiy/estring/actions/workflows/ci.yml) 6 | ![The MSRV](https://img.shields.io/badge/MSRV-1.59.0-red.svg) 7 | 8 | ```toml 9 | [dependencies] 10 | estring = "0.3" 11 | ``` 12 | 13 | A simple way to parse a string using type annotations. 14 | 15 | This package was originally designed for [enve]. 16 | 17 | [enve]: https://github.com/pleshevskiy/enve 18 | 19 | ## [Documentation](https://docs.rs/estring) 20 | 21 | For more details, see [examples]. 22 | 23 | [examples]: https://github.com/pleshevskiy/estring/tree/main/examples 24 | 25 | ## Usage 26 | 27 | Basic 28 | 29 | ```rust 30 | use estring::EString; 31 | 32 | fn main() -> estring::Result<()> { 33 | let res: i32 = EString::from("10").parse()?; 34 | assert_eq!(res, 10); 35 | Ok(()) 36 | } 37 | ``` 38 | 39 | You can use predefined structs like `SepVec` if you enable the `structs` 40 | feature. 41 | 42 | Note: You can use custom types as annotations! Just implement `ParseFragment`! 43 | 44 | ```rust 45 | use estring::{SepVec, EString}; 46 | 47 | type PlusVec = SepVec; 48 | type MulVec = SepVec; 49 | 50 | fn main() -> estring::Result<()> { 51 | let res = EString::from("10+5*2+3") 52 | .parse::>>()? 53 | .iter() 54 | .map(|m| m.iter().product::()) 55 | .sum::(); 56 | 57 | assert_eq!(res, 23.0); 58 | Ok(()) 59 | } 60 | ``` 61 | 62 | You can also use predefined aggregators if you enable the `aggs` feature. 63 | 64 | ```rust 65 | use estring::{Aggregate, EString, Product, SepVec, Sum}; 66 | 67 | type PlusVec = SepVec; 68 | type MulVec = SepVec; 69 | 70 | fn main() -> estring::Result<()> { 71 | let res = EString::from("10+5*2+3") 72 | .parse::>>>>()? 73 | .agg(); 74 | 75 | assert_eq!(res, 23.0); 76 | Ok(()) 77 | } 78 | ``` 79 | 80 | ## Contact Us 81 | 82 | Join us in: 83 | 84 | [![Matrix](https://img.shields.io/badge/matrix-%23enve_team:matrix.org-blueviolet.svg?style=flat-square)](https://matrix.to/#/#enve_team:matrix.org) 85 | 86 | ## License 87 | 88 | **MIT**. See [LICENSE](https://github.com/pleshevskiy/estring/LICENSE) to see 89 | the full text. 90 | 91 | ## Contributors 92 | 93 | [pleshevskiy](https://github.com/pleshevskiy) (Dmitriy Pleshevskiy) – creator, 94 | maintainer. 95 | -------------------------------------------------------------------------------- /examples/calc.rs: -------------------------------------------------------------------------------- 1 | use estring::{Aggregate, EString, Product, SepVec, Sum}; 2 | 3 | type PlusVec = SepVec; 4 | type MulVec = SepVec; 5 | 6 | fn main() -> estring::Result<()> { 7 | let res = EString::from("10+5*2+3") 8 | .parse::>>>>()? 9 | .agg(); 10 | 11 | assert_eq!(res, 23.0); 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/dotenv.rs: -------------------------------------------------------------------------------- 1 | use estring::{EString, Pair, SepVec, Trim}; 2 | 3 | const DOTENV_CONTENT: &str = " 4 | DATABASE_URL=postgres://user:password@localhost:5432/recipes 5 | APP_HOST=http://localhost:3000 6 | "; 7 | 8 | fn main() -> estring::Result<()> { 9 | EString::from(DOTENV_CONTENT) 10 | .parse::>>()? 11 | .iter() 12 | .for_each(|p @ Pair(key, value)| { 13 | println!("pair: {}", p); 14 | 15 | std::env::set_var(key, value); 16 | }); 17 | 18 | println!( 19 | "envs: {:#?}", 20 | std::env::vars() 21 | .filter(|(k, ..)| ["DATABASE_URL", "APP_HOST"].contains(&k.as_str())) 22 | .collect::>() 23 | ); 24 | 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /src/agg.rs: -------------------------------------------------------------------------------- 1 | //! This module will contain aggregate functions (Sum, Product, etc) 2 | //! 3 | 4 | mod product; 5 | mod sum; 6 | 7 | pub use product::*; 8 | pub use sum::*; 9 | -------------------------------------------------------------------------------- /src/agg/product.rs: -------------------------------------------------------------------------------- 1 | use crate::{Aggregatable, Aggregate, EString, ParseFragment}; 2 | 3 | /// Aggregate struct, that can multiply inner aggregatable [items](Aggregatable::items) if 4 | /// [``Aggregatable::Item``] implements [``std::iter::Product``](std::iter::Product) 5 | /// 6 | /// # Examples 7 | /// 8 | /// ```rust 9 | /// use estring::{Aggregate, EString, SepVec, Product}; 10 | /// let res = EString::from("1*2*3*4") 11 | /// .parse::>>() 12 | /// .unwrap() 13 | /// .agg(); 14 | /// assert_eq!(res, 24); 15 | /// ``` 16 | #[derive(Debug, PartialEq, Eq)] 17 | pub struct Product(pub T); 18 | 19 | impl ParseFragment for Product 20 | where 21 | T: ParseFragment, 22 | { 23 | fn parse_frag(es: EString) -> crate::Result { 24 | T::parse_frag(es).map(Self) 25 | } 26 | } 27 | 28 | impl Aggregate for Product 29 | where 30 | R: std::iter::Product, 31 | T: Aggregatable, 32 | { 33 | type Target = R; 34 | 35 | fn agg(self) -> Self::Target { 36 | self.0.items().into_iter().product() 37 | } 38 | } 39 | 40 | impl Aggregatable for Product 41 | where 42 | R: std::iter::Product, 43 | T: Aggregatable, 44 | { 45 | type Item = R; 46 | 47 | fn items(self) -> Vec { 48 | vec![self.agg()] 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use crate::SepVec; 55 | 56 | use super::*; 57 | 58 | type CommaVec = SepVec; 59 | type MulVec = SepVec; 60 | 61 | #[test] 62 | fn should_parse_vec() { 63 | let es = EString::from("1,2,3"); 64 | match es.parse::>>() { 65 | Ok(res) => assert_eq!(res, Product(CommaVec::from(vec![1, 2, 3]))), 66 | _ => unreachable!(), 67 | } 68 | } 69 | 70 | #[test] 71 | fn should_aggregate_vector() { 72 | let es = EString::from("1,2,3"); 73 | let expr = es.parse::>>().unwrap(); 74 | assert_eq!(expr.agg(), 6); 75 | } 76 | 77 | #[test] 78 | fn should_aggregate_vector_with_inner_vector() { 79 | let es = EString::from("1*2,2,3"); 80 | let expr = es.parse::>>>().unwrap(); 81 | assert_eq!(expr.agg(), 12); 82 | } 83 | 84 | #[test] 85 | fn should_aggregate_vector_with_inner_aggregation() { 86 | let es = EString::from("1*2,2,3"); 87 | let expr = es 88 | .parse::>>>>() 89 | .unwrap(); 90 | assert_eq!(expr.agg(), 12); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/agg/sum.rs: -------------------------------------------------------------------------------- 1 | use crate::{Aggregatable, Aggregate, EString, ParseFragment}; 2 | 3 | /// Aggregate struct, that can sum inner aggregatable [items](Aggregatable::items) if 4 | /// [``Aggregatable::Item``] implements [``std::iter::Sum``](std::iter::Sum) 5 | /// 6 | /// # Examples 7 | /// 8 | /// ```rust 9 | /// use estring::{Aggregate, EString, SepVec, Sum}; 10 | /// let res = EString::from("1+2+3+4") 11 | /// .parse::>>() 12 | /// .unwrap() 13 | /// .agg(); 14 | /// assert_eq!(res, 10); 15 | /// ``` 16 | #[derive(Debug, PartialEq, Eq)] 17 | pub struct Sum(pub T); 18 | 19 | impl ParseFragment for Sum 20 | where 21 | T: ParseFragment, 22 | { 23 | fn parse_frag(es: EString) -> crate::Result { 24 | T::parse_frag(es).map(Self) 25 | } 26 | } 27 | 28 | impl Aggregate for Sum 29 | where 30 | R: std::iter::Sum, 31 | T: Aggregatable, 32 | { 33 | type Target = R; 34 | 35 | fn agg(self) -> Self::Target { 36 | self.0.items().into_iter().sum() 37 | } 38 | } 39 | 40 | impl Aggregatable for Sum 41 | where 42 | R: std::iter::Sum, 43 | T: Aggregatable, 44 | { 45 | type Item = R; 46 | 47 | fn items(self) -> Vec { 48 | vec![self.agg()] 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use crate::SepVec; 55 | 56 | use super::*; 57 | 58 | type CommaVec = SepVec; 59 | type PlusVec = SepVec; 60 | 61 | #[test] 62 | fn should_parse_vec() { 63 | let es = EString::from("1,2,3"); 64 | match es.parse::>>() { 65 | Ok(res) => assert_eq!(res, Sum(CommaVec::from(vec![1, 2, 3]))), 66 | _ => unreachable!(), 67 | } 68 | } 69 | 70 | #[test] 71 | fn should_aggregate_vector() { 72 | let es = EString::from("1,2,3"); 73 | let expr = es.parse::>>().unwrap(); 74 | assert_eq!(expr.agg(), 6); 75 | } 76 | 77 | #[test] 78 | fn should_aggregate_vector_with_inner_vector() { 79 | let es = EString::from("1+2,2,3"); 80 | let expr = es.parse::>>>().unwrap(); 81 | assert_eq!(expr.agg(), 8); 82 | } 83 | 84 | #[test] 85 | fn should_aggregate_vector_with_inner_aggregation() { 86 | let es = EString::from("1+2,2,3"); 87 | let expr = es.parse::>>>>().unwrap(); 88 | assert_eq!(expr.agg(), 8); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | //! Contains the ``EString`` type, as well as the basic implementation of conversions to 2 | //! string types 3 | //! 4 | 5 | /// Format this type and wrap into ``EString``. 6 | /// 7 | /// ``ToEString``’s `to_estring` method is often used implicitly, through ``EString``’s from. 8 | /// 9 | /// # Examples 10 | /// 11 | /// Basic implementation of ``ToEString`` on an example ``Point``. 12 | /// 13 | /// ```rust 14 | /// use std::fmt::Write; 15 | /// use estring::{EString, ToEString}; 16 | /// 17 | /// #[derive(Debug, PartialEq)] 18 | /// struct Point { 19 | /// x: i32, 20 | /// y: i32, 21 | /// } 22 | /// 23 | /// impl ToEString for Point { 24 | /// fn to_estring(&self) -> EString { 25 | /// let mut res = String::new(); 26 | /// write!(res, "({},{})", self.x, self.y) 27 | /// .ok() 28 | /// .expect("Cannot format Point into EString"); 29 | /// EString(res) 30 | /// } 31 | /// } 32 | /// 33 | /// let point = Point { x: 1, y: 2 }; 34 | /// assert_eq!(point.to_estring(), EString::from("(1,2)")); 35 | /// ``` 36 | /// 37 | pub trait ToEString { 38 | /// Format this type and returns ``EString``. 39 | /// 40 | /// # Examples 41 | /// 42 | /// ```rust 43 | /// use estring::{EString, ToEString}; 44 | /// 45 | /// let i = 5; 46 | /// let five = EString::from(5); 47 | /// assert_eq!(five, i.to_estring()); 48 | /// ``` 49 | fn to_estring(&self) -> EString; 50 | } 51 | 52 | /// Parse a value fragment from a ``EString``. 53 | /// 54 | /// ``ParseFragment``’s `parse_frag` method is often used implicitly, through ``EString``’s parse. 55 | /// See [parse](EString::parse)’s documentation for examples. 56 | /// 57 | /// # Examples 58 | /// 59 | /// Basic implementation of ``ParseFragment`` on an example ``Point``. 60 | /// 61 | /// ```rust 62 | /// use estring::{EString, ParseFragment, Reason}; 63 | /// 64 | /// #[derive(Debug, PartialEq)] 65 | /// struct Point { 66 | /// x: i32, 67 | /// y: i32, 68 | /// } 69 | /// 70 | /// impl ParseFragment for Point { 71 | /// fn parse_frag(es: EString) -> estring::Result { 72 | /// let orig = es.clone(); 73 | /// let (x, y) = es 74 | /// .trim_matches(|p| p == '(' || p == ')') 75 | /// .split_once(',') 76 | /// .ok_or(estring::Error(orig, Reason::Split))?; 77 | /// 78 | /// let (x, y) = (EString::from(x), EString::from(y)); 79 | /// let x = x.clone().parse::() 80 | /// .map_err(|_| estring::Error(x, Reason::Parse))?; 81 | /// let y = y.clone().parse::() 82 | /// .map_err(|_| estring::Error(y, Reason::Parse))?; 83 | /// 84 | /// Ok(Point { x, y }) 85 | /// } 86 | /// } 87 | /// 88 | /// let fragment = EString::from("(1,2)"); 89 | /// let res = Point::parse_frag(fragment).unwrap(); 90 | /// assert_eq!(res, Point { x: 1, y: 2 }) 91 | /// ``` 92 | /// 93 | pub trait ParseFragment: Sized { 94 | /// Parses a ``EString`` fragment `es` to return a value of this type. 95 | /// 96 | /// # Errors 97 | /// 98 | /// If parsing is not succeeds, returns ``Error`` inside ``Err`` with original fragment `es` 99 | /// and reason ``Reason``. 100 | /// 101 | /// # Examples 102 | /// 103 | /// ```rust 104 | /// use estring::{EString, ParseFragment}; 105 | /// 106 | /// let fragment = EString::from("5"); 107 | /// let res = i32::parse_frag(fragment).unwrap(); 108 | /// assert_eq!(res, 5); 109 | /// ``` 110 | fn parse_frag(es: EString) -> crate::Result; 111 | } 112 | 113 | // TODO: add example 114 | /// Trait to represent structures that can act as aggregators. 115 | /// 116 | /// The `agg` method works with data that has already been parsed. For this reason, **this trait 117 | /// should never fail**. 118 | pub trait Aggregate { 119 | /// The resulting type after aggregation. 120 | type Target: ?Sized; 121 | 122 | /// Aggregates the value. 123 | fn agg(self) -> Self::Target; 124 | } 125 | 126 | // TODO: add example 127 | /// Trait to represent structures that can iterate values for the aggregator. 128 | pub trait Aggregatable { 129 | /// The type of the elements being iterated over. 130 | type Item; 131 | 132 | /// Returns Vec of aggregatable values 133 | fn items(self) -> Vec; 134 | } 135 | 136 | /// Wrapper under ``String`` type. 137 | /// 138 | /// # Examples 139 | /// 140 | /// You can create a ``EString`` from a any type that implement ``ToEString`` with ``EString::from`` 141 | /// 142 | /// ```rust 143 | /// # use estring::EString; 144 | /// let hello = EString::from("Hello, world"); 145 | /// let num = EString::from("999"); 146 | /// ``` 147 | /// 148 | /// You can use ``ToEString::to_estring`` directly on the type. 149 | /// 150 | /// ```rust 151 | /// # use estring::ToEString; 152 | /// let some_opt = Some(999).to_estring(); 153 | /// let none_opt = None::.to_estring(); 154 | /// ``` 155 | /// 156 | #[derive(Debug, Default, PartialEq, Eq, Clone)] 157 | pub struct EString(pub String); 158 | 159 | impl std::fmt::Display for EString { 160 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 161 | write!(f, "{}", self.0) 162 | } 163 | } 164 | 165 | impl EString { 166 | /// Creates a new empty ``EString``. 167 | /// 168 | /// This will not allocate any inital buffer. 169 | /// 170 | /// # Examples 171 | /// 172 | /// Basic usage: 173 | /// 174 | /// ```rust 175 | /// # use estring::EString; 176 | /// let s = EString::new(); 177 | /// ``` 178 | #[must_use] 179 | #[inline] 180 | pub fn new() -> Self { 181 | Self(String::new()) 182 | } 183 | 184 | /// Parses this inner string into another type. 185 | /// 186 | /// `parse` can parse into any type that implements the ``ParseFragment`` trait. 187 | /// 188 | /// # Errors 189 | /// 190 | /// Will return `Err` if estring cannot parse inner fragment into the desired type. 191 | /// 192 | /// # Examples 193 | /// 194 | /// Basic usage 195 | /// 196 | /// ```rust 197 | /// # use estring::{EString, ParseFragment}; 198 | /// let fragment = EString::from("5"); 199 | /// let res = i32::parse_frag(fragment); 200 | /// assert_eq!(res, Ok(5)); 201 | /// ``` 202 | /// 203 | /// Failing to parse: 204 | /// 205 | /// ```rust 206 | /// # use estring::{EString, ParseFragment, Error, Reason}; 207 | /// let fragment = EString::from("j"); 208 | /// let res = i32::parse_frag(fragment.clone()); 209 | /// assert_eq!(res, Err(Error(fragment, Reason::Parse))); 210 | /// ``` 211 | #[inline] 212 | pub fn parse(self) -> crate::Result { 213 | T::parse_frag(self) 214 | } 215 | } 216 | 217 | impl From for EString 218 | where 219 | T: ToEString, 220 | { 221 | #[inline] 222 | fn from(val: T) -> Self { 223 | val.to_estring() 224 | } 225 | } 226 | 227 | impl std::ops::Deref for EString { 228 | type Target = String; 229 | 230 | #[inline] 231 | fn deref(&self) -> &Self::Target { 232 | &self.0 233 | } 234 | } 235 | 236 | impl ParseFragment for EString { 237 | #[inline] 238 | fn parse_frag(es: EString) -> crate::Result { 239 | Ok(es) 240 | } 241 | } 242 | 243 | #[cfg(feature = "aggs")] 244 | impl Aggregatable for EString { 245 | type Item = Self; 246 | 247 | #[inline] 248 | fn items(self) -> Vec { 249 | vec![self] 250 | } 251 | } 252 | 253 | impl ParseFragment for String { 254 | #[inline] 255 | fn parse_frag(es: EString) -> crate::Result { 256 | Ok(es.0) 257 | } 258 | } 259 | 260 | impl ToEString for String { 261 | #[inline] 262 | fn to_estring(&self) -> EString { 263 | EString(self.clone()) 264 | } 265 | } 266 | 267 | #[cfg(feature = "aggs")] 268 | impl Aggregatable for String { 269 | type Item = Self; 270 | 271 | #[inline] 272 | fn items(self) -> Vec { 273 | vec![self] 274 | } 275 | } 276 | 277 | impl ParseFragment for &'static str { 278 | #[inline] 279 | fn parse_frag(es: EString) -> crate::Result { 280 | Ok(Box::leak(es.0.into_boxed_str())) 281 | } 282 | } 283 | 284 | impl<'a> ToEString for &'a str { 285 | #[inline] 286 | fn to_estring(&self) -> EString { 287 | EString((*self).to_string()) 288 | } 289 | } 290 | 291 | #[cfg(feature = "aggs")] 292 | impl<'a> Aggregatable for &'a str { 293 | type Item = Self; 294 | 295 | #[inline] 296 | fn items(self) -> Vec { 297 | vec![self] 298 | } 299 | } 300 | 301 | #[cfg(test)] 302 | mod tests { 303 | use super::*; 304 | 305 | #[test] 306 | fn should_deref_to_string() { 307 | let estr = EString::from("hello"); 308 | assert_eq!(*estr, String::from("hello")); 309 | } 310 | 311 | #[test] 312 | fn should_parse_into_itself() { 313 | let estr = EString::from("hello"); 314 | match estr.parse::() { 315 | Ok(res) => assert_eq!(res, EString::from("hello")), 316 | _ => unreachable!(), 317 | } 318 | } 319 | 320 | #[test] 321 | fn should_parse_into_string() { 322 | let estr = EString::from("hello"); 323 | match estr.parse::() { 324 | Ok(res) => assert_eq!(res, String::from("hello")), 325 | _ => unreachable!(), 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::core::EString; 2 | 3 | /// The error type for operations interacting with ``EString``’s fragments. 4 | #[derive(Debug, PartialEq, Eq)] 5 | pub struct Error(pub EString, pub Reason); 6 | 7 | /// The reason for the failure to parse. 8 | #[derive(Debug, PartialEq, Eq)] 9 | pub enum Reason { 10 | /// Cannot split fragment 11 | Split, 12 | /// Cannot parse fragment 13 | Parse, 14 | } 15 | 16 | impl std::fmt::Display for Error { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | write!( 19 | f, 20 | r#"Failed to parse "{:?}" with reason {:?}"#, 21 | self.0, self.1 22 | ) 23 | } 24 | } 25 | 26 | impl std::error::Error for Error {} 27 | 28 | impl std::ops::Deref for Error { 29 | type Target = String; 30 | 31 | fn deref(&self) -> &Self::Target { 32 | &self.0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! # ``EString`` 3 | //! 4 | //! A simple way to parse a string using type annotations. 5 | //! 6 | //! This package was originally designed for [enve] 7 | //! 8 | //! [enve]: https://github.com/pleshevskiy/enve 9 | //! 10 | //! ## Usage 11 | //! 12 | //! Basic 13 | //! 14 | //! ```rust 15 | //! use estring::EString; 16 | //! 17 | //! fn main() -> estring::Result<()> { 18 | //! let res: i32 = EString::from("10").parse()?; 19 | //! assert_eq!(res, 10); 20 | //! Ok(()) 21 | //! } 22 | //! ``` 23 | //! 24 | //! You can use predefined structs like ``SepVec`` if you enable `structs` feature. 25 | //! 26 | //! Note: You can use custom types as annotations! Just implement ``ParseFragment``! 27 | //! 28 | //! ```rust 29 | //! use estring::{SepVec, EString}; 30 | //! 31 | //! type PlusVec = SepVec; 32 | //! type MulVec = SepVec; 33 | //! 34 | //! fn main() -> estring::Result<()> { 35 | //! let res = EString::from("10+5*2+3") 36 | //! .parse::>>()? 37 | //! .iter() 38 | //! .map(|m| m.iter().product::()) 39 | //! .sum::(); 40 | //! 41 | //! assert_eq!(res, 23.0); 42 | //! Ok(()) 43 | //! } 44 | //! ``` 45 | //! 46 | //! You can also use predefined aggregators if you enable `aggs` feature. 47 | //! 48 | //! ```rust 49 | //! use estring::{Aggregate, EString, Product, SepVec, Sum}; 50 | //! 51 | //! type PlusVec = SepVec; 52 | //! type MulVec = SepVec; 53 | //! 54 | //! fn main() -> estring::Result<()> { 55 | //! let res = EString::from("10+5*2+3") 56 | //! .parse::>>>>()? 57 | //! .agg(); 58 | //! 59 | //! assert_eq!(res, 23.0); 60 | //! Ok(()) 61 | //! } 62 | //! ``` 63 | //! 64 | //! --- 65 | //! 66 | //! For more details, see [examples]. 67 | //! 68 | //! [examples]: https://github.com/pleshevskiy/estring/tree/main/examples 69 | //! 70 | #![deny(clippy::pedantic)] 71 | #![allow(clippy::module_name_repetitions)] 72 | #![warn(missing_docs)] 73 | 74 | mod error; 75 | pub use error::{Error, Reason}; 76 | /// The type returned by parser methods. 77 | /// 78 | /// # Examples 79 | /// 80 | /// ```rust 81 | /// use estring::{EString, ParseFragment, Reason}; 82 | /// 83 | /// #[derive(Debug, PartialEq)] 84 | /// struct Point { 85 | /// x: i32, 86 | /// y: i32, 87 | /// } 88 | /// 89 | /// impl ParseFragment for Point { 90 | /// fn parse_frag(es: EString) -> estring::Result { 91 | /// let orig = es.clone(); 92 | /// let (x, y) = es 93 | /// .trim_matches(|p| p == '(' || p == ')') 94 | /// .split_once(',') 95 | /// .ok_or(estring::Error(orig, Reason::Split))?; 96 | /// 97 | /// let (x, y) = (EString::from(x), EString::from(y)); 98 | /// let x = x.clone().parse::() 99 | /// .map_err(|_| estring::Error(x, Reason::Parse))?; 100 | /// let y = y.clone().parse::() 101 | /// .map_err(|_| estring::Error(y, Reason::Parse))?; 102 | /// 103 | /// Ok(Point { x, y }) 104 | /// } 105 | /// } 106 | /// 107 | /// let fragment = EString::from("(1,2)"); 108 | /// let res = Point::parse_frag(fragment).unwrap(); 109 | /// assert_eq!(res, Point { x: 1, y: 2 }) 110 | /// ``` 111 | pub type Result = ::std::result::Result; 112 | 113 | pub mod core; 114 | pub mod std; 115 | 116 | #[cfg(feature = "aggs")] 117 | pub mod agg; 118 | #[cfg(feature = "aggs")] 119 | pub use agg::*; 120 | 121 | #[cfg(feature = "low-level")] 122 | pub mod low; 123 | #[cfg(feature = "low-level")] 124 | pub use low::*; 125 | #[cfg(feature = "structs")] 126 | pub mod structs; 127 | #[cfg(feature = "structs")] 128 | pub use structs::*; 129 | 130 | pub use crate::core::*; 131 | -------------------------------------------------------------------------------- /src/low.rs: -------------------------------------------------------------------------------- 1 | //! Contains the low-level api parse string more accurate! 2 | //! 3 | //! **NOTE**: Require the enabling the `low-level` feature. 4 | //! 5 | 6 | mod trim; 7 | pub use trim::*; 8 | -------------------------------------------------------------------------------- /src/low/trim.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{EString, ParseFragment, ToEString}; 2 | 3 | /// Wrapper that allow to trim substring before continue 4 | /// 5 | /// **NOTE**: Required the enabling of the `low-level` feature. 6 | /// 7 | /// # Examples 8 | /// 9 | /// ```rust 10 | /// use estring::{EString, Trim}; 11 | /// 12 | /// fn main() -> estring::Result<()> { 13 | /// let res = EString::from(" 99 ").parse::>()?; 14 | /// assert_eq!(res, Trim(99)); 15 | /// Ok(()) 16 | /// } 17 | /// ``` 18 | /// 19 | #[derive(Debug, PartialEq, Eq)] 20 | pub struct Trim(pub T); 21 | 22 | impl std::ops::Deref for Trim { 23 | type Target = T; 24 | 25 | fn deref(&self) -> &Self::Target { 26 | &self.0 27 | } 28 | } 29 | 30 | impl std::fmt::Display for Trim { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | write!(f, "{}", self.0) 33 | } 34 | } 35 | 36 | impl ParseFragment for Trim 37 | where 38 | T: ParseFragment, 39 | { 40 | fn parse_frag(value: EString) -> crate::Result { 41 | T::parse_frag(EString::from(value.trim())).map(Trim) 42 | } 43 | } 44 | 45 | impl ToEString for Trim 46 | where 47 | T: ToEString, 48 | { 49 | fn to_estring(&self) -> EString { 50 | self.0.to_estring() 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn should_trim_string() { 60 | let estr = EString::from(" 999 "); 61 | 62 | match estr.parse::>() { 63 | Ok(res) => assert_eq!(*res, "999"), 64 | _ => unreachable!(), 65 | } 66 | } 67 | 68 | #[test] 69 | fn should_trim_and_convert_to_number() { 70 | let estr = EString::from(" 999 "); 71 | 72 | match estr.parse::>() { 73 | Ok(res) => assert_eq!(*res, 999), 74 | _ => unreachable!(), 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/std.rs: -------------------------------------------------------------------------------- 1 | //! Contains implementations for standard types (`bool`, numbers, `Option`, etc.) 2 | //! 3 | 4 | mod bool; 5 | mod number; 6 | mod option; 7 | -------------------------------------------------------------------------------- /src/std/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{EString, ParseFragment, ToEString}; 2 | use crate::error::{Error, Reason}; 3 | 4 | impl ParseFragment for bool { 5 | #[inline] 6 | fn parse_frag(s: EString) -> crate::Result { 7 | match s.to_lowercase().as_str() { 8 | "true" | "t" | "yes" | "y" | "on" | "1" => Ok(true), 9 | "false" | "f" | "no" | "n" | "off" | "0" | "" => Ok(false), 10 | _ => Err(Error(s, Reason::Parse)), 11 | } 12 | } 13 | } 14 | 15 | impl ToEString for bool { 16 | #[inline] 17 | fn to_estring(&self) -> EString { 18 | EString(self.to_string()) 19 | } 20 | } 21 | 22 | #[cfg(feature = "aggs")] 23 | impl crate::core::Aggregatable for bool { 24 | type Item = Self; 25 | 26 | #[inline] 27 | fn items(self) -> Vec { 28 | vec![self] 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn should_parse_bool_variable() { 38 | let test_cases = [ 39 | ("1", true), 40 | ("0", false), 41 | ("y", true), 42 | ("f", false), 43 | ("yes", true), 44 | ("true", true), 45 | ("false", false), 46 | ("t", true), 47 | ("f", false), 48 | ("on", true), 49 | ("off", false), 50 | ]; 51 | 52 | for (val, expected) in test_cases { 53 | let estr = EString::from(val); 54 | match estr.parse::() { 55 | Ok(res) => assert_eq!(res, expected), 56 | _ => unreachable!(), 57 | }; 58 | } 59 | } 60 | 61 | #[test] 62 | fn should_throw_parse_error() { 63 | let estr = EString::from("something"); 64 | match estr.parse::() { 65 | Err(crate::Error(orig, reason)) => { 66 | assert_eq!(orig, EString::from("something")); 67 | assert_eq!(reason, crate::Reason::Parse); 68 | } 69 | _ => unreachable!(), 70 | }; 71 | } 72 | 73 | #[test] 74 | fn should_format_bool() { 75 | assert_eq!(true.to_estring(), EString(String::from("true"))); 76 | assert_eq!(false.to_estring(), EString(String::from("false"))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/std/number.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{EString, ParseFragment, ToEString}; 2 | use crate::error::{Error, Reason}; 3 | 4 | #[doc(hidden)] 5 | macro_rules! from_env_string_numbers_impl { 6 | ($($ty:ty),+$(,)?) => { 7 | $( 8 | impl ParseFragment for $ty { 9 | #[inline] 10 | fn parse_frag(s: EString) -> crate::Result { 11 | s.0.parse::().map_err(|_| Error(s, Reason::Parse)) 12 | } 13 | } 14 | 15 | impl ToEString for $ty { 16 | #[inline] 17 | fn to_estring(&self) -> EString { 18 | EString(self.to_string()) 19 | } 20 | } 21 | 22 | #[cfg(feature = "aggs")] 23 | impl crate::core::Aggregatable for $ty { 24 | type Item = Self; 25 | 26 | #[inline] 27 | fn items(self) -> Vec { 28 | vec![self] 29 | } 30 | } 31 | )+ 32 | }; 33 | } 34 | 35 | #[rustfmt::skip] 36 | from_env_string_numbers_impl![ 37 | i8, i16, i32, i64, i128, isize, 38 | u8, u16, u32, u64, u128, usize, 39 | f32, f64 40 | ]; 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn should_parse_number() { 48 | let estr = EString::from("-10"); 49 | match estr.parse::() { 50 | Ok(res) => assert_eq!(res, -10), 51 | _ => unreachable!(), 52 | }; 53 | } 54 | 55 | #[test] 56 | fn should_parse_float_number() { 57 | let estr = EString::from("-0.15"); 58 | match estr.parse::() { 59 | #[allow(clippy::float_cmp)] 60 | Ok(res) => assert_eq!(res, -0.15), 61 | _ => unreachable!(), 62 | }; 63 | } 64 | 65 | #[test] 66 | fn should_throw_parse_error() { 67 | let estr = EString::from("-10"); 68 | match estr.parse::() { 69 | Err(Error(orig, reason)) => { 70 | assert_eq!(orig, EString::from("-10")); 71 | assert_eq!(reason, Reason::Parse); 72 | } 73 | _ => unreachable!(), 74 | }; 75 | } 76 | 77 | #[test] 78 | fn should_format_number() { 79 | assert_eq!((-1).to_estring(), EString(String::from("-1"))); 80 | assert_eq!(10.to_estring(), EString(String::from("10"))); 81 | assert_eq!(1.1.to_estring(), EString(String::from("1.1"))); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/std/option.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{EString, ParseFragment, ToEString}; 2 | 3 | impl ToEString for Option 4 | where 5 | T: ToEString, 6 | { 7 | fn to_estring(&self) -> EString { 8 | match self { 9 | Some(inner) => inner.to_estring(), 10 | None => EString::new(), 11 | } 12 | } 13 | } 14 | 15 | impl ParseFragment for Option 16 | where 17 | T: ParseFragment, 18 | { 19 | fn parse_frag(es: EString) -> crate::Result { 20 | if es.is_empty() { 21 | Ok(None) 22 | } else { 23 | es.parse().map(Some) 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "aggs")] 29 | impl crate::core::Aggregatable for Option 30 | where 31 | T: crate::core::Aggregatable, 32 | { 33 | type Item = T::Item; 34 | 35 | fn items(self) -> Vec { 36 | self.map(T::items).unwrap_or_default() 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use crate::structs::Pair; 44 | 45 | #[test] 46 | fn should_parse_empty_string_as_none() { 47 | let estr = EString::new(); 48 | match estr.parse::>() { 49 | Ok(res) => assert_eq!(res, None), 50 | _ => unreachable!(), 51 | } 52 | } 53 | 54 | #[test] 55 | fn should_parse_number_as_some() { 56 | let estr = EString::from("99"); 57 | match estr.parse::>() { 58 | Ok(res) => assert_eq!(res, Some(99)), 59 | _ => unreachable!(), 60 | } 61 | } 62 | 63 | #[test] 64 | fn should_parse_pair() { 65 | let estr = EString::from("1+2"); 66 | match estr.parse::>>() { 67 | Ok(res) => assert_eq!(res, Some(Pair(1, 2))), 68 | _ => unreachable!(), 69 | } 70 | } 71 | 72 | #[test] 73 | fn should_format_option() { 74 | assert_eq!(None::.to_estring(), EString::new()); 75 | assert_eq!(Some(99).to_estring(), EString(String::from("99"))); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/structs.rs: -------------------------------------------------------------------------------- 1 | //! Contains the predefined types (``SepVec``, ``Pair``, etc.) 2 | //! 3 | //! **NOTE**: Require the enabling the `structs` feature. 4 | //! 5 | 6 | mod pair; 7 | mod sep_vec; 8 | mod trio; 9 | 10 | pub use pair::*; 11 | pub use sep_vec::*; 12 | pub use trio::*; 13 | -------------------------------------------------------------------------------- /src/structs/pair.rs: -------------------------------------------------------------------------------- 1 | //! Contains the implementations to pair tuple type 2 | //! 3 | 4 | use crate::core::{EString, ParseFragment, ToEString}; 5 | use crate::{Error, Reason}; 6 | use std::fmt::Write; 7 | 8 | /// Wrapper for pair (A, B) tuple to split string by a separator (`S1`). 9 | /// 10 | /// **NOTE**: Required the enabling of the `structs` feature. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ```rust 15 | /// use estring::{Pair, EString}; 16 | /// 17 | /// type EqPair = Pair) -> std::fmt::Result { 42 | write!(f, "{}{}{}", self.0, S1, self.1) 43 | } 44 | } 45 | 46 | impl ToEString for Pair 47 | where 48 | A: ToEString, 49 | B: ToEString, 50 | { 51 | fn to_estring(&self) -> EString { 52 | let mut res = String::new(); 53 | write!(res, "{}{}{}", self.0.to_estring(), S1, self.1.to_estring()) 54 | .ok() 55 | .expect("Cannot parse Pair to EString"); 56 | EString(res) 57 | } 58 | } 59 | 60 | impl ParseFragment for Pair 61 | where 62 | A: ParseFragment, 63 | B: ParseFragment, 64 | { 65 | fn parse_frag(value: EString) -> crate::Result { 66 | value 67 | .clone() 68 | .split_once(S1) 69 | .ok_or(Error(value, Reason::Split)) 70 | .and_then(|(a, b)| { 71 | let (a, b) = (EString::from(a), EString::from(b)); 72 | let a = A::parse_frag(a.clone()).map_err(|_| Error(a, Reason::Parse))?; 73 | let b = B::parse_frag(b.clone()).map_err(|_| Error(b, Reason::Parse))?; 74 | Ok(Self(a, b)) 75 | }) 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use super::*; 82 | use crate::structs::SepVec; 83 | 84 | type EqPair = Pair; 105 | 106 | #[test] 107 | fn should_parse_vec_of_pairs() { 108 | let estr = EString::from( 109 | "foo=bar 110 | hello=bar", 111 | ); 112 | match estr.parse::>>() { 113 | Ok(res) => assert_eq!(res, SepVec(vec![Pair("foo", "bar"), Pair("hello", "bar"),])), 114 | _ => unreachable!(), 115 | }; 116 | } 117 | 118 | #[test] 119 | fn should_format_pair() { 120 | let pair = Pair::<_, '+', _>(1, 2); 121 | assert_eq!(pair.to_estring(), EString(String::from("1+2"))); 122 | let pair_in_pair = Pair::<_, '=', _>(3, pair); 123 | assert_eq!(pair_in_pair.to_estring(), EString(String::from("3=1+2"))); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/structs/sep_vec.rs: -------------------------------------------------------------------------------- 1 | //! Contains the implementations to vec type 2 | //! 3 | 4 | use crate::core::{EString, ParseFragment, ToEString}; 5 | use std::fmt::Write; 6 | 7 | /// Wrapper for ``Vec`` to split string by a separator (`SEP`). 8 | /// 9 | /// **NOTE**: Required the enabling of the `structs` feature. 10 | /// 11 | /// # Examples 12 | /// 13 | /// ```rust 14 | /// use estring::{SepVec, EString}; 15 | /// 16 | /// type CommaVec = SepVec; 17 | /// 18 | /// fn main() -> estring::Result<()> { 19 | /// let res = EString::from("1,2,3").parse::>()?; 20 | /// assert_eq!(*res, vec![1, 2, 3]); 21 | /// Ok(()) 22 | /// } 23 | /// ``` 24 | /// 25 | #[derive(Debug, PartialEq, Clone)] 26 | pub struct SepVec(pub Vec); 27 | 28 | impl std::ops::Deref for SepVec { 29 | type Target = Vec; 30 | 31 | #[inline] 32 | fn deref(&self) -> &Self::Target { 33 | &self.0 34 | } 35 | } 36 | 37 | impl From> for SepVec { 38 | #[inline] 39 | fn from(vec: Vec) -> Self { 40 | Self(vec) 41 | } 42 | } 43 | 44 | impl std::fmt::Display for SepVec 45 | where 46 | T: std::fmt::Display, 47 | { 48 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 49 | self.0.iter().enumerate().try_for_each(|(i, part)| { 50 | if i != 0 { 51 | f.write_char(SEP)?; 52 | } 53 | 54 | write!(f, "{}", part) 55 | }) 56 | } 57 | } 58 | 59 | impl ToEString for SepVec 60 | where 61 | T: ToEString, 62 | { 63 | fn to_estring(&self) -> EString { 64 | self.0 65 | .iter() 66 | .enumerate() 67 | .try_fold(String::new(), |mut res, (i, part)| { 68 | if i != 0 { 69 | res.write_char(SEP).ok()?; 70 | } 71 | 72 | write!(res, "{}", part.to_estring()).ok()?; 73 | Some(res) 74 | }) 75 | .map(EString) 76 | .expect("Cannot format SepVec ${self.0} to EString") 77 | } 78 | } 79 | 80 | impl ParseFragment for SepVec 81 | where 82 | T: ParseFragment, 83 | { 84 | fn parse_frag(value: EString) -> crate::Result { 85 | let inner = value 86 | .split(SEP) 87 | .map(str::trim) 88 | .map(EString::from) 89 | .map(T::parse_frag) 90 | .collect::>>()?; 91 | Ok(Self(inner)) 92 | } 93 | } 94 | 95 | #[cfg(feature = "aggs")] 96 | impl crate::core::Aggregatable for SepVec 97 | where 98 | T: crate::core::Aggregatable, 99 | { 100 | type Item = T::Item; 101 | 102 | fn items(self) -> Vec { 103 | self.0.into_iter().flat_map(T::items).collect() 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::*; 110 | use crate::Aggregatable; 111 | use crate::Pair; 112 | use crate::{Error, Reason}; 113 | 114 | type CommaVec = SepVec; 115 | type SemiVec = SepVec; 116 | 117 | #[test] 118 | fn should_parse_into_vec() { 119 | let estr = EString::from("a,b,c,d,e"); 120 | match estr.parse::>() { 121 | Ok(res) => assert_eq!(*res, vec!["a", "b", "c", "d", "e"]), 122 | _ => unreachable!(), 123 | }; 124 | } 125 | 126 | #[test] 127 | fn should_trim_identations_before_parsing() { 128 | let input = " 129 | a , b, c, 130 | d,e"; 131 | let estr = EString::from(input); 132 | match estr.parse::>() { 133 | Ok(res) => assert_eq!(*res, vec!["a", "b", "c", "d", "e"]), 134 | _ => unreachable!(), 135 | }; 136 | } 137 | 138 | #[test] 139 | fn should_parse_into_vector_of_vectors() { 140 | let estr = EString::from("a,b; c,d,e; f,g"); 141 | match estr.parse::>>() { 142 | Ok(res) => assert_eq!( 143 | res, 144 | SemiVec::from(vec![ 145 | CommaVec::from(vec!["a", "b"]), 146 | CommaVec::from(vec!["c", "d", "e"]), 147 | CommaVec::from(vec!["f", "g"]) 148 | ]) 149 | ), 150 | _ => unreachable!(), 151 | }; 152 | } 153 | 154 | #[test] 155 | fn should_parse_into_num_vec() { 156 | let estr = EString::from("1,2,3,4,5"); 157 | match estr.parse::>() { 158 | Ok(res) => assert_eq!(*res, vec![1, 2, 3, 4, 5]), 159 | _ => unreachable!(), 160 | }; 161 | } 162 | 163 | #[test] 164 | fn should_throw_parse_vec_error() { 165 | let estr = EString::from("1,2,3,4,5"); 166 | match estr.parse::>() { 167 | Err(Error(orig, reason)) => { 168 | assert_eq!(orig, EString::from("1,2,3,4,5")); 169 | assert_eq!(reason, Reason::Parse); 170 | } 171 | _ => unreachable!(), 172 | }; 173 | } 174 | 175 | #[test] 176 | fn should_format_vec() { 177 | type PlusPair = Pair; 178 | 179 | let vec = SepVec::<_, ','>::from(vec![1, 2, 3]); 180 | assert_eq!(vec.to_estring(), EString(String::from("1,2,3"))); 181 | let vec = SepVec::<_, ','>::from(vec![PlusPair::from((1, 2)), PlusPair::from((3, 4))]); 182 | assert_eq!(vec.to_estring(), EString(String::from("1+2,3+4"))); 183 | } 184 | 185 | #[test] 186 | fn should_returns_aggregatable_items() { 187 | let estr = EString::from("1,2,3,4,5"); 188 | let res = estr.parse::>().unwrap(); 189 | assert_eq!(res.items(), vec![1, 2, 3, 4, 5]); 190 | } 191 | 192 | #[test] 193 | fn should_returns_flatten_aggregatable_items() { 194 | let estr = EString::from("1,2; 3,4,5; 6,7"); 195 | let res = estr.parse::>>().unwrap(); 196 | assert_eq!(res.items(), vec![1, 2, 3, 4, 5, 6, 7]); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/structs/trio.rs: -------------------------------------------------------------------------------- 1 | //! Contains the implementations to parse triple-tuple type 2 | //! 3 | 4 | use super::Pair; 5 | use crate::core::{EString, ParseFragment, ToEString}; 6 | use std::fmt::Write; 7 | 8 | /// Wrapper for trio (A, B, C) tuple to split string by separators (`S1` and `S2`). 9 | /// 10 | /// **NOTE**: Required the enabling of the `structs` feature. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ```rust 15 | /// use estring::{Trio, EString}; 16 | /// 17 | /// fn main() -> estring::Result<()> { 18 | /// let res = EString::from("one+two=free").parse::) -> std::fmt::Result { 41 | f.write_str(&self.0.to_string())?; 42 | f.write_char(S1)?; 43 | f.write_str(&self.1.to_string())?; 44 | f.write_char(S2)?; 45 | f.write_str(&self.2.to_string()) 46 | } 47 | } 48 | 49 | impl ToEString for Trio 50 | where 51 | A: ToEString, 52 | B: ToEString, 53 | C: ToEString, 54 | { 55 | fn to_estring(&self) -> EString { 56 | let mut res = String::new(); 57 | write!( 58 | res, 59 | "{}{}{}{}{}", 60 | self.0.to_estring(), 61 | S1, 62 | self.1.to_estring(), 63 | S2, 64 | self.2.to_estring() 65 | ) 66 | .ok() 67 | .expect("Cannot parse Pair to EString"); 68 | EString(res) 69 | } 70 | } 71 | 72 | impl ParseFragment for Trio 73 | where 74 | A: ParseFragment, 75 | B: ParseFragment, 76 | C: ParseFragment, 77 | { 78 | fn parse_frag(value: EString) -> crate::Result { 79 | Pair::::parse_frag(value).and_then(|Pair(a, rest)| { 80 | Pair::::parse_frag(rest).map(|Pair(b, c)| Self(a, b, c)) 81 | }) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | 89 | type EqTrio = Trio; 90 | 91 | #[test] 92 | fn should_parse_into_trio() { 93 | let estr = EString::from("hello=world=hello"); 94 | match estr.parse::>() { 95 | Ok(res) => assert_eq!((res.0, res.1, res.2), ("hello", "world", "hello")), 96 | _ => unreachable!(), 97 | }; 98 | } 99 | 100 | #[test] 101 | fn should_parse_into_trio_with_alternate_delims() { 102 | let estr = EString::from("hello-world^hello"); 103 | match estr.parse::>() { 104 | Ok(res) => assert_eq!((res.0, res.1, res.2), ("hello", "world", "hello")), 105 | _ => unreachable!(), 106 | }; 107 | } 108 | 109 | #[test] 110 | fn should_parse_rest_as_trio() { 111 | let estr = EString::from("hello=world=hello=world=hello"); 112 | match estr.parse::>>() { 113 | Ok(res) => assert_eq!(res, Trio("hello", "world", Trio("hello", "world", "hello"))), 114 | _ => unreachable!(), 115 | }; 116 | } 117 | 118 | #[test] 119 | fn should_format_trio() { 120 | let trio = Trio::<_, '+', _, '-', _>::from(("foo", "baz", "bar")); 121 | assert_eq!( 122 | trio.clone().to_estring(), 123 | EString(String::from("foo+baz-bar")) 124 | ); 125 | 126 | let trio_in_trio = Trio::<_, '*', _, '=', _>::from(("foo", "baz", trio)); 127 | assert_eq!( 128 | trio_in_trio.clone().to_estring(), 129 | EString(String::from("foo*baz=foo+baz-bar")) 130 | ); 131 | } 132 | } 133 | --------------------------------------------------------------------------------