nimbus_fml/backends/kotlin/gen_structs/
bundled.rs
1use 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 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 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 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 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 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 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 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}