macrofied_toolbox/
lib.rs

1#![deny(clippy::all)]
2#![deny(clippy::pedantic)]
3#![deny(clippy::nursery)]
4#![deny(clippy::cargo)]
5#![deny(missing_docs)]
6// ==============================================================
7#![allow(clippy::module_name_repetitions)]
8#![allow(clippy::items_after_statements)]
9// ==============================================================
10#![doc(html_root_url = "https://docs.rs/macrofied-toolbox/0.4.2")]
11
12//! This library provides an ergonomic experience of adding debugging messages to rust's
13//! `Result<T,E>` and `Option<T>` patterns
14//!
15//! Just like the [`cli-toolbox`](https://crates.io/crates/cli-toolbox) crate, that the debug logic
16//! resembles, this is not a logging alternative; it's intended to produce debugging output to be
17//! used during application development.
18//!
19//! Although the macros were designed to make debugging more ergonomic, they include variations that
20//! do not include debugging to provide coding consistency, so you have the option to use the same syntax
21//! consistently throughout your project.
22//!
23//! ### `Result<T,E>`
24//! ```rust,no_run
25//! # use std::fs::File;
26//! # use std::io;
27//! # use std::io::{BufWriter, Write};
28//! use macrofied_toolbox::result;
29//!
30//! fn main() -> io::Result<()> {
31//!     let file_name = "foo.txt";
32//!
33//!     // attempts to create a file
34//!     result! {
35//!         // * if you use the try "?" operator, the result! macro will
36//!         //   still output debug or error before returning the Result::Err
37//!         @when  File::create(file_name)?;
38//!         // if the file is successfully created, write some content
39//!         @ok    (file) => {
40//!             let mut out = BufWriter::new(file);
41//!
42//!             writeln!(out, "some content")?;
43//!             writeln!(out, "some more content")?;
44//!         }
45//!         // if an exception occurs output debug message to stdout
46//!         @debug "problem creating file: {:?} - {}", file_name, err
47//!
48//!         // * debug messages are conditionally compiled
49//!         //   and do not output anything in release builds
50//!         // * "err" contains the Result::Err value and can be referenced,
51//!         //   it is discarded if it is not referenced
52//!     }
53//!
54//!     Ok(())
55//! }
56//! ```
57//!
58//! ### `Option<T>`
59//!
60//! ```rust,no_run
61//! # use std::fs::File;
62//! # use std::io;
63//! # use std::io::{BufWriter, Write};
64//! # use std::process::exit;
65//! use macrofied_toolbox::option;
66//!
67//! fn main() {
68//!     let file_name = "foo.txt";
69//!
70//!     if let None = example(file_name) {
71//!         eprintln!("failed to create {:?} file!", file_name);
72//!         exit(-1);
73//!     }
74//! }
75//!
76//! fn example(file_name: &str) -> Option<()> {
77//!     // attempts to create a file
78//!     option! {
79//!         // * if you use the try "?" operator, the result! macro will
80//!         //   still output debug or error before returning the Result::Err
81//!         @when  File::create(file_name).ok()?;
82//!         // if the file is successfully created, write some content
83//!         @some  (file) => {
84//!             let mut out = BufWriter::new(file);
85//!
86//!             writeln!(out, "some content").ok()?;
87//!             writeln!(out, "some more content").ok()?;
88//!         }
89//!         // if an exception occurs output debug message to stdout
90//!         @debug "problem creating file: {:?}", file_name
91//!
92//!         // * debug messages are conditionally compiled
93//!         //   and do not output anything in release builds
94//!         // * "err" contains the Result::Err value and can be referenced,
95//!         //   it is discarded if it is not referenced
96//!     }
97//!
98//!     Some(())
99//! }
100//! ```
101
102#[cfg(any(feature = "result", feature = "option"))]
103#[macro_use]
104extern crate bitflags;
105#[cfg(any(feature = "result", feature = "option"))]
106#[macro_use]
107extern crate cfg_if;
108#[cfg(any(feature = "result", feature = "option"))]
109#[macro_use]
110extern crate quote;
111#[cfg(any(feature = "result", feature = "option"))]
112#[macro_use]
113extern crate syn;
114
115#[cfg(any(feature = "result", feature = "option"))]
116use proc_macro::TokenStream;
117
118#[cfg(any(feature = "result", feature = "option"))]
119use quote::ToTokens;
120
121#[cfg(any(feature = "option", feature = "result"))]
122mod common;
123
124#[cfg(feature = "option")]
125mod option_macro;
126
127#[cfg(feature = "result")]
128mod result_macro;
129
130#[cfg(test)]
131mod tests;
132
133/// a macro for making debugging more ergonomic when handling `Option<T>` results
134///
135/// ## Anotomy of the `option!` macro
136///
137/// The `option!` macro consists of a `@when` section and one to three optional evaluation 
138/// sections `@some`, `@debug` and/or `@none`, at least one must be defined.
139/// 
140/// When the `option!` macro is used in place of an expression and the intention is to
141/// assign the `Some(T)` value, the `@when` section can be skipped and replaced with a
142/// simplified `@some` section, which behaves as the `@when` section, _* see below for
143/// more details_
144///
145/// <br/>\* _code block_ `<expr>`_s, can not be terminated with a_
146/// `;`_, i.e._ `{ ... }`~~`;`~~<br/>
147///
148/// ### `@when`
149///
150/// The `@when` section is defined as `[@when] <expr>[?][;]`
151///
152/// * `@when` - _optional_, section identifier
153/// * `<expr>` - an expression that must evaluate to an `Option<T>` value
154/// * `[?]` - _optional_, try operator, returns `None` after completing
155///           `@debug` and/or `@none`
156/// * `[;]` - _optional_, section terminator
157///
158/// __`Example A:`__ `@when foo();`<br/>
159/// __`Example B:`__ `@when foo()?;`<br/>
160///
161/// ### `@some`
162///
163/// The `@some` section is defined as `@some <[[(identifier) =>]<message|expr>[;]|[<expr>[?][;]]]`
164///
165/// * `@some` - required section identifier
166/// * __In Success Mode__
167///     * `[(identifier) =>]` - _optional_, custom defined identifier which maps to
168///                             the `Some(T)` value
169///     * `<message|expr>`
170///         * `message` - outputs to `stdout` with a `println!` statement, therefore has
171///                       the same `args`
172///         * `expr` - any expression to evaluate<br/><br/>
173/// _* can access_ `Some(T)` _value with the_ `some` _keyword or custom identifier_<br/><br/>
174///     * `[;]` - _optional_, section terminator<br/><br/>
175/// \* _only evaluates if the result of the_ `@when` _expression is_ `Option::Some`<br/><br/>
176/// __`Example A:`__ `@some "success: {}", some;`<br/>
177/// __`Example B:`__ `@some (foo) => "success: {}", foo;`<br/>
178/// __`Example C:`__ `@some (foo) => { success(foo); }`<br/>
179/// * __In Expression Mode__
180///     * `<expr>` - an expression that must evaluate to an `Option<T>` value
181///     * `[?]` - _optional_, try operator, returns `None` after completing
182///               `@debug` and/or `@none`
183///     * `[;]` - _optional_, section terminator<br/><br/>
184/// __`Example A:`__ `@some foo();`<br/>
185/// __`Example B:`__ `@some foo()?;`<br/>
186/// ### `@debug`
187///
188/// The `@debug` section is defined as `@debug <message>[;]`
189///
190/// \* _only evaluates if the result of the_ `@when` _expression is_ `Option::None`
191///
192/// * `@debug` - required section identifier
193/// * `message` - outputs to `stdout` with a `println!` statement, therefore has the same `args`
194/// * `[;]` - _optional_, section terminator
195///
196/// __`Example:`__ `@debug "dbg: foo failed!";`
197///
198/// ### `@none`
199///
200/// The `@none` section is defined as `@none [<message>[;]][<expr>][;]`, must
201/// provide at least a `message` and/or `expr`
202///
203/// \* _only evaluates if the result of the_ `@when` _expression is_ `Option::None`
204///
205/// * `@none` - required section identifier
206/// * `[message][;]` - _optional_, outputs to_ `stderr` _with a_ `eprintln!` _statement,
207///                    therefore accepts the same_ `args`<br/><br/>
208/// \* _requires the `;` terminator if an_ `<expr>[;]` _is also defined_<br/><br/>
209/// * `[<expr>]` - _optional_, any expression to evaluate
210/// * `[;]` - _optional_, section terminator
211///
212/// __`Example A:`__ `@none { on_fail_baz(); }`<br/>
213/// __`Example B:`__ `@none "err: foo failed!"`<br/>
214/// __`Example C:`__ `@none "err: foo failed!"; { on_fail_baz(); }`<br/>
215///
216/// ## Example
217///
218/// * Success Mode
219/// ```rust,no_run
220/// # use std::fs::File;
221/// # use std::io;
222/// # use std::io::{BufWriter, Write};
223/// # use std::process::exit;
224/// use macrofied_toolbox::option;
225///
226/// fn main() {
227///     let file_name = "foo.txt";
228///
229///     if let None = example(file_name) {
230///         eprintln!("failed to create {:?} file!", file_name);
231///         exit(-1);
232///     }
233/// }
234///
235/// fn example(file_name: &str) -> Option<()> {
236///     option! {
237///         @when  File::create(file_name).ok()?;
238///         @some  (file) => {
239///                    let mut out = BufWriter::new(file);
240///
241///                    writeln!(out, "some content").ok()?;
242///                    writeln!(out, "some more content").ok()?;
243///                }
244///         @debug "problem creating file: {:?}", file_name;
245///         @none  "{:?} failed; attempting recovery ...", file_name;
246///                recovery_from_fail(file_name);
247///     }
248///
249///     Some(())
250/// }
251///
252/// fn recovery_from_fail(_: &str) {
253///     // some very import recovery logic
254/// }
255/// ```
256/// * Expression Mode
257/// ```rust
258/// use macrofied_toolbox::option;
259///
260/// let result = option! {
261///     @some  computed_value(21)
262///     @debug "Invalid input"
263///     @none  0
264/// };
265///
266/// assert_eq!(42, result);
267///
268/// fn computed_value(input: usize) -> Option<usize> {
269///     if input == 21 {
270///         Some(input * 2)
271///     } else {
272///         None
273///     }
274/// }
275/// ```
276#[cfg(feature = "option")]
277#[proc_macro]
278pub fn option(input: TokenStream) -> TokenStream {
279    parse_macro_input!(input as option_macro::OptionMacro).into_token_stream().into()
280}
281
282/// a macro for making debugging more ergonomic when handling `Result<T,E>` results
283///
284/// ## Anotomy of the `result!` macro
285///
286/// The `result!` macro consists of a required `@when` section and one to three
287/// optional evaluation sections `@ok`, `@debug` and/or `@error`, at least one must be
288/// defined.
289///
290/// When the `result!` macro is used in place of an expression and the intention is to
291/// assign the `Ok(T)` value, the `@when` section can be skipped and replaced with an
292/// `@ok` section, which behaves as the `@when` section, _* see below for more details_
293///
294/// <br/>\* _code block_ `<expr>`_s, can not be terminated with a_
295/// `;`_, i.e._ `{ ... }`~~`;`~~<br/>
296///
297/// ### `@when`
298///
299/// The `@when` section is defined as `[@when] <expr>[?][;]`
300///
301/// * `@when` - _optional_, section identifier
302/// * `<expr>` - an expression that must evaluate to a `Result<T,E>` value
303/// * `[?]` - _optional_, try operator will, returns `Result::Err` after completing
304///           `@debug` and/or `@error`
305/// * `[;]` - _optional_, section terminator
306///
307/// __`Example A:`__ `@when foo()?;`<br/>
308/// __`Example B:`__ `@when foo()?;`<br/>
309///
310/// ### `@ok`
311///
312/// The `@ok` section is defined as `@ok [[(identifier) =>]<message|expr>[;]|[<expr>[?][;]]`
313///
314/// \* _only evaluates if the result of the_ `@when` _expression is_ `Result::Ok`
315///
316/// * `@ok` - required section identifier
317/// * __In Success Mode__
318///     * `[(identifier) =>]` - _optional_, custom defined identifier which maps to
319///                             the `Ok(T)` value
320///     * `<message|expr>` -
321///         * `message` - outputs to `stdout` with a `println!` statement, therefore has
322///                       the same `args`
323///         * `expr` - any expression to evaluate<br/><br/>
324/// _* can access_ `Ok(T)` _value with the_ `ok` _keyword or custom identifier_<br/><br/>
325///     * `[;]` - _optional_, section terminator<br/><br/>
326/// __`Example:`__ `@ok "success: {}", ok;`<br/>
327/// __`Example:`__ `@ok (foo) => "success: {}", foo;`<br/>
328/// __`Example:`__ `@ok (foo) => { success(foo) }`<br/>
329/// * __In Expression Mode__
330///     * `<expr>` - an expression that must evaluate to an `Option<T>` value
331///     * `[?]` - _optional_, try operator, returns `None` after completing
332///               `@debug` and/or `@none`
333///     * `[;]` - _optional_, section terminator<br/><br/>
334/// __`Example A:`__ `@ok foo();`<br/>
335/// __`Example B:`__ `@ok foo()?;`<br/>
336///
337/// ### `@debug`
338///
339/// The `@debug` section is defined as `@debug <message>[;]`
340///
341/// \* _only evaluates if the result of the_ `@when` _expression is_ `Result::Err`
342///
343/// * `@debug` - required section identifier
344/// * `message` - outputs to `stdout` with a `println!` statement, therefore has
345///               the same `args`<br/><br/>
346/// \* _can access_ `Result::Err(err)` _with_ `err` _keyword_<br/><br/>
347/// * `[;]` - _optional_, section terminator
348///
349/// __`Example:`__ `@debug "dbg: foo failed! - {}", err;`
350///
351/// ### `@none`
352///
353/// The `@error` section is defined as `@error [<message>[;]][<expr>][;]`, must
354/// provide at least a `message` and/or `expr`
355///
356/// \* _only evaluates if the result of the_ `@when` _expression is_ `Result::Err`
357///
358/// * `@error` - required section identifier
359/// * `[message][;]` - _optional_, outputs to `stderr` with a `eprintln!` statement, therefore
360///                    has the same `args`<br/><br/>
361/// \* _requires the_ `;` _terminator if an_ `<expr>[;]` _is also defined_<br/>
362/// \* _can access_ `Result::Err(err)` _with_ `err` _keyword_<br/><br/>
363/// * `[<expr>]` - _optional_, any expression to evaluate<br/><br/>
364/// \* _can access_ `Result::Err(err)` _with_ `err` _keyword_<br/><br/>
365/// * `[;]` - _optional_, section terminator
366///
367/// __`Example A:`__ `@err "err: foo failed! - {}", err`<br/>
368/// __`Example B:`__ `{ on_fail_baz(err); }`<br/>
369/// __`Example C:`__ `@err "err: foo failed! - {}", err; { on_fail_baz(err); }`<br/>
370///
371/// ## Example
372///
373/// * Success Mode
374///
375/// ```rust,no_run
376/// # use std::fs::File;
377/// # use std::io;
378/// # use std::io::{BufWriter, Write};
379/// # use std::process::exit;
380/// use macrofied_toolbox::result;
381///
382/// fn main() -> io::Result<()> {
383///     let file_name = "foo.txt";
384///
385///     result! {
386///         @when  File::create(file_name)?;
387///         @ok    (file) => {
388///                    let mut out = BufWriter::new(file);
389///
390///                    writeln!(out, "some content")?;
391///                    writeln!(out, "some more content")?;
392///                }
393///         @debug "problem creating file: {:?} - {}", file_name, err;
394///         @error "{:?} failed - {}; attempting recovery ...", file_name, err;
395///                recovery_from_fail(file_name);
396///     }
397///
398///     Ok(())
399/// }
400///
401/// fn recovery_from_fail(_: &str) {
402///     // some very import recovery logic
403/// }
404/// ```
405/// * Expression Mode
406/// ```rust
407/// use macrofied_toolbox::result;
408///
409/// let result = result! {
410///     @ok    computed_value(21)
411///     @debug "ERR: {:?}", err
412///     @error 0
413/// };
414///
415/// assert_eq!(42, result);
416///
417/// fn computed_value(input: usize) -> Result<usize, &'static str> {
418///     if input == 21 {
419///         Ok(input * 2)
420///     } else {
421///         Err("I can't let you do that")
422///     }
423/// }
424/// ```
425#[cfg(feature = "result")]
426#[proc_macro]
427pub fn result(input: TokenStream) -> TokenStream {
428    parse_macro_input!(input as result_macro::ResultMacro).into_token_stream().into()
429}