@@ -127,6 +127,7 @@ impl<'c> GoalPreprocessorWithContext<'c> {
127
127
fn process_book_item ( & mut self , book_item : & mut BookItem ) -> anyhow:: Result < ( ) > {
128
128
match book_item {
129
129
BookItem :: Chapter ( chapter) => {
130
+ self . replace_metadata_placeholders ( chapter) ?;
130
131
self . replace_team_asks ( chapter) ?;
131
132
self . replace_goal_lists ( chapter) ?;
132
133
self . replace_goal_count ( chapter) ?;
@@ -173,14 +174,27 @@ impl<'c> GoalPreprocessorWithContext<'c> {
173
174
}
174
175
175
176
fn replace_goal_lists ( & mut self , chapter : & mut Chapter ) -> anyhow:: Result < ( ) > {
176
- self . replace_goal_lists_helper ( chapter, & re:: FLAGSHIP_GOAL_LIST , |status| status. is_flagship && status. is_not_not_accepted ( ) ) ?;
177
- self . replace_goal_lists_helper ( chapter, & re:: OTHER_GOAL_LIST , |status| !status. is_flagship && status. is_not_not_accepted ( ) ) ?;
178
- self . replace_goal_lists_helper ( chapter, & re:: GOAL_LIST , |status| status. is_not_not_accepted ( ) ) ?;
179
- self . replace_goal_lists_helper ( chapter, & re:: GOAL_NOT_ACCEPTED_LIST , |status| !status. is_not_not_accepted ( ) ) ?;
177
+ self . replace_goal_lists_helper ( chapter, & re:: FLAGSHIP_GOAL_LIST , |status| {
178
+ status. is_flagship && status. is_not_not_accepted ( )
179
+ } ) ?;
180
+ self . replace_goal_lists_helper ( chapter, & re:: OTHER_GOAL_LIST , |status| {
181
+ !status. is_flagship && status. is_not_not_accepted ( )
182
+ } ) ?;
183
+ self . replace_goal_lists_helper ( chapter, & re:: GOAL_LIST , |status| {
184
+ status. is_not_not_accepted ( )
185
+ } ) ?;
186
+ self . replace_goal_lists_helper ( chapter, & re:: GOAL_NOT_ACCEPTED_LIST , |status| {
187
+ !status. is_not_not_accepted ( )
188
+ } ) ?;
180
189
Ok ( ( ) )
181
190
}
182
-
183
- fn replace_goal_lists_helper ( & mut self , chapter : & mut Chapter , regex : & Regex , filter : impl Fn ( Status ) -> bool ) -> anyhow:: Result < ( ) > {
191
+
192
+ fn replace_goal_lists_helper (
193
+ & mut self ,
194
+ chapter : & mut Chapter ,
195
+ regex : & Regex ,
196
+ filter : impl Fn ( Status ) -> bool ,
197
+ ) -> anyhow:: Result < ( ) > {
184
198
loop {
185
199
let Some ( m) = regex. find ( & chapter. content ) else {
186
200
return Ok ( ( ) ) ;
@@ -193,7 +207,8 @@ impl<'c> GoalPreprocessorWithContext<'c> {
193
207
194
208
// Extract out the list of goals with the given status.
195
209
let goals = self . goal_documents ( chapter_path) ?;
196
- let mut goals_with_status: Vec < & GoalDocument > = goals. iter ( ) . filter ( |g| filter ( g. metadata . status ) ) . collect ( ) ;
210
+ let mut goals_with_status: Vec < & GoalDocument > =
211
+ goals. iter ( ) . filter ( |g| filter ( g. metadata . status ) ) . collect ( ) ;
197
212
198
213
goals_with_status. sort_by_key ( |g| & g. metadata . title ) ;
199
214
@@ -246,25 +261,22 @@ impl<'c> GoalPreprocessorWithContext<'c> {
246
261
Ok ( ( ) )
247
262
}
248
263
264
+ /// Find the goal documents for the milestone in which this `chapter_path` resides.
265
+ /// e.g., if invoked with `2024h2/xxx.md`, will find all goal documents in `2024h2`.
249
266
fn goal_documents ( & mut self , chapter_path : & Path ) -> anyhow:: Result < Arc < Vec < GoalDocument > > > {
250
- // let chapter_path = self.ctx.config.book.src.join(chapter_path);
251
267
252
- if let Some ( goals) = self . goal_document_map . get ( chapter_path) {
268
+ let Some ( milestone_path) = chapter_path. parent ( ) else {
269
+ anyhow:: bail!( "cannot get goal documents from `{chapter_path:?}`" )
270
+ } ;
271
+
272
+ if let Some ( goals) = self . goal_document_map . get ( milestone_path) {
253
273
return Ok ( goals. clone ( ) ) ;
254
274
}
255
275
256
- let goal_documents = goal:: goals_in_dir (
257
- self . ctx
258
- . config
259
- . book
260
- . src
261
- . join ( chapter_path)
262
- . parent ( )
263
- . unwrap ( ) ,
264
- ) ?;
276
+ let goal_documents = goal:: goals_in_dir ( & self . ctx . config . book . src . join ( milestone_path) ) ?;
265
277
let goals = Arc :: new ( goal_documents) ;
266
278
self . goal_document_map
267
- . insert ( chapter_path . to_path_buf ( ) , goals. clone ( ) ) ;
279
+ . insert ( milestone_path . to_path_buf ( ) , goals. clone ( ) ) ;
268
280
Ok ( goals)
269
281
}
270
282
@@ -365,4 +377,63 @@ impl<'c> GoalPreprocessorWithContext<'c> {
365
377
}
366
378
Ok ( ( ) )
367
379
}
380
+
381
+ /// Replace placeholders like TASK_OWNERS and TEAMS_WITH_ASKS.
382
+ /// All goal documents should have this in their metadata table;
383
+ /// that is enforced during goal parsing.
384
+ fn replace_metadata_placeholders ( & mut self , chapter : & mut Chapter ) -> anyhow:: Result < ( ) > {
385
+ self . replace_metadata_placeholder ( chapter, & re:: TASK_OWNERS , |goal| {
386
+ goal. task_owners . iter ( ) . cloned ( ) . collect ( )
387
+ } ) ?;
388
+
389
+ self . replace_metadata_placeholder ( chapter, & re:: TEAMS_WITH_ASKS , |goal| {
390
+ goal. teams_with_asks ( )
391
+ . iter ( )
392
+ . map ( |team_name| team_name. name ( ) )
393
+ . collect ( )
394
+ } ) ?;
395
+
396
+ Ok ( ( ) )
397
+ }
398
+
399
+ /// Replace one of the placeholders that occur in the goal document metadata,
400
+ /// like [`re::TASK_OWNERS`][].
401
+ fn replace_metadata_placeholder (
402
+ & mut self ,
403
+ chapter : & mut Chapter ,
404
+ regex : & Regex ,
405
+ op : impl Fn ( & GoalDocument ) -> Vec < String > ,
406
+ ) -> anyhow:: Result < ( ) > {
407
+ let Some ( m) = regex. find ( & chapter. content ) else {
408
+ return Ok ( ( ) ) ;
409
+ } ;
410
+ let range = m. range ( ) ;
411
+
412
+ let Some ( chapter_path) = chapter. path . as_ref ( ) else {
413
+ anyhow:: bail!(
414
+ "goal chapter `{}` matches placeholder regex but has no path" ,
415
+ chapter. name
416
+ ) ;
417
+ } ;
418
+
419
+ // Hack: leave this stuff alone in the template
420
+ if chapter_path. file_name ( ) . unwrap ( ) == "TEMPLATE.md" {
421
+ return Ok ( ( ) ) ;
422
+ }
423
+
424
+ let goals = self . goal_documents ( & chapter_path) ?;
425
+ let chapter_in_context = self . ctx . config . book . src . join ( chapter_path) ;
426
+ let Some ( goal) = goals. iter ( ) . find ( |gd| gd. path == chapter_in_context) else {
427
+ anyhow:: bail!(
428
+ "goal chapter `{}` has no goal document at path {:?}" ,
429
+ chapter. name,
430
+ chapter_path,
431
+ ) ;
432
+ } ;
433
+
434
+ let replacement = op ( goal) . join ( ", " ) ;
435
+ chapter. content . replace_range ( range, & replacement) ;
436
+
437
+ Ok ( ( ) )
438
+ }
368
439
}
0 commit comments