cl_format/
lib.rs

1#![doc = r#"`cl-format` s the Rust implementation of the Common Lisp [format](http://www.lispworks.com/documentation/lw50/CLHS/Body/f_format.htm) function.
2
3## Usage ##
4
5There are two ways to use this library. You can use the `cl_format!` macro, or generate the control string and format your arguments by yourself for more flexibility.
6
7First, add `cl-format = "0.2"` in your `Cargo.toml`.
8
9### Use macro ###
10
11`~a` is the most common directive I like to use, so let's start from normal `~a`:
12
13```rust
14use cl_format::*;
15
16let a = cl_format!("~a, ~a, ~a", &1_i32, &2, &3);
17assert_eq!(String::from("1, 2, 3"), a.unwrap());
18```
19
20All arguments used for formatting have to be borrowed, and they must implement the `TildeAble` trait. Check the [Implement for custom type](#implement-for-custom-type) section for more details.
21
22Here is more usage of the macro. Escaping the double quote symbol for strings:
23
24```rust
25use cl_format::*;
26let s = String::from("abc");
27let a = cl_format!("~a, ~a, ~a, ~S", &1_i32, &2, &3, &s);
28assert_eq!(String::from("1, 2, 3, \"abc\""), a.unwrap());
29```
30
31Or not:
32
33```rust
34use cl_format::*;
35let a = cl_format!("start ~a, ~a, ~a, ~a, here", &1_i32, &2, &3, &s);
36assert_eq!(String::from("start 1, 2, 3, abc, here"), a.unwrap());
37```
38
39Let's make some loops inside the control string like Lispers do:
40
41```rust
42use cl_format::*;
43let ll: Vec<&dyn TildeAble> = vec![&1, &2, &3];
44let a = cl_format!("~a, ~a, ~a, ~{~a,~}", &1_i32, &2, &3, &ll);
45assert_eq!(String::from("1, 2, 3, 1,2,3,"), a.unwrap());
46```
47
48Wait, we have an unnecessary comma at the end of the result, let's clean it up:
49
50```rust
51use cl_format::*;
52let a = cl_format!("~a, ~a, ~a, ~{~a~^,~}", &1_i32, &2, &3, &ll);
53assert_eq!(String::from("1, 2, 3, 1,2,3"), a.unwrap());
54```
55
56I suddenly don't want to loop the Vec anymore:
57
58```rust
59use cl_format::*;
60let l = vec![&1 as &dyn TildeAble, &2, &3];
61let a = cl_format!("The value is:\n ~a", &l);
62assert_eq!(String::from("The value is:\n [1, 2, 3]"), a.unwrap());
63```
64
65Now, we have some inconsistency between Common Lisp and Rust. In Common Lisp, `~%` in the control string is the new line, but we are in Rust now, so `\n` is going to work.
66
67I think I am a bit tired of showing the type as `&dyn TildeAble` to elements inside Vec. But I haven't found a way to avoid it yet. If you know, let me know. So I added some macros:
68
69
70```rust
71use cl_format::*;
72let l = vec![tilde!(&1), &2, &3];
73let a = cl_format!("The value is:\n ~a", &l);
74assert_eq!(String::from("The value is:\n [1, 2, 3]"), a.unwrap());
75```
76
77As in Common Lisp, we can loop through all arguments instead of putting them inside a Vec:
78
79```rust
80use cl_format::*;
81let a = cl_format!("~@{~a~^, ~}", &1, &2, &3);
82assert_eq!(String::from("1, 2, 3"), a.unwrap());
83```
84
85Now, let's try some condition control (you can get the meaning of the condition control string in the `Conditional Formatting` chapter of [A Few FORMAT Recipes](https://gigamonkeys.com/book/a-few-format-recipes.html)):
86
87```rust
88use cl_format::*;
89let l = vec![tilde!(&1), &2, &3];
90let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
91assert_eq!(String::from("1, 2, and 3"), a.unwrap());
92
93let l = vec![tilde!(&1), &2, &3, &4];
94let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
95assert_eq!(String::from("1, 2, 3, and 4"), a.unwrap());
96```
97
98### Manually ###
99
100Using macros will generate the control string instance every time. It might be wasteful if you are trying to use a control string everywhere because it is flexible enough for multiple uses.
101
102We can generate it by ourselves:
103
104```rust
105let cs = cl_format::ControlStr::from("~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~}").unwrap();
106```
107
108Then we can generate the Args for the control string to reveal:
109
110```rust
111use cl_format::Args;
112let mut list = vec![];
113let args = Args::new(vec![&list]);
114```
115
116Let's use it several times by giving different lengths of arguments:
117
118```rust
119use cl_format::*;
120use cl_format::cl_format;
121
122// this equal cl_format!(cs, &list)
123assert_eq!(cs.reveal(args).unwrap(), "".to_string());
124
125list.push(&1);
126let args = Args::new(vec![&list]);
127assert_eq!(cs.reveal(args).unwrap(), "1".to_string());
128
129list.push(&2);
130let args = Args::new(vec![&list]);
131assert_eq!(cs.reveal(args).unwrap(), "1 and 2".to_string());
132
133list.push(&3);
134let args = Args::new(vec![&list]);
135assert_eq!(cs.reveal(args).unwrap(), "1, 2, and 3".to_string());
136
137list.push(&4);
138let args = Args::new(vec![&list]);
139assert_eq!(cs.reveal(args).unwrap(), "1, 2, 3, and 4".to_string());
140```
141
142Let's try a mixed example: 
143
144```rust
145use cl_format::*;
146
147let my_team = String::from("STeam");
148let my_stars = vec![
149    String::from("Adam Lambert"),
150    String::from("Queen"),
151    String::from("snoop dogg"),
152];
153
154let stars = my_stars
155    .iter()
156    .map(|s| tilde!(s))
157    .collect::<Vec<&dyn TildeAble>>();
158	
159assert_eq!(
160    String::from("my favorite team \"STeam\" will win the superbowl LVIII. And Adam Lambert, Queen, and snoop dogg will in half time show. And the scores should be 38:35"),
161    cl_format!(
162        "my favorite team ~S will win the superbowl ~@R. And ~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~} will in half time show. And the scores should be ~d:~d",
163        &my_team,
164        &58,
165        &stars,
166        &38,
167        &35
168    )
169    .unwrap()
170);
171
172```
173
174### Implement for custom type ###
175
176So far, we have only shown the basic types. It would be better if we could make our type be revealed as well.
177
178Here is a demo on how to implement:
179
180```rust
181use cl_format::*;
182
183// has to derive to Debug
184#[derive(Debug)]
185struct MyStruct {
186    a: usize,
187    b: String,
188}
189
190impl TildeAble for MyStruct {
191	// there are a lot methods inside, but not every of them
192	// we are need.
193	
194	// ~a is good enough
195	fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa> {
196        Some(self)
197    }
198	
199	// ~d just for show case
200	fn into_tildekind_digit(&self) -> Option<&dyn TildeKindDigit> {
201        Some(self)
202    }
203	
204	// how many elements you want cl_format treat this type
205	// 1 is enough. And this one has to implement
206	fn len(&self) -> usize {
207        1
208    }
209}
210
211/// By now, your IDE should give you some errors, letting you implement `TildeKindVa` and `TildeKindDigit`. 
212
213impl TildeKindVa for MyStruct {
214    fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
215        buf.push_str(&format!("a: {}, b: {}", self.a, self.b));
216        Ok(())
217    }
218}
219
220impl TildeKindDigit for MyStruct {
221    fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
222        buf.push_str(&format!("{}", self.a));
223        Ok(())
224    }
225}
226```
227
228Now `MyStruct` can be used by `cl_format`, but as you guessed, only for `~a` and `~d`
229
230```rust
231use cl_format::*;
232
233let s = MyStruct {
234    a: 1,
235    b: "b".to_string(),
236};
237
238assert_eq!("a: 1, b: b".to_string(), cl_format!("~a", &s).unwrap());
239assert_eq!(
240    "a: 1, b: b lalalal a: 1, b: b".to_string(),
241    cl_format!("~a lalalal ~a", &s, &s).unwrap()
242);
243
244assert_eq!("1".to_string(), cl_format!("~d", &s).unwrap());
245assert_eq!(
246    "First: a: 1, b: b; Second: 1".to_string(),
247    cl_format!("First: ~a; Second: ~d", &s, &s).unwrap()
248);
249```
250
251## Format directives ##
252
253This is the table of which directives have been implemented:
254
255| tilde                     | rust type                                                                                    |
256|:-------------------------:|:--------------------------------------------------------------------------------------------:|
257| `~a`                      | f32, f64, char, i8, i16, i32, i64, i128, isize, bool, u8, u16, u32, u64, u128, usize, String |
258| `~s`                      | f32, f64, char, i32, i64, usize, bool, u32, u64, String                                      |
259| `~d`                      | i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize                               |
260| `~C`                      | char                                                                                         |
261| `~[~]` (normal condition) | bool, usize                                                                                  |
262| `~R`                      | i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize                               |
263
264"#]
265#![feature(let_chains)]
266#![feature(pattern)]
267
268mod control_str;
269mod tildes;
270
271pub use control_str::*;
272pub use tildes::*;
273
274#[doc = r#"Helper macro for implementing type with specific Tilde traits
275
276For example `multi_tilde_impl!(TildeKindVa, [float, char, String], self, {Err("un-implenmented yet".into())})`
277
278will expand
279
280```rust
281impl TildeKindVa for float {
282    fn format(&self, tkind: &TildeKind) -> Result<String, Box<dyn std::error::Error>> {
283        Err("un-implenmented yet".into())
284    }
285}
286```"#]
287#[macro_export]
288macro_rules! multi_tilde_impl {
289    ($implName:ident, [$($y:ident),+], $s:ident, $buf:ident, $body:block) => {
290		$(
291			impl $implName for $y {
292				fn format(&$s, _: &TildeKind, $buf: &mut String) -> Result<(), TildeError>
293					$body
294
295			}
296		)+
297    };
298}
299
300#[doc = r"Macro for adding `as &dyn crate::TildeAble` to the expr"]
301#[macro_export]
302macro_rules! tilde {
303    ($arg:expr) => {
304        $arg as &dyn cl_format::TildeAble
305    };
306}
307
308#[doc = r#"`cl_format!` is the macro for quick using cl-format
309
310```rust
311cl_format!(control_str, &a, &b, &c) => {
312     let c = control_str::ControlStr::new("~a, ~a, ~a").expect("making control string has issue");
313     let a = Into::<
314            tildes::Args<'_>,
315        >::into([
316            &1 as &dyn tildes::TildeAble,
317            &2 as &dyn tildes::TildeAble,
318            &3 as &dyn tildes::TildeAble,
319        ]);
320        c.reveal(a)
321}
322```
323For example:
324
325```rust
326let l = vec![tilde!(&1), &2, &3, &4];
327let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
328assert_eq!(String::from("1, 2, 3, and 4"), a.unwrap());
329```"#]
330#[macro_export]
331macro_rules! cl_format {
332	($control_str:expr) =>	{
333		{
334			let c = cl_format::ControlStr::new($control_str).expect("making control string has issue");
335			let a = cl_format::Args::new(vec![]);
336			c.reveal(a)
337		}
338	};
339    ($control_str:expr, $($ele:expr),*) =>	{
340		{
341			let c = cl_format::ControlStr::new($control_str).expect("making control string has issue");
342			let a = Into::<Args<'_,'_>>::into([$(tilde!($ele)),*]);
343			c.reveal(a)
344		}
345	}
346
347}
348
349#[cfg(test)]
350mod tests {}