1 | use fluent_syntax::ast; |
2 | use fluent_syntax::parser::{parse_runtime, ParserError}; |
3 | |
4 | use self_cell::self_cell; |
5 | |
6 | type Resource<'s> = ast::Resource<&'s str>; |
7 | |
8 | self_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)] |
51 | pub struct FluentResource(InnerFluentResource); |
52 | |
53 | impl 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 | |