@@ -5,165 +5,230 @@ use serde_json;
5
5
use handlebars:: { Context , Handlebars , Helper , RenderContext , RenderError , Renderable } ;
6
6
7
7
8
- // Handlebars helper for navigation
8
+ type StringMap = BTreeMap < String , String > ;
9
9
10
- pub fn previous ( _h : & Helper , r : & Handlebars , rc : & mut RenderContext ) -> Result < ( ) , RenderError > {
11
- debug ! ( "[fn]: previous (handlebars helper)" ) ;
10
+ /// Target for `find_chapter`.
11
+ enum Target {
12
+ Previous ,
13
+ Next ,
14
+ }
15
+
16
+ impl Target {
17
+ /// Returns target if found.
18
+ fn find ( & self ,
19
+ base_path : & String ,
20
+ current_path : & String ,
21
+ current_item : & StringMap ,
22
+ previous_item : & StringMap ,
23
+ ) -> Result < Option < StringMap > , RenderError > {
24
+ match self {
25
+ & Target :: Next => {
26
+ let previous_path = previous_item. get ( "path" ) . ok_or_else ( || {
27
+ RenderError :: new ( "No path found for chapter in JSON data" )
28
+ } ) ?;
29
+
30
+ if previous_path == base_path {
31
+ return Ok ( Some ( current_item. clone ( ) ) ) ;
32
+ }
33
+ } ,
34
+
35
+ & Target :: Previous => {
36
+ if current_path == base_path {
37
+ return Ok ( Some ( previous_item. clone ( ) ) ) ;
38
+ }
39
+ }
40
+ }
41
+
42
+ Ok ( None )
43
+ }
44
+ }
12
45
46
+ fn find_chapter (
47
+ rc : & mut RenderContext ,
48
+ target : Target
49
+ ) -> Result < Option < StringMap > , RenderError > {
13
50
debug ! ( "[*]: Get data from context" ) ;
51
+
14
52
let chapters = rc. evaluate_absolute ( "chapters" ) . and_then ( |c| {
15
- serde_json:: value:: from_value :: < Vec < BTreeMap < String , String > > > ( c. clone ( ) )
53
+ serde_json:: value:: from_value :: < Vec < StringMap > > ( c. clone ( ) )
16
54
. map_err ( |_| RenderError :: new ( "Could not decode the JSON data" ) )
17
55
} ) ?;
18
56
19
- let current = rc. evaluate_absolute ( "path" ) ?
20
- . as_str ( )
21
- . ok_or_else ( || RenderError :: new ( "Type error for `path`, string expected" ) ) ?
22
- . replace ( "\" " , "" ) ;
57
+ let base_path = rc. evaluate_absolute ( "path" ) ?
58
+ . as_str ( )
59
+ . ok_or_else ( || RenderError :: new ( "Type error for `path`, string expected" ) ) ?
60
+ . replace ( "\" " , "" ) ;
61
+
62
+ let mut previous: Option < StringMap > = None ;
23
63
24
- let mut previous : Option < BTreeMap < String , String > > = None ;
64
+ debug ! ( "[*]: Search for chapter" ) ;
25
65
26
- debug ! ( "[*]: Search for current Chapter" ) ;
27
- // Search for current chapter and return previous entry
28
66
for item in chapters {
29
67
match item. get ( "path" ) {
30
68
Some ( path) if !path. is_empty ( ) => {
31
- if path == & current {
32
- debug ! ( "[*]: Found current chapter" ) ;
33
- if let Some ( previous) = previous {
34
- debug ! ( "[*]: Creating BTreeMap to inject in context" ) ;
35
- // Create new BTreeMap to extend the context: 'title' and 'link'
36
- let mut previous_chapter = BTreeMap :: new ( ) ;
37
-
38
- // Chapter title
39
- previous. get ( "name" )
40
- . ok_or_else ( || {
41
- RenderError :: new ( "No title found for chapter in \
42
- JSON data")
43
- } )
44
- . and_then ( |n| {
45
- previous_chapter. insert ( "title" . to_owned ( ) , json ! ( n) ) ;
46
- Ok ( ( ) )
47
- } ) ?;
48
-
49
-
50
- // Chapter link
51
- previous. get ( "path" )
52
- . ok_or_else ( || {
53
- RenderError :: new ( "No path found for chapter in \
54
- JSON data")
55
- } )
56
- . and_then ( |p| {
57
- Path :: new ( p) . with_extension ( "html" )
58
- . to_str ( )
59
- . ok_or_else ( || {
60
- RenderError :: new ( "Link could not be \
61
- converted to str")
62
- } )
63
- . and_then ( |p| {
64
- previous_chapter
65
- . insert ( "link" . to_owned ( ) , json ! ( p. replace( "\\ " , "/" ) ) ) ;
66
- Ok ( ( ) )
67
- } )
68
- } ) ?;
69
-
70
-
71
- debug ! ( "[*]: Render template" ) ;
72
- // Render template
73
- _h. template ( )
74
- . ok_or_else ( || RenderError :: new ( "Error with the handlebars template" ) )
75
- . and_then ( |t| {
76
- let mut local_rc = rc. with_context ( Context :: wraps ( & previous_chapter) ?) ;
77
- t. render ( r, & mut local_rc)
78
- } ) ?;
69
+ if let Some ( previous) = previous {
70
+ if let Some ( item) = target. find ( & base_path, & path, & item, & previous) ? {
71
+ return Ok ( Some ( item) ) ;
79
72
}
80
- break ;
81
- } else {
82
- previous = Some ( item. clone ( ) ) ;
83
73
}
74
+
75
+ previous = Some ( item. clone ( ) ) ;
84
76
}
85
77
_ => continue ,
86
78
}
87
79
}
88
80
81
+ Ok ( None )
82
+ }
83
+
84
+ fn render (
85
+ _h : & Helper ,
86
+ r : & Handlebars ,
87
+ rc : & mut RenderContext ,
88
+ chapter : & StringMap ,
89
+ ) -> Result < ( ) , RenderError > {
90
+ debug ! ( "[*]: Creating BTreeMap to inject in context" ) ;
91
+
92
+ let mut context = BTreeMap :: new ( ) ;
93
+
94
+ chapter. get ( "name" )
95
+ . ok_or_else ( || RenderError :: new ( "No title found for chapter in JSON data" ) )
96
+ . map ( |name| context. insert ( "title" . to_owned ( ) , json ! ( name) ) ) ?;
97
+
98
+ chapter. get ( "path" )
99
+ . ok_or_else ( || RenderError :: new ( "No path found for chapter in JSON data" ) )
100
+ . and_then ( |p| {
101
+ Path :: new ( p) . with_extension ( "html" )
102
+ . to_str ( )
103
+ . ok_or_else ( || RenderError :: new ( "Link could not be converted to str" ) )
104
+ . map ( |p| context. insert ( "link" . to_owned ( ) , json ! ( p. replace( "\\ " , "/" ) ) ) )
105
+ } ) ?;
106
+
107
+ debug ! ( "[*]: Render template" ) ;
108
+
109
+ _h. template ( )
110
+ . ok_or_else ( || RenderError :: new ( "Error with the handlebars template" ) )
111
+ . and_then ( |t| {
112
+ let mut local_rc = rc. with_context ( Context :: wraps ( & context) ?) ;
113
+ t. render ( r, & mut local_rc)
114
+ } ) ?;
115
+
89
116
Ok ( ( ) )
90
117
}
91
118
119
+ pub fn previous ( _h : & Helper , r : & Handlebars , rc : & mut RenderContext ) -> Result < ( ) , RenderError > {
120
+ debug ! ( "[fn]: previous (handlebars helper)" ) ;
92
121
122
+ if let Some ( previous) = find_chapter ( rc, Target :: Previous ) ? {
123
+ render ( _h, r, rc, & previous) ?;
124
+ }
93
125
126
+ Ok ( ( ) )
127
+ }
94
128
95
129
pub fn next ( _h : & Helper , r : & Handlebars , rc : & mut RenderContext ) -> Result < ( ) , RenderError > {
96
130
debug ! ( "[fn]: next (handlebars helper)" ) ;
97
131
98
- debug ! ( "[*]: Get data from context" ) ;
99
- let chapters = rc. evaluate_absolute ( "chapters" ) . and_then ( |c| {
100
- serde_json:: value:: from_value :: < Vec < BTreeMap < String , String > > > ( c. clone ( ) )
101
- . map_err ( |_| RenderError :: new ( "Could not decode the JSON data" ) )
102
- } ) ?;
103
- let current = rc. evaluate_absolute ( "path" ) ?
104
- . as_str ( )
105
- . ok_or_else ( || RenderError :: new ( "Type error for `path`, string expected" ) ) ?
106
- . replace ( "\" " , "" ) ;
107
-
108
- let mut previous: Option < BTreeMap < String , String > > = None ;
132
+ if let Some ( next) = find_chapter ( rc, Target :: Next ) ? {
133
+ render ( _h, r, rc, & next) ?;
134
+ }
109
135
110
- debug ! ( "[*]: Search for current Chapter" ) ;
111
- // Search for current chapter and return previous entry
112
- for item in chapters {
113
- match item. get ( "path" ) {
114
- Some ( path) if !path. is_empty ( ) => {
115
- if let Some ( previous) = previous {
116
- let previous_path = previous. get ( "path" ) . ok_or_else ( || {
117
- RenderError :: new ( "No path found for chapter in JSON data" )
118
- } ) ?;
119
-
120
- if previous_path == & current {
121
- debug ! ( "[*]: Found current chapter" ) ;
122
- debug ! ( "[*]: Creating BTreeMap to inject in context" ) ;
123
- // Create new BTreeMap to extend the context: 'title' and 'link'
124
- let mut next_chapter = BTreeMap :: new ( ) ;
125
-
126
- item. get ( "name" )
127
- . ok_or_else ( || {
128
- RenderError :: new ( "No title found for chapter in JSON \
129
- data")
130
- } )
131
- . and_then ( |n| {
132
- next_chapter. insert ( "title" . to_owned ( ) , json ! ( n) ) ;
133
- Ok ( ( ) )
134
- } ) ?;
135
-
136
- Path :: new ( path) . with_extension ( "html" )
137
- . to_str ( )
138
- . ok_or_else ( || {
139
- RenderError :: new ( "Link could not converted \
140
- to str")
141
- } )
142
- . and_then ( |l| {
143
- debug ! ( "[*]: Inserting link: {:?}" , l) ;
144
- // Hack for windows who tends to use `\` as separator instead of `/`
145
- next_chapter. insert ( "link" . to_owned ( ) , json ! ( l. replace( "\\ " , "/" ) ) ) ;
146
- Ok ( ( ) )
147
- } ) ?;
148
-
149
- debug ! ( "[*]: Render template" ) ;
150
-
151
- // Render template
152
- _h. template ( )
153
- . ok_or_else ( || RenderError :: new ( "Error with the handlebars template" ) )
154
- . and_then ( |t| {
155
- let mut local_rc = rc. with_context ( Context :: wraps ( & next_chapter) ?) ;
156
- t. render ( r, & mut local_rc)
157
- } ) ?;
158
- break ;
159
- }
160
- }
136
+ Ok ( ( ) )
137
+ }
161
138
162
- previous = Some ( item. clone ( ) ) ;
139
+ #[ cfg( test) ]
140
+ mod tests {
141
+ use super :: * ;
142
+
143
+ static TEMPLATE : & ' static str =
144
+ "{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}" ;
145
+
146
+ #[ test]
147
+ fn test_next_previous ( ) {
148
+ let data = json ! ( {
149
+ "name" : "two" ,
150
+ "path" : "two.path" ,
151
+ "chapters" : [
152
+ {
153
+ "name" : "one" ,
154
+ "path" : "one.path"
155
+ } ,
156
+ {
157
+ "name" : "two" ,
158
+ "path" : "two.path" ,
159
+ } ,
160
+ {
161
+ "name" : "three" ,
162
+ "path" : "three.path"
163
+ }
164
+ ]
165
+ } ) ;
166
+
167
+ let mut h = Handlebars :: new ( ) ;
168
+ h. register_helper ( "previous" , Box :: new ( previous) ) ;
169
+ h. register_helper ( "next" , Box :: new ( next) ) ;
170
+
171
+ assert_eq ! (
172
+ h. template_render( TEMPLATE , & data) . unwrap( ) ,
173
+ "one: one.html|three: three.html" ) ;
174
+ }
175
+
176
+ #[ test]
177
+ fn test_first ( ) {
178
+ let data = json ! ( {
179
+ "name" : "one" ,
180
+ "path" : "one.path" ,
181
+ "chapters" : [
182
+ {
183
+ "name" : "one" ,
184
+ "path" : "one.path"
185
+ } ,
186
+ {
187
+ "name" : "two" ,
188
+ "path" : "two.path" ,
189
+ } ,
190
+ {
191
+ "name" : "three" ,
192
+ "path" : "three.path"
193
+ }
194
+ ]
195
+ } ) ;
196
+
197
+ let mut h = Handlebars :: new ( ) ;
198
+ h. register_helper ( "previous" , Box :: new ( previous) ) ;
199
+ h. register_helper ( "next" , Box :: new ( next) ) ;
200
+
201
+ assert_eq ! (
202
+ h. template_render( TEMPLATE , & data) . unwrap( ) ,
203
+ "|two: two.html" ) ;
204
+ }
205
+ #[ test]
206
+ fn test_last ( ) {
207
+ let data = json ! ( {
208
+ "name" : "three" ,
209
+ "path" : "three.path" ,
210
+ "chapters" : [
211
+ {
212
+ "name" : "one" ,
213
+ "path" : "one.path"
214
+ } ,
215
+ {
216
+ "name" : "two" ,
217
+ "path" : "two.path" ,
218
+ } ,
219
+ {
220
+ "name" : "three" ,
221
+ "path" : "three.path"
163
222
}
223
+ ]
224
+ } ) ;
164
225
165
- _ => continue ,
166
- }
167
- }
168
- Ok ( ( ) )
226
+ let mut h = Handlebars :: new ( ) ;
227
+ h. register_helper ( "previous" , Box :: new ( previous) ) ;
228
+ h. register_helper ( "next" , Box :: new ( next) ) ;
229
+
230
+ assert_eq ! (
231
+ h. template_render( TEMPLATE , & data) . unwrap( ) ,
232
+ "two: two.html|" ) ;
233
+ }
169
234
}
0 commit comments