fluent_bundle/
resource.rs

1use fluent_syntax::ast;
2use fluent_syntax::parser::{parse_runtime, ParserError};
3
4use self_cell::self_cell;
5
6type Resource<'s> = ast::Resource<&'s str>;
7
8self_cell!(
9    pub struct InnerFluentResource {
10        owner: String,
11
12        #[covariant]
13        dependent: Resource,
14    }
15
16    impl {Debug}
17);
18
19/// A resource containing a list of localization messages.
20///
21/// [`FluentResource`] wraps an [`Abstract Syntax Tree`](../fluent_syntax/ast/index.html) produced by the
22/// [`parser`](../fluent_syntax/parser/index.html) and provides an access to a list
23/// of its entries.
24///
25/// A good mental model for a resource is a single FTL file, but in the future
26/// there's nothing preventing a resource from being stored in a data base,
27/// pre-parsed format or in some other structured form.
28///
29/// # Example
30///
31/// ```
32/// use fluent_bundle::FluentResource;
33///
34/// let source = r#"
35///
36/// hello-world = Hello World!
37///
38/// "#;
39///
40/// let resource = FluentResource::try_new(source.to_string())
41///     .expect("Errors encountered while parsing a resource.");
42///
43/// assert_eq!(resource.entries().count(), 1);
44/// ```
45///
46/// # Ownership
47///
48/// A resource owns the source string and the AST contains references
49/// to the slices of the source.
50#[derive(Debug)]
51pub struct FluentResource(InnerFluentResource);
52
53impl FluentResource {
54    /// A fallible constructor of a new [`FluentResource`].
55    ///
56    /// It takes an encoded `Fluent Translation List` string, parses
57    /// it and stores both, the input string and the AST view of it,
58    /// for runtime use.
59    ///
60    /// # Example
61    ///
62    /// ```
63    /// use fluent_bundle::FluentResource;
64    ///
65    /// let source = r#"
66    ///
67    /// hello-world = Hello, { $user }!
68    ///
69    /// "#;
70    ///
71    /// let resource = FluentResource::try_new(source.to_string());
72    ///
73    /// assert!(resource.is_ok());
74    /// ```
75    ///
76    /// # Errors
77    ///
78    /// The method will return the resource irrelevant of parse errors
79    /// encountered during parsing of the source, but in case of errors,
80    /// the `Err` variant will contain both the structure and a vector
81    /// of errors.
82    pub fn try_new(source: String) -> Result<Self, (Self, Vec<ParserError>)> {
83        let mut errors = None;
84
85        let res = InnerFluentResource::new(source, |source| match parse_runtime(source.as_str()) {
86            Ok(ast) => ast,
87            Err((ast, err)) => {
88                errors = Some(err);
89                ast
90            }
91        });
92
93        match errors {
94            None => Ok(Self(res)),
95            Some(err) => Err((Self(res), err)),
96        }
97    }
98
99    /// Returns a reference to the source string that was used
100    /// to construct the [`FluentResource`].
101    ///
102    /// # Example
103    ///
104    /// ```
105    /// use fluent_bundle::FluentResource;
106    ///
107    /// let source = "hello-world = Hello, { $user }!";
108    ///
109    /// let resource = FluentResource::try_new(source.to_string())
110    ///     .expect("Failed to parse FTL.");
111    ///
112    /// assert_eq!(
113    ///     resource.source(),
114    ///     "hello-world = Hello, { $user }!"
115    /// );
116    /// ```
117    pub fn source(&self) -> &str {
118        self.0.borrow_owner()
119    }
120
121    /// Returns an iterator over [`entries`](fluent_syntax::ast::Entry) of the [`FluentResource`].
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// use fluent_bundle::FluentResource;
127    /// use fluent_syntax::ast;
128    ///
129    /// let source = r#"
130    ///
131    /// hello-world = Hello, { $user }!
132    ///
133    /// "#;
134    ///
135    /// let resource = FluentResource::try_new(source.to_string())
136    ///     .expect("Failed to parse FTL.");
137    ///
138    /// assert_eq!(
139    ///     resource.entries().count(),
140    ///     1
141    /// );
142    /// assert!(matches!(resource.entries().next(), Some(ast::Entry::Message(_))));
143    /// ```
144    pub fn entries(&self) -> impl Iterator<Item = &ast::Entry<&str>> {
145        self.0.borrow_dependent().body.iter()
146    }
147
148    /// Returns an [`Entry`](fluent_syntax::ast::Entry) at the
149    /// given index out of the [`FluentResource`].
150    ///
151    /// # Example
152    ///
153    /// ```
154    /// use fluent_bundle::FluentResource;
155    /// use fluent_syntax::ast;
156    ///
157    /// let source = r#"
158    ///
159    /// hello-world = Hello, { $user }!
160    ///
161    /// "#;
162    ///
163    /// let resource = FluentResource::try_new(source.to_string())
164    ///     .expect("Failed to parse FTL.");
165    ///
166    /// assert!(matches!(resource.get_entry(0), Some(ast::Entry::Message(_))));
167    /// ```
168    pub fn get_entry(&self, idx: usize) -> Option<&ast::Entry<&str>> {
169        self.0.borrow_dependent().body.get(idx)
170    }
171}