@@ -3,12 +3,31 @@ use std::ffi::CStr;
3
3
use once_cell:: sync:: OnceCell ;
4
4
use regex:: Regex ;
5
5
6
+ #[ cfg_attr( test, derive( Debug , PartialEq , Eq ) ) ]
6
7
pub struct Locale < ' a > {
7
8
lang : Option < & ' a str > ,
8
9
country : Option < & ' a str > ,
9
10
modifier : Option < & ' a str > ,
10
11
}
11
12
13
+ const LOCALE_REGEX : & str = r#"(?x)
14
+ ^
15
+ ([[:alpha:]]+) # lang
16
+ (?:_([[:alpha:]]+))? # country
17
+ (?:\.[^@]*)? # encoding
18
+ (?:@(.*))? # modifier
19
+ $"# ;
20
+
21
+ impl < ' a > Locale < ' a > {
22
+ fn from_caputres ( s : & ' a str , captures : regex:: Captures < ' _ > ) -> Self {
23
+ Self {
24
+ lang : captures. get ( 1 ) . map ( |m| & s[ m. range ( ) ] ) ,
25
+ country : captures. get ( 2 ) . map ( |m| & s[ m. range ( ) ] ) ,
26
+ modifier : captures. get ( 3 ) . map ( |m| & s[ m. range ( ) ] ) ,
27
+ }
28
+ }
29
+ }
30
+
12
31
impl Locale < ' static > {
13
32
pub fn current < ' a > ( ) -> & ' a Self {
14
33
static LOCALE : OnceCell < Option < Locale < ' static > > > = OnceCell :: new ( ) ;
@@ -24,24 +43,11 @@ impl Locale<'static> {
24
43
. to_str ( )
25
44
. ok ( ) ?;
26
45
27
- let re = Regex :: new (
28
- r#"(?x)
29
- ^
30
- ([[:alpha:]]+) # lang
31
- (?:_([[:alpha:]]+))? # country
32
- (?:\.[^@]*)? # encoding
33
- (?:@(.*))? # modifier
34
- $"# ,
35
- )
36
- . unwrap ( ) ;
46
+ let re = Regex :: new ( LOCALE_REGEX ) . unwrap ( ) ;
37
47
38
48
let c = re. captures ( s) ?;
39
49
40
- Some ( Self {
41
- lang : c. get ( 1 ) . map ( |m| & s[ m. range ( ) ] ) ,
42
- country : c. get ( 2 ) . map ( |m| & s[ m. range ( ) ] ) ,
43
- modifier : c. get ( 3 ) . map ( |m| & s[ m. range ( ) ] ) ,
44
- } )
50
+ Some ( Self :: from_caputres ( s, c) )
45
51
} )
46
52
. as_ref ( )
47
53
. unwrap_or ( & Self {
@@ -75,3 +81,55 @@ impl Locale<'static> {
75
81
. into_iter ( )
76
82
}
77
83
}
84
+
85
+ #[ cfg( test) ]
86
+ mod tests {
87
+ use super :: * ;
88
+
89
+ use test_case:: test_case;
90
+
91
+ #[ test]
92
+ fn regex_compiles ( ) {
93
+ let _ = Regex :: new ( LOCALE_REGEX ) . unwrap ( ) ;
94
+ }
95
+
96
+ #[ test]
97
+ fn regex_doesnt_match_empty ( ) {
98
+ let re = Regex :: new ( LOCALE_REGEX ) . unwrap ( ) ;
99
+ assert ! ( re. captures( "" ) . is_none( ) ) ;
100
+ }
101
+
102
+ impl Locale < ' static > {
103
+ fn new (
104
+ lang : impl Into < Option < & ' static str > > ,
105
+ country : impl Into < Option < & ' static str > > ,
106
+ modifier : impl Into < Option < & ' static str > > ,
107
+ ) -> Self {
108
+ Self {
109
+ lang : lang. into ( ) ,
110
+ country : country. into ( ) ,
111
+ modifier : modifier. into ( ) ,
112
+ }
113
+ }
114
+ }
115
+
116
+ #[ test_case( "qw" , Locale :: new( "qw" , None , None ) ; "lang" ) ]
117
+ #[ test_case( "qw_ER" , Locale :: new( "qw" , "ER" , None ) ; "lang, country" ) ]
118
+ #[ test_case( "qw_ER.ty" , Locale :: new( "qw" , "ER" , None ) ; "lang, country, encoding" ) ]
119
+ #[ test_case(
120
+ "qw_ER.ty@ui" ,
121
+ Locale :: new( "qw" , "ER" , "ui" ) ;
122
+ "lang, country, encoding, modifier"
123
+ ) ]
124
+ #[ test_case( "qw@ui" , Locale :: new( "qw" , None , "ui" ) ; "lang, modifier" ) ]
125
+ fn regex_compiles ( s : & str , x : Locale < ' static > ) {
126
+ let re = Regex :: new ( LOCALE_REGEX ) . unwrap ( ) ;
127
+ let c = re. captures ( s) . unwrap ( ) ;
128
+
129
+ let m = c. get ( 0 ) . unwrap ( ) ;
130
+ assert_eq ! ( m. start( ) , 0 ) ;
131
+ assert_eq ! ( m. end( ) , s. len( ) ) ;
132
+
133
+ assert_eq ! ( Locale :: from_caputres( s, c) , x) ;
134
+ }
135
+ }
0 commit comments