gitronics 0.5.25__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitronics-0.5.25 → gitronics-0.6.0}/Cargo.lock +20 -7
- {gitronics-0.5.25 → gitronics-0.6.0}/Cargo.toml +2 -2
- {gitronics-0.5.25 → gitronics-0.6.0}/PKG-INFO +2 -2
- {gitronics-0.5.25 → gitronics-0.6.0}/pyproject.toml +1 -1
- {gitronics-0.5.25 → gitronics-0.6.0}/python/gitronics/__init__.py +2 -1
- {gitronics-0.5.25 → gitronics-0.6.0}/python/gitronics/gitronics.pyi +16 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/build_model.rs +212 -185
- gitronics-0.6.0/src/migrate_model.rs +193 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/project_manager/load_metadata.rs +7 -6
- gitronics-0.6.0/src/project_manager/load_project_files.rs +104 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/project_manager.rs +1 -1
- {gitronics-0.5.25 → gitronics-0.6.0}/src/python.rs +14 -1
- {gitronics-0.5.25 → gitronics-0.6.0}/src/types.rs +0 -43
- {gitronics-0.5.25 → gitronics-0.6.0}/src/utils.rs +65 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/tests/test_example_project.rs +39 -0
- gitronics-0.5.25/src/migrate_model.rs +0 -235
- gitronics-0.5.25/src/project_manager/load_project_files.rs +0 -148
- {gitronics-0.5.25 → gitronics-0.6.0}/.github/workflows/ci.yml +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/.github/workflows/docs.yml +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/.github/workflows/release.yml +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/.gitignore +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/.vscode/settings.json +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/LICENSE +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/README.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/assets/logo.png +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/best_practices.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/examples.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/assessments.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/building_a_model.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/card_id_numbers.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/concepts.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/configuration_file.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/file_types.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/migrating.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/new_project.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started/project_structure.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/getting_started.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/hooks/version_from_git.py +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/index.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/installation.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/requirements.txt +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/usage/build.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/docs/usage/migrate.md +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/assessment_specific/filler_model_3.mcnp +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/assessment_specific/filler_model_3.metadata +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/assessment_specific/small_override.yaml +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/configurations/valid_configuration.yaml +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/output/.gitignore +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/data_cards/fine_mesh.tally +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/data_cards/materials.mat +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/data_cards/my_transform.transform +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/data_cards/volumetric_source.source +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/envelope_structure.mcnp +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/envelope_structure.metadata +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/filler_models/filler_model_1.mcnp +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/filler_models/filler_model_1.metadata +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/filler_models/filler_model_2.mcnp +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/example_project/reference_model/filler_models/filler_model_2.metadata +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/mkdocs.yml +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/python/gitronics/__main__.py +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/python/tests/test_build_model.py +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/python/tests/test_cli_works.py +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/resources/simple_model.mcnp +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/build_report.rs +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/cli.rs +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/lib.rs +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/main.rs +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/model_config.rs +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/project_manager/load_model_config.rs +0 -0
- {gitronics-0.5.25 → gitronics-0.6.0}/src/report.css +0 -0
|
@@ -390,7 +390,7 @@ dependencies = [
|
|
|
390
390
|
|
|
391
391
|
[[package]]
|
|
392
392
|
name = "gitronics"
|
|
393
|
-
version = "0.
|
|
393
|
+
version = "0.6.0"
|
|
394
394
|
dependencies = [
|
|
395
395
|
"chrono",
|
|
396
396
|
"clap",
|
|
@@ -636,14 +636,21 @@ dependencies = [
|
|
|
636
636
|
|
|
637
637
|
[[package]]
|
|
638
638
|
name = "migjorn"
|
|
639
|
-
version = "0.
|
|
639
|
+
version = "0.2.2"
|
|
640
640
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
641
|
-
checksum = "
|
|
641
|
+
checksum = "ce11924d4ef1f0496f0be14e68860ab11bbc0b63222057d03bc92b22b4084f3d"
|
|
642
642
|
dependencies = [
|
|
643
|
-
"
|
|
644
|
-
"
|
|
645
|
-
|
|
646
|
-
|
|
643
|
+
"migjorn-syntax",
|
|
644
|
+
"rustc-hash",
|
|
645
|
+
]
|
|
646
|
+
|
|
647
|
+
[[package]]
|
|
648
|
+
name = "migjorn-syntax"
|
|
649
|
+
version = "0.2.2"
|
|
650
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
651
|
+
checksum = "42d3806f43fee8380cfaa0e26e9fee3a2dbece142e5314b6c7f9a0babf03fe05"
|
|
652
|
+
dependencies = [
|
|
653
|
+
"rustc-hash",
|
|
647
654
|
]
|
|
648
655
|
|
|
649
656
|
[[package]]
|
|
@@ -858,6 +865,12 @@ version = "0.8.10"
|
|
|
858
865
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
859
866
|
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
860
867
|
|
|
868
|
+
[[package]]
|
|
869
|
+
name = "rustc-hash"
|
|
870
|
+
version = "2.1.3"
|
|
871
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
872
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
873
|
+
|
|
861
874
|
[[package]]
|
|
862
875
|
name = "rustix"
|
|
863
876
|
version = "1.1.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "gitronics"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
description = "Build MCNP neutronics models from modular components"
|
|
6
6
|
license = "EUPL-1.2"
|
|
@@ -16,7 +16,7 @@ crate-type = ["cdylib", "rlib"]
|
|
|
16
16
|
|
|
17
17
|
[dependencies]
|
|
18
18
|
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py39"] }
|
|
19
|
-
migjorn = { version = "0.
|
|
19
|
+
migjorn = { version = "0.2.2" }
|
|
20
20
|
walkdir = "2.5.0"
|
|
21
21
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
22
22
|
serde-saphyr = "0.0.26"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitronics
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Summary: Build MCNP neutronics models from modular components
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.9
|
|
@@ -6,7 +6,7 @@ build-backend = "maturin"
|
|
|
6
6
|
name = "gitronics"
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Build MCNP neutronics models from modular components"
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Programming Language :: Rust",
|
|
12
12
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
@@ -11,9 +11,10 @@ from __future__ import annotations
|
|
|
11
11
|
import sys
|
|
12
12
|
|
|
13
13
|
from .gitronics import py_build_model as build_model # type: ignore[import]
|
|
14
|
+
from .gitronics import py_migrate_model as migrate_model # type: ignore[import]
|
|
14
15
|
from .gitronics import run # type: ignore[import]
|
|
15
16
|
|
|
16
|
-
__all__ = ["build_model", "run"]
|
|
17
|
+
__all__ = ["build_model", "migrate_model", "run"]
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def _cli_main() -> None:
|
|
@@ -43,3 +43,19 @@ def py_build_model(config_path: Path | str, output_path: Path | str) -> None:
|
|
|
43
43
|
)
|
|
44
44
|
"""
|
|
45
45
|
...
|
|
46
|
+
|
|
47
|
+
def py_migrate_model(mcnp_input: Path | str, output_path: Path | str) -> None:
|
|
48
|
+
"""Migrate a monolithic MCNP model into a new gitronics project.
|
|
49
|
+
|
|
50
|
+
Splits the deck at *mcnp_input* into an envelope structure, per-universe
|
|
51
|
+
filler models, and a data-cards file, and writes a baseline configuration
|
|
52
|
+
that rebuilds the original model with :func:`build_model`.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
mcnp_input: Path to the monolithic MCNP input file.
|
|
56
|
+
output_path: Directory where the new project will be created.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
RuntimeError: If any step of the migration fails.
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
use crate::build_report::{BuildReport, EnvelopeEntry, FillerEntry};
|
|
2
2
|
use crate::project_manager::ProjectManager;
|
|
3
|
-
use crate::types::{
|
|
3
|
+
use crate::types::{EnvelopeName, FillerName, UniverseId};
|
|
4
4
|
use crate::utils::GitronicsError;
|
|
5
5
|
|
|
6
6
|
use git2::Repository;
|
|
7
7
|
use log::{info, warn};
|
|
8
|
-
use migjorn::
|
|
8
|
+
use migjorn::Model;
|
|
9
9
|
use regex::Regex;
|
|
10
10
|
use std::collections::HashSet;
|
|
11
|
+
use std::fs;
|
|
11
12
|
use std::{collections::HashMap, path::Path, sync::LazyLock};
|
|
12
|
-
use std::{env, fs};
|
|
13
13
|
|
|
14
14
|
pub fn build_model(config_path: &Path, output_path: &Path) -> Result<(), GitronicsError> {
|
|
15
15
|
info!(
|
|
@@ -19,34 +19,35 @@ pub fn build_model(config_path: &Path, output_path: &Path) -> Result<(), Gitroni
|
|
|
19
19
|
|
|
20
20
|
let mut project_manager = ProjectManager::new(config_path, output_path)?;
|
|
21
21
|
|
|
22
|
-
// Load all files
|
|
22
|
+
// Load all files.
|
|
23
23
|
let mut envelope_structure = project_manager.load_envelope_structure()?;
|
|
24
24
|
let unordered_fillers = project_manager.load_fillers()?;
|
|
25
|
-
let
|
|
26
|
-
let
|
|
27
|
-
let
|
|
25
|
+
let transforms = project_manager.load_transforms()?;
|
|
26
|
+
let materials = project_manager.load_materials()?;
|
|
27
|
+
let tallies = project_manager.load_tallies()?;
|
|
28
28
|
let source = project_manager.load_source()?;
|
|
29
29
|
|
|
30
|
-
//
|
|
31
|
-
let fillers =
|
|
32
|
-
let transforms = order_data_cards_groups_by_id(unordered_transforms);
|
|
33
|
-
let materials = order_data_cards_groups_by_id(unordered_materials);
|
|
34
|
-
let tallies = order_data_cards_groups_by_id(unordered_tallies);
|
|
30
|
+
// Order fillers by their first cell id for deterministic output.
|
|
31
|
+
let fillers = order_fillers_by_cell_id(unordered_fillers)?;
|
|
35
32
|
|
|
36
|
-
// Load metadata for each filler and cache it in the ProjectManager
|
|
37
|
-
project_manager.load_metadata_for_fillers(
|
|
33
|
+
// Load metadata for each filler and cache it in the ProjectManager.
|
|
34
|
+
project_manager.load_metadata_for_fillers(fillers.iter().map(|(name, _)| name))?;
|
|
38
35
|
|
|
39
|
-
//
|
|
36
|
+
// Universe id of every filler (read from its first cell's `u=`).
|
|
40
37
|
let universe_ids: HashMap<FillerName, UniverseId> = fillers
|
|
41
38
|
.iter()
|
|
42
|
-
.map(|
|
|
39
|
+
.map(|(name, model)| {
|
|
40
|
+
let universe = filler_universe(model)
|
|
41
|
+
.ok_or_else(|| GitronicsError::FirstCellWithoutUniverseID(name.clone()))?;
|
|
42
|
+
Ok((name.clone(), universe))
|
|
43
|
+
})
|
|
43
44
|
.collect::<Result<HashMap<_, _>, GitronicsError>>()?;
|
|
44
45
|
|
|
45
|
-
//
|
|
46
|
+
// Insert FILL cards into the placeholder envelope cells.
|
|
46
47
|
info!("Adapting envelope structure with FILL cards");
|
|
47
48
|
add_fill_cards_to_envelopes(&project_manager, &universe_ids, &mut envelope_structure)?;
|
|
48
49
|
|
|
49
|
-
// Collect build-report data before
|
|
50
|
+
// Collect build-report data before the models are consumed by composition.
|
|
50
51
|
let report = collect_build_report(
|
|
51
52
|
config_path,
|
|
52
53
|
&project_manager,
|
|
@@ -55,31 +56,51 @@ pub fn build_model(config_path: &Path, output_path: &Path) -> Result<(), Gitroni
|
|
|
55
56
|
&universe_ids,
|
|
56
57
|
)?;
|
|
57
58
|
|
|
58
|
-
// Compose
|
|
59
|
+
// Compose: drop the ignored data blocks, then merge every filler's geometry
|
|
60
|
+
// into the envelope structure (collision-checked against the disjoint-range
|
|
61
|
+
// convention).
|
|
59
62
|
info!("Composing model");
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
envelope_structure.data_cards.extend(tallies);
|
|
67
|
-
envelope_structure.data_cards.extend(source);
|
|
68
|
-
|
|
69
|
-
// Perform validation checks
|
|
70
|
-
info!("Performing validation checks on the assembled model");
|
|
63
|
+
envelope_structure.clear_data_cards();
|
|
64
|
+
let mut filler_models = fillers;
|
|
65
|
+
for (_, model) in filler_models.iter_mut() {
|
|
66
|
+
model.clear_data_cards();
|
|
67
|
+
}
|
|
68
|
+
let filler_refs: Vec<&Model> = filler_models.iter().map(|(_, model)| model).collect();
|
|
71
69
|
envelope_structure
|
|
72
|
-
.
|
|
73
|
-
.map_err(|
|
|
70
|
+
.merge(&filler_refs)
|
|
71
|
+
.map_err(|conflicts| GitronicsError::MergeConflicts(format_conflicts(&conflicts)))?;
|
|
72
|
+
|
|
73
|
+
// Append the configured data cards to the data block.
|
|
74
|
+
let data_text = [transforms, materials, tallies, source]
|
|
75
|
+
.into_iter()
|
|
76
|
+
.filter(|s| !s.is_empty())
|
|
77
|
+
.collect::<Vec<_>>()
|
|
78
|
+
.join("\n");
|
|
79
|
+
let mut assembled_source = envelope_structure.to_source();
|
|
80
|
+
if !data_text.is_empty() {
|
|
81
|
+
if !assembled_source.ends_with('\n') {
|
|
82
|
+
assembled_source.push('\n');
|
|
83
|
+
}
|
|
84
|
+
assembled_source.push_str(&data_text);
|
|
85
|
+
assembled_source.push('\n');
|
|
86
|
+
}
|
|
87
|
+
let assembled_model = Model::parse(assembled_source);
|
|
88
|
+
|
|
89
|
+
// Validate the assembled model.
|
|
90
|
+
info!("Performing validation checks on the assembled model");
|
|
91
|
+
let problems = assembled_model.validate();
|
|
92
|
+
if !problems.is_empty() {
|
|
93
|
+
return Err(GitronicsError::ValidationError(problems.join("\n")));
|
|
94
|
+
}
|
|
74
95
|
|
|
75
|
-
// Write model
|
|
96
|
+
// Write the assembled model with the provenance banner.
|
|
76
97
|
info!("Writing assembled model to file");
|
|
77
98
|
let assembled_path = project_manager.output_path().join("assembled.mcnp");
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
let final_source = insert_banner(&assembled_model.to_source(), config_path);
|
|
100
|
+
fs::write(&assembled_path, final_source)?;
|
|
80
101
|
fs::write(output_path.join(".gitignore"), "*\n")?;
|
|
81
102
|
|
|
82
|
-
// Write build report
|
|
103
|
+
// Write the HTML build report.
|
|
83
104
|
info!("Writing HTML build report");
|
|
84
105
|
let report_path = project_manager.output_path().join("build_report.html");
|
|
85
106
|
fs::write(&report_path, report.generate_html())?;
|
|
@@ -94,17 +115,134 @@ pub fn build_model(config_path: &Path, output_path: &Path) -> Result<(), Gitroni
|
|
|
94
115
|
static ENVELOPE_RE: LazyLock<Regex> =
|
|
95
116
|
LazyLock::new(|| Regex::new(r"\$\s*@env:\s*([[:alnum:]_.-]+)\s*").unwrap());
|
|
96
117
|
|
|
118
|
+
/// The directory that contains the configuration file — the anchor for git
|
|
119
|
+
/// repository discovery (the project, not the process's working directory).
|
|
120
|
+
fn project_dir(config_path: &Path) -> &Path {
|
|
121
|
+
config_path.parent().unwrap_or(Path::new("."))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// The universe id declared by a filler's first cell (`u=`), if any.
|
|
125
|
+
fn filler_universe(model: &Model) -> Option<UniverseId> {
|
|
126
|
+
let first = model.cells().next()?;
|
|
127
|
+
model
|
|
128
|
+
.cell_universe(first.card_index)
|
|
129
|
+
.map(|u| UniverseId::new(u as u32))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Order fillers by the id of their first cell, erroring on a filler with no
|
|
133
|
+
/// cells.
|
|
134
|
+
fn order_fillers_by_cell_id(
|
|
135
|
+
fillers: Vec<(FillerName, Model)>,
|
|
136
|
+
) -> Result<Vec<(FillerName, Model)>, GitronicsError> {
|
|
137
|
+
let mut keyed: Vec<(i64, FillerName, Model)> = fillers
|
|
138
|
+
.into_iter()
|
|
139
|
+
.map(|(name, model)| {
|
|
140
|
+
let cell_id = model
|
|
141
|
+
.cells()
|
|
142
|
+
.next()
|
|
143
|
+
.map(|c| c.id)
|
|
144
|
+
.ok_or_else(|| GitronicsError::NoCellID(name.clone()))?;
|
|
145
|
+
Ok((cell_id, name, model))
|
|
146
|
+
})
|
|
147
|
+
.collect::<Result<Vec<_>, GitronicsError>>()?;
|
|
148
|
+
keyed.sort_by_key(|(cell_id, _, _)| *cell_id);
|
|
149
|
+
Ok(keyed
|
|
150
|
+
.into_iter()
|
|
151
|
+
.map(|(_, name, model)| (name, model))
|
|
152
|
+
.collect())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn add_fill_cards_to_envelopes(
|
|
156
|
+
project_manager: &ProjectManager,
|
|
157
|
+
universe_ids: &HashMap<FillerName, UniverseId>,
|
|
158
|
+
envelope_structure: &mut Model,
|
|
159
|
+
) -> Result<(), GitronicsError> {
|
|
160
|
+
let mut missing_envelopes_in_file: HashSet<EnvelopeName> =
|
|
161
|
+
project_manager.envelopes_in_config().cloned().collect();
|
|
162
|
+
|
|
163
|
+
// Collect cell card indices up front: FILL insertion is a token splice that
|
|
164
|
+
// leaves indices stable, so we can read then mutate by the same index.
|
|
165
|
+
let cell_indices: Vec<usize> = envelope_structure.cells().map(|c| c.card_index).collect();
|
|
166
|
+
|
|
167
|
+
for card_index in cell_indices {
|
|
168
|
+
let original_text = envelope_structure.card_source(card_index);
|
|
169
|
+
let Some(caps) = ENVELOPE_RE.captures(&original_text) else {
|
|
170
|
+
continue;
|
|
171
|
+
};
|
|
172
|
+
let envelope_name =
|
|
173
|
+
EnvelopeName::new(caps.get(1).map(|m| m.as_str()).ok_or_else(|| {
|
|
174
|
+
GitronicsError::FailedToExtractEnvelopeName(
|
|
175
|
+
ENVELOPE_RE.to_string(),
|
|
176
|
+
original_text.clone(),
|
|
177
|
+
)
|
|
178
|
+
})?);
|
|
179
|
+
|
|
180
|
+
let Some(env_config) = project_manager.filler_by_envelope(&envelope_name) else {
|
|
181
|
+
warn!(
|
|
182
|
+
"The envelope `{envelope_name}` is not considered in the configuration file. \
|
|
183
|
+
It is better to explicitly set it as `{envelope_name}: null` if you want the \
|
|
184
|
+
envelope to not be filled with any model."
|
|
185
|
+
);
|
|
186
|
+
continue;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// We found the envelope in the file.
|
|
190
|
+
missing_envelopes_in_file.remove(&envelope_name);
|
|
191
|
+
|
|
192
|
+
// Envelope explicitly set to null in config: leave it unfilled.
|
|
193
|
+
let Some(filler_name) = env_config.as_ref() else {
|
|
194
|
+
continue;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
let universe_id = universe_ids
|
|
198
|
+
.get(filler_name)
|
|
199
|
+
.ok_or_else(|| GitronicsError::FillerUniverseIdMissing(filler_name.clone()))?;
|
|
200
|
+
|
|
201
|
+
let transform = project_manager
|
|
202
|
+
.transformation(filler_name, &envelope_name)?
|
|
203
|
+
.unwrap_or("");
|
|
204
|
+
let fill_card_text = if let Some(transform_without_star) = transform.strip_prefix('*') {
|
|
205
|
+
format!("*fill={universe_id} {transform_without_star}")
|
|
206
|
+
} else {
|
|
207
|
+
format!("fill={universe_id} {transform}")
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
envelope_structure
|
|
211
|
+
.add_cell_param(card_index, fill_card_text.trim())
|
|
212
|
+
.map_err(|e| GitronicsError::InvalidFillCard(fill_card_text, e.to_string()))?;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if !missing_envelopes_in_file.is_empty() {
|
|
216
|
+
warn!(
|
|
217
|
+
"The following envelopes were defined in the configuration file but not found in the envelope structure file: {}. \
|
|
218
|
+
Please check that the `$ @env:envelope_name` pattern is satisfied.",
|
|
219
|
+
missing_envelopes_in_file
|
|
220
|
+
.into_iter()
|
|
221
|
+
.map(|e| e.to_string())
|
|
222
|
+
.collect::<Vec<_>>()
|
|
223
|
+
.join(", ")
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
Ok(())
|
|
227
|
+
}
|
|
228
|
+
|
|
97
229
|
fn collect_build_report(
|
|
98
230
|
config_path: &Path,
|
|
99
231
|
project_manager: &ProjectManager,
|
|
100
232
|
envelope_structure: &Model,
|
|
101
|
-
fillers: &[Model],
|
|
233
|
+
fillers: &[(FillerName, Model)],
|
|
102
234
|
universe_ids: &HashMap<FillerName, UniverseId>,
|
|
103
235
|
) -> Result<BuildReport, GitronicsError> {
|
|
104
|
-
let total_cells =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
236
|
+
let total_cells = envelope_structure.cells().count()
|
|
237
|
+
+ fillers
|
|
238
|
+
.iter()
|
|
239
|
+
.map(|(_, m)| m.cells().count())
|
|
240
|
+
.sum::<usize>();
|
|
241
|
+
let total_surfaces = envelope_structure.surfaces().count()
|
|
242
|
+
+ fillers
|
|
243
|
+
.iter()
|
|
244
|
+
.map(|(_, m)| m.surfaces().count())
|
|
245
|
+
.sum::<usize>();
|
|
108
246
|
|
|
109
247
|
let envelope_entries: Vec<EnvelopeEntry> = project_manager
|
|
110
248
|
.envelopes_in_config()
|
|
@@ -146,16 +284,15 @@ fn collect_build_report(
|
|
|
146
284
|
|
|
147
285
|
let filler_entries: Vec<FillerEntry> = fillers
|
|
148
286
|
.iter()
|
|
149
|
-
.filter_map(|
|
|
150
|
-
let
|
|
151
|
-
let
|
|
152
|
-
let envelope_count = *filler_envelope_counts.get(&name).unwrap_or(&0);
|
|
287
|
+
.filter_map(|(name, model)| {
|
|
288
|
+
let universe_id = *universe_ids.get(name)?;
|
|
289
|
+
let envelope_count = *filler_envelope_counts.get(name).unwrap_or(&0);
|
|
153
290
|
Some(FillerEntry {
|
|
154
291
|
universe_id,
|
|
155
292
|
envelope_count,
|
|
156
|
-
cell_count:
|
|
157
|
-
surface_count:
|
|
158
|
-
name,
|
|
293
|
+
cell_count: model.cells().count(),
|
|
294
|
+
surface_count: model.surfaces().count(),
|
|
295
|
+
name: name.clone(),
|
|
159
296
|
})
|
|
160
297
|
})
|
|
161
298
|
.collect();
|
|
@@ -163,7 +300,7 @@ fn collect_build_report(
|
|
|
163
300
|
Ok(BuildReport {
|
|
164
301
|
config_path: config_path.display().to_string(),
|
|
165
302
|
gitronics_version: env!("CARGO_PKG_VERSION"),
|
|
166
|
-
commit_hash: get_hash_of_project(),
|
|
303
|
+
commit_hash: get_hash_of_project(project_dir(config_path)),
|
|
167
304
|
date_time: chrono::Utc::now()
|
|
168
305
|
.format("%Y-%m-%d %H:%M:%S UTC")
|
|
169
306
|
.to_string(),
|
|
@@ -190,153 +327,43 @@ fn collect_build_report(
|
|
|
190
327
|
})
|
|
191
328
|
}
|
|
192
329
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
for cell in envelope_structure.cells.iter_mut() {
|
|
202
|
-
let original_text = cell.original_text();
|
|
203
|
-
let Some(caps) = ENVELOPE_RE.captures(original_text) else {
|
|
204
|
-
continue;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
let envelope_name =
|
|
208
|
-
EnvelopeName::new(caps.get(1).map(|m| m.as_str()).ok_or_else(|| {
|
|
209
|
-
GitronicsError::FailedToExtractEnvelopeName(
|
|
210
|
-
ENVELOPE_RE.to_string(),
|
|
211
|
-
original_text.to_string(),
|
|
212
|
-
)
|
|
213
|
-
})?);
|
|
214
|
-
|
|
215
|
-
let Some(env_config) = project_manager.filler_by_envelope(&envelope_name) else {
|
|
216
|
-
warn!(
|
|
217
|
-
"The envelope `{envelope_name}` is not considered in the configuration file. \
|
|
218
|
-
It is better to explicitly set it as `{envelope_name}: null` if you want the \
|
|
219
|
-
envelope to not be filled with any model."
|
|
220
|
-
);
|
|
221
|
-
continue;
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// Remove the envelope from the set of missing envelopes, as we have found it in the file
|
|
225
|
-
missing_envelopes_in_file.remove(&envelope_name);
|
|
226
|
-
|
|
227
|
-
// Envelope explicitly set to null in config, so we skip it
|
|
228
|
-
let Some(filler_name) = env_config.as_ref() else {
|
|
229
|
-
continue;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
let universe_id = universe_ids
|
|
233
|
-
.get(filler_name)
|
|
234
|
-
.ok_or_else(|| GitronicsError::FillerUniverseIdMissing(filler_name.clone()))?;
|
|
235
|
-
|
|
236
|
-
let transform = project_manager.transformation(filler_name, &envelope_name)?;
|
|
237
|
-
let transform = transform.unwrap_or("");
|
|
238
|
-
|
|
239
|
-
let fill_card_text = if let Some(transform_without_star) = transform.strip_prefix('*') {
|
|
240
|
-
format!("*FILL={} {}", universe_id, transform_without_star)
|
|
241
|
-
} else {
|
|
242
|
-
format!("FILL={} {}", universe_id, transform)
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
cell.insert_param(
|
|
246
|
-
cell.params().len(),
|
|
247
|
-
CellParam::try_from(fill_card_text.clone())
|
|
248
|
-
.map_err(|e| GitronicsError::InvalidFillCard(fill_card_text, e.to_string()))?,
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if !missing_envelopes_in_file.is_empty() {
|
|
253
|
-
warn!(
|
|
254
|
-
"The following envelopes were defined in the configuration file but not found in the envelope structure file: {}. \
|
|
255
|
-
Please check that the `$ @env:envelope_name` pattern is satisfied.",
|
|
256
|
-
missing_envelopes_in_file
|
|
257
|
-
.into_iter()
|
|
258
|
-
.map(|e| e.to_string())
|
|
259
|
-
.collect::<Vec<_>>()
|
|
260
|
-
.join(", ")
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
Ok(())
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
fn order_models_by_cell_id(models: Vec<Model>) -> Result<Vec<Model>, GitronicsError> {
|
|
267
|
-
let mut decorated: Vec<(CellId, Model)> = models
|
|
268
|
-
.into_iter()
|
|
269
|
-
.map(|filler| {
|
|
270
|
-
let cell_id = filler
|
|
271
|
-
.cells
|
|
272
|
-
.first()
|
|
273
|
-
.map(|cell| CellId::new(cell.cell_id()))
|
|
274
|
-
.ok_or_else(|| GitronicsError::NoCellID(FillerName::from(&filler)))?;
|
|
275
|
-
Ok((cell_id, filler))
|
|
276
|
-
})
|
|
277
|
-
.collect::<Result<Vec<_>, GitronicsError>>()?;
|
|
278
|
-
decorated.sort_by_key(|(cell_id, _)| *cell_id);
|
|
279
|
-
Ok(decorated.into_iter().map(|(_, filler)| filler).collect())
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
fn order_data_cards_groups_by_id(mut data_cards_groups: Vec<Vec<DataCard>>) -> Vec<DataCard> {
|
|
283
|
-
data_cards_groups.sort_by_key(|data_cards| data_cards.first().map(|card| card.card_id()));
|
|
284
|
-
data_cards_groups.into_iter().flatten().collect()
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
fn get_universe_id_of_model(model: &Model) -> Result<UniverseId, GitronicsError> {
|
|
288
|
-
model
|
|
289
|
-
.cells
|
|
290
|
-
.first()
|
|
291
|
-
.and_then(|cell| {
|
|
292
|
-
cell.params()
|
|
293
|
-
.iter()
|
|
294
|
-
.find_map(|param| match param.param_type {
|
|
295
|
-
ParamType::U(id) => Some(UniverseId::new(id)),
|
|
296
|
-
_ => None,
|
|
297
|
-
})
|
|
298
|
-
})
|
|
299
|
-
.ok_or_else(|| GitronicsError::FirstCellWithoutUniverseID(FillerName::from(model)))
|
|
330
|
+
/// Render merge conflicts as a human-readable, newline-separated list.
|
|
331
|
+
fn format_conflicts(conflicts: &[migjorn::MergeConflict]) -> String {
|
|
332
|
+
conflicts
|
|
333
|
+
.iter()
|
|
334
|
+
.map(|c| c.to_string())
|
|
335
|
+
.collect::<Vec<_>>()
|
|
336
|
+
.join("\n")
|
|
300
337
|
}
|
|
301
338
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
config_path: &Path,
|
|
305
|
-
) -> Result<(), GitronicsError> {
|
|
339
|
+
/// Insert the provenance banner as comment lines just after the model's title.
|
|
340
|
+
fn insert_banner(source: &str, config_path: &Path) -> String {
|
|
306
341
|
let configuration = config_path.display().to_string();
|
|
307
342
|
let date_time = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
308
343
|
let gitronics_version = env!("CARGO_PKG_VERSION");
|
|
309
|
-
let commit_hash = get_hash_of_project();
|
|
344
|
+
let commit_hash = get_hash_of_project(project_dir(config_path));
|
|
310
345
|
|
|
311
346
|
let banner = format!(
|
|
312
|
-
"C
|
|
313
|
-
C Built by gitronics v{gitronics_version}
|
|
314
|
-
C Configuration : {configuration}
|
|
315
|
-
C Git commit : {commit_hash}
|
|
316
|
-
C Date / time : {date_time}
|
|
317
|
-
C ============================================================"
|
|
347
|
+
"C ============================================================\n\
|
|
348
|
+
C Built by gitronics v{gitronics_version}\n\
|
|
349
|
+
C Configuration : {configuration}\n\
|
|
350
|
+
C Git commit : {commit_hash}\n\
|
|
351
|
+
C Date / time : {date_time}\n\
|
|
352
|
+
C ============================================================"
|
|
318
353
|
);
|
|
319
|
-
let new_card_text = format!(
|
|
320
|
-
"{banner}\n{}",
|
|
321
|
-
assembled_model
|
|
322
|
-
.cells
|
|
323
|
-
.first()
|
|
324
|
-
.map(|c| c.original_text())
|
|
325
|
-
.unwrap_or_default()
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
let banner_card = CellCard::try_from(new_card_text.as_str())
|
|
329
|
-
.expect("Adding comments as header should not fail");
|
|
330
354
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
355
|
+
match source.split_once('\n') {
|
|
356
|
+
Some((title, rest)) => format!("{title}\n{banner}\n{rest}"),
|
|
357
|
+
None => format!("{source}\n{banner}\n"),
|
|
358
|
+
}
|
|
334
359
|
}
|
|
335
360
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
361
|
+
/// Describe the git state of the repository that contains `start_dir` (the
|
|
362
|
+
/// project being built), not the process's current working directory — so the
|
|
363
|
+
/// recorded commit reflects what was actually assembled regardless of where the
|
|
364
|
+
/// binary was invoked from.
|
|
365
|
+
fn get_hash_of_project(start_dir: &Path) -> String {
|
|
366
|
+
let repo = Repository::discover(start_dir).ok();
|
|
340
367
|
repo.as_ref()
|
|
341
368
|
.and_then(|r| {
|
|
342
369
|
let mut opts = git2::DescribeOptions::new();
|