Skip to content

Commit 8a6ff0c

Browse files
committed
Htkl pages update
1 parent 4c0ac8d commit 8a6ff0c

Some content is hidden

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

52 files changed

+25387
-14233
lines changed

doc/logo.png

-4.65 KB
Loading
16.3 KB
Loading

docs/assets/apple-touch-icon.png

14.7 KB
Loading

docs/assets/favicon.ico

4.19 KB
Binary file not shown.

docs/assets/logo.png

17.1 KB
Loading

docs/containment.html

Lines changed: 775 additions & 573 deletions
Large diffs are not rendered by default.

docs/core/src/containment.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
//! Containment hierarchy representation and generation
2+
//!
3+
//! This module provides data structures and functions for building and
4+
//! representing the physical containment hierarchy (folders, files, elements)
5+
//! of a requirements model.
6+
7+
use crate::element::{Element, ElementType};
8+
use crate::error::ReqvireError;
9+
use crate::graph_registry::GraphRegistry;
10+
use serde::Serialize;
11+
use std::collections::BTreeMap;
12+
use std::path::Path;
13+
14+
/// Represents a single element in the containment hierarchy
15+
#[derive(Debug, Clone, Serialize)]
16+
pub struct ContainmentElement {
17+
pub id: String,
18+
pub name: String,
19+
pub element_type: ElementType,
20+
pub file_path: String,
21+
pub identifier: String,
22+
}
23+
24+
impl ContainmentElement {
25+
pub fn from_element(element: &Element) -> Self {
26+
ContainmentElement {
27+
id: element.id.clone(),
28+
name: element.name.clone(),
29+
element_type: element.element_type.clone(),
30+
file_path: element.file_path.clone(),
31+
identifier: element.identifier.clone(),
32+
}
33+
}
34+
}
35+
36+
/// Represents a file containing elements
37+
#[derive(Debug, Clone, Serialize)]
38+
pub struct ContainmentFile {
39+
pub path: String,
40+
pub name: String,
41+
pub elements: Vec<ContainmentElement>,
42+
}
43+
44+
/// Represents a folder containing files and subfolders
45+
#[derive(Debug, Clone, Serialize)]
46+
pub struct ContainmentFolder {
47+
pub name: String,
48+
pub path: Vec<String>,
49+
pub files: Vec<ContainmentFile>,
50+
pub subfolders: Vec<ContainmentFolder>,
51+
}
52+
53+
/// Root containment hierarchy structure
54+
#[derive(Debug, Clone, Serialize)]
55+
pub struct ContainmentHierarchy {
56+
pub root_folder: ContainmentFolder,
57+
}
58+
59+
impl ContainmentHierarchy {
60+
/// Build containment hierarchy from a registry
61+
///
62+
/// When `short` is true, shows only root elements (those without hierarchical parents).
63+
/// When `short` is false (default), shows all elements.
64+
pub fn build(registry: &GraphRegistry, short: bool) -> Result<Self, ReqvireError> {
65+
// Group elements by file
66+
let mut files_map: BTreeMap<String, Vec<&Element>> = BTreeMap::new();
67+
for element in registry.get_all_elements() {
68+
files_map.entry(element.file_path.clone())
69+
.or_insert_with(Vec::new)
70+
.push(element);
71+
}
72+
73+
// Build elements map - filter if short mode, otherwise show all
74+
let mut elements_map: BTreeMap<String, Vec<ContainmentElement>> = BTreeMap::new();
75+
for (file_path, elements) in files_map.iter() {
76+
let selected_elements: Vec<ContainmentElement> = if short {
77+
// Short mode: only top-level elements
78+
filter_top_level_elements(elements)
79+
.iter()
80+
.map(|e| ContainmentElement::from_element(e))
81+
.collect()
82+
} else {
83+
// Full mode: all elements
84+
elements.iter()
85+
.map(|e| ContainmentElement::from_element(e))
86+
.collect()
87+
};
88+
elements_map.insert(file_path.clone(), selected_elements);
89+
}
90+
91+
// Build folder structure
92+
let root_folder = build_folder_structure(&elements_map);
93+
94+
Ok(ContainmentHierarchy { root_folder })
95+
}
96+
}
97+
98+
/// Filter elements to show only top-level parents (those without hierarchical parents in same file)
99+
fn filter_top_level_elements<'a>(elements: &[&'a Element]) -> Vec<&'a Element> {
100+
use std::collections::HashSet;
101+
102+
// Get hierarchical relation types (derivedFrom)
103+
let hierarchical_types = crate::relation::get_hierarchical_relation_types();
104+
105+
// Collect all element IDs (fragments) in this file
106+
let element_ids: HashSet<String> = elements.iter()
107+
.map(|e| e.id.clone())
108+
.collect();
109+
110+
// Find elements that have derivedFrom relations pointing to elements in the same file
111+
let mut child_elements: HashSet<String> = HashSet::new();
112+
for element in elements {
113+
for relation in &element.relations {
114+
if hierarchical_types.contains(&relation.relation_type.name) {
115+
// Check if the target element_id is in the same file
116+
if let Some(target_id) = &relation.target.element_id {
117+
if element_ids.contains(target_id) {
118+
// This element has a parent in the same file, so it's a child
119+
child_elements.insert(element.identifier.clone());
120+
}
121+
}
122+
}
123+
}
124+
}
125+
126+
// Return only elements that are NOT children (i.e., top-level)
127+
elements.iter()
128+
.filter(|e| !child_elements.contains(&e.identifier))
129+
.copied()
130+
.collect()
131+
}
132+
133+
/// Build folder structure from files map
134+
fn build_folder_structure(files_map: &BTreeMap<String, Vec<ContainmentElement>>) -> ContainmentFolder {
135+
// Build intermediate structure: folder_path -> files in that folder
136+
let mut folder_files: BTreeMap<Vec<String>, Vec<ContainmentFile>> = BTreeMap::new();
137+
138+
// Track all folder paths (including intermediate folders without direct files)
139+
let mut all_folder_paths: std::collections::HashSet<Vec<String>> = std::collections::HashSet::new();
140+
141+
for (file_path, elements) in files_map {
142+
let path = Path::new(file_path);
143+
let folder_path: Vec<String> = path.parent()
144+
.map(|p| p.components()
145+
.filter_map(|c| c.as_os_str().to_str())
146+
.map(String::from)
147+
.collect())
148+
.unwrap_or_default();
149+
150+
// Add all intermediate folder paths (e.g., for "a/b/c", add "", "a", "a/b", "a/b/c")
151+
for i in 0..=folder_path.len() {
152+
all_folder_paths.insert(folder_path[..i].to_vec());
153+
}
154+
155+
let file_name = path.file_name()
156+
.and_then(|n| n.to_str())
157+
.unwrap_or("")
158+
.to_string();
159+
160+
let file = ContainmentFile {
161+
path: file_path.clone(),
162+
name: file_name,
163+
elements: elements.clone(),
164+
};
165+
166+
folder_files.entry(folder_path)
167+
.or_insert_with(Vec::new)
168+
.push(file);
169+
}
170+
171+
// Build hierarchical folder structure using all folder paths
172+
build_folder_recursive(&[], &folder_files, &all_folder_paths)
173+
}
174+
175+
/// Recursively build folder structure
176+
fn build_folder_recursive(
177+
current_path: &[String],
178+
folder_files: &BTreeMap<Vec<String>, Vec<ContainmentFile>>,
179+
all_folder_paths: &std::collections::HashSet<Vec<String>>
180+
) -> ContainmentFolder {
181+
let folder_name = current_path.last()
182+
.map(|s| s.clone())
183+
.unwrap_or_else(|| "Reqvire root".to_string());
184+
185+
// Get files directly in this folder
186+
let files = folder_files.get(current_path)
187+
.cloned()
188+
.unwrap_or_default();
189+
190+
// Find all immediate subfolders (using all_folder_paths to include intermediate folders)
191+
let mut subfolders = Vec::new();
192+
let current_depth = current_path.len();
193+
194+
let mut seen_subfolders: std::collections::HashSet<String> = std::collections::HashSet::new();
195+
196+
for folder_path in all_folder_paths.iter() {
197+
if folder_path.len() == current_depth + 1 {
198+
// Check if this is an immediate child
199+
let is_child = current_path.iter()
200+
.zip(folder_path.iter())
201+
.all(|(a, b)| a == b);
202+
203+
if is_child {
204+
if let Some(subfolder_name) = folder_path.last() {
205+
if seen_subfolders.insert(subfolder_name.clone()) {
206+
let subfolder = build_folder_recursive(folder_path, folder_files, all_folder_paths);
207+
subfolders.push(subfolder);
208+
}
209+
}
210+
}
211+
}
212+
}
213+
214+
// Sort subfolders for deterministic output
215+
subfolders.sort_by(|a, b| a.name.cmp(&b.name));
216+
217+
ContainmentFolder {
218+
name: folder_name,
219+
path: current_path.to_vec(),
220+
files,
221+
subfolders,
222+
}
223+
}

