Ta vare på manuelle endringer ved ny import
«enok tillegg modell.ifc bygning.toml» skriver forskjellen mellom modellen og en håndredigert bygningsbeskrivelse som en tilleggsfil: endrede verdier, nye oppføringer, «slett = true» for det bare modellen har, og scenarioene. Kommandoen kontrollerer at tillegget gjenskaper filen det er laget av, og sier fra om noe ikke lar seg uttrykke, siden fletting ikke kan fjerne et enkeltfelt. «enok import» nekter å skrive over en fil enok ikke har laget selv, og viser til kommandoen over. --overskriv går forbi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GeH3y8fwTaTudaD88P57o
This commit is contained in:
@@ -71,6 +71,87 @@ pub fn slaa_sammen_scenario(b: &Bygning, s: &Scenario) -> Result<Bygning> {
|
||||
.with_context(|| format!("scenarioet «{}» gir en ugyldig bygningsbeskrivelse", s.navn))
|
||||
}
|
||||
|
||||
/// Forskjellen mellom to bygningsbeskrivelser, som en tilleggsfil.
|
||||
///
|
||||
/// Resultatet er det minste som må flettes inn i `fra` for å få `til`: endrede og nye felter,
|
||||
/// nye elementer i lister, og `slett = true` for elementer som bare finnes i `fra`. Fletting
|
||||
/// kan ikke fjerne et enkeltfelt, så felter som bare finnes i `fra`, listes som merknader.
|
||||
pub fn differanse(fra: &Value, til: &Value) -> (Value, Vec<String>) {
|
||||
let mut merknader = Vec::new();
|
||||
let v = diff_verdi(fra, til, "", &mut merknader).unwrap_or(Value::Table(Default::default()));
|
||||
(v, merknader)
|
||||
}
|
||||
|
||||
fn diff_verdi(fra: &Value, til: &Value, sti: &str, merknader: &mut Vec<String>) -> Option<Value> {
|
||||
if fra == til {
|
||||
return None;
|
||||
}
|
||||
match (fra, til) {
|
||||
(Value::Table(f), Value::Table(t)) => {
|
||||
let mut ut = toml::map::Map::new();
|
||||
for (k, v) in t {
|
||||
let bane = if sti.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{sti}.{k}")
|
||||
};
|
||||
match f.get(k) {
|
||||
Some(gammel) => {
|
||||
if let Some(d) = diff_verdi(gammel, v, &bane, merknader) {
|
||||
ut.insert(k.clone(), d);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
ut.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for k in f.keys() {
|
||||
if !t.contains_key(k) {
|
||||
let bane = if sti.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{sti}.{k}")
|
||||
};
|
||||
merknader.push(format!(
|
||||
"«{bane}» finnes i modellen, men ikke i bygningsbeskrivelsen. En tilleggsfil kan ikke fjerne et felt."
|
||||
));
|
||||
}
|
||||
}
|
||||
(!ut.is_empty()).then_some(Value::Table(ut))
|
||||
}
|
||||
(Value::Array(f), Value::Array(t)) if t.iter().all(|e| navnenokkel(e).is_some()) => {
|
||||
let mut ut: Vec<Value> = Vec::new();
|
||||
for element in t {
|
||||
let navn = navnenokkel(element).unwrap_or_default();
|
||||
match f.iter().find(|e| navnenokkel(e).as_deref() == Some(&navn)) {
|
||||
Some(gammel) => {
|
||||
let bane = format!("{sti}.{navn}");
|
||||
if let Some(Value::Table(mut endring)) =
|
||||
diff_verdi(gammel, element, &bane, merknader)
|
||||
{
|
||||
endring.insert("navn".into(), Value::String(navn));
|
||||
ut.push(Value::Table(endring));
|
||||
}
|
||||
}
|
||||
None => ut.push(element.clone()),
|
||||
}
|
||||
}
|
||||
for gammel in f {
|
||||
let navn = navnenokkel(gammel).unwrap_or_default();
|
||||
if !t.iter().any(|e| navnenokkel(e).as_deref() == Some(&navn)) {
|
||||
let mut slett = toml::map::Map::new();
|
||||
slett.insert("navn".into(), Value::String(navn));
|
||||
slett.insert("slett".into(), Value::Boolean(true));
|
||||
ut.push(Value::Table(slett));
|
||||
}
|
||||
}
|
||||
(!ut.is_empty()).then_some(Value::Array(ut))
|
||||
}
|
||||
_ => Some(til.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Om `navn` passer på mønsteret, der `*` står for vilkårlig tekst.
|
||||
fn passer(moenster: &str, navn: &str) -> bool {
|
||||
let deler: Vec<&str> = moenster.split('*').collect();
|
||||
@@ -237,6 +318,52 @@ u_verdi = 0.8
|
||||
assert_eq!(bom, vec!["Kjeller*".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn differansen_gjenskaper_den_manuelle_filen() {
|
||||
let importert: Bygning = toml::from_str(GRUNN).unwrap();
|
||||
let manuell: Bygning = toml::from_str(
|
||||
r#"
|
||||
[bygning]
|
||||
navn = "Testhus"
|
||||
kategori = "smaahus"
|
||||
byggeaar = 1948
|
||||
oppvarmet_bra = 100.0
|
||||
|
||||
[[flate]]
|
||||
navn = "Yttervegg"
|
||||
type = "vegg"
|
||||
areal = 80.0
|
||||
u_verdi = 0.22
|
||||
|
||||
[[flate]]
|
||||
navn = "Kjellervegg"
|
||||
type = "vegg"
|
||||
areal = 30.0
|
||||
u_verdi = 3.13
|
||||
|
||||
[[scenario]]
|
||||
navn = "Nye vinduer"
|
||||
endringer.flate = [{ navn = "Yttervegg", u_verdi = 0.18 }]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let (patch, merknader) = differanse(
|
||||
&Value::try_from(&importert).unwrap(),
|
||||
&Value::try_from(&manuell).unwrap(),
|
||||
);
|
||||
// Taket finnes bare i importen, og skal slettes av tillegget.
|
||||
let tekst = toml::to_string(&patch).unwrap();
|
||||
assert!(tekst.contains("slett"), "{tekst}");
|
||||
assert!(merknader.is_empty(), "{merknader:?}");
|
||||
|
||||
let (gjenskapt, bom) = flett_tillegg(&importert, &patch).unwrap();
|
||||
assert!(bom.is_empty());
|
||||
assert_eq!(
|
||||
Value::try_from(&gjenskapt).unwrap(),
|
||||
Value::try_from(&manuell).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moenster_passer() {
|
||||
assert!(passer("V-2*", "V-2 #3306"));
|
||||
|
||||
Reference in New Issue
Block a user