diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2021-05-21 17:39:39 +0000 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2021-05-28 11:54:59 +0000 |
| commit | 2881f3eb28b24534a016dddf7fa21dce0a1fffb3 (patch) | |
| tree | e9ebf48f06a703b16347b905f61b37575327ebf6 /android | |
| parent | 7b82c66f0aea7e4d350c06f3624d641859b837e2 (diff) | |
| download | mullvadvpn-2881f3eb28b24534a016dddf7fa21dce0a1fffb3.tar.xz mullvadvpn-2881f3eb28b24534a016dddf7fa21dce0a1fffb3.zip | |
Refactor `match_str` to have extra features
It now allows matching prefixes and suffixes.
Diffstat (limited to 'android')
| -rw-r--r-- | android/translations-converter/src/gettext/match_str.rs | 213 | ||||
| -rw-r--r-- | android/translations-converter/src/gettext/mod.rs | 10 |
2 files changed, 209 insertions, 14 deletions
diff --git a/android/translations-converter/src/gettext/match_str.rs b/android/translations-converter/src/gettext/match_str.rs index 30733cbcd3..d09a639fb3 100644 --- a/android/translations-converter/src/gettext/match_str.rs +++ b/android/translations-converter/src/gettext/match_str.rs @@ -1,16 +1,211 @@ /// A helper macro to match a string to various prefix and suffix combinations. +/// +/// This macro can be used in a way that's similar to matching slices. It's possible to match an +/// input string to: +/// +/// - a specified string; +/// - a string with a specified prefix; +/// - a string with a specified suffix; +/// - a string with a specified prefix and a specified suffix. +/// +/// Multiple match patterns can be specified for the same match arm, as long as there are no +/// bindings for that match arm. When matching with prefixes and/or suffixes, the known parts of the +/// string can be removed, and the rest of the string is bound to a binding with a specified name. +/// +/// The macro has a limitation where all match arm bodies must be separated by commas, even if the +/// body is inside braces (`{}`). +/// +/// # Examples +/// +/// When not using any bindings, multiple match patterns can be on the same match arm. +/// +/// ``` +/// # let input_string = ""; +/// +/// match_str! { (input_string.trim()) +/// ["exact_string"] => { +/// println!("Exact match") +/// }, // Note: even though the body is enclosed by braces, a comma is still necessary +/// ["prefix", ..] | [.., "suffix"] => println!("Partial match"), +/// no_match => println!("Input {:?} did not match", no_match), +/// } +/// ``` +/// +/// If a match arm uses a binding, it must only have one match pattern. +/// +/// ``` +/// # let input_string = ""; +/// +/// match_str! { (input_string.trim()) +/// ["prefix", string] => println!("Prefixed: {:?}", string), +/// [string, "suffix"] => println!("Suffixed: {:?}", string), +/// ["prefix", string, "suffix"] => println!("Prefixed and Suffixed: {:?}", string), +/// // The following does not work because the match arm has a binding and therefore can't have +/// // more than one pattern: +/// // ["prefix", string] | [string, "suffix"] => { +/// // println!("Prefixed or Suffixed: {:?}", string) +/// // } +/// } +/// ``` +/// +/// # Implementation details +/// +/// The macro starts by extracting the matched expression and binding it to a local variable. It +/// will then call itself recursively to build an `if`-`else` chain to match that variable +/// according to the desired prefix/suffix patterns. +/// +/// When calling itself recursively, a `@match_str` marker is used to mark that the macro is +/// inside an inner call. The marker is follows by an initial state, which consists of three parts. +/// The first part is the condition expression, which is built by all the match patterns of that +/// arm. The second part is the binding for the input string. The third part is the binding used +/// for that match arm. +/// +/// The third part of the state initially starts out empty, but is later replaced by either a +/// binding expression or a `@no_bindings` marker. The marker allows the condition to grow with +/// other patterns in the same match arm. macro_rules! match_str { + // Start of matching + ( ($string:expr) $(|)* $( $match_body:tt )* ) => { + { + let string_to_match = $string; + + match_str!(@match_str((false), string_to_match) | $( $match_body )*) + } + }; + + // Match a whole string + ( + @match_str($conditions:tt, $input:ident $(, @no_bindings)*) + | [$string:literal] $( $rest:tt )* + ) => { + match_str!(@match_str(($conditions || $input == $string), $input, @no_bindings) $( $rest )*) + }; + + // Match a string with a given prefix + ( + @match_str($conditions:tt, $input:ident $(, @no_bindings)*) + | [$prefix:literal, ..] $( $rest:tt )* + ) => { + match_str!( + @match_str(($conditions || $input.starts_with($prefix)), $input, @no_bindings) + $( $rest )* + ) + }; + + // Match a string with a given suffix + ( + @match_str($conditions:tt, $input:ident $(, @no_bindings)*) + | [.., $suffix:literal] $( $rest:tt )* ) => { + match_str!( + @match_str(($conditions || $input.ends_with($suffix)), $input, @no_bindings) + $( $rest )* + ) + }; + + // Match a string with a given prefix and suffix + ( + @match_str($conditions:tt, $input:ident $(, @no_bindings)*) + | [$prefix:literal, .., $suffix:literal] + $( $rest:tt )* + ) => { + match_str!( + @match_str( + ($conditions || ($input.starts_with($prefix) && $input.ends_with($suffix))), + $input, + @no_bindings + ) + $( $rest )* + ) + }; + + // Match a string with a given prefix, binding the rest of the string after the prefix + ( + @match_str($conditions:tt, $input:ident) + | [$prefix:literal, $binding:ident] $( $rest:tt )* + ) => { + match_str!( + @match_str( + ($conditions || $input.starts_with($prefix)), + $input, + @binding $binding = &$input[$prefix.len()..] + ) + $( $rest )* + ) + }; + + // Match a string with a given suffix, binding the start of the string up to before the suffix + ( + @match_str($conditions:tt, $input:ident) + | [$binding:ident, $suffix:literal] $( $rest:tt )* + ) => { + match_str!( + @match_str( + ($conditions || $input.ends_with($suffix)), + $input, + @binding $binding = &$input[..($input.len()-$suffix.len())] + ) + $( $rest )* + ) + }; + + // Match a string with a given prefix and suffix, binding the middle of the string, starting + // after the prefix and ending before the suffix + ( + @match_str($conditions:tt, $input:ident) + | [$prefix:literal, $binding:ident, $suffix:literal] $( $rest:tt )* + ) => { + match_str!( + @match_str( + ($conditions || ($input.starts_with($prefix) && $input.ends_with($suffix))), + $input, + @binding $binding = &$input[$prefix.len()..($input.len()-$suffix.len())] + ) + $( $rest )* + ) + }; + + // Final empty `else` body + ( @match_str((false), $input:ident) |) => { {} }; + + // Final empty `else` body + ( @match_str((false), $input:ident) | _ => $body:expr $(,)*) => { + { + $body + } + }; + + // Final `else` body with a catch-all binding + ( @match_str((false), $input:ident) | $binding:ident => $body:expr $(,)* ) => { + { + let $binding = $input; + + $body + } + }; + + // Build `if` body + ( + @match_str($conditions:tt, $input:ident, @no_bindings) + => $body:expr , $(,)* $(|)* $( $rest:tt )* + ) => { + if $conditions { + $body + } else { + match_str!(@match_str((false), $input) | $( $rest )*) + } + }; + + // Build `if` body with a specified binding ( - ( $string:expr ) - $( [$start:expr, $middle:ident, $end:expr] => $body:tt )* - _ => $else:expr $(,)* + @match_str($conditions:tt, $input:ident, @binding $binding:ident = $binding_expr:expr) + => $body:expr , $(,)* $(|)* $( $rest:tt )* ) => { - $( - if let Some($middle) = parse_line($string, $start, $end) { - $body - } else - )* { - $else + if $conditions { + let $binding = $binding_expr; + + $body + } else { + match_str!(@match_str((false), $input) | $( $rest )*) } }; } diff --git a/android/translations-converter/src/gettext/mod.rs b/android/translations-converter/src/gettext/mod.rs index e0ac141082..244401009a 100644 --- a/android/translations-converter/src/gettext/mod.rs +++ b/android/translations-converter/src/gettext/mod.rs @@ -83,7 +83,7 @@ impl Translation { match_str! { (line.trim()) ["msgid \"", msg_id, "\""] => { current_id = Some(MsgString::from_escaped(msg_id)); - } + }, ["msgstr \"", translation, "\""] => { if let Some(id) = current_id.take() { let value = MsgValue::Invariant(MsgString::from_escaped(translation)); @@ -95,11 +95,11 @@ impl Translation { current_id = None; current_plural_id = None; - } + }, ["msgid_plural \"", plural_id, "\""] => { current_plural_id = Some(MsgString::from_escaped(plural_id)); parsing_header = false; - } + }, ["msgstr[", plural_translation, "\""] => { let variant_id_end = plural_translation .chars() @@ -113,14 +113,14 @@ impl Translation { variants.insert(variant_id, MsgString::from_escaped(variant_msg)); parsing_header = false; - } + }, ["\"", header, "\\n\""] => { if parsing_header { if let Some(plural_formula) = parse_line(header, "Plural-Forms: ", ";") { plural_form = PluralForm::from_formula(plural_formula); } } - } + }, _ => { if let Some(plural_id) = current_plural_id.take() { let id = current_id.take().expect("Missing msgid for plural message"); |
