local_fmt_macros/lib.rs
1/// A procedural macro to define localized formatted messages.
2///
3/// This macro generates a set of constant messages that can be used for localization purposes.
4/// It allows you to define messages in multiple languages and switch between them dynamically
5/// based on a language supplier function.
6///
7/// # Arguments
8///
9/// * `name` - The name of the generated static message set.
10/// * `lang` - The enumeration representing the supported languages.
11/// * `message` - The struct containing the constant messages.
12/// * `supplier` - The language supplier, a function of type `fn() -> Lang`.
13/// It determines how the current language is selected dynamically at runtime.
14/// * `def location` - Specifies the location of the language definition files. This can be either:
15/// * `lang_file` - The path to a single language definition file.
16/// * `lang_folder` - The folder containing multiple language definition files, one for each language.
17///
18/// # Notes
19/// * The language definition file(s) must be in the TOML format.
20/// * The `def location` expands to `CARGO_MANIFEST_DIR/{your_path}`, where `CARGO_MANIFEST_DIR`
21/// is an environment variable representing the directory containing the Cargo.toml file of your project.
22/// This ensures that paths are resolved relative to the project's root directory.
23///
24/// ## Message Nesting
25/// * The `message` struct can be nested, allowing for organized grouping of related messages.
26/// For example, you can have a struct for action messages nested within a main message struct.
27/// This helps in maintaining a clean and structured message hierarchy.
28///
29/// ## Static String Loading
30/// * If a message does not require any arguments, it can be loaded as a `&'static str`.
31/// This allows for efficient handling of static messages without the need for formatting.
32/// Simply define the message field as `&'static str` in your message struct.
33///
34/// # Example
35///
36/// ## Example with `lang_file = "lang.toml"`
37///
38/// The language definition file should be structured as follows:
39///
40/// ```toml
41/// # in lang.toml
42///
43/// # The table name corresponds to the language enumeration variant,
44/// # and the message field matches the field in the message struct.
45/// [EN]
46/// hello = "Hello, world! {0}"
47///
48/// [JA]
49/// hello = "こんにちは、世界!{0}"
50/// ```
51///
52/// ## Example with `lang_folder = "langs"`
53///
54/// The folder should contain separate TOML files for each language:
55///
56/// <pre>
57/// /langs
58/// ├── EN.toml
59/// └── JA.toml
60/// </pre>
61///
62/// Each file should be formatted as follows:
63///
64/// ```toml
65/// # in EN.toml
66///
67/// # The table name corresponds to the language enumeration variant.
68/// hello = "Hello, world! {0}"
69/// ```
70/// ```toml
71/// # in JA.toml
72///
73/// # The table name corresponds to the language enumeration variant.
74/// hello = "こんにちは、世界!{0}"
75/// ```
76///
77///
78/// # Example 1
79///
80/// ```rust
81/// # #![cfg(feature = "toml")]
82///
83/// use std::sync::RwLock;
84/// use enum_table::Enumable;
85/// use local_fmt::{def_local_fmt, StaticMessage};
86///
87/// #[derive(Clone, Copy, Enumable)]
88/// enum Lang {
89/// EN,
90/// JA,
91/// }
92///
93/// struct Messages {
94/// pub hello: StaticMessage<1>,
95/// }
96///
97/// static LANG: RwLock<Lang> = RwLock::new(Lang::EN);
98///
99/// def_local_fmt!(
100/// name = MESSAGES,
101/// lang = Lang,
102/// message = Messages,
103/// supplier = || *LANG.read().unwrap(),
104/// file_type = "toml",
105/// lang_folder = "doctest/langs"
106/// );
107///
108/// assert_eq!(MESSAGES.hello.format(&["Rust"]), "Hello, world! Rust");
109///
110/// *LANG.write().unwrap() = Lang::JA;
111///
112/// assert_eq!(MESSAGES.hello.format(&["Rust"]), "こんにちは、世界! Rust");
113/// ```
114///
115/// # Example 2
116/// ```
117/// # #![cfg(feature = "json")]
118///
119/// use enum_table::Enumable;
120/// use local_fmt::{def_local_fmt, StaticMessage, LocalFmt};
121/// use std::sync::RwLock;
122///
123/// #[derive(Clone, Copy, Enumable)]
124/// enum Lang {
125/// EN,
126/// JA,
127/// }
128///
129/// struct ActionMessages {
130/// pub attack: &'static str,
131/// pub run: &'static str,
132/// }
133///
134/// struct Messages {
135/// pub actions: ActionMessages,
136/// pub hello: StaticMessage<1>,
137/// }
138///
139/// static LANG: RwLock<Lang> = RwLock::new(Lang::EN);
140///
141/// def_local_fmt!(
142/// name = MESSAGES,
143/// lang = Lang,
144/// message = Messages {
145/// actions: ActionMessages,
146/// },
147/// supplier = || *LANG.read().unwrap(),
148/// file_type = "json",
149/// lang_file = "doctest/lang.json"
150/// );
151///
152/// assert_eq!(MESSAGES.hello.format(&["Rust"]), "Hello, world! Rust");
153///
154/// *LANG.write().unwrap() = Lang::JA;
155///
156/// assert_eq!(MESSAGES.hello.format(&["Rust"]), "こんにちは、世界! Rust");
157/// ```
158#[proc_macro]
159pub fn def_local_fmt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
160 let args = syn::parse_macro_input!(input as local_fmt_macros_internal::def_local_fmt::Args);
161
162 local_fmt_macros_internal::def_local_fmt::generate(args)
163 .unwrap_or_else(syn::Error::into_compile_error)
164 .into()
165}
166
167/// Generates a static message with placeholders for arguments.
168///
169/// This macro creates a `StaticMessage` that can be used to format strings with
170/// a fixed number of arguments. The placeholders in the message are denoted by
171/// `{0}`, `{1}`, etc., which correspond to the arguments provided during formatting.
172///
173/// # Notes
174///
175/// - The number of placeholders in the message must match the number of arguments
176/// specified in the `StaticMessage` type.
177/// - The macro supports using constants within the message string.
178/// - You can include numeric constants directly in the message using the `{u:}` or `{i:}` syntax
179/// for unsigned and signed integers, respectively.
180///
181/// # Examples
182///
183/// ```rust
184/// use local_fmt::{gen_static_message, StaticMessage};
185///
186/// // Example with argument
187/// {
188/// const MESSAGE: StaticMessage<1> = gen_static_message!("Hello! {0}");
189/// let text = MESSAGE.format(&["World!"]);
190/// assert_eq!(text, "Hello! World!");
191/// }
192///
193/// // Example with const placeholder
194/// {
195/// const HELLO: &str = "Hello";
196/// const MESSAGE: StaticMessage<2> = gen_static_message!("{HELLO} {0} World! {1}");
197/// let text = MESSAGE.format(&["Beautiful", "Rust!"]);
198/// assert_eq!(text, "Hello Beautiful World! Rust!");
199/// }
200///
201/// // Example with duplicate arguments
202/// {
203/// const MESSAGE: StaticMessage<1> = gen_static_message!("{0} World! {0}");
204/// let text = MESSAGE.format(&["Beautiful"]);
205/// assert_eq!(text, "Beautiful World! Beautiful");
206/// }
207///
208/// // Example with unsigned number
209/// {
210/// const NUM: usize = 123456789;
211/// const MESSAGE: StaticMessage<1> = gen_static_message!("Hello! {0} {u:NUM}");
212/// let text = MESSAGE.format(&["World!"]);
213/// assert_eq!(text, "Hello! World! 123456789");
214/// }
215///
216/// // Example with signed number
217/// {
218/// const NUM: i32 = -123456789;
219/// const MESSAGE: StaticMessage<1> = gen_static_message!("Hello! {0} {i:NUM}");
220/// let text = MESSAGE.format(&["World!"]);
221/// assert_eq!(text, "Hello! World! -123456789");
222/// }
223#[proc_macro]
224pub fn gen_static_message(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
225 let args = syn::parse_macro_input!(input as local_fmt_macros_internal::util_macro::Args);
226
227 args.to_token(true)
228 .unwrap_or_else(|err| err.to_compile_error())
229 .into()
230}
231
232/// Generates an allocatable message with placeholders for arguments.
233///
234/// This macro creates an `AllocMessage` that can be used to format strings with
235/// a fixed number of arguments. The placeholders in the message are denoted by
236/// `{0}`, `{1}`, etc., which correspond to the arguments provided during formatting.
237///
238/// # Notes
239///
240/// - The number of placeholders in the message must match the number of arguments
241/// specified in the `AllocMessage` type.
242/// - The macro supports using ident within the message string.
243///
244/// # Examples
245///
246/// ```rust
247/// use local_fmt::{gen_alloc_message, AllocMessage};
248///
249/// // Example with argument
250/// {
251/// let message: AllocMessage<1> = gen_alloc_message!("Hello! {0}");
252/// let text = message.format(&["World!"]);
253/// assert_eq!(text, "Hello! World!");
254/// }
255///
256/// // Example with string placeholder
257/// {
258/// let hello: String = "Hello".to_string();
259/// let message: AllocMessage<2> = gen_alloc_message!("{hello} {0} World! {1}");
260/// let text = message.format(&["Beautiful", "Rust!"]);
261/// assert_eq!(text, "Hello Beautiful World! Rust!");
262/// }
263///
264/// // Example with duplicate arguments
265/// {
266/// let message: AllocMessage<1> = gen_alloc_message!("{0} World! {0}");
267/// let text = message.format(&["Beautiful"]);
268/// assert_eq!(text, "Beautiful World! Beautiful");
269/// }
270#[proc_macro]
271pub fn gen_alloc_message(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
272 let args = syn::parse_macro_input!(input as local_fmt_macros_internal::util_macro::Args);
273
274 args.to_token(false)
275 .unwrap_or_else(|err| err.to_compile_error())
276 .into()
277}