1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/// An internal macro for adding the translations manager to an instance of an app based on whether or not one has been provided. This can only be used internally.
/// Regardless of the outcome of this, the locales must already have been set with `.locales_lit()`.
#[doc(hidden)]
#[macro_export]
macro_rules! add_translations_manager {
	($app:expr, $tm:expr, $locales:expr) => {
        // We're not using `FsTranslationsManager`, set up the locales separately from the translations manager
        $app = $app.locales_list($locales);
		$app = $app.translations_manager($tm);
	};
    ($app:expr, $locales:expr) => {
        // We're using `FsTranslationsManager`, and we have locales information, so there's a nice convenience function to do all the caching stuff for us!
        $app = $app.locales_lit_and_translations_manager($locales);
    };
}

#[cfg(feature = "standalone")]
#[doc(hidden)]
/// The default translations directory when we're running as a standalone binary.
pub static DFLT_TRANSLATIONS_DIR: &str = "./translations";
#[cfg(not(feature = "standalone"))]
#[doc(hidden)]
/// The default translations directory when we're running with the `.perseus/` support structure.
pub static DFLT_TRANSLATIONS_DIR: &str = "../translations";

/// Defines the components to create an entrypoint for the app. The actual entrypoint is created in the `.perseus/` crate (where we can
/// get all the dependencies without driving the user's `Cargo.toml` nuts). This also defines the template map. This is intended to make
/// compatibility with the Perseus CLI significantly easier.
///
/// Warning: all properties must currently be in the correct order (`root`, `templates`, `error_pages`, `global_state_creator`, `locales`, `static_aliases`,
/// `plugins`, `dist_path`, `mutable_store`, `translations_manager`).
///
/// Note: as of v0.3.4, this is just a wrapper over `PerseusAppBase`, which is the recommended way to create a new Perseus app (no macros involved).
#[macro_export]
macro_rules! define_app {
    // With locales
    {
        $(root: $root_selector:literal,)?
        templates: [
            $($template:expr),+
        ],
        error_pages: $error_pages:expr,
        $(global_state_creator: $global_state_creator:expr,)?
        // This deliberately enforces verbose i18n definition, and forces developers to consider i18n as integral
        locales: {
            default: $default_locale:literal,
            // The user doesn't have to define any other locales
            other: [$($other_locale:literal),*]
        }
        $(,static_aliases: {
            $($url:literal => $resource:literal),*
        })?
        $(,plugins: $plugins:expr)?
        $(,dist_path: $dist_path:literal)?
        $(,mutable_store: $mutable_store:expr)?
        $(,translations_manager: $translations_manager:expr)?
    } => {
        $crate::define_app!(
            @define_app,
            {
                $(root: $root_selector,)?
                templates: [
                    $($template),+
                ],
                error_pages: $error_pages,
                $(global_state_creator: $global_state_creator,)?
                locales: {
                    default: $default_locale,
                    // The user doesn't have to define any other locales (but they'll still get locale detection and the like)
                    other: [$($other_locale),*],
                    no_i18n: false
                }
                $(,static_aliases: {
                    $($url => $resource),*
                })?
                $(,plugins: $plugins)?
                $(,dist_path: $dist_path)?
                $(,mutable_store: $mutable_store)?
                $(,translations_manager: $translations_manager)?
            }
        );
    };
    // Without locales (default locale is set to xx-XX)
    {
        $(root: $root_selector:literal,)?
        templates: [
            $($template:expr),+
        ],
        error_pages: $error_pages:expr
        $(,global_state_creator: $global_state_creator:expr)?
        $(,static_aliases: {
            $($url:literal => $resource:literal),*
        })?
        $(,plugins: $plugins:expr)?
        $(,dist_path: $dist_path:literal)?
        $(,mutable_store: $mutable_store:expr)?
    } => {
        $crate::define_app!(
            @define_app,
            {
                $(root: $root_selector,)?
                templates: [
                    $($template),+
                ],
                error_pages: $error_pages,
                $(global_state_creator: $global_state_creator,)?
                // This deliberately enforces verbose i18n definition, and forces developers to consider i18n as integral
                locales: {
                    default: "xx-XX",
                    other: [],
                    no_i18n: true
                }
                $(,static_aliases: {
                    $($url => $resource),*
                })?
                $(,plugins: $plugins)?
                $(,dist_path: $dist_path)?
                $(,mutable_store: $mutable_store)?
            }
        );
    };
    // This is internal, and allows syntax abstractions and defaults
    (
        @define_app,
        {
            $(root: $root_selector:literal,)?
            templates: [
                $($template:expr),+
            ],
            error_pages: $error_pages:expr,
            $(global_state_creator: $global_state_creator:expr,)?
            // This deliberately enforces verbose i18n definition, and forces developers to consider i18n as integral
            locales: {
                default: $default_locale:literal,
                // The user doesn't have to define any other locales
                other: [$($other_locale:literal),*],
                // If this is `true`
                no_i18n: $no_i18n:literal
            }
            $(,static_aliases: {
                $($url:literal => $resource:literal),*
            })?
            $(,plugins: $plugins:expr)?
            $(,dist_path: $dist_path:literal)?
            $(,mutable_store: $mutable_store:expr)?
            $(,translations_manager: $translations_manager:expr)?
        }
    ) => {
        #[$crate::main]
        pub fn main<G: $crate::Html>() -> $crate::PerseusAppBase<G, impl $crate::stores::MutableStore, impl $crate::internal::i18n::TranslationsManager> {
            let mut app = $crate::PerseusAppBase::new();
            // If we have a mutable store, we'll actually initialize in a completely different way
            $(
                let mut app = $crate::PerseusAppBase::new_with_mutable_store($mutable_store).await;
            )?;
            // Conditionally add each property the user provided
            $(
                app = app.root($root_selector);
            )?;
            $(
                app = app.template(|| $template);
            )+;
            app = app.error_pages(|| $error_pages);
            $(
                app = app.global_state_creator($global_state_creator);
            )?;
            $($(
                app = app.static_alias($url, $resource);
            )*)?;
            $(
                app = app.plugins($plugins);
            )?;
            // Use `index.html` for the index view (for backward compatibility)
            // We need the filesystem here, and we don't need on it in the browser
            // We can't modify `app` if this is all in a block, so we compromise a bit
            let index_html = if cfg!(target_arch = "wasm32") {
                // In the browser, this would turn into using the hardocded default, but we don't need the index view there anyway
                ::std::result::Result::Err(::std::io::Error::from(::std::io::ErrorKind::NotFound))
            } else {
                ::std::fs::read_to_string("../index.html")
            };
            if let ::std::result::Result::Ok(index_html) = index_html {
                app = app.index_view_str(&index_html)
            }
            // Build a `Locales` instance
            let mut other_locales = ::std::vec::Vec::new();
            $(
                other_locales.push($other_locale.to_string());
            )*;
            // We can't guarantee that the user is using `FsTranslationsManager`, so we set only the locales, and the translations manager separately later
            let locales = $crate::internal::i18n::Locales {
                default: $default_locale.to_string(),
                other: other_locales,
                using_i18n: !$no_i18n
            };

            // Set the translations manager and locales information with a helper macro that can handle two different paths of provision
            $crate::add_translations_manager!(app, $($translations_manager,)? locales);

            app
        }

    };
}