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}