Skip to content

Commit ddd0cb2

Browse files
Merge pull request #13 from Reqvire/gitrepo-root-main
Major rework of the logic in reqvire.
2 parents 77a0e2f + 10d17fa commit ddd0cb2

File tree

90 files changed

+7678
-8198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+7678
-8198
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ Cargo.lock
1515
*.bak
1616

1717
CLAUDE.md
18+
codefetch
19+
.codefetchignore
20+
codefetch.config.mjs

Cargo.toml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ globset = "0.4.16"
3838
rustc-hash = "1.1"
3939
difference = "2.0"
4040
reqvire = { path = "core" }
41+
once_cell = "1.19"
42+
43+
44+
# dev dependencies
45+
assert_fs = "1.0"
46+
predicates = "3.0"
47+
tempfile = "3.8"
48+
serial_test = "0.5"
4149

42-
# Dev dependencies
43-
assert_fs = "1.0"
44-
predicates = "3.0"
45-
tempfile = "3.8"

SpecificationIndex.md

Lines changed: 242 additions & 0 deletions
Large diffs are not rendered by default.

cli/src/cli.rs

Lines changed: 122 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ use anyhow::Result;
44
use log::{info};
55
use serde::Serialize;
66
use reqvire::error::ReqvireError;
7-
use reqvire::{html_export, linting, ModelManager};
7+
use reqvire::{linting, ModelManager};
88
use reqvire::index_generator;
99
use globset::GlobSet;
1010
use reqvire::reports;
11+
use reqvire::diagrams;
12+
use reqvire::export;
1113
use reqvire::change_impact;
1214
use reqvire::git_commands;
1315
use reqvire::matrix_generator;
@@ -160,22 +162,17 @@ pub struct Args {
160162
#[clap(long, short = 'c')]
161163
pub config: Option<PathBuf>,
162164

163-
/// Output LLM context document
164-
/// Generates a comprehensive context document with information about Reqvire
165-
/// methodology, document structure, relation types, and CLI usage to help
166-
/// Large Language Models understand and work with Reqvire-based projects
167-
#[clap(long)]
168-
pub llm_context: bool,
169-
170-
171165
/// Analise change impact and provides report
172166
#[clap(long)]
173167
pub change_impact: bool,
174168

175169
/// Git commit hash to use when comparing models
176170
#[clap(long, requires = "change_impact", default_value = "HEAD")]
177171
pub git_commit: String,
178-
172+
173+
/// Process only files within a specific subdirectory relative to git root (hidden flag for testing)
174+
#[clap(long, hide = true)]
175+
pub subdirectory: Option<String>,
179176

180177
}
181178

