oxedyne/fe2o3/fe2o3_namex/src/db.rs
17.4 KiB, 53 runs
created by r1870400018:519, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | use crate::id::NamexId; |
| 2 | |
| 3 | use oxedyne_fe2o3_core::{ |
| 4 | prelude::*, |
| 5 | map::MapMut, |
| 6 | }; |
| 7 | use oxedyne_fe2o3_jdat::{ |
| 8 | prelude::*, |
| 9 | daticle::Vek, |
| 10 | string::{ |
| 11 | dec::DecoderConfig, |
| 12 | enc::{ |
| 13 | ByteEncoding, |
| 14 | EncoderConfig, |
| 15 | }, |
| 16 | }, |
| 17 | usr::{ |
| 18 | UsrKind, |
| 19 | UsrKindCode, |
| 20 | UsrKindId, |
| 21 | UsrKinds, |
| 22 | }, |
| 23 | }; |
| 24 | use oxedyne_fe2o3_text::string::Stringer; |
| 25 | |
| 26 | use std::{ |
| 27 | collections::BTreeMap, |
| 28 | fmt, |
| 29 | io::Write, |
| 30 | path::Path, |
| 31 | }; |
| 32 | |
| 33 | |
| 34 | #[derive(Clone, Debug, Default, PartialEq)] |
| 35 | pub struct Entity { |
| 36 | // All lists maximum 10 members. |
| 37 | pub nams: Vec<String>, // Preferred names. |
| 38 | pub lang: String, // Language. |
| 39 | pub desc: Option<String>, // A brief description (<= 100 words) of the entity. |
| 40 | pub vers: Option<String>, // A string specifying the version. |
| 41 | pub tags: Option<Vec<String>>, // Words or phrases helpful for categorisation (>= 1). |
| 42 | pub tim1: Option<String>, // Origin date in Holocene Era format e.g. YYYYY-MM-DD. |
| 43 | pub tim2: Option<String>, // End date in Holocene Era format e.g. YYYYY-MM-DD. |
| 44 | pub refs: Option<Vec<String>>, // A list of references to more detailed information. |
| 45 | pub alts: Option<BTreeMap<String, String>>, // Alternative identifiers. |
| 46 | pub lnks: Option<BTreeMap<NamexId, String>>, // Relationships to other entities. |
| 47 | } |
| 48 | |
| 49 | impl Entity { |
| 50 | |
| 51 | pub fn to_datmap< |
| 52 | M1: MapMut<MapKey, Entity> + Clone + fmt::Debug + Default + Send, |
| 53 | M2: MapMut<NamexId, Entity> + Clone + fmt::Debug + Default + Send, |
| 54 | >( |
| 55 | &self, |
| 56 | ) |
| 57 | -> Outcome<Dat> |
| 58 | { |
| 59 | let mut map = BTreeMap::new(); |
| 60 | let ukinds = res!(Namex::<M1, M2>::ukinds()); |
| 61 | for (klab, ukid) in ukinds.label_to_id_map() { |
| 62 | match klab.as_str() { |
| 63 | "nams" => { |
| 64 | map.insert( |
| 65 | oxedyne_fe2o3_jdat::map::MapKey::new(100, Dat::Usr(ukid.clone(), None)), |
| 66 | dat!(self.nams.clone()), |
| 67 | ); |
| 68 | }, |
| 69 | "lang" => { |
| 70 | map.insert( |
| 71 | oxedyne_fe2o3_jdat::map::MapKey::new(200, Dat::Usr(ukid.clone(), None)), |
| 72 | dat!(self.lang.clone()), |
| 73 | ); |
| 74 | }, |
| 75 | "desc" => if let Some(desc) = &self.desc { |
| 76 | map.insert( |
| 77 | oxedyne_fe2o3_jdat::map::MapKey::new(300, Dat::Usr(ukid.clone(), None)), |
| 78 | dat!(desc.clone()), |
| 79 | ); |
| 80 | }, |
| 81 | "vers" => if let Some(vers) = &self.vers { |
| 82 | map.insert( |
| 83 | oxedyne_fe2o3_jdat::map::MapKey::new(350, Dat::Usr(ukid.clone(), None)), |
| 84 | dat!(vers.clone()), |
| 85 | ); |
| 86 | }, |
| 87 | "tim1" => if let Some(tim1) = &self.tim1 { |
| 88 | map.insert( |
| 89 | oxedyne_fe2o3_jdat::map::MapKey::new(400, Dat::Usr(ukid.clone(), None)), |
| 90 | dat!(tim1.clone()), |
| 91 | ); |
| 92 | }, |
| 93 | "tim2" => if let Some(tim2) = &self.tim2 { |
| 94 | map.insert( |
| 95 | oxedyne_fe2o3_jdat::map::MapKey::new(500, Dat::Usr(ukid.clone(), None)), |
| 96 | dat!(tim2.clone()), |
| 97 | ); |
| 98 | }, |
| 99 | "refs" => if let Some(refs) = &self.refs { |
| 100 | map.insert( |
| 101 | oxedyne_fe2o3_jdat::map::MapKey::new(600, Dat::Usr(ukid.clone(), None)), |
| 102 | dat!(refs.clone()), |
| 103 | ); |
| 104 | }, |
| 105 | "alts" => if let Some(alts) = &self.alts { |
| 106 | map.insert( |
| 107 | oxedyne_fe2o3_jdat::map::MapKey::new(650, Dat::Usr(ukid.clone(), None)), |
| 108 | { |
| 109 | let mut map = BTreeMap::new(); |
| 110 | for (k, v) in alts { |
| 111 | map.insert(k.clone(), v.clone()); |
| 112 | } |
| 113 | dat!(map) |
| 114 | } |
| 115 | ); |
| 116 | }, |
| 117 | "lnks" => if let Some(lnks) = &self.lnks { |
| 118 | map.insert( |
| 119 | oxedyne_fe2o3_jdat::map::MapKey::new(700, Dat::Usr(ukid.clone(), None)), |
| 120 | { |
| 121 | // Keys are written in the canonical base64 text form, not as |
| 122 | // `b32` daticles, whose quoted form uses a different alphabet. |
| 123 | let mut map = BTreeMap::new(); |
| 124 | for (k, v) in lnks { |
| 125 | map.insert(k.to_string(), v.clone()); |
| 126 | } |
| 127 | dat!(map) |
| 128 | } |
| 129 | ); |
| 130 | }, |
| 131 | "tags" => if let Some(tags) = &self.tags { |
| 132 | map.insert( |
| 133 | oxedyne_fe2o3_jdat::map::MapKey::new(800, Dat::Usr(ukid.clone(), None)), |
| 134 | dat!(tags.clone()), |
| 135 | ); |
| 136 | }, |
| 137 | _ => (), |
| 138 | } |
| 139 | } |
| 140 | Ok(Dat::OrdMap(map)) |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | #[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] |
| 145 | pub struct MapKey { |
| 146 | ord: u64, |
| 147 | id: NamexId, |
| 148 | } |
| 149 | |
| 150 | impl MapKey { |
| 151 | pub fn new(ord: u64, id: NamexId) -> Self { |
| 152 | Self { |
| 153 | ord, |
| 154 | id, |
| 155 | } |
| 156 | } |
| 157 | pub fn ord(&self) -> u64 { self.ord } |
| 158 | pub fn id(&self) -> &NamexId { &self.id } |
| 159 | } |
| 160 | |
| 161 | #[derive(Clone, Debug, Default, PartialEq)] |
| 162 | pub struct Namex< |
| 163 | M1: MapMut<MapKey, Entity> + Clone + fmt::Debug + Default + Send, |
| 164 | M2: MapMut<NamexId, Entity> + Clone + fmt::Debug + Default + Send, |
| 165 | > { |
| 166 | ordered: M1, |
| 167 | map: M2, |
| 168 | } |
| 169 | |
| 170 | /// The Namex is distributed, meaning that users can store, manage and update what they regard |
| 171 | /// as the entire codex, or more likely, a part of it which we call a "tagset". For better |
| 172 | /// efficiency, tagsets are subsets of the codex resulting from boolean searches of the "(tags)" |
| 173 | /// field. Dividing the monolithic namex into smaller tagsets for specific purposes makes for |
| 174 | /// faster loading and search. |
| 175 | // 256 bits vs chars |
| 176 | // binary 32: - |
| 177 | // base64 44: 4IaH4F8elJw60EkIr2N9+S1avkvUDHX5IaH1GkEKoXQ= |
| 178 | // hex 64: e08687e05f1e949c3ad04908af637df92d5abe4bd40c75f921a1f51a410aa174 |
| 179 | // decimal 78: 101555773374134092463836759906846316697927485927201504874204680125044748886388 |
| 180 | impl< |
| 181 | M1: MapMut<MapKey, Entity> + Clone + fmt::Debug + Default + Send, |
| 182 | M2: MapMut<NamexId, Entity> + Clone + fmt::Debug + Default + Send, |
| 183 | > |
| 184 | Namex<M1, M2> |
| 185 | { |
| 186 | pub fn by_id(&self, id: &NamexId) -> Option<&Entity> { |
| 187 | self.map.get(id) |
| 188 | } |
| 189 | |
| 190 | pub fn ukinds() -> Outcome<UsrKinds< |
| 191 | BTreeMap<UsrKindCode, UsrKind>, |
| 192 | BTreeMap<String, UsrKindId> |
| 193 | >> { |
| 194 | let mut uks = UsrKinds::new(BTreeMap::new(), BTreeMap::new()); |
| 195 | res!(uks.add(res!(Self::usr_key("nams")))); |
| 196 | res!(uks.add(res!(Self::usr_key("lang")))); |
| 197 | res!(uks.add(res!(Self::usr_key("desc")))); |
| 198 | res!(uks.add(res!(Self::usr_key("vers")))); |
| 199 | res!(uks.add(res!(Self::usr_key("tim1")))); |
| 200 | res!(uks.add(res!(Self::usr_key("tim2")))); |
| 201 | res!(uks.add(res!(Self::usr_key("refs")))); |
| 202 | res!(uks.add(res!(Self::usr_key("alts")))); |
| 203 | res!(uks.add(res!(Self::usr_key("lnks")))); |
| 204 | res!(uks.add(res!(Self::usr_key("tags")))); |
| 205 | Ok(uks) |
| 206 | } |
| 207 | |
| 208 | /// The `UsrKindId` code establishes an order when these kinds are used for `Map` keys, but |
| 209 | /// will be overridden by the order indices in `Entity::to_datmap` when forming an `OrdMap`. |
| 210 | /// The codes here have been set to differ from the ordinals in `to_datmap` just to emphasise |
| 211 | /// the difference. |
| 212 | pub fn usr_key(label: &str) -> Outcome<UsrKindId> { |
| 213 | Ok(match label { |
| 214 | "nams" => UsrKindId::new(10, Some("nams"), None), |
| 215 | "lang" => UsrKindId::new(20, Some("lang"), None), |
| 216 | "desc" => UsrKindId::new(30, Some("desc"), None), |
| 217 | "vers" => UsrKindId::new(35, Some("vers"), None), |
| 218 | "tim1" => UsrKindId::new(40, Some("tim1"), None), |
| 219 | "tim2" => UsrKindId::new(50, Some("tim2"), None), |
| 220 | "refs" => UsrKindId::new(60, Some("refs"), None), |
| 221 | "alts" => UsrKindId::new(65, Some("alts"), None), |
| 222 | "lnks" => UsrKindId::new(70, Some("lnks"), None), |
| 223 | "tags" => UsrKindId::new(80, Some("tags"), None), |
| 224 | _ => return Err(err!( |
| 225 | "Unrecognised usr key '{}'.", label; |
| 226 | Input, Invalid, Unknown, Bug)), |
| 227 | }) |
| 228 | } |
| 229 | |
| 230 | pub fn load<P: AsRef<Path>>(path: P) -> Outcome<Self> { |
| 231 | let s = res!(std::fs::read_to_string(path.as_ref())); |
| 232 | let mut jdat_dec = DecoderConfig::<_, _>::jdat(Some(res!(Namex::<M1, M2>::ukinds()))); |
| 233 | // The registry preserves the order of entities and of each entity's attributes, so the |
| 234 | // decoder must retain the ordinals rather than collapsing maps to plain `Map`s. |
| 235 | jdat_dec.use_ordmaps = true; |
| 236 | let dat = res!(Dat::decode_string_with_config(s, &jdat_dec)); |
| 237 | if let Dat::OrdMap(map0) = dat { |
| 238 | let mut map1 = M1::default(); |
| 239 | let mut map2 = M2::default(); |
| 240 | for (mk, vdat) in map0 { |
| 241 | let kdat = mk.dat(); |
| 242 | let k = res!(NamexId::try_from(kdat)); |
| 243 | // Manually build Entity from datmap. |
| 244 | let mut entity = Entity::default(); |
| 245 | match res!(vdat.map_get_type( |
| 246 | &res!(Dat::try_from((res!(Self::usr_key("nams")), None))), |
| 247 | &[&Kind::List, &Kind::Vek], |
| 248 | )) { |
| 249 | Some(Dat::List(vecdat)) | Some(Dat::Vek(Vek(vecdat))) => { |
| 250 | let mut vecstr = Vec::new(); |
| 251 | for d in vecdat { |
| 252 | let s = try_extract_dat!(d, Str); |
| 253 | vecstr.push(s.clone()); |
| 254 | } |
| 255 | entity.nams = vecstr; |
| 256 | }, |
| 257 | _ => return Err(err!( |
| 258 | "The key {:?} is missing required attribute 'nams'.", kdat; |
| 259 | Input, Missing)), |
| 260 | } |
| 261 | match res!(vdat.map_get_type( |
| 262 | &res!(Dat::try_from((res!(Self::usr_key("lang")), None))), |
| 263 | &[&Kind::Str], |
| 264 | )) { |
| 265 | Some(Dat::Str(s)) => entity.lang = s.clone(), |
| 266 | _ => return Err(err!( |
| 267 | "The key {:?} is missing required attribute 'lang'.", kdat; |
| 268 | Input, Missing)), |
| 269 | } |
| 270 | if let Some(Dat::Str(s)) = res!(vdat.map_get_type( |
| 271 | &res!(Dat::try_from((res!(Self::usr_key("desc")), None))), |
| 272 | &[&Kind::Str], |
| 273 | )) { |
| 274 | entity.desc = Some(s.clone()); |
| 275 | } |
| 276 | if let Some(Dat::Str(s)) = res!(vdat.map_get_type( |
| 277 | &res!(Dat::try_from((res!(Self::usr_key("vers")), None))), |
| 278 | &[&Kind::Str], |
| 279 | )) { |
| 280 | entity.vers = Some(s.clone()); |
| 281 | } |
| 282 | match res!(vdat.map_get_type( |
| 283 | &res!(Dat::try_from((res!(Self::usr_key("tags")), None))), |
| 284 | &[&Kind::List, &Kind::Vek], |
| 285 | )) { |
| 286 | Some(Dat::List(vecdat)) | Some(Dat::Vek(Vek(vecdat))) => { |
| 287 | let mut vecstr = Vec::new(); |
| 288 | for d in vecdat { |
| 289 | let s = try_extract_dat!(d, Str); |
| 290 | vecstr.push(s.clone()); |
| 291 | } |
| 292 | entity.tags = Some(vecstr); |
| 293 | }, |
| 294 | _ => (), |
| 295 | } |
| 296 | if let Some(Dat::Str(s)) = res!(vdat.map_get_type( |
| 297 | &res!(Dat::try_from((res!(Self::usr_key("tim1")), None))), |
| 298 | &[&Kind::Str], |
| 299 | )) { |
| 300 | entity.tim1 = Some(s.clone()); // TODO validate date |
| 301 | } |
| 302 | if let Some(Dat::Str(s)) = res!(vdat.map_get_type( |
| 303 | &res!(Dat::try_from((res!(Self::usr_key("tim2")), None))), |
| 304 | &[&Kind::Str], |
| 305 | )) { |
| 306 | entity.tim2 = Some(s.clone()); // TODO validate date |
| 307 | } |
| 308 | match res!(vdat.map_get_type(&dat!("refs"), &[&Kind::List, &Kind::Vek])) { |
| 309 | Some(Dat::List(vecdat)) | Some(Dat::Vek(Vek(vecdat))) => { |
| 310 | let mut vecstr = Vec::new(); |
| 311 | for d in vecdat { |
| 312 | let s = try_extract_dat!(d, Str); |
| 313 | vecstr.push(s.clone()); |
| 314 | } |
| 315 | entity.tags = Some(vecstr); |
| 316 | }, |
| 317 | _ => (), |
| 318 | } |
| 319 | match res!(vdat.map_get_type( |
| 320 | &res!(Dat::try_from((res!(Self::usr_key("refs")), None))), |
| 321 | &[&Kind::List, &Kind::Vek], |
| 322 | )) { |
| 323 | Some(Dat::List(vecdat)) | Some(Dat::Vek(Vek(vecdat))) => { |
| 324 | let mut vecstr = Vec::new(); |
| 325 | for d in vecdat { |
| 326 | let s = try_extract_dat!(d, Str); |
| 327 | vecstr.push(s.clone()); |
| 328 | } |
| 329 | entity.refs = Some(vecstr); |
| 330 | }, |
| 331 | _ => (), |
| 332 | } |
| 333 | match res!(vdat.map_get_type( |
| 334 | &res!(Dat::try_from((res!(Self::usr_key("alts")), None))), |
| 335 | &[&Kind::OrdMap], |
| 336 | )) { |
| 337 | Some(Dat::OrdMap(datordmap)) => { |
| 338 | let mut altsmap = BTreeMap::new(); |
| 339 | for (mapkey, vdat2) in datordmap { |
| 340 | let k2 = try_extract_dat!(mapkey.dat(), Str); |
| 341 | let v2 = try_extract_dat!(vdat2, Str); |
| 342 | altsmap.insert(k2.clone(), v2.clone()); |
| 343 | } |
| 344 | entity.alts = Some(altsmap); |
| 345 | }, |
| 346 | _ => (), |
| 347 | } |
| 348 | match res!(vdat.map_get_type( |
| 349 | &res!(Dat::try_from((res!(Self::usr_key("lnks")), None))), |
| 350 | &[&Kind::OrdMap], |
| 351 | )) { |
| 352 | Some(Dat::OrdMap(datordmap)) => { |
| 353 | let mut linkmap = BTreeMap::new(); |
| 354 | for (mapkey, vdat2) in datordmap { |
| 355 | let mkdat = res!(NamexId::try_from(mapkey.dat())); |
| 356 | let v2 = try_extract_dat!(vdat2, Str); |
| 357 | linkmap.insert(mkdat.clone(), v2.clone()); |
| 358 | } |
| 359 | entity.lnks = Some(linkmap); |
| 360 | }, |
| 361 | _ => (), |
| 362 | } |
| 363 | map1.insert( |
| 364 | MapKey { |
| 365 | ord: mk.ord(), |
| 366 | id: NamexId::from(k.clone()), |
| 367 | }, |
| 368 | entity.clone(), |
| 369 | ); |
| 370 | map2.insert( |
| 371 | NamexId::from(k), |
| 372 | entity, |
| 373 | ); |
| 374 | } |
| 375 | Ok(Namex { |
| 376 | ordered: map1, |
| 377 | map: map2, |
| 378 | }) |
| 379 | } else { |
| 380 | Err(err!( |
| 381 | "Require a Daticle::Map, found a {}.", dat.kind(); |
| 382 | Invalid, Input)) |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | pub fn to_datmap(&self) -> Outcome<Dat> { |
| 387 | let mut map = BTreeMap::new(); |
| 388 | for (umk, v) in self.ordered.iter() { |
| 389 | map.insert( |
| 390 | oxedyne_fe2o3_jdat::map::MapKey::new(umk.ord(), dat!(umk.id().to_string())), |
| 391 | res!(v.to_datmap::<M1, M2>()), |
| 392 | ); |
| 393 | } |
| 394 | Ok(Dat::OrdMap(map)) |
| 395 | } |
| 396 | |
| 397 | pub fn export_jdat(&self) -> Outcome<Vec<String>> { |
| 398 | let dat = res!(self.to_datmap()); |
| 399 | let mut jdat_enc = EncoderConfig::<_, _>::jdat(Some(res!(Namex::<M1, M2>::ukinds()))); |
| 400 | jdat_enc.hide_usr_types = false; |
| 401 | let jdatstr = res!(dat.encode_string_with_config(&jdat_enc)); |
| 402 | let lines = Stringer::new(jdatstr).to_lines("\t"); |
| 403 | Ok(lines) |
| 404 | } |
| 405 | |
| 406 | pub fn export_json(&self) -> Outcome<Vec<String>> { |
| 407 | let mut dat = res!(self.to_datmap()); |
| 408 | // Scrub all strings in the daticle of escape characters. |
| 409 | res!(dat.normalise_string_values(" ")); |
| 410 | let mut json_enc = EncoderConfig::<_, _>::json(Some(res!(Namex::<M1, M2>::ukinds()))); |
| 411 | json_enc.byte_encoding = ByteEncoding::Base2x; |
| 412 | let jsonstr = res!(dat.encode_string_with_config(&json_enc)); |
| 413 | let lines = Stringer::new(jsonstr).to_lines("\t"); |
| 414 | Ok(lines) |
| 415 | } |
| 416 | |
| 417 | pub fn to_file( |
| 418 | &self, |
| 419 | lines: Vec<String>, |
| 420 | path: &str, |
| 421 | ) |
| 422 | -> Outcome<()> |
| 423 | { |
| 424 | let mut file = res!(std::fs::File::create(path)); |
| 425 | info!("Writing namex to file {}...", path); |
| 426 | for mut line in lines { |
| 427 | line.push_str("\n"); |
| 428 | res!(file.write(line.as_bytes())); |
| 429 | } |
| 430 | Ok(()) |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | //pub enum Relationship { |
| 435 | // Inheritance, // "is a" |
| 436 | // Composition, // "has a" |
| 437 | // Aggregation, // "is part of", "is contained in" |
| 438 | // Ownership, // "is owned by" |
| 439 | // Control, // "is controlled by" |
| 440 | // Association, // "is associated with" |
| 441 | // Dependency, // "depends on" |
| 442 | // Creation, // "was created by" |
| 443 | // Sibling, // "has the same creator as" |
| 444 | //} |