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}