@@ -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,23 @@ 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
267
// let chapter_path = self.ctx.config.book.src.join(chapter_path);
251
268
252
- if let Some ( goals) = self . goal_document_map . get ( chapter_path) {
269
+ let Some ( milestone_path) = chapter_path. parent ( ) else {
270
+ anyhow:: bail!( "cannot get goal documents from `{chapter_path:?}`" )
271
+ } ;
272
+
273
+ if let Some ( goals) = self . goal_document_map . get ( milestone_path) {
253
274
return Ok ( goals. clone ( ) ) ;
254
275
}
255
276
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
- ) ?;
277
+ let goal_documents = goal:: goals_in_dir ( & self . ctx . config . book . src . join ( milestone_path) ) ?;
265
278
let goals = Arc :: new ( goal_documents) ;
266
279
self . goal_document_map
267
- . insert ( chapter_path . to_path_buf ( ) , goals. clone ( ) ) ;
280
+ . insert ( milestone_path . to_path_buf ( ) , goals. clone ( ) ) ;
268
281
Ok ( goals)
269
282
}
270
283
@@ -365,4 +378,63 @@ impl<'c> GoalPreprocessorWithContext<'c> {
365
378
}
366
379
Ok ( ( ) )
367
380
}
381
+
382
+ /// Replace TEAMS_WITH_ASKS placeholder with a list of teams.
383
+ /// All goal documents should have this in their metadata table;
384
+ /// that is enforced during goal parsing.
385
+ fn replace_metadata_placeholders ( & mut self , chapter : & mut Chapter ) -> anyhow:: Result < ( ) > {
386
+ self . replace_metadata_placeholder ( chapter, & re:: TASK_OWNERS , |goal| {
387
+ goal. task_owners . iter ( ) . cloned ( ) . collect ( )
388
+ } ) ?;
389
+
390
+ self . replace_metadata_placeholder ( chapter, & re:: TEAMS_WITH_ASKS , |goal| {
391
+ goal. teams_with_asks ( )
392
+ . iter ( )
393
+ . map ( |team_name| team_name. name ( ) )
394
+ . collect ( )
395
+ } ) ?;
396
+
397
+ Ok ( ( ) )
398
+ }
399
+
400
+ /// Replace one of the placeholders that occur in the goal document metadata,
401
+ /// like [`re::TASK_OWNERS`][].
402
+ fn replace_metadata_placeholder (
403
+ & mut self ,
404
+ chapter : & mut Chapter ,
405
+ regex : & Regex ,
406
+ op : impl Fn ( & GoalDocument ) -> Vec < String > ,
407
+ ) -> anyhow:: Result < ( ) > {
408
+ let Some ( m) = regex. find ( & chapter. content ) else {
409
+ return Ok ( ( ) ) ;
410
+ } ;
411
+ let range = m. range ( ) ;
412
+
413
+ let Some ( chapter_path) = chapter. path . as_ref ( ) else {
414
+ anyhow:: bail!(
415
+ "goal chapter `{}` has TEAMS_WITH_ASKS placeholder but no path" ,
416
+ chapter. name
417
+ ) ;
418
+ } ;
419
+
420
+ // Hack: leave this stuff alone in the template
421
+ if chapter_path. file_name ( ) . unwrap ( ) == "TEMPLATE.md" {
422
+ return Ok ( ( ) ) ;
423
+ }
424
+
425
+ let goals = self . goal_documents ( & chapter_path) ?;
426
+ let chapter_in_context = self . ctx . config . book . src . join ( chapter_path) ;
427
+ let Some ( goal) = goals. iter ( ) . find ( |gd| gd. path == chapter_in_context) else {
428
+ anyhow:: bail!(
429
+ "goal chapter `{}` has no goal document at path {:?}" ,
430
+ chapter. name,
431
+ chapter_path,
432
+ ) ;
433
+ } ;
434
+
435
+ let replacement = op ( goal) . join ( ", " ) ;
436
+ chapter. content . replace_range ( range, & replacement) ;
437
+
438
+ Ok ( ( ) )
439
+ }
368
440
}
0 commit comments