@@ -141,9 +141,9 @@ pub mod text_processing {
141141 let new_section = format ! ( "\n {}" , new_section_content) ;
142142
143143 // Find a good place to insert the new section
144- // Look for the last timeframe section or insert at the beginning
145- // Match both lowercase and uppercase H
146- let re = Regex :: new ( r"# ⏳ \d{4}[hH][12] goal process" ) . unwrap ( ) ;
144+ // Look for the last timeframe section or insert after # Summary
145+ // Match both year-only (2027) and half-year (2026H1) formats
146+ let re = Regex :: new ( r"# ⏳ \d{4}( [hH][12])? goal process" ) . unwrap ( ) ;
147147
148148 if let Some ( last_match) = re. find_iter ( & content) . last ( ) {
149149 // Find the end of this section (next section or end of file)
@@ -155,8 +155,20 @@ pub mod text_processing {
155155 new_content. push_str ( & new_section) ;
156156 }
157157 } else {
158- // No existing timeframe sections, insert at the beginning
159- new_content = new_section + & content;
158+ // No existing ⏳ timeframe sections, find the first dated section (⏳ or ⚙️) and insert before it
159+ let dated_section_re = Regex :: new ( r"\n# (⏳|⚙️) \d{4}" ) . unwrap ( ) ;
160+ if let Some ( first_dated) = dated_section_re. find ( & content) {
161+ new_content. insert_str ( first_dated. start ( ) , & new_section) ;
162+ } else {
163+ // No dated sections at all, insert after "# Summary\n\n"
164+ if let Some ( summary_pos) = content. find ( "# Summary" ) {
165+ let insert_pos = summary_pos + "# Summary\n \n " . len ( ) ;
166+ new_content. insert_str ( insert_pos, & new_section) ;
167+ } else {
168+ // No # Summary found, prepend
169+ new_content = new_section + & content;
170+ }
171+ }
160172 }
161173
162174 new_content
@@ -170,28 +182,38 @@ pub mod text_processing {
170182 ) -> String {
171183 let mut new_content = content. to_string ( ) ;
172184
173- // Extract year and half from timeframe
174- let _year = & timeframe[ 0 ..4 ] ;
175- let half = & timeframe[ 4 ..] . to_lowercase ( ) ;
185+ // Create the new section to add with capitalized H
186+ let capitalized_timeframe = normalize_timeframe ( timeframe) ;
176187
177- // Determine the months based on the half
178- let ( start_month, end_month) = if half == "h1" {
179- ( "January" , "June" )
188+ // Check if this is a year-only timeframe (no H1/H2)
189+ let new_section = if timeframe. len ( ) == 4 {
190+ // Year-only format - no date range
191+ format ! (
192+ "\n ## Next goal period ({})\n \n \
193+ The next goal period will be {}. \
194+ We are currently in the process of assembling goals. \
195+ [Click here](./{}/goals.md) to see the current list. \
196+ If you'd like to propose a goal, [instructions can be found here](./how_to/propose_a_goal.md).\n ",
197+ capitalized_timeframe, capitalized_timeframe, lowercase_timeframe
198+ )
180199 } else {
181- ( "July" , "December" )
200+ // Half-year format - include date range
201+ let half = & timeframe[ 4 ..] . to_lowercase ( ) ;
202+ let ( start_month, end_month) = if half == "h1" {
203+ ( "January" , "June" )
204+ } else {
205+ ( "July" , "December" )
206+ } ;
207+ format ! (
208+ "\n ## Next goal period ({})\n \n \
209+ The next goal period will be {}, running from the start of {} to the end of {}. \
210+ We are currently in the process of assembling goals. \
211+ [Click here](./{}/goals.md) to see the current list. \
212+ If you'd like to propose a goal, [instructions can be found here](./how_to/propose_a_goal.md).\n ",
213+ capitalized_timeframe, capitalized_timeframe, start_month, end_month, lowercase_timeframe
214+ )
182215 } ;
183216
184- // Create the new section to add with capitalized H
185- let capitalized_timeframe = normalize_timeframe ( timeframe) ;
186- let new_section = format ! (
187- "\n ## Next goal period ({})\n \n \
188- The next goal period will be {}, running from the start of {} to the end of {}. \
189- We are currently in the process of assembling goals. \
190- [Click here](./{}/goals.md) to see the current list. \
191- If you'd like to propose a goal, [instructions can be found here](./how_to/propose_a_goal.md).\n ",
192- capitalized_timeframe, capitalized_timeframe, start_month, end_month, lowercase_timeframe
193- ) ;
194-
195217 // First check for an existing entry for this specific timeframe
196218 let this_period_pattern = Regex :: new ( & format ! (
197219 r"## Next goal period(?:\s*\({}\))?\s*\n" ,
@@ -537,15 +559,39 @@ mod tests {
537559 }
538560
539561 #[ test]
540- fn test_process_summary_content_no_existing_section ( ) {
541- // Test adding a new section when no timeframe sections exist
542- let content = "# Summary\n \n [Introduction](./README.md)\n " ;
562+ fn test_process_summary_content_new_section ( ) {
563+ // Test adding a new section before an existing dated section
564+ let content = "# Summary\n \n [👋 Introduction](./README.md) \n \n # ⚙️ 2025H2 goal process \n \n - [Overview](./2025h2 /README.md)\n " ;
543565 let result = process_summary_content ( content, "2026h1" , "2026h1" ) ;
544566
545567 assert ! ( result. contains( "# ⏳ 2026H1 goal process" ) ) ;
546568 assert ! ( result. contains( "- [Overview](./2026h1/README.md)" ) ) ;
547569 assert ! ( result. contains( "- [Proposed goals](./2026h1/goals.md)" ) ) ;
548570 assert ! ( result. contains( "- [Goals not accepted](./2026h1/not_accepted.md)" ) ) ;
571+ // Verify the section is inserted after Introduction, before the existing dated section
572+ let intro_pos = result. find ( "[👋 Introduction]" ) . unwrap ( ) ;
573+ let goal_process_pos = result. find ( "# ⏳ 2026H1 goal process" ) . unwrap ( ) ;
574+ let existing_section_pos = result. find ( "# ⚙️ 2025H2" ) . unwrap ( ) ;
575+ assert ! ( intro_pos < goal_process_pos, "Goal process section should come after Introduction" ) ;
576+ assert ! ( goal_process_pos < existing_section_pos, "New section should come before existing dated section" ) ;
577+ }
578+
579+ #[ test]
580+ fn test_process_summary_content_year_only_timeframe ( ) {
581+ // Test adding a new section with year-only timeframe (no H1/H2)
582+ let content = "# Summary\n \n [👋 Introduction](./README.md)\n \n # ⚙️ 2025H2 goal process\n \n - [Overview](./2025h2/README.md)\n " ;
583+ let result = process_summary_content ( content, "2027" , "2027" ) ;
584+
585+ assert ! ( result. contains( "# ⏳ 2027 goal process" ) ) ;
586+ assert ! ( result. contains( "- [Overview](./2027/README.md)" ) ) ;
587+ assert ! ( result. contains( "- [Proposed goals](./2027/goals.md)" ) ) ;
588+ assert ! ( result. contains( "- [Goals not accepted](./2027/not_accepted.md)" ) ) ;
589+ // Verify the section is inserted after Introduction, before the existing dated section
590+ let intro_pos = result. find ( "[👋 Introduction]" ) . unwrap ( ) ;
591+ let goal_process_pos = result. find ( "# ⏳ 2027 goal process" ) . unwrap ( ) ;
592+ let existing_section_pos = result. find ( "# ⚙️ 2025H2" ) . unwrap ( ) ;
593+ assert ! ( intro_pos < goal_process_pos, "Goal process section should come after Introduction" ) ;
594+ assert ! ( goal_process_pos < existing_section_pos, "New section should come before existing dated section" ) ;
549595 }
550596
551597 #[ test]
@@ -645,4 +691,16 @@ mod tests {
645691 assert ! ( result. contains( "## Next goal period (2026H1)" ) ) ;
646692 assert ! ( result. contains( "running from the start of January to the end of June" ) ) ;
647693 }
694+
695+ #[ test]
696+ fn test_process_readme_content_year_only ( ) {
697+ // Test that year-only timeframes don't include date ranges
698+ let content = "# Project goals\n \n ## Current goal period (2026)\n \n The 2026 goal period." ;
699+ let result = process_readme_content ( content, "2027" , "2027" ) ;
700+
701+ assert ! ( result. contains( "## Next goal period (2027)" ) ) ;
702+ assert ! ( result. contains( "The next goal period will be 2027." ) ) ;
703+ assert ! ( !result. contains( "running from" ) ) ;
704+ assert ! ( result. contains( "[Click here](./2027/goals.md)" ) ) ;
705+ }
648706}
0 commit comments