documented_macros/
lib.rs

1mod attr_impl;
2mod config;
3mod derive_impl;
4pub(crate) mod util;
5
6use proc_macro::TokenStream;
7use syn::{parse_macro_input, Error};
8
9use crate::{
10    attr_impl::docs_const_impl,
11    derive_impl::{documented_fields_impl, documented_impl, documented_variants_impl, DocType},
12};
13
14/// Derive proc-macro for `Documented` trait.
15///
16/// # Example
17///
18/// ```rust
19/// use documented::Documented;
20///
21/// /// Nice.
22/// /// Multiple single-line doc comments are supported.
23/// ///
24/// /** Multi-line doc comments are supported too.
25///     Each line of the multi-line block is individually trimmed by default.
26///     Note the lack of spaces in front of this line.
27/// */
28/// #[doc = "Attribute-style documentation is supported too."]
29/// #[derive(Documented)]
30/// struct BornIn69;
31///
32/// let doc_str = "Nice.
33/// Multiple single-line doc comments are supported.
34///
35/// Multi-line doc comments are supported too.
36/// Each line of the multi-line block is individually trimmed by default.
37/// Note the lack of spaces in front of this line.
38///
39/// Attribute-style documentation is supported too.";
40/// assert_eq!(BornIn69::DOCS, doc_str);
41/// ```
42///
43/// # Configuration
44///
45/// With the `customise` feature enabled, you can customise this macro's
46/// behaviour using the `#[documented(...)]` attribute.
47///
48/// Currently, you can:
49///
50/// ## 1. set a default value when doc comments are absent like so:
51///
52/// ```rust
53/// # use documented::Documented;
54/// #[derive(Documented)]
55/// #[documented(default = "The answer is fries.")]
56/// struct WhosTurnIsIt;
57///
58/// assert_eq!(WhosTurnIsIt::DOCS, "The answer is fries.");
59/// ```
60///
61/// This option is primarily designed for [`DocumentedFields`] and
62/// [`DocumentedVariants`], so it's probably not very useful here. But it could
63/// conceivably come in handy in some niche meta-programming contexts.
64///
65/// ## 2. disable line-trimming like so:
66///
67/// ```rust
68/// # use documented::Documented;
69/// ///     Terrible.
70/// #[derive(Documented)]
71/// #[documented(trim = false)]
72/// struct Frankly;
73///
74/// assert_eq!(Frankly::DOCS, "     Terrible.");
75/// ```
76///
77/// If there are other configuration options you wish to have, please submit an
78/// issue or a PR.
79#[cfg_attr(not(feature = "customise"), proc_macro_derive(Documented))]
80#[cfg_attr(
81    feature = "customise",
82    proc_macro_derive(Documented, attributes(documented))
83)]
84pub fn documented(input: TokenStream) -> TokenStream {
85    documented_impl(parse_macro_input!(input), DocType::Str)
86        .unwrap_or_else(Error::into_compile_error)
87        .into()
88}
89
90/// Derive proc-macro for `DocumentedOpt` trait.
91///
92/// See [`Documented`] for usage.
93#[cfg_attr(not(feature = "customise"), proc_macro_derive(DocumentedOpt))]
94#[cfg_attr(
95    feature = "customise",
96    proc_macro_derive(DocumentedOpt, attributes(documented))
97)]
98pub fn documented_opt(input: TokenStream) -> TokenStream {
99    documented_impl(parse_macro_input!(input), DocType::OptStr)
100        .unwrap_or_else(Error::into_compile_error)
101        .into()
102}
103
104/// Derive proc-macro for `DocumentedFields` trait.
105///
106/// # Example
107///
108/// ```rust
109/// use documented::DocumentedFields;
110///
111/// #[derive(DocumentedFields)]
112/// struct BornIn69 {
113///     /// Cry like a grandmaster.
114///     rawr: String,
115///     /// Before what?
116///     explosive: usize,
117/// };
118///
119/// assert_eq!(
120///     BornIn69::FIELD_DOCS,
121///     ["Cry like a grandmaster.", "Before what?"]
122/// );
123/// ```
124///
125/// You can also use `DocumentedFields::get_field_docs` to access the fields'
126/// documentation using their names.
127///
128/// ```rust
129/// # use documented::{DocumentedFields, Error};
130/// #
131/// # #[derive(DocumentedFields)]
132/// # struct BornIn69 {
133/// #     /// Cry like a grandmaster.
134/// #     rawr: String,
135/// #     /// Before what?
136/// #     explosive: usize,
137/// # };
138/// #
139/// assert_eq!(
140///     BornIn69::get_field_docs("rawr"),
141///     Ok("Cry like a grandmaster.")
142/// );
143/// assert_eq!(BornIn69::get_field_docs("explosive"), Ok("Before what?"));
144/// assert_eq!(
145///     BornIn69::get_field_docs("gotcha"),
146///     Err(Error::NoSuchField("gotcha".to_string()))
147/// );
148/// ```
149///
150/// # Configuration
151///
152/// With the `customise` feature enabled, you can customise this macro's
153/// behaviour using the `#[documented_fields(...)]` attribute. Note that this
154/// attribute works on both the container and each individual field, with the
155/// per-field configurations overriding container configurations, which
156/// override the default.
157///
158/// Currently, you can:
159///
160/// ## 1. set a different case convention for `get_field_docs` like so:
161///
162/// ```rust
163/// # use documented::DocumentedFields;
164/// #[derive(DocumentedFields)]
165/// #[documented_fields(rename_all = "kebab-case")]
166/// struct BooksYouShouldWrite {
167///     /// It's my move?
168///     whose_turn_is_it: String,
169///     /// Isn't it checkmate?
170///     #[documented_fields(rename_all = "PascalCase")]
171///     how_many_legal_moves_do_you_have: String,
172/// }
173///
174/// assert_eq!(
175///     BooksYouShouldWrite::get_field_docs("whose-turn-is-it"),
176///     Ok("It's my move?")
177/// );
178/// assert_eq!(
179///     BooksYouShouldWrite::get_field_docs("HowManyLegalMovesDoYouHave"),
180///     Ok("Isn't it checkmate?")
181/// );
182/// ```
183///
184/// ## 2. set a custom name for a specific field for `get_field_docs` like so:
185///
186/// ```rust
187/// # use documented::DocumentedFields;
188/// #[derive(DocumentedFields)]
189/// // #[documented_field(rename = "fries")] // this is not allowed
190/// struct ThisPosition {
191///     /// I'm guessing, but I'm not really guessing.
192///     #[documented_fields(rename = "knows")]
193///     esserman_knows: bool,
194///     /// And that's true if you're van Wely.
195///     #[documented_fields(rename = "doesnt_know")]
196///     van_wely_doesnt: bool,
197/// }
198///
199/// assert_eq!(
200///     ThisPosition::get_field_docs("knows"),
201///     Ok("I'm guessing, but I'm not really guessing.")
202/// );
203/// assert_eq!(
204///     ThisPosition::get_field_docs("doesnt_know"),
205///     Ok("And that's true if you're van Wely.")
206/// );
207/// ```
208///
209/// Obviously this option is only available on each individual field.
210/// It makes no sense on the container.
211///
212/// This option also always takes priority over `rename_all`.
213///
214/// ## 3. set a default value when doc comments are absent like so:
215///
216/// ```rust
217/// # use documented::DocumentedFields;
218/// #[derive(DocumentedFields)]
219/// #[documented_fields(default = "Confusing the audience.")]
220/// struct SettingUpForTheNextGame {
221///     rh8: bool,
222///     ng8: bool,
223///     /// Always play:
224///     bf8: bool,
225/// }
226///
227/// assert_eq!(
228///     SettingUpForTheNextGame::FIELD_DOCS,
229///     [
230///         "Confusing the audience.",
231///         "Confusing the audience.",
232///         "Always play:"
233///     ]
234/// );
235/// ```
236///
237/// ## 4. (selectively) disable line-trimming like so:
238///
239/// ```rust
240/// # use documented::DocumentedFields;
241/// #[derive(DocumentedFields)]
242/// #[documented_fields(trim = false)]
243/// struct Frankly {
244///     ///     Delicious.
245///     perrier: usize,
246///     ///     I'm vegan.
247///     #[documented_fields(trim = true)]
248///     fried_liver: bool,
249/// }
250///
251/// assert_eq!(Frankly::FIELD_DOCS, ["     Delicious.", "I'm vegan."]);
252/// ```
253///
254/// If there are other configuration options you wish to have, please
255/// submit an issue or a PR.
256#[cfg_attr(not(feature = "customise"), proc_macro_derive(DocumentedFields))]
257#[cfg_attr(
258    feature = "customise",
259    proc_macro_derive(DocumentedFields, attributes(documented_fields))
260)]
261pub fn documented_fields(input: TokenStream) -> TokenStream {
262    documented_fields_impl(parse_macro_input!(input), DocType::Str)
263        .unwrap_or_else(Error::into_compile_error)
264        .into()
265}
266
267/// Derive proc-macro for `DocumentedFieldsOpt` trait.
268///
269/// See [`DocumentedFields`] for usage.
270#[cfg_attr(not(feature = "customise"), proc_macro_derive(DocumentedFieldsOpt))]
271#[cfg_attr(
272    feature = "customise",
273    proc_macro_derive(DocumentedFieldsOpt, attributes(documented_fields))
274)]
275pub fn documented_fields_opt(input: TokenStream) -> TokenStream {
276    documented_fields_impl(parse_macro_input!(input), DocType::OptStr)
277        .unwrap_or_else(Error::into_compile_error)
278        .into()
279}
280
281/// Derive proc-macro for `DocumentedVariants` trait.
282///
283/// # Example
284///
285/// ```rust
286/// use documented::{DocumentedVariants, Error};
287///
288/// #[derive(DocumentedVariants)]
289/// enum NeverPlay {
290///     /// Terrible.
291///     F3,
292///     /// I fell out of my chair.
293///     F6,
294/// }
295///
296/// assert_eq!(NeverPlay::F3.get_variant_docs(), "Terrible.");
297/// assert_eq!(NeverPlay::F6.get_variant_docs(), "I fell out of my chair.");
298/// ```
299///
300/// # Configuration
301///
302/// With the `customise` feature enabled, you can customise this macro's
303/// behaviour using the `#[documented_variants(...)]` attribute. Note that this
304/// attribute works on both the container and each individual variant, with the
305/// per-variant configurations overriding container configurations, which
306/// override the default.
307///
308/// Currently, you can:
309///
310/// ## 1. set a default value when doc comments are absent like so:
311///
312/// ```rust
313/// # use documented::DocumentedVariants;
314/// #[derive(DocumentedVariants)]
315/// #[documented_variants(default = "Still theory.")]
316/// enum OurHeroPlayed {
317///     G4Mate,
318///     OOOMate,
319///     /// Frankly ridiculous.
320///     Bf1g2Mate,
321/// }
322///
323/// assert_eq!(OurHeroPlayed::G4Mate.get_variant_docs(), "Still theory.");
324/// assert_eq!(OurHeroPlayed::OOOMate.get_variant_docs(), "Still theory.");
325/// assert_eq!(
326///     OurHeroPlayed::Bf1g2Mate.get_variant_docs(),
327///     "Frankly ridiculous."
328/// );
329/// ```
330///
331/// ## 2. (selectively) disable line-trimming like so:
332///
333/// ```rust
334/// # use documented::DocumentedVariants;
335/// #[derive(DocumentedVariants)]
336/// #[documented_variants(trim = false)]
337/// enum Always {
338///     ///     Or the quality.
339///     SacTheExchange,
340///     ///     Like a Frenchman.
341///     #[documented_variants(trim = true)]
342///     Retreat,
343/// }
344/// assert_eq!(
345///     Always::SacTheExchange.get_variant_docs(),
346///     "     Or the quality."
347/// );
348/// assert_eq!(Always::Retreat.get_variant_docs(), "Like a Frenchman.");
349/// ```
350///
351/// If there are other configuration options you wish to have, please
352/// submit an issue or a PR.
353#[cfg_attr(not(feature = "customise"), proc_macro_derive(DocumentedVariants))]
354#[cfg_attr(
355    feature = "customise",
356    proc_macro_derive(DocumentedVariants, attributes(documented_variants))
357)]
358pub fn documented_variants(input: TokenStream) -> TokenStream {
359    documented_variants_impl(parse_macro_input!(input), DocType::Str)
360        .unwrap_or_else(Error::into_compile_error)
361        .into()
362}
363
364/// Derive proc-macro for `DocumentedVariantsOpt` trait.
365///
366/// See [`DocumentedVariants`] for usage.
367#[cfg_attr(not(feature = "customise"), proc_macro_derive(DocumentedVariantsOpt))]
368#[cfg_attr(
369    feature = "customise",
370    proc_macro_derive(DocumentedVariantsOpt, attributes(documented_variants))
371)]
372pub fn documented_variants_opt(input: TokenStream) -> TokenStream {
373    documented_variants_impl(parse_macro_input!(input), DocType::OptStr)
374        .unwrap_or_else(Error::into_compile_error)
375        .into()
376}
377
378/// Macro to extract the documentation on any item that accepts doc comments
379/// and store it in a const variable.
380///
381/// By default, this const variable inherits visibility from its parent item.
382/// This can be manually configured; see configuration section below.
383///
384/// # Examples
385///
386/// ```rust
387/// use documented::docs_const;
388///
389/// /// This is a test function
390/// #[docs_const]
391/// fn test_fn() {}
392///
393/// assert_eq!(TEST_FN_DOCS, "This is a test function");
394/// ```
395///
396/// # Configuration
397///
398/// With the `customise` feature enabled, you can customise this macro's
399/// behaviour using attribute arguments.
400///
401/// Currently, you can:
402///
403/// ## 1. set a custom constant visibility like so:
404///
405/// ```rust
406/// mod submodule {
407///     # use documented::docs_const;
408///     /// Boo!
409///     #[docs_const(vis = pub)]
410///     struct Wooooo;
411/// }
412///
413/// // notice how the constant can be seen from outside
414/// assert_eq!(submodule::WOOOOO_DOCS, "Boo!");
415/// ```
416///
417/// ## 2. set a custom constant name like so:
418///
419/// ```rust
420/// # use documented::docs_const;
421/// /// If you have a question raise your hand
422/// #[docs_const(rename = "DONT_RAISE_YOUR_HAND")]
423/// mod whatever {}
424///
425/// assert_eq!(DONT_RAISE_YOUR_HAND, "If you have a question raise your hand");
426/// ```
427///
428/// ## 3. set a default value when doc comments are absent like so:
429///
430/// ```rust
431/// use documented::docs_const;
432///
433/// #[docs_const(default = "In this position many of you blunder.")]
434/// trait StartingPosition {}
435///
436/// assert_eq!(
437///     STARTING_POSITION_DOCS,
438///     "In this position many of you blunder."
439/// );
440/// ```
441///
442/// This option is primarily designed for [`DocumentedFields`] and
443/// [`DocumentedVariants`], so it's probably not very useful here. But it could
444/// conceivably come in handy in some niche meta-programming contexts.
445///
446/// ## 4. disable line-trimming like so:
447///
448/// ```rust
449/// # use documented::docs_const;
450/// ///     This is a test constant
451/// #[docs_const(trim = false)]
452/// const test_const: u8 = 0;
453///
454/// assert_eq!(TEST_CONST_DOCS, "     This is a test constant");
455/// ```
456///
457/// ---
458///
459/// Multiple option can be specified in a list like so:
460/// `name = "FOO", trim = false`.
461///
462/// If there are other configuration options you wish to have, please
463/// submit an issue or a PR.
464#[proc_macro_attribute]
465pub fn docs_const(#[allow(unused_variables)] attr: TokenStream, item: TokenStream) -> TokenStream {
466    #[cfg(not(feature = "customise"))]
467    let ts = docs_const_impl(parse_macro_input!(item));
468    #[cfg(feature = "customise")]
469    let ts = docs_const_impl(parse_macro_input!(item), parse_macro_input!(attr));
470
471    ts.unwrap_or_else(Error::into_compile_error).into()
472}