From 8264597fcd7626ca25f85c9b2bb3b01cfa9a7c1e Mon Sep 17 00:00:00 2001 From: Dekirisu Date: Mon, 12 Sep 2022 17:17:37 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20v0.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + CHANGELOG.md | 4 + Cargo.toml | 20 +++ LICENSE-APACHE | 201 ++++++++++++++++++++++++ LICENSE-MIT | 21 +++ README.md | 234 ++++++++++++++++++++++++++++ derive/.gitignore | 2 + derive/Cargo.toml | 18 +++ derive/README.md | 7 + derive/src/lib.rs | 219 ++++++++++++++++++++++++++ examples/cascade.rs | 31 ++++ examples/static_per_file.rs | 22 +++ examples/usage.rs | 106 +++++++++++++ src/lib.rs | 302 ++++++++++++++++++++++++++++++++++++ 14 files changed, 1189 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 derive/.gitignore create mode 100644 derive/Cargo.toml create mode 100644 derive/README.md create mode 100644 derive/src/lib.rs create mode 100644 examples/cascade.rs create mode 100644 examples/static_per_file.rs create mode 100644 examples/usage.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a6f33a1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# v0.1.0 (2022-09-12) + +## First Version +* Easy access to field via crates **strung** `./` and **strung_derive** `./derive`. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8301b92 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "strung" +version = "0.1.0" +edition = "2021" + +authors = ["Dekirisu "] +license = "MIT OR Apache-2.0" +description = "Easy access of struct fields in strings using different/custom pre/postfix: \"Hello, {field}\"" +repository = "https://github.com/dekirisu/strung/" +keywords = ["string","struct","format","fmt","replace"] +categories = ["value-formatting","rust-patterns"] + +[dependencies] +strung_derive = {version="0.1.0", optional=true} + +[patch.crates-io] +strung_derive = {path="./derive"} + +[features] +default = ["dep:strung_derive"] \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..a9d2e0e --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2022 Dekirisu + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..59d64da --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Dekirisu + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..38a6afe --- /dev/null +++ b/README.md @@ -0,0 +1,234 @@ +[![GitHub](https://img.shields.io/badge/github-dekirisu/strung-ee6677)](https://github.com/dekirisu/strung) +[![Version](https://img.shields.io/crates/v/strung)](https://crates.io/crates/strung) +[![Docs](https://img.shields.io/docsrs/strung)](https://crates.io/crates/strung) +[![License](https://img.shields.io/crates/l/strung)](https://crates.io/crates/strung) +[![Discord](https://img.shields.io/discord/515100001903312898)](https://discord.gg/kevWvBuPFg) + +String formatter/builder with easy access of struct fields, which implement the *Display* trait. +If they do not, they can be marked to be ignored. + +# Usage +Here's most you have to know! +```rust +use strung::prelude::*; // import everything from prelude + +fn main(){ + + // create structs - defined below cause instant action! + let NAMED = Test {num: 1, name: "st"}; + let TUPLE = TestTup (1, "st"); + let CUSTOM = TestCustom {num: 1, nop: NoDsply}; + + // most general - you'll probably mostly use this! - using {field_name} + let text = NAMED.strung("{num}{name}"); + assert_eq!(&text,"1st"); + + // also works with String, just reference it + let s: String = "{num}{name}".into(); + let text = NAMED.strung(&s); + assert_eq!(&text,"1st"); + + // it will always replace every occurrence + let text = NAMED.strung("{num}{num}th < {num}{name}"); + assert_eq!(&text,"11th < 1st"); + + // for tuple structs, use the fields index number, instead of the name + let text = TUPLE.strung("{0}{1}"); + assert_eq!(&text,"1st"); + + // the [strung] function will change if you set custom pre/postfix - see TestCustom below + let text = CUSTOM.strung("%num%st"); + assert_eq!(&text,"1st"); + + // there are different presets, so you can still use {field_name} using [strung_curly] + let text = CUSTOM.strung_curly("{num}st"); + assert_eq!(&text,"1st"); + + // note: {nop} is not available, cause it's ignored - see TestCustom below + let text = CUSTOM.strung_curly("{num}st {nop}"); + assert_eq!(&text,"1st {nop}"); + + // [strung_dollar] for $field_name + let text = NAMED.strung_dollar("$num$name"); + assert_eq!(&text,"1st"); + + // [strung_dollry] for ${field_name} + let text = NAMED.strung_dollry("${num}${name}"); + assert_eq!(&text,"1st"); + + // [strung_hashtag] for #field_name + let text = NAMED.strung_hashtag("#num#name"); + assert_eq!(&text,"1st"); + + // most flexible - inline setting via [strung_dynamic] - a bit less efficient + let text = NAMED.strung_dynamic("<",">",""); + assert_eq!(&text,"1st"); + + // also flexible - global static variables, you can easily change ... + strung::config::static_global("+","+"); + let text = NAMED.strung_static("+num++name+"); + assert_eq!(&text,"1st"); + + // ... whenever you want, but usually you'll just need it once at the start of main() + strung::config::static_global("[","]"); + let text = NAMED.strung_static("[num][name]"); + assert_eq!(&text,"1st"); + + // [strung_hashtag] and [strung_dollar] also enable cascading + let CASCADE = TestCascade {tup: TestTup(2,"nd")}; + let text = CASCADE.strung_dollar("$tup.0$tup.1"); + assert_eq!(&text,"2nd"); + +} + +// named struct +#[derive(Strung)] // easy derive +struct Test { + num: u32, + name: &'static str, +} + +// tuple struct +#[derive(Strung)] // easy derive +struct TestTup(u32,&'static str); + +// custom pre/postfix per struct + ignore +#[derive(Strung)] // easy derive +#[strung("%","%")] // custom pre/postfix! +struct TestCustom { + num: u32, + // ignore: makes this field unavailable + // this would fail w/o the ignore, cause no [Display]! + // other usage: gain a lil more performance + #[strung(ignore)] nop: NoDsply +} + +// custom pre/postfix per struct + ignore +#[derive(Strung)] // easy derive +struct TestCascade { + // cascade: makes the fields of another Strung available + // the ignore only affects the the struct itself, not its fields + // and this would fail w/o it cause it doesn't implement it! + #[strung(cascade,ignore)] + tup: TestTup +} + +// struct with no Display trait +struct NoDsply; +``` + +# About Statics +Prelude imports two static variables [config::STRUNG_PRE] and [config::STRUNG_POST], which can be used +to set the prefix and postfix as a configuration. [Strung::strung_static] uses anything called STRUNG_PRE or STRUNG_POST +on the file. + +[config::static_global] changes these variables, as you saw in the walkthrough, there's another method of +changing it per file. It's not included in the walkthrough cause it shadows these variables, it's a macro called [config::static_on_file]. + +πŸ“ Note: This will maybe change in future, so these variables dont have to always be imported. + +But: here's how it can be used for now: +```rust +use strung::prelude::*; // Import everything from prelude +strung::config::static_on_file!("[","]"); // 🌱 Shadow static pre/postfix for this file. +#[derive(Strung)] // You know the drill :) +struct Test { + text: &'static str, + num: u32 +} +fn main(){ + let test = Test { // Create struct as usual + text: "5k", + num: 5000 + }; + let text = test.strung_static("[text]=[num]"); // 🌱 Use whatever you've set above + assert_eq!(&text,"5k=5000"); +} +``` +# Ignore fields +Sometimes you wanna ignore certain fields - e.g. in these scenarios: +- Get even moar efficiency πŸ“ˆ +- A field-type doesn't implement [std::fmt::Display] +This can be done with the #[strung(ignore)] attribute: +```rust +use strung::prelude::*; // Import everything from prelude + +struct CustomField (u32); // 🌱 A struct, not impl Display + +#[derive(Strung)] +struct Test { + num: u32, + #[strung(ignore)] nope: CustomField // 🌱 Would fail without the attribute! +} + +#[derive(Strung)] +struct TestTup ( + u32, + #[strung(ignore)] CustomField, // 🌱 Would fail without the attribute! + &'static str +); + +fn main(){ + /* ------------------------------ Named Fields ------------------------------ */ + let test = Test { // Create struct as usual + num: 1, + nope: CustomField(0), // 🌱 + }; + let text = test.strung("Number {num} {nope}"); // 🌱 {nope} not available! + assert_eq!(&text,"Number 1 {nope}"); + + /* ------------------------- Unnamed Fields (Tuple) ------------------------- */ + let test = TestTup(1,CustomField(0),":)"); // Create struct as usual + let text = test.strung("Number {0} {1} {2}"); // 🌱 {1} not available! + assert_eq!(&text,"Number 1 {1} :)"); +} +``` + +# ❗ Experimental: Cascading ❗ +There's also the possibility of cascading. e.g.: `$field.0.num`, it's experimentally implemented for [Strung::strung_dollar] and [Strung::strung_hashtag] at the moment, +cause it was the easiest to do. πŸ¦€ + +For this to work, the field-type has to derive [Strung] via derive macro and mark it with the `#[strung(cascade)]` attribute: +``` +use strung::prelude::*; + +// #[strung(ignore)] just because none of them are implementing Display! +#[derive(Strung)] struct A {#[strung(cascade,ignore)]field:B} +#[derive(Strung)] struct B (u32,#[strung(cascade,ignore)]C); +#[derive(Strung)] struct C {#[strung(cascade,ignore)]field:D,num:u32} +#[derive(Strung)] struct D (#[strung(cascade,ignore)]E); +#[derive(Strung)] struct E {num:u32} + +fn main(){ + let test = A{ + field: B(500,C{ + num: 123, + field: D(E{ + num: 623 + }) + }) + }; + let text = test.strung_dollar( + "Hello, $field.0 + $field.1.num = $field.1.field.0.num" + ); + assert_eq!(&text,"Hello, 500 + 123 = 623"); + + let text = test.strung_hashtag( + "Hello, #field.0 + #field.1.num = #field.1.field.0.num" + ); + assert_eq!(&text,"Hello, 500 + 123 = 623"); +} +``` + +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + +
+ +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + \ No newline at end of file diff --git a/derive/.gitignore b/derive/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/derive/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..9b18257 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "strung_derive" +version = "0.1.0" +edition = "2021" + +authors = ["Dekirisu "] +license = "MIT OR Apache-2.0" +description = "proc-macro for strung!" +repository = "https://github.com/dekirisu/strung/" +keywords = ["string","struct","format","fmt","replace"] +categories = ["value-formatting","rust-patterns"] + +[lib] +proc-macro = true + +[dependencies] +syn = "1.0" +quote = "1.0" \ No newline at end of file diff --git a/derive/README.md b/derive/README.md new file mode 100644 index 0000000..eae6bb2 --- /dev/null +++ b/derive/README.md @@ -0,0 +1,7 @@ +[![GitHub](https://img.shields.io/badge/github-dekirisu/strung-ee6677)](https://github.com/dekirisu/strung) +[![Version](https://img.shields.io/crates/v/strung-derive)](https://crates.io/crates/strung-derive) +[![Docs](https://img.shields.io/docsrs/strung-derive)](https://crates.io/crates/strung-derive) +![License](https://img.shields.io/crates/l/strung-derive) +[![Discord](https://img.shields.io/discord/515100001903312898)](https://discord.gg/kevWvBuPFg) + +Proc-macro for [strung](https://crates.io/crates/strung), usage is shown there! \ No newline at end of file diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..197ad05 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,219 @@ +/// Proc macro for strung! + +use proc_macro::TokenStream; +use quote::quote; +use syn; + +/// THE proc-macro, generating needed functions! +#[proc_macro_derive(Strung, attributes(strung))] +pub fn strung_macro_derive(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + impl_strung_macro(&ast) +} + +fn impl_strung_macro(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let (mut pre, mut post) = ("{".to_string(),"}".to_string()); + for aaa in &ast.attrs { + let ewr = aaa.path.get_ident(); + if &ewr.as_ref().unwrap().to_string() == "strung" { + if let Ok(bbb) = aaa.parse_meta() { + if let syn::Meta::List(list) = bbb { + let mut i = 0; + for abc in list.nested { + if let syn::NestedMeta::Lit(lit) = abc { + if let syn::Lit::Str(string) = lit { + if i==0 {pre = string.value();} + else {post = string.value();} + } + } + i += 1; + if i==2 {break;} + } + } + } + } + } + if let syn::Data::Struct(strct) = &ast.data { + + let mut types = vec![]; + + let mut idents = vec![]; + let mut fnames = vec![]; + let mut fnames_curly = vec![]; + let mut fnames_dollar = vec![]; + let mut fnames_dollry = vec![]; + let mut fnames_hashtag = vec![]; + let mut fnamesraw = vec![]; + + let mut unn = vec![]; + let mut unn_curly = vec![]; + let mut unn_dollar = vec![]; + let mut unn_dollry = vec![]; + let mut unn_hashtag = vec![]; + let mut unnraw = vec![]; + let mut unnid = vec![]; + + let mut cscd_nmd = vec![]; + let mut cscd_nmd_str = vec![]; + let mut cscd_nmd_str_h = vec![]; + let mut cscd_unmd = vec![]; + let mut cscd_unmd_str = vec![]; + let mut cscd_unmd_str_h = vec![]; + + match &strct.fields { + syn::Fields::Named(fields) => { + for field in &fields.named { + + let f_ident = field.ident.as_ref().unwrap().clone(); + let f_name = field.ident.as_ref().unwrap().to_string(); + + let mut ignore = false; + for aaa in &field.attrs { + let ewr = aaa.path.get_ident(); + let nme = ewr.as_ref().unwrap().to_string(); + if &nme == "strung" { + if let Ok(bbb) = aaa.parse_meta() { + if let syn::Meta::List(list) = bbb { + for abc in list.nested { + if let syn::NestedMeta::Meta(meta) = abc { + if let syn::Meta::Path(pp) = meta { + let nident = pp.get_ident().as_ref().unwrap().to_string(); + if &nident == "cascade" { + cscd_nmd.push(f_ident.clone()); + cscd_nmd_str.push(format!("${}.",&f_name)); + cscd_nmd_str_h.push(format!("#{}.",&f_name)); + } else if &nident == "ignore" { + ignore = true; + } + } + } + } + } + } + } + } + if ignore {continue;} + + types.push(field.ty.clone()); + idents.push(f_ident); + fnamesraw.push(f_name.clone()); + fnames.push(format!("{}{}{}",&pre,&f_name,&post)); + fnames_curly.push(format!("{{{}}}",&f_name,)); + fnames_dollar.push(format!("${}",&f_name)); + fnames_dollry.push(format!("${{{}}}",&f_name)); + fnames_hashtag.push(format!("#{}",&f_name)); + } + }, + syn::Fields::Unnamed(fields) => { + let mut i = 0; + for field in &fields.unnamed { + let mut ignore = false; + for aaa in &field.attrs { + let ewr = aaa.path.get_ident(); + let nme = ewr.as_ref().unwrap().to_string(); + if &nme == "strung" { + if let Ok(bbb) = aaa.parse_meta() { + if let syn::Meta::List(list) = bbb { + for abc in list.nested { + if let syn::NestedMeta::Meta(meta) = abc { + if let syn::Meta::Path(pp) = meta { + let nident = pp.get_ident().as_ref().unwrap().to_string(); + if &nident == "cascade" { + cscd_unmd.push(syn::Index::from(i)); + cscd_unmd_str.push(format!("${}.",i)); + cscd_unmd_str_h.push(format!("#{}.",i)); + } else if &nident == "ignore" { + ignore = true; + } + } + } + } + } + } + } + } + if !ignore { + types.push(field.ty.clone()); + + unn.push(format!("{}{}{}",&pre,i,&post)); + unnraw.push(i.to_string()); + unnid.push(syn::Index::from(i)); + + unn_curly.push(format!("{{{}}}",i)); + unn_dollar.push(format!("${}",i)); + unn_dollry.push(format!("${{{}}}",i)); + unn_hashtag.push(format!("#{}",i)); + } + i += 1; + } + }, + _ => {}, + } + let gen = quote! { + impl Strung for #name { + fn strung(&self, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&#fnames,&self.#idents.to_string());)* + #(output = output.replace(&#unn,&self.#unnid.to_string());)* + output + } + fn strung_static(&self, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&format!("{}{}{}",unsafe{STRUNG_PRE},&#fnamesraw,unsafe{STRUNG_POST}),&self.#idents.to_string());)* + #(output = output.replace(&format!("{}{}{}",unsafe{STRUNG_PRE},&#unnraw,unsafe{STRUNG_POST}),&self.#unnid.to_string());)* + output + } + fn strung_dynamic(&self, pre: &str, post:&str, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&format!("{}{}{}",pre,&#fnamesraw,post),&self.#idents.to_string());)* + #(output = output.replace(&format!("{}{}{}",pre,&#unnraw,post),&self.#unnid.to_string());)* + output + } + + fn strung_curly(&self, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&#fnames_curly,&self.#idents.to_string());)* + #(output = output.replace(&#unn_curly,&self.#unnid.to_string());)* + output + } + fn strung_dollar(&self, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&#fnames_dollar,&self.#idents.to_string());)* + #(output = output.replace(&#unn_dollar,&self.#unnid.to_string());)* + #( + output = output.replace(&#cscd_nmd_str,"$"); + output = self.#cscd_nmd.strung_dollar(&output); + )* + #( + output = output.replace(&#cscd_unmd_str,"$"); + output = self.#cscd_unmd.strung_dollar(&output); + )* + output + } + fn strung_dollry(&self, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&#fnames_dollry,&self.#idents.to_string());)* + #(output = output.replace(&#unn_dollry,&self.#unnid.to_string());)* + output + } + fn strung_hashtag(&self, text: &str) -> String { + let mut output = text.to_string(); + #(output = output.replace(&#fnames_hashtag,&self.#idents.to_string());)* + #(output = output.replace(&#unn_hashtag,&self.#unnid.to_string());)* + #( + output = output.replace(&#cscd_nmd_str_h,"#"); + output = self.#cscd_nmd.strung_hashtag(&output); + )* + #( + output = output.replace(&#cscd_unmd_str_h,"#"); + output = self.#cscd_unmd.strung_hashtag(&output); + )* + output + } + + } + }; + gen.into() + } else {panic!("Not a Struct!")} +} \ No newline at end of file diff --git a/examples/cascade.rs b/examples/cascade.rs new file mode 100644 index 0000000..ce2e212 --- /dev/null +++ b/examples/cascade.rs @@ -0,0 +1,31 @@ +// There’s also the possibility of cascading. e.g.: $field.0.num, it’s experimentally implemented for +// Strung::strung_dollar and Strung::strung_hashtag at the moment, cause it was the easiest to do. πŸ¦€ + +use strung::prelude::*; + +// #[strung(ignore)] just because none of them are implementing Display! +#[derive(Strung)] struct A {#[strung(cascade,ignore)]field:B} +#[derive(Strung)] struct B (u32,#[strung(cascade,ignore)]C); +#[derive(Strung)] struct C {#[strung(cascade,ignore)]field:D,num:u32} +#[derive(Strung)] struct D (#[strung(cascade,ignore)]E); +#[derive(Strung)] struct E {num:u32} + +fn main(){ + let test = A{ + field: B(500,C{ + num: 123, + field: D(E{ + num: 623 + }) + }) + }; + let text = test.strung_dollar( + "Hello, $field.0 + $field.1.num = $field.1.field.0.num" + ); + println!("{}",&text); + + let text = test.strung_hashtag( + "Hello, #field.0 + #field.1.num = #field.1.field.0.num" + ); + println!("{}",&text); +} \ No newline at end of file diff --git a/examples/static_per_file.rs b/examples/static_per_file.rs new file mode 100644 index 0000000..e3c62f0 --- /dev/null +++ b/examples/static_per_file.rs @@ -0,0 +1,22 @@ +// Import everything from prelude +use strung::prelude::*; + +// Shadow static pre/postfix for this file. +strung::config::static_on_file!("[","]"); + + +#[derive(Strung)] +struct Test { + text: &'static str, + num: u32 +} + +fn main(){ + let test = Test { + text: "5k", + num: 5000 + }; + // Use whatever you've set above + let text = test.strung_static("[text]=[num]"); + println!("{}",&text); +} \ No newline at end of file diff --git a/examples/usage.rs b/examples/usage.rs new file mode 100644 index 0000000..3899891 --- /dev/null +++ b/examples/usage.rs @@ -0,0 +1,106 @@ +#![allow(non_snake_case,dead_code)] +use strung::prelude::*; // import everything from prelude + +// named struct +#[derive(Strung)] // easy derive +struct Test { + num: u32, + name: &'static str, +} + +// tuple struct +#[derive(Strung)] // easy derive +struct TestTup(u32,&'static str); + +// custom pre/postfix per struct + ignore +#[derive(Strung)] // easy derive +#[strung("%","%")] // custom pre/postfix! +struct TestCustom { + num: u32, + // ignore: makes this field unavailable + // this would fail w/o the ignore, cause no [Display]! + // other usage: gain a lil more performance + #[strung(ignore)] nop: NoDsply +} + +// custom pre/postfix per struct + ignore +#[derive(Strung)] // easy derive +struct TestCascade { + // cascade: makes the fields of another Strung available + // the ignore only affects the the struct itself, not its fields + // and this would fail w/o it cause it doesn't implement it! + #[strung(cascade,ignore)] + tup: TestTup +} + +// struct with no Display trait +struct NoDsply; + +fn main(){ + + // create structs! + let NAMED = Test {num: 1, name: "st"}; + let TUPLE = TestTup (1, "st"); + let CUSTOM = TestCustom {num: 1, nop: NoDsply}; + + // most general - you'll probably mostly use this! - using {field_name} + let text = NAMED.strung("{num}{name}"); + println!("strung: {}",&text); + + // also works with String, just reference it + let s: String = "{num}{name}".into(); + let text = NAMED.strung(&s); + println!("strung with String: {}",&text); + + // it will always replace every occurrence + let text = NAMED.strung("{num}{num}th < {num}{name}"); + println!("strung multi: {}",&text); + + // for tuple structs, use the fields index number, instead of the name + let text = TUPLE.strung("{0}{1}"); + println!("strung tuple: {}",&text); + + // the [strung] function will change if you set custom pre/postfix - see TestCustom above + let text = CUSTOM.strung("%num%st"); + println!("strung custom per struct: {}",&text); + + // there are different presets, so you can still use {field_name} using [strung_curly] + let text = CUSTOM.strung_curly("{num}st"); + println!("strung_curly: {}",&text); + + // note: {nop} is not available, cause it's ignored - see TestCustom above + let text = CUSTOM.strung_curly("{num}st {nop}"); + println!("strung ignored: {}",&text); + + // [strung_dollar] for $field_name + let text = NAMED.strung_dollar("$num$name"); + println!("strung_dollar: {}",&text); + + // [strung_dollry] for ${field_name} + let text = NAMED.strung_dollry("${num}${name}"); + println!("strung_dollry: {}",&text); + + // [strung_hashtag] for #field_name + let text = NAMED.strung_hashtag("#num#name"); + println!("strung_hashtag: {}",&text); + + // most flexible - inline setting via [strung_dynamic] - a bit less efficient + let text = NAMED.strung_dynamic("<",">",""); + println!("strung_dynamic: {}",&text); + + // also flexible - global static variables, you can easily change ... + strung::config::static_global("+","+"); + let text = NAMED.strung_static("+num++name+"); + println!("strung_static[1]: {}",&text); + + // ... whenever you want, but usually you'll just need it once at the start of the main + strung::config::static_global("[","]"); + let text = NAMED.strung_static("[num][name]"); + println!("strung_static[2]: {}",&text); + + // [strung_hashtag] and [strung_dollar] also enable cascading + let CASCADE = TestCascade {tup: TestTup(2,"nd")}; + let text = CASCADE.strung_dollar("$tup.0$tup.1"); + println!("strung_dollar cascading: {}",&text); + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..97a12db --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,302 @@ +//! String formatter/builder with easy access of struct fields, which implement the [std::fmt::Display] trait. +//! If they do not, they can be marked to be ignored. +// ! # ⏱ tl;dr +// ! ``` +// ! #[derive(Strung)] struct Nmd {n:u32} +// ! Nmd{n:1}.strung("{n}st"); // "1st" +// ! #[derive(Strung)] struct Tup (u32); +// ! Tup(1).strung("{0}st"); // "1st" +// ! #[derive(Strung)]#[strung("<",">")] struct Cst (u32); +// ! Cst(1).strung("<0>st"); // "1st" +// ! #[derive(Strung)] struct Rec (#[strung(cascade,ignore)]Tup); +// ! Rec(Tup(1)).strung_dollar("$0.0st"); // 1st +// ! ``` +//! # Usage +//! Here's most you have to know! +//! ``` +//! use strung::prelude::*; // import everything from prelude +//! +//! fn main(){ +//! +//! // create structs - defined below cause instant action! +//! let NAMED = Test {num: 1, name: "st"}; +//! let TUPLE = TestTup (1, "st"); +//! let CUSTOM = TestCustom {num: 1, nop: NoDsply}; +//! +//! // most general - you'll probably mostly use this! - using {field_name} +//! let text = NAMED.strung("{num}{name}"); +//! assert_eq!(&text,"1st"); +//! +//! // also works with String, just reference it +//! let s: String = "{num}{name}".into(); +//! let text = NAMED.strung(&s); +//! assert_eq!(&text,"1st"); +//! +//! // it will always replace every occurrence +//! let text = NAMED.strung("{num}{num}th < {num}{name}"); +//! assert_eq!(&text,"11th < 1st"); +//! +//! // for tuple structs, use the fields index number, instead of the name +//! let text = TUPLE.strung("{0}{1}"); +//! assert_eq!(&text,"1st"); +//! +//! // the [strung] function will change if you set custom pre/postfix - see TestCustom below +//! let text = CUSTOM.strung("%num%st"); +//! assert_eq!(&text,"1st"); +//! +//! // there are different presets, so you can still use {field_name} using [strung_curly] +//! let text = CUSTOM.strung_curly("{num}st"); +//! assert_eq!(&text,"1st"); +//! +//! // note: {nop} is not available, cause it's ignored - see TestCustom below +//! let text = CUSTOM.strung_curly("{num}st {nop}"); +//! assert_eq!(&text,"1st {nop}"); +//! +//! // [strung_dollar] for $field_name +//! let text = NAMED.strung_dollar("$num$name"); +//! assert_eq!(&text,"1st"); +//! +//! // [strung_dollry] for ${field_name} +//! let text = NAMED.strung_dollry("${num}${name}"); +//! assert_eq!(&text,"1st"); +//! +//! // [strung_hashtag] for #field_name +//! let text = NAMED.strung_hashtag("#num#name"); +//! assert_eq!(&text,"1st"); +//! +//! // most flexible - inline setting via [strung_dynamic] - a bit less efficient +//! let text = NAMED.strung_dynamic("<",">",""); +//! assert_eq!(&text,"1st"); +//! +//! // also flexible - global static variables, you can easily change ... +//! strung::config::static_global("+","+"); +//! let text = NAMED.strung_static("+num++name+"); +//! assert_eq!(&text,"1st"); +//! +//! // ... whenever you want, but usually you'll just need it once at the start of main() +//! strung::config::static_global("[","]"); +//! let text = NAMED.strung_static("[num][name]"); +//! assert_eq!(&text,"1st"); +//! +//! // [strung_hashtag] and [strung_dollar] also enable cascading +//! let CASCADE = TestCascade {tup: TestTup(2,"nd")}; +//! let text = CASCADE.strung_dollar("$tup.0$tup.1"); +//! assert_eq!(&text,"2nd"); +//! +//! } +//! +//! // named struct +//! #[derive(Strung)] // easy derive +//! struct Test { +//! num: u32, +//! name: &'static str, +//! } +//! +//! // tuple struct +//! #[derive(Strung)] // easy derive +//! struct TestTup(u32,&'static str); +//! +//! // custom pre/postfix per struct + ignore +//! #[derive(Strung)] // easy derive +//! #[strung("%","%")] // custom pre/postfix! +//! struct TestCustom { +//! num: u32, +//! // ignore: makes this field unavailable +//! // this would fail w/o the ignore, cause no [Display]! +//! // other usage: gain a lil more performance +//! #[strung(ignore)] nop: NoDsply +//! } +//! +//! // custom pre/postfix per struct + ignore +//! #[derive(Strung)] // easy derive +//! struct TestCascade { +//! // cascade: makes the fields of another Strung available +//! // the ignore only affects the the struct itself, not its fields +//! // and this would fail w/o it cause it doesn't implement it! +//! #[strung(cascade,ignore)] +//! tup: TestTup +//! } +//! +//! // struct with no Display trait +//! struct NoDsply; +//! ``` +//! +//! # About Statics +//! Prelude imports two static variables [config::STRUNG_PRE] and [config::STRUNG_POST], which can be used +//! to set the prefix and postfix as a configuration. [Strung::strung_static] uses anything called STRUNG_PRE or STRUNG_POST +//! on the file. +//! +//! [config::static_global] changes these variables, as you saw in the walkthrough, there's another method of +//! changing it per file. It's not included in the walkthrough cause it shadows these variables, it's a macro called [config::static_on_file]. +//! +//! πŸ“ Note: This will maybe change in future, so these variables dont have to always be imported. +//! +//! But: here's how it can be used for now: +//! ``` +//! use strung::prelude::*; // Import everything from prelude +//! strung::config::static_on_file!("[","]"); // 🌱 Shadow static pre/postfix for this file. +//! #[derive(Strung)] // You know the drill :) +//! struct Test { +//! text: &'static str, +//! num: u32 +//! } +//! fn main(){ +//! let test = Test { // Create struct as usual +//! text: "5k", +//! num: 5000 +//! }; +//! let text = test.strung_static("[text]=[num]"); // 🌱 Use whatever you've set above +//! assert_eq!(&text,"5k=5000"); +//! } +//! ``` +//! # Ignore fields +//! Sometimes you wanna ignore certain fields - e.g. in these scenarios: +//! - Get even moar efficiency πŸ“ˆ +//! - A field-type doesn't implement [std::fmt::Display] +//! This can be done with the #[strung(ignore)] attribute: +//! ``` +//! use strung::prelude::*; // Import everything from prelude +//! +//! struct CustomField (u32); // 🌱 A struct, not impl Display +//! +//! #[derive(Strung)] +//! struct Test { +//! num: u32, +//! #[strung(ignore)] nope: CustomField // 🌱 Would fail without the attribute! +//! } +//! +//! #[derive(Strung)] +//! struct TestTup ( +//! u32, +//! #[strung(ignore)] CustomField, // 🌱 Would fail without the attribute! +//! &'static str +//! ); +//! +//! fn main(){ +//! /* ------------------------------ Named Fields ------------------------------ */ +//! let test = Test { // Create struct as usual +//! num: 1, +//! nope: CustomField(0), // 🌱 +//! }; +//! let text = test.strung("Number {num} {nope}"); // 🌱 {nope} not available! +//! assert_eq!(&text,"Number 1 {nope}"); +//! +//! /* ------------------------- Unnamed Fields (Tuple) ------------------------- */ +//! let test = TestTup(1,CustomField(0),":)"); // Create struct as usual +//! let text = test.strung("Number {0} {1} {2}"); // 🌱 {1} not available! +//! assert_eq!(&text,"Number 1 {1} :)"); +//! } +//! ``` +//! +//! # ❗ Experimental: Cascading ❗ +//! There's also the possibility of cascading. e.g.: `$field.0.num`, it's experimentally implemented for [Strung::strung_dollar] and [Strung::strung_hashtag] at the moment, +//! cause it was the easiest to do. πŸ¦€ +//! +//! For this to work, the field-type has to derive [Strung] via derive macro and mark it with the `#[strung(cascade)]` attribute: +//! ``` +//! use strung::prelude::*; +//! +//! // #[strung(ignore)] just because none of them are implementing Display! +//! #[derive(Strung)] struct A {#[strung(cascade,ignore)]field:B} +//! #[derive(Strung)] struct B (u32,#[strung(cascade,ignore)]C); +//! #[derive(Strung)] struct C {#[strung(cascade,ignore)]field:D,num:u32} +//! #[derive(Strung)] struct D (#[strung(cascade,ignore)]E); +//! #[derive(Strung)] struct E {num:u32} +//! +//! fn main(){ +//! let test = A{ +//! field: B(500,C{ +//! num: 123, +//! field: D(E{ +//! num: 623 +//! }) +//! }) +//! }; +//! let text = test.strung_dollar( +//! "Hello, $field.0 + $field.1.num = $field.1.field.0.num" +//! ); +//! assert_eq!(&text,"Hello, 500 + 123 = 623"); +//! +//! let text = test.strung_hashtag( +//! "Hello, #field.0 + #field.1.num = #field.1.field.0.num" +//! ); +//! assert_eq!(&text,"Hello, 500 + 123 = 623"); +//! } +//! ``` +//! + +/// Designed to be used with the strung-derive crate! +pub trait Strung { + /// Default text replacement via `{field_name}`, changable via `#[strung("pre","post")]` + /// This is the most efficient cause pre- anf postfixes are merged with the field-names on compilation time! + fn strung(&self, text: &str) -> String; + /// Replacement with custom static postfixes and prefixes + /// - STRUNG_PRE for the prefix - Default: "$" + /// - STRUNG_POST for the postfix - Default: "" + /// Therefore by default replacement via `$field_name`. + /// + /// Easy changable: + /// - File scope (shadowing): [config::static_on_file] + /// - Global scope (mut): [config::static_global] + fn strung_static(&self, text: &str) -> String; + /// Replacement with custom inline Postfixes and Prefixes + fn strung_dynamic(&self, pre: &str, post:&str, text: &str) -> String; + /// Same as [Strung::strung] but not changable and always addressable by `{field_name}` + fn strung_curly(&self, text: &str) -> String; + /// Same as [Strung::strung] but not changable and always addressable by `$field_name` + fn strung_dollar(&self, text: &str) -> String; + /// Same as [Strung::strung] but not changable and always addressable by `${field_name}` + fn strung_dollry(&self, text: &str) -> String; + /// Same as [Strung::strung] but not changable and always addressable by `#field_name` + fn strung_hashtag(&self, text: &str) -> String; +} + +pub mod config { + //! Configurations, currently just static pre- and postfix! πŸ‘» + + /// Prefix used by [super::Strung::strung_static] + pub static mut STRUNG_PRE: &'static str = "$"; + /// Postfix used by [super::Strung::strung_static] + pub static mut STRUNG_POST: &'static str = ""; + + /// Quickly shadow [STRUNG_PRE] and [STRUNG_POST] for the current file. + /// + /// Use it after importing the prelude: + /// ``` + /// use strung::prelude::*; + /// strung::config::static_on_file!("{","}"); + /// // ... + /// ``` + #[macro_export] + macro_rules! static_on_file {($pre: expr, $post: expr) => { + const STRUNG_PRE: &'static str = $pre; + const STRUNG_POST: &'static str = $post; + };} + pub use static_on_file; + + /// Quickly change [STRUNG_PRE] and [STRUNG_POST] globally. + /// + /// Use anywhere within the runtime: + /// ``` + /// use strung::prelude::*; + /// fn main(){ + /// strung::config::static_global("{","}"); + /// // ... + /// } + /// // ... + /// ``` + pub fn static_global(pre: &'static str, post: &'static str){ + unsafe { + STRUNG_PRE = pre; + STRUNG_POST = post; + } + } + +} + +pub mod prelude { + //! All needed goods! + pub use strung_derive::*; + pub use super::{Strung}; + pub use super::config::{STRUNG_PRE,STRUNG_POST}; +} \ No newline at end of file