1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use fluent_syntax::ast;
use fluent_syntax::parser::{parse_runtime, ParserError};

use self_cell::self_cell;

type Resource<'s> = ast::Resource<&'s str>;

self_cell!(
    pub struct InnerFluentResource {
        owner: String,

        #[covariant]
        dependent: Resource,
    }

    impl {Debug}
);

/// A resource containing a list of localization messages.
///
/// [`FluentResource`] wraps an [`Abstract Syntax Tree`](../fluent_syntax/ast/index.html) produced by the
/// [`parser`](../fluent_syntax/parser/index.html) and provides an access to a list
/// of its entries.
///
/// A good mental model for a resource is a single FTL file, but in the future
/// there's nothing preventing a resource from being stored in a data base,
/// pre-parsed format or in some other structured form.
///
/// # Example
///
/// ```
/// use fluent_bundle::FluentResource;
///
/// let source = r#"
///
/// hello-world = Hello World!
///
/// "#;
///
/// let resource = FluentResource::try_new(source.to_string())
///     .expect("Errors encountered while parsing a resource.");
///
/// assert_eq!(resource.entries().count(), 1);
/// ```
///
/// # Ownership
///
/// A resource owns the source string and the AST contains references
/// to the slices of the source.
#[derive(Debug)]
pub struct FluentResource(InnerFluentResource);

impl FluentResource {
    /// A fallible constructor of a new [`FluentResource`].
    ///
    /// It takes an encoded `Fluent Translation List` string, parses
    /// it and stores both, the input string and the AST view of it,
    /// for runtime use.
    ///
    /// # Example
    ///
    /// ```
    /// use fluent_bundle::FluentResource;
    ///
    /// let source = r#"
    ///
    /// hello-world = Hello, { $user }!
    ///
    /// "#;
    ///
    /// let resource = FluentResource::try_new(source.to_string());
    ///
    /// assert!(resource.is_ok());
    /// ```
    ///
    /// # Errors
    ///
    /// The method will return the resource irrelevant of parse errors
    /// encountered during parsing of the source, but in case of errors,
    /// the `Err` variant will contain both the structure and a vector
    /// of errors.
    pub fn try_new(source: String) -> Result<Self, (Self, Vec<ParserError>)> {
        let mut errors = None;

        let res = InnerFluentResource::new(source, |source| match parse_runtime(source.as_str()) {
            Ok(ast) => ast,
            Err((ast, err)) => {
                errors = Some(err);
                ast
            }
        });

        match errors {
            None => Ok(Self(res)),
            Some(err) => Err((Self(res), err)),
        }
    }

    /// Returns a reference to the source string that was used
    /// to construct the [`FluentResource`].
    ///
    /// # Example
    ///
    /// ```
    /// use fluent_bundle::FluentResource;
    ///
    /// let source = "hello-world = Hello, { $user }!";
    ///
    /// let resource = FluentResource::try_new(source.to_string())
    ///     .expect("Failed to parse FTL.");
    ///
    /// assert_eq!(
    ///     resource.source(),
    ///     "hello-world = Hello, { $user }!"
    /// );
    /// ```
    pub fn source(&self) -> &str {
        &self.0.borrow_owner()
    }

    /// Returns an iterator over [`entries`](fluent_syntax::ast::Entry) of the [`FluentResource`].
    ///
    /// # Example
    ///
    /// ```
    /// use fluent_bundle::FluentResource;
    /// use fluent_syntax::ast;
    ///
    /// let source = r#"
    ///
    /// hello-world = Hello, { $user }!
    ///
    /// "#;
    ///
    /// let resource = FluentResource::try_new(source.to_string())
    ///     .expect("Failed to parse FTL.");
    ///
    /// assert_eq!(
    ///     resource.entries().count(),
    ///     1
    /// );
    /// assert!(matches!(resource.entries().next(), Some(ast::Entry::Message(_))));
    /// ```
    pub fn entries(&self) -> impl Iterator<Item = &ast::Entry<&str>> {
        self.0.borrow_dependent().body.iter()
    }

    /// Returns an [`Entry`](fluent_syntax::ast::Entry) at the
    /// given index out of the [`FluentResource`].
    ///
    /// # Example
    ///
    /// ```
    /// use fluent_bundle::FluentResource;
    /// use fluent_syntax::ast;
    ///
    /// let source = r#"
    ///
    /// hello-world = Hello, { $user }!
    ///
    /// "#;
    ///
    /// let resource = FluentResource::try_new(source.to_string())
    ///     .expect("Failed to parse FTL.");
    ///
    /// assert!(matches!(resource.get_entry(0), Some(ast::Entry::Message(_))));
    /// ```
    pub fn get_entry(&self, idx: usize) -> Option<&ast::Entry<&str>> {
        self.0.borrow_dependent().body.get(idx)
    }
}