nimbus_fml/backends/kotlin/gen_structs/
bundled.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use std::fmt::Display;
6
7use super::common::{code_type, quoted};
8use crate::backends::{CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType};
9use crate::intermediate_representation::{Literal, TypeRef};
10use heck::SnakeCase;
11use unicode_segmentation::UnicodeSegmentation;
12
13pub(crate) struct TextCodeType;
14
15impl CodeType for TextCodeType {
16    /// The language specific label used to reference this type. This will be used in
17    /// method signatures and property declarations.
18    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
19        "String".into()
20    }
21
22    fn property_getter(
23        &self,
24        oracle: &dyn CodeOracle,
25        vars: &dyn Display,
26        prop: &dyn Display,
27        default: &dyn Display,
28    ) -> String {
29        code_type::property_getter(self, oracle, vars, prop, default)
30    }
31
32    fn value_getter(
33        &self,
34        oracle: &dyn CodeOracle,
35        vars: &dyn Display,
36        prop: &dyn Display,
37    ) -> String {
38        code_type::value_getter(self, oracle, vars, prop)
39    }
40
41    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
42        code_type::value_mapper(self, oracle)
43    }
44
45    /// The name of the type as it's represented in the `Variables` object.
46    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
47    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
48        VariablesType::Text
49    }
50
51    fn defaults_type(&self, _oracle: &dyn CodeOracle) -> String {
52        "StringHolder".to_string()
53    }
54
55    fn defaults_mapper(
56        &self,
57        _oracle: &dyn CodeOracle,
58        value: &dyn Display,
59        vars: &dyn Display,
60    ) -> Option<String> {
61        Some(format!(
62            "{value}.toString({vars}.context)",
63            vars = vars,
64            value = value
65        ))
66    }
67
68    /// A representation of the given literal for this type.
69    /// N.B. `Literal` is aliased from `serde_json::Value`.
70    fn literal(
71        &self,
72        _oracle: &dyn CodeOracle,
73        _ctx: &dyn Display,
74        _renderer: &dyn LiteralRenderer,
75        literal: &Literal,
76    ) -> String {
77        match literal {
78            serde_json::Value::String(v) => {
79                if !is_resource_id(v) {
80                    format!("Res.string({literal})", literal = quoted(v))
81                } else {
82                    format!("Res.string(R.string.{id})", id = v.to_snake_case())
83                }
84            }
85            _ => unreachable!("Expecting a string"),
86        }
87    }
88
89    fn preference_getter(
90        &self,
91        oracle: &dyn CodeOracle,
92        prefs: &dyn Display,
93        pref_key: &dyn Display,
94    ) -> Option<String> {
95        let ct = oracle.find(&TypeRef::String);
96        ct.preference_getter(oracle, prefs, pref_key)
97    }
98
99    fn is_resource_id(&self, literal: &Literal) -> bool {
100        match literal {
101            serde_json::Value::String(v) => is_resource_id(v),
102            _ => unreachable!("Expecting a string"),
103        }
104    }
105
106    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
107        Some(vec![
108            "android.content.Context".to_string(),
109            "org.mozilla.experiments.nimbus.Res".to_string(),
110            "org.mozilla.experiments.nimbus.StringHolder".to_string(),
111        ])
112    }
113}
114
115fn is_resource_id(string: &str) -> bool {
116    // In Android apps, resource identifiers are [a-z_][a-z0-9_]*
117    // We don't use the regex crate, so we need some code.
118    let start = "abcdefghijklmnopqrstuvwxyz_";
119    let rest = "abcdefghijklmnopqrstuvwxyz_0123456789";
120    !string.is_empty()
121        && string
122            .grapheme_indices(true)
123            .all(|(i, c)| -> bool { (i > 0 && rest.contains(c)) || start.contains(c) })
124}
125
126pub(crate) struct ImageCodeType;
127
128impl CodeType for ImageCodeType {
129    /// The language specific label used to reference this type. This will be used in
130    /// method signatures and property declarations.
131    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
132        "Res<Drawable>".into()
133    }
134
135    fn property_getter(
136        &self,
137        oracle: &dyn CodeOracle,
138        vars: &dyn Display,
139        prop: &dyn Display,
140        default: &dyn Display,
141    ) -> String {
142        code_type::property_getter(self, oracle, vars, prop, default)
143    }
144
145    fn value_getter(
146        &self,
147        oracle: &dyn CodeOracle,
148        vars: &dyn Display,
149        prop: &dyn Display,
150    ) -> String {
151        code_type::value_getter(self, oracle, vars, prop)
152    }
153
154    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
155        code_type::value_mapper(self, oracle)
156    }
157
158    /// The name of the type as it's represented in the `Variables` object.
159    /// The string return may be used to combine with an identifier, e.g. a `Variables` method name.
160    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
161        VariablesType::Image
162    }
163
164    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
165        oracle.find(&TypeIdentifier::Int).type_label(oracle)
166    }
167
168    fn defaults_mapper(
169        &self,
170        _oracle: &dyn CodeOracle,
171        value: &dyn Display,
172        vars: &dyn Display,
173    ) -> Option<String> {
174        Some(format!(
175            "Res.drawable({vars}.context, {value})",
176            vars = vars,
177            value = value
178        ))
179    }
180
181    fn as_json_transform(&self, _oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
182        Some(format!("{}.resourceName", prop))
183    }
184
185    /// A representation of the given literal for this type.
186    /// N.B. `Literal` is aliased from `serde_json::Value`.
187    fn literal(
188        &self,
189        _oracle: &dyn CodeOracle,
190        _ctx: &dyn Display,
191        _renderer: &dyn LiteralRenderer,
192        literal: &Literal,
193    ) -> String {
194        match literal {
195            serde_json::Value::String(v) if is_resource_id(v) => {
196                format!(r#"R.drawable.{id}"#, id = v.to_snake_case())
197            }
198            _ => unreachable!("Expecting a string matching an image/drawable resource"),
199        }
200    }
201
202    fn is_resource_id(&self, literal: &Literal) -> bool {
203        match literal {
204            serde_json::Value::String(v) => is_resource_id(v),
205            _ => unreachable!(
206                "Expecting a string matching an image resource, with pattern [a-z][a-z0-9_]*"
207            ),
208        }
209    }
210
211    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
212        Some(vec![
213            "android.graphics.drawable.Drawable".to_string(),
214            "org.mozilla.experiments.nimbus.Res".to_string(),
215        ])
216    }
217}
218
219#[cfg(test)]
220mod unit_tests {
221
222    use super::*;
223    use crate::error::Result;
224
225    #[test]
226    fn test_is_resource_id() -> Result<()> {
227        assert!(is_resource_id("ok"));
228        assert!(is_resource_id("_ok"));
229        assert!(is_resource_id("ok_then"));
230        assert!(!is_resource_id("https://foo.com"));
231        assert!(!is_resource_id("Ok then"));
232        assert!(!is_resource_id("ok then"));
233        assert!(!is_resource_id("ok!"));
234        assert!(!is_resource_id("1ok"));
235
236        Ok(())
237    }
238}