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}
172