@@ -5,14 +5,28 @@ use std::process::Command;
55
66use maximus_core:: {
77 is_concrete_env_file_name, is_template_env_file_name, looks_like_secret, make_finding,
8- parse_env, plan_create_env_example, plan_sync_env_example, read_text_if_exists,
9- render_env_template, sort_findings, unique_fixes, FileKind , FindingInput , FixPlan , ProjectFile ,
10- ProjectSnapshot , Severity ,
8+ parse_env, plan_create_env_example, plan_create_env_example_with_groups, plan_sync_env_example,
9+ plan_sync_env_example_with_groups, read_text_if_exists, render_env_template,
10+ render_env_template_groups, sort_findings, unique_fixes, EnvTemplateRenderOptions ,
11+ EnvTemplateSourceGroup , FileKind , FindingInput , FixPlan , ProjectFile , ProjectSnapshot ,
12+ Severity ,
1113} ;
1214
1315use crate :: check_outcome:: CheckOutcome ;
1416
1517pub fn run_env_check ( project : & ProjectSnapshot ) -> io:: Result < CheckOutcome > {
18+ run_env_check_with_options ( project, & EnvCheckOptions :: default ( ) )
19+ }
20+
21+ #[ derive( Debug , Clone , PartialEq , Eq , Default ) ]
22+ pub struct EnvCheckOptions {
23+ pub template_render : EnvTemplateRenderOptions ,
24+ }
25+
26+ pub fn run_env_check_with_options (
27+ project : & ProjectSnapshot ,
28+ options : & EnvCheckOptions ,
29+ ) -> io:: Result < CheckOutcome > {
1630 let mut findings = Vec :: new ( ) ;
1731 let mut fixes = Vec :: new ( ) ;
1832 let mut planned_fixes = Vec :: new ( ) ;
@@ -112,6 +126,8 @@ pub fn run_env_check(project: &ProjectSnapshot) -> io::Result<CheckOutcome> {
112126 . filter ( |record| is_concrete_env_file_name ( & record. file . name ) )
113127 . collect :: < Vec < _ > > ( ) ;
114128 let contract_keys = collect_contract_keys ( & concrete_records) ;
129+ let contract_groups =
130+ collect_contract_groups ( & project. root_dir , & concrete_records, & contract_keys) ;
115131
116132 let gitignore_sources =
117133 read_ancestor_gitignore_sources ( & gitignore_traversal_root, & directory. dir ) ?;
@@ -176,11 +192,20 @@ pub fn run_env_check(project: &ProjectSnapshot) -> io::Result<CheckOutcome> {
176192 ) ,
177193 files : vec ! [ output_path] ,
178194 } ) ;
179- planned_fixes. push ( plan_create_env_example (
180- & project. root_dir ,
181- & directory. dir ,
182- & contract_keys,
183- ) ) ;
195+ if options. template_render . source_comments {
196+ planned_fixes. push ( plan_create_env_example_with_groups (
197+ & project. root_dir ,
198+ & directory. dir ,
199+ contract_groups. clone ( ) ,
200+ options. template_render . clone ( ) ,
201+ ) ) ;
202+ } else {
203+ planned_fixes. push ( plan_create_env_example (
204+ & project. root_dir ,
205+ & directory. dir ,
206+ & contract_keys,
207+ ) ) ;
208+ }
184209 }
185210
186211 if let Some ( example_record) = example_record {
@@ -223,12 +248,27 @@ pub fn run_env_check(project: &ProjectSnapshot) -> io::Result<CheckOutcome> {
223248 ) ,
224249 files : vec ! [ example_record. file. path. clone( ) ] ,
225250 } ) ;
226- planned_fixes. push ( plan_sync_env_example (
227- & project. root_dir ,
228- & example_record. file . path ,
229- & example_record. parsed_source_text ,
230- & missing_keys,
231- ) ) ;
251+ if options. template_render . source_comments {
252+ let missing_groups = collect_contract_groups (
253+ & project. root_dir ,
254+ & concrete_records,
255+ & missing_keys,
256+ ) ;
257+ planned_fixes. push ( plan_sync_env_example_with_groups (
258+ & project. root_dir ,
259+ & example_record. file . path ,
260+ & example_record. parsed_source_text ,
261+ missing_groups,
262+ options. template_render . clone ( ) ,
263+ ) ) ;
264+ } else {
265+ planned_fixes. push ( plan_sync_env_example (
266+ & project. root_dir ,
267+ & example_record. file . path ,
268+ & example_record. parsed_source_text ,
269+ & missing_keys,
270+ ) ) ;
271+ }
232272 }
233273
234274 for contract_record in & contract_records {
@@ -367,6 +407,29 @@ pub fn render_synced_env_example(existing_text: &str, missing_keys: &[String]) -
367407 format ! ( "{existing_text}{prefix}{addition}" )
368408}
369409
410+ pub fn render_created_env_example_with_sources ( groups : Vec < EnvTemplateSourceGroup > ) -> String {
411+ render_env_template_groups (
412+ groups,
413+ & EnvTemplateRenderOptions {
414+ source_comments : true ,
415+ } ,
416+ )
417+ }
418+
419+ pub fn render_synced_env_example_with_sources (
420+ existing_text : & str ,
421+ groups : Vec < EnvTemplateSourceGroup > ,
422+ ) -> String {
423+ let prefix = if existing_text. ends_with ( '\n' ) || existing_text. is_empty ( ) {
424+ ""
425+ } else {
426+ "\n "
427+ } ;
428+ let addition = render_created_env_example_with_sources ( groups) ;
429+
430+ format ! ( "{existing_text}{prefix}{addition}" )
431+ }
432+
370433#[ derive( Debug , Clone ) ]
371434struct ParsedEnvRecord {
372435 file : ProjectFile ,
@@ -389,6 +452,37 @@ fn collect_contract_keys(records: &[&ParsedEnvRecord]) -> Vec<String> {
389452 keys
390453}
391454
455+ fn collect_contract_groups (
456+ root_dir : & Path ,
457+ records : & [ & ParsedEnvRecord ] ,
458+ selected_keys : & [ String ] ,
459+ ) -> Vec < EnvTemplateSourceGroup > {
460+ let selected = selected_keys. iter ( ) . cloned ( ) . collect :: < BTreeSet < _ > > ( ) ;
461+ let mut seen = BTreeSet :: new ( ) ;
462+ let mut groups = Vec :: new ( ) ;
463+
464+ for record in records {
465+ let keys = record
466+ . parsed
467+ . order
468+ . iter ( )
469+ . filter ( |key| selected. contains ( * key) && seen. insert ( ( * key) . clone ( ) ) )
470+ . cloned ( )
471+ . collect :: < Vec < _ > > ( ) ;
472+
473+ if keys. is_empty ( ) {
474+ continue ;
475+ }
476+
477+ groups. push ( EnvTemplateSourceGroup {
478+ source : Some ( relative_display_path ( root_dir, & record. file . path ) ) ,
479+ keys,
480+ } ) ;
481+ }
482+
483+ groups
484+ }
485+
392486fn relative_display_path ( root_dir : & Path , target : & Path ) -> String {
393487 root_dir
394488 . strip_prefix ( root_dir)
0 commit comments