Skip to content

Commit

Permalink
Add general documentation to fluent-bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
gregtatum committed Oct 27, 2022
1 parent 03af84f commit 3faecfa
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 6 deletions.
14 changes: 13 additions & 1 deletion fluent-bundle/src/memoizer.rs
Expand Up @@ -2,17 +2,29 @@ use crate::types::FluentType;
use intl_memoizer::Memoizable;
use unic_langid::LanguageIdentifier;

/// This trait contains thread-safe methods which extend [intl_memoizer::IntlLangMemoizer].
/// It is used as the generic bound in this crate when a memoizer is needed.
pub trait MemoizerKind: 'static {
fn new(lang: LanguageIdentifier) -> Self
where
Self: Sized;

fn with_try_get_threadsafe<I, R, U>(&self, args: I::Args, cb: U) -> Result<R, I::Error>
/// A threadsafe variant of `with_try_get` from [intl_memoizer::IntlLangMemoizer].
/// The generics enforce that `Self` and its arguments are actually threadsafe.
///
/// `I` - The [Memoizable](intl_memoizer::Memoizable) internationalization formatter.
///
/// `R` - The result from the format operation.
///
/// `U` - The callback that accepts the instance of the intl formatter, and generates
/// some kind of results `R`.
fn with_try_get_threadsafe<I, R, U>(&self, args: I::Args, callback: U) -> Result<R, I::Error>
where
Self: Sized,
I: Memoizable + Send + Sync + 'static,
I::Args: Send + Sync + 'static,
U: FnOnce(&I) -> R;

/// Wires up the `as_string` or `as_string_threadsafe` variants for [`FluentType`].
fn stringify_value(&self, value: &dyn FluentType) -> std::borrow::Cow<'static, str>;
}
81 changes: 76 additions & 5 deletions fluent-bundle/src/types/mod.rs
Expand Up @@ -28,9 +28,19 @@ use crate::memoizer::MemoizerKind;
use crate::resolver::Scope;
use crate::resource::FluentResource;

/// Custom types can implement the [`FluentType`] trait in order to generate a string
/// value for use in the message generation process.
pub trait FluentType: fmt::Debug + AnyEq + 'static {
/// Create a clone of the underlying type.
fn duplicate(&self) -> Box<dyn FluentType + Send>;

/// Convert the custom type into a string value, for instance a custom DateTime
/// type could return "Oct. 27, 2022".
fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str>;

/// Convert the custom type into a string value, for instance a custom DateTime
/// type could return "Oct. 27, 2022". This operation is provided the threadsafe
/// [IntlLangMemoizer](intl_memoizer::concurrent::IntlLangMemoizer).
fn as_string_threadsafe(
&self,
intls: &intl_memoizer::concurrent::IntlLangMemoizer,
Expand Down Expand Up @@ -101,15 +111,72 @@ impl<'s> Clone for FluentValue<'s> {
}

impl<'source> FluentValue<'source> {
pub fn try_number<S: ToString>(v: S) -> Self {
let s = v.to_string();
if let Ok(num) = FluentNumber::from_str(&s) {
num.into()
/// Attempts to parse the string representation of a `value` that supports
/// [`ToString`] into a [`FluentValue::Number`]. If it fails, it will instead
/// convert it to a [`FluentValue::String`].
///
/// ```
/// use fluent_bundle::types::{FluentNumber, FluentNumberOptions, FluentValue};
///
/// // "2" parses into a `FluentNumber`
/// assert_eq!(
/// FluentValue::try_number("2"),
/// FluentValue::Number(FluentNumber::new(2.0, FluentNumberOptions::default()))
/// );
///
/// // Floats can be parsed as well.
/// assert_eq!(
/// FluentValue::try_number("3.141569"),
/// FluentValue::Number(FluentNumber::new(
/// 3.141569,
/// FluentNumberOptions {
/// minimum_fraction_digits: Some(6),
/// ..Default::default()
/// }
/// ))
/// );
///
/// // When a value is not a valid number, it falls back to a `FluentValue::String`
/// assert_eq!(
/// FluentValue::try_number("A string"),
/// FluentValue::String("A string".into())
/// );
/// ```
pub fn try_number<S: ToString>(value: S) -> Self {
let string = value.to_string();
if let Ok(number) = FluentNumber::from_str(&string) {
number.into()
} else {
s.into()
string.into()
}
}

/// Checks to see if two [`FluentValues`](FluentValue) match each other by having the
/// same type and contents. The special exception is in the case of a string being
/// compared to a number. Here attempt to check that the plural rule category matches.
///
/// ```
/// use fluent_bundle::resolver::Scope;
/// use fluent_bundle::{types::FluentValue, FluentBundle, FluentResource};
/// use unic_langid::langid;
///
/// let langid_ars = langid!("en");
/// let bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_ars]);
/// let scope = Scope::new(&bundle, None, None);
///
/// // Matching examples:
/// assert!(FluentValue::try_number("2").matches(&FluentValue::try_number("2"), &scope));
/// assert!(FluentValue::from("fluent").matches(&FluentValue::from("fluent"), &scope));
/// assert!(
/// FluentValue::from("one").matches(&FluentValue::try_number("1"), &scope),
/// "Plural rules are matched."
/// );
///
/// // Non-matching examples:
/// assert!(!FluentValue::try_number("2").matches(&FluentValue::try_number("3"), &scope));
/// assert!(!FluentValue::from("fluent").matches(&FluentValue::from("not fluent"), &scope));
/// assert!(!FluentValue::from("two").matches(&FluentValue::try_number("100"), &scope),);
/// ```
pub fn matches<R: Borrow<FluentResource>, M>(
&self,
other: &FluentValue,
Expand All @@ -131,6 +198,8 @@ impl<'source> FluentValue<'source> {
"other" => PluralCategory::OTHER,
_ => return false,
};
// This string matches a plural rule keyword. Check if the number
// matches the plural rule category.
scope
.bundle
.intls
Expand All @@ -144,6 +213,7 @@ impl<'source> FluentValue<'source> {
}
}

/// Write out a string version of the [`FluentValue`] to `W`.
pub fn write<W, R, M>(&self, w: &mut W, scope: &Scope<R, M>) -> fmt::Result
where
W: fmt::Write,
Expand All @@ -164,6 +234,7 @@ impl<'source> FluentValue<'source> {
}
}

/// Converts the [`FluentValue`] to a string.
pub fn as_string<R: Borrow<FluentResource>, M>(&self, scope: &Scope<R, M>) -> Cow<'source, str>
where
M: MemoizerKind,
Expand Down
13 changes: 13 additions & 0 deletions fluent-syntax/src/ast/mod.rs
Expand Up @@ -1438,9 +1438,22 @@ pub enum InlineExpression<S> {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum Expression<S> {
/// A select expression such as:
/// ```ftl
/// key = { $var ->
/// [key1] Value 1
/// *[other] Value 2
/// }
/// ```
Select {
selector: InlineExpression<S>,
variants: Vec<Variant<S>>,
},

/// An inline expression such as `${ username }`:
///
/// ```ftl
/// hello-user = Hello ${ username }
/// ```
Inline(InlineExpression<S>),
}

0 comments on commit 3faecfa

Please sign in to comment.