docs/core/src/diagrams.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use serde::Serialize;
1818
const AUTOGEN_DIAGRAM_MARKER: &str = "REQVIRE-AUTOGENERATED-DIAGRAM";
1919

2020
/// Default diagram direction (LR = left-to-right, TD = top-down)
21-
pub const DEFAULT_DIAGRAM_DIRECTION: &str = "LR";
21+
pub const DEFAULT_DIAGRAM_DIRECTION: &str = "TD";
2222

2323
/// Model diagram report containing the complete hierarchical structure
2424
#[derive(Debug, Serialize)]
@@ -218,13 +218,13 @@ impl<'a> ModelDiagramGenerator<'a> {
218218
// Add auto-generation marker
219219
diagram.push_str(&format!(" %% {}\n", AUTOGEN_DIAGRAM_MARKER));
220220

221-
// Define Mermaid graph styles
221+
// Define Mermaid graph styles with MBSE color scheme
222222
diagram.push_str(" %% Graph styling\n");
223-
diagram.push_str(" classDef userRequirement fill:#f9d6d6,stroke:#f55f5f,stroke-width:1px;\n");
224-
diagram.push_str(" classDef systemRequirement fill:#fce4e4,stroke:#e68a8a,stroke-width:1px;\n");
225-
diagram.push_str(" classDef verification fill:#d6f9d6,stroke:#5fd75f,stroke-width:1px;\n");
226-
diagram.push_str(" classDef folder fill:#f0f0f0,stroke:#666666,stroke-width:3px;\n");
227-
diagram.push_str(" classDef file fill:#ffffff,stroke:#999999,stroke-width:2px;\n");
223+
diagram.push_str(" classDef userRequirement fill:#dbeafe,stroke:#2563EB,stroke-width:2px;\n");
224+
diagram.push_str(" classDef systemRequirement fill:#dbeafe,stroke:#2563EB,stroke-width:1px;\n");
225+
diagram.push_str(" classDef verification fill:#d1fae5,stroke:#059669,stroke-width:2px;\n");
226+
diagram.push_str(" classDef folder fill:#f1f5f9,stroke:#64748B,stroke-width:3px;\n");
227+
diagram.push_str(" classDef file fill:#ffffff,stroke:#64748B,stroke-width:2px;\n");
228228
diagram.push_str(" classDef default fill:#f5f5f5,stroke:#333333,stroke-width:1px;\n\n");
229229

230230
// Add folders, files, and elements
@@ -396,9 +396,9 @@ fn generate_file_diagram(
396396

397397
// Define Mermaid graph styles
398398
diagram.push_str(" %% Graph styling\n");
399-
diagram.push_str(" classDef userRequirement fill:#f9d6d6,stroke:#f55f5f,stroke-width:1px;\n");
400-
diagram.push_str(" classDef systemRequirement fill:#fce4e4,stroke:#e68a8a,stroke-width:1px;\n");
401-
diagram.push_str(" classDef verification fill:#d6f9d6,stroke:#5fd75f,stroke-width:1px;\n");
399+
diagram.push_str(" classDef userRequirement fill:#dbeafe,stroke:#2563EB,stroke-width:2px;\n");
400+
diagram.push_str(" classDef systemRequirement fill:#dbeafe,stroke:#2563EB,stroke-width:1px;\n");
401+
diagram.push_str(" classDef verification fill:#d1fae5,stroke:#059669,stroke-width:2px;\n");
402402
diagram.push_str(" classDef default fill:#f5f5f5,stroke:#333333,stroke-width:1px;\n\n");
403403

404404
let mut included_elements = HashSet::new();
@@ -1045,13 +1045,13 @@ pub fn generate_containment_diagram(registry: &GraphRegistry, short: bool) -> Re
10451045

10461046
// CSS class definitions
10471047
output.push_str(" %% Graph styling\n");
1048-
output.push_str(" classDef userRequirement fill:#f9d6d6,stroke:#f55f5f,stroke-width:1px;\n");
1049-
output.push_str(" classDef systemRequirement fill:#fce4e4,stroke:#e68a8a,stroke-width:1px;\n");
1050-
output.push_str(" classDef requirement fill:#fce4e4,stroke:#e68a8a,stroke-width:1px;\n");
1051-
output.push_str(" classDef verification fill:#d6f9d6,stroke:#5fd75f,stroke-width:1px;\n");
1048+
output.push_str(" classDef userRequirement fill:#dbeafe,stroke:#2563EB,stroke-width:2px;\n");
1049+
output.push_str(" classDef systemRequirement fill:#dbeafe,stroke:#2563EB,stroke-width:1px;\n");
1050+
output.push_str(" classDef requirement fill:#dbeafe,stroke:#2563EB,stroke-width:1px;\n");
1051+
output.push_str(" classDef verification fill:#d1fae5,stroke:#059669,stroke-width:2px;\n");
10521052
output.push_str(" classDef default fill:#f5f5f5,stroke:#333333,stroke-width:1px;\n");
1053-
output.push_str(" classDef folder fill:#e8f4f8,stroke:#4a90a4,stroke-width:2px;\n");
1054-
output.push_str(" classDef file fill:#fff8e1,stroke:#f9a825,stroke-width:2px;\n\n");
1053+
output.push_str(" classDef folder fill:#f1f5f9,stroke:#64748B,stroke-width:2px;\n");
1054+
output.push_str(" classDef file fill:#fef3c7,stroke:#D97706,stroke-width:2px;\n\n");
10551055

10561056
// Define root node
10571057
output.push_str(" root[\"📁 Reqvire root\"]\n");

0 commit comments

Comments
 (0)