Skip to main content

bern_target_api/
names.rs

1//! Identifiers generators derive from Bern's `snake_case` names. Each
2//! target escapes the results that are keywords of its own language.
3
4/// `main_window` → `MainWindow`. A hyphen separates words too.
5pub fn pascal(snake: &str) -> String {
6    snake
7        .split(['_', '-'])
8        .filter(|part| !part.is_empty())
9        .map(|part| {
10            let mut chars = part.chars();
11            match chars.next() {
12                Some(first) => first.to_uppercase().chain(chars).collect::<String>(),
13                None => String::new(),
14            }
15        })
16        .collect()
17}
18
19/// The name an application value type (a `#[value]` enum or record)
20/// has in generated Swift and Kotlin: `Layout` → `App_Layout`.
21///
22/// Those types sit at the top of the module the generated code shares
23/// with the platform's frameworks and Bern's runtime, so a bare name like
24/// SwiftUI's `Layout` or `View`, or Compose's `Modifier`, would hide the
25/// framework's type from every file of the module. No type of those
26/// frameworks and none of Bern's (`Bern…`) begins with `App_`, and the
27/// prefix keeps distinct names distinct.
28pub fn value_type(name: &str) -> String {
29    format!("App_{name}")
30}
31
32/// The class of an application session (a `#[session]` struct) in
33/// generated Swift and Kotlin: `URL` → `App_URL`.
34///
35/// The declared name with `Session` appended was Foundation's
36/// `URLSession` for a session named `URL`. Bern type names are unique
37/// per application, so sessions share the prefix of value types.
38pub fn session_type(name: &str) -> String {
39    value_type(name)
40}
41
42/// The variable through which a view of generated Swift and Kotlin reads
43/// a session: `Window` → `session_Window`.
44///
45/// `bern` and the declared name gave the views' own `bernWindow` and
46/// `bernEngine` for sessions named `Window` and `Engine`. The names
47/// generated for parameters and bindings have no underscore.
48pub fn session_variable(name: &str) -> String {
49    format!("session_{name}")
50}
51
52/// The view of a component in generated Swift and Kotlin: `empty` →
53/// `AppView_empty`.
54///
55/// The name in Pascal case with `View` appended was SwiftUI's
56/// `EmptyView`, `AnyView` or `ScrollView`, or a runtime's
57/// `BernPlayerView`, for components named `empty`, `any`, `scroll` or
58/// `bern_player`. No type or function of those frameworks or of the
59/// runtimes begins with `AppView_`, none of the types' `App_` names
60/// does, and the declared name follows unchanged, so distinct components
61/// keep distinct names.
62pub fn component(name: &str) -> String {
63    format!("AppView_{name}")
64}
65
66/// `last_action` → `lastAction`.
67pub fn camel(snake: &str) -> String {
68    let mut out = String::new();
69    let mut upper = false;
70    for (index, c) in snake.chars().enumerate() {
71        if c == '_' {
72            upper = index > 0;
73            continue;
74        }
75        if upper {
76            out.extend(c.to_uppercase());
77            upper = false;
78        } else {
79            out.push(c);
80        }
81    }
82    out
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn snake_case_becomes_pascal_and_camel_case() {
91        assert_eq!(pascal("main_window"), "MainWindow");
92        assert_eq!(pascal("svg-demo"), "SvgDemo");
93        assert_eq!(pascal("a__b_"), "AB");
94        assert_eq!(camel("last_action"), "lastAction");
95        assert_eq!(camel("_private"), "private");
96        assert_eq!(camel("volume_2"), "volume2");
97        assert_eq!(value_type("Layout"), "App_Layout");
98        assert_eq!(session_type("URL"), "App_URL");
99        assert_eq!(session_variable("Window"), "session_Window");
100        assert_eq!(
101            component("content_unavailable"),
102            "AppView_content_unavailable"
103        );
104    }
105}