@@ -218,157 +215,137 @@ fn print_validation_results(errors: &[ReqvireError], json_output: bool) {
218215

219216
pub fn handle_command(
220217
args: Args,
221-
specification_folder_path: &PathBuf,
222-
external_folders_path: &[PathBuf],
223218
output_folder_path: &PathBuf,
224219
excluded_filename_patterns: &GlobSet,
225-
diagram_direction: &str
220+
diagram_direction: &str,
221+
diagrams_with_blobs: bool,
222+
user_requirements_root_folder: &Option<PathBuf>
226223
) -> Result<i32,ReqvireError> {
227-
224+
228225
let mut model_manager = ModelManager::new();
229-
230-
231-
// Handle LLM context
232-
if args.llm_context {
233-
// Include the LLM context content directly in the binary
234-
let llm_context = include_str!("llm_context.md");
235-
println!("{}", llm_context);
236-
Args::print_help();
237-
return Ok(0);
238-
}else{
239-
240-
let parse_result=model_manager.parse_and_validate(None, &specification_folder_path, &external_folders_path,excluded_filename_patterns);
241-
242-
243-
if args.validate {
244-
match parse_result {
245-
Ok(errors) => {
246-
if errors.is_empty() {
247-
println!("✅ Validation completed successfully with no errors.");
248-
} else {
249-
print_validation_results(&errors, args.json);
250-
}
251-
return Ok(0);
252-
}
253-
Err(e) => {
254-
eprintln!("❌ Validation failed: {}", e);
255-
return Ok(0);
226+
let parse_result = model_manager.parse_and_validate(
227+
None,
228+
user_requirements_root_folder,
229+
excluded_filename_patterns
230+
);
231+
232+
if args.validate {
233+
match parse_result {
234+
Ok(errors) => {
235+
if errors.is_empty() {
236+
println!("✅ Validation completed successfully with no errors.");
237+
} else {
238+
print_validation_results(&errors, args.json);
256239
}
257-
}
258-
}else if args.generate_index {
259-
info!("Generating index.....");
260-
261-
let _index_context = index_generator::generate_readme_index(
262-
&model_manager.element_registry,
263-
&specification_folder_path,
264-
&external_folders_path
240+
return Ok(0);
241+
}
242+
Err(e) => {
243+
eprintln!("❌ Validation failed: {}", e);
244+
return Ok(0);
245+
}
246+
}
247+
}else if args.generate_index {
248+
info!("Generating index.....");
249+
let _index_context = index_generator::generate_readme_index(
250+
&model_manager.element_registry,
251+
&output_folder_path
265252
).map_err(|e| {
266253
ReqvireError::ProcessError(format!("❌ Failed to generate README.md: {:?}", e))
267254
})?;
268255

269-
return Ok(0);
256+
return Ok(0);
270257

271-
}else if args.generate_diagrams {
272-
info!("Generating mermaid diagrams in {:?}", specification_folder_path);
273-
// Only collect identifiers and process files to add diagrams
274-
// Skip validation checks for diagram generation mode
275-
model_manager.process_diagrams(&specification_folder_path, &external_folders_path, diagram_direction)?;
258+
}else if args.generate_diagrams {
259+
info!("Generating mermaid diagrams");
260+
// Only collect identifiers and process files to add diagrams
261+
// Skip validation checks for diagram generation mode
262+
diagrams::process_diagrams(&model_manager.element_registry,diagram_direction,diagrams_with_blobs)?;
276263

277-
info!("Requirements diagrams updated in source files");
278-
return Ok(0);
279-
280-
}else if args.model_summary {
281-
let filters = Filters::new(
282-
args.filter_file.as_deref(),
283-
args.filter_name.as_deref(),
284-
args.filter_section.as_deref(),
285-
args.filter_type.as_deref(),
286-
args.filter_content.as_deref(),
287-
args.filter_is_not_verified,
288-
args.filter_is_not_satisfied,
289-
).map_err(|e| {
290-
ReqvireError::ProcessError(format!("❌ Failed to construct filters: {}", e))
291-
})?;
292-
293-
let output_format = if args.cypher {
294-
reports::SummaryOutputFormat::Cypher
295-
} else if args.json {
296-
reports::SummaryOutputFormat::Json
297-
} else {
298-
reports::SummaryOutputFormat::Text
299-
};
300-
301-
302-
reports::print_registry_summary(&model_manager.element_registry,output_format, &filters);
303-
return Ok(0);
304-
305-
306-
}else if args.change_impact {
307-
308-
let current_commit = git_commands::get_commit_hash().map_err(|_| {
309-
ReqvireError::ProcessError("❌ Failed to retrieve the current commit hash.".to_string())
310-
})?;
311-
312-
let repo_root = git_commands::repository_root().map_err(|_| {
313-
ReqvireError::ProcessError("❌ Failed to determine repository root.".to_string())
314-
})?;
315-
316-
let base_url = git_commands::get_repository_base_url().map_err(|_| {
317-
ReqvireError::ProcessError("❌ Failed to determine repository base url.".to_string())
318-
})?;
319-
264+
info!("Requirements diagrams updated in source files");
265+
return Ok(0);
320266

321-
let mut refference_model_manager = ModelManager::new();
322-
let _not_interested=refference_model_manager.parse_and_validate(Some(&args.git_commit), &specification_folder_path, &external_folders_path,excluded_filename_patterns);
323-
324-
let report=change_impact::compute_change_impact(
325-
&model_manager.element_registry,
326-
&refference_model_manager.element_registry,
327-
&repo_root,
328-
&specification_folder_path,
329-
&external_folders_path
330-
)
331-
.map_err(|e| ReqvireError::ProcessError(format!("❌ Failed to generate change impact report: {:?}", e)))?;
267+
}else if args.model_summary {
268+
let filters = Filters::new(
269+
args.filter_file.as_deref(),
270+
args.filter_name.as_deref(),
271+
args.filter_section.as_deref(),
272+
args.filter_type.as_deref(),
273+
args.filter_content.as_deref(),
274+
args.filter_is_not_verified,
275+
args.filter_is_not_satisfied,
276+
).map_err(|e| {
277+
ReqvireError::ProcessError(format!("❌ Failed to construct filters: {}", e))
278+
})?;
279+
280+
let output_format = if args.cypher {
281+
reports::SummaryOutputFormat::Cypher
282+
} else if args.json {
283+
reports::SummaryOutputFormat::Json
284+
} else {
285+
reports::SummaryOutputFormat::Text
286+
};
287+
288+
reports::print_registry_summary(&model_manager.element_registry,output_format, &filters);
289+
return Ok(0);
290+
332291

333-
report.print(&base_url, &current_commit, &args.git_commit, args.json);
334-
335-
return Ok(0);
292+
}else if args.change_impact {
293+
294+
let base_url = git_commands::get_repository_base_url().map_err(|_| {
295+
ReqvireError::ProcessError("❌ Failed to determine repository base url.".to_string())
296+
})?;
297+
298+
let current_commit = git_commands::get_commit_hash().map_err(|_| {
299+
ReqvireError::ProcessError("❌ Failed to retrieve the current commit hash.".to_string())
300+
})?;
301+
302+
let mut refference_model_manager = ModelManager::new();
303+
let _not_interested=refference_model_manager.parse_and_validate(Some(&args.git_commit), user_requirements_root_folder, excluded_filename_patterns);
304+
305+
let report=change_impact::compute_change_impact(
306+
&model_manager.element_registry,
307+
&refference_model_manager.element_registry
308+
)
309+
.map_err(|e| ReqvireError::ProcessError(format!("❌ Failed to generate change impact report: {:?}", e)))?;
310+
311+
report.print(&base_url, &current_commit, &args.git_commit, args.json);
336312

313+
return Ok(0);
314+
337315

338-
}else if args.lint {
339-
linting::run_linting(&specification_folder_path, &external_folders_path,excluded_filename_patterns, args.dry_run)?;
340-
return Ok(0);
341-
342-
343-
}else if args.traces {
344-
let matrix_config = matrix_generator::MatrixConfig::default();
316+
}else if args.lint {
317+
linting::run_linting(excluded_filename_patterns, args.dry_run, args.subdirectory.as_deref())?;
318+
return Ok(0);
319+
320+
}else if args.traces {
321+
let matrix_config = matrix_generator::MatrixConfig::default();
345322

346-
let matrix_output = reqvire::matrix_generator::generate_matrix(
347-
&model_manager.element_registry,
348-
&matrix_config,
349-
if args.json {
350-
matrix_generator::MatrixFormat::Json
351-
} else if args.svg {
352-
matrix_generator::MatrixFormat::Svg
353-
} else {
354-
matrix_generator::MatrixFormat::Markdown
355-
},
356-
);
323+
let matrix_output = reqvire::matrix_generator::generate_matrix(
324+
&model_manager.element_registry,
325+
&matrix_config,
326+
if args.json {
327+
matrix_generator::MatrixFormat::Json
328+
} else if args.svg {
329+
matrix_generator::MatrixFormat::Svg
330+
} else {
331+
matrix_generator::MatrixFormat::Markdown
332+
},
333+
);
357334

358-
println!("{}", matrix_output);
359-
return Ok(0);
335+
println!("{}", matrix_output);
336+
return Ok(0);
360337

361338

362-
} else if args.html {
363-
let processed_count = html_export::export_markdown_to_html(specification_folder_path,&external_folders_path, output_folder_path)?;
364-
info!("{} markdown files converted to HTML", processed_count);
365-
return Ok(0);
366-
}else{
367-
Args::print_help();
339+
} else if args.html {
340+
let processed_count = export::export_model(&model_manager.element_registry, output_folder_path)?;
341+
info!("{} markdown files converted to HTML", processed_count);
342+
343+
return Ok(0);
344+
}else{
345+
Args::print_help();
368346

369-
}
370-
371347
}
348+
372349
Ok(1)
373350
}
374351

@@ -402,7 +379,6 @@ mod tests {
402379
fn test_handle_command() {
403380
// Mock CLI arguments
404381
let args = Args {
405-
llm_context: false,
406382
html: false,
407383
lint: false,
408384
dry_run: false,
@@ -423,7 +399,8 @@ mod tests {
423399
validate: false,
424400
config: None, // No custom config file for the test
425401
change_impact: false, // Add the missing field
426-
git_commit: "HEAD".to_string(), // Add the missing field with default value
402+
git_commit: "HEAD".to_string(), // Add the missing field
403+
subdirectory: None
427404
};
428405

429406

@@ -444,13 +421,14 @@ mod tests {
444421

445422

446423
// Run the handle_command function
424+
let user_requirements_root = None;
447425
let result = handle_command(
448426
args,
449-
&specification_folder_path,
450-
&external_folders_path,
451427
&output_folder_path,
452428
&build_glob_set(&excluded_filename_patterns),
453429
"TD",
430+
false,
431+
&user_requirements_root
454432
);
455433

456434
// Assert that it runs without error

0 commit comments

Comments
 (0)