1#![forbid(unsafe_op_in_unsafe_fn)]
7#![deny(missing_docs)]
8#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
9#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
10#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
11#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
12
13use adze_bdd_grid_core::{BddPhase, BddScenario, bdd_progress};
14use adze_feature_policy_core::{ParserBackend, ParserFeatureProfile};
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
30pub struct ParserFeatureProfileSnapshot {
31 pub pure_rust: bool,
33 pub tree_sitter_standard: bool,
35 pub tree_sitter_c2rust: bool,
37 pub glr: bool,
39}
40
41impl ParserFeatureProfileSnapshot {
42 #[must_use]
55 pub const fn new(
56 pure_rust: bool,
57 tree_sitter_standard: bool,
58 tree_sitter_c2rust: bool,
59 glr: bool,
60 ) -> Self {
61 Self {
62 pure_rust,
63 tree_sitter_standard,
64 tree_sitter_c2rust,
65 glr,
66 }
67 }
68
69 #[must_use]
86 pub const fn from_profile(profile: ParserFeatureProfile) -> Self {
87 Self {
88 pure_rust: profile.pure_rust,
89 tree_sitter_standard: profile.tree_sitter_standard,
90 tree_sitter_c2rust: profile.tree_sitter_c2rust,
91 glr: profile.glr,
92 }
93 }
94
95 #[must_use]
97 pub const fn as_profile(self) -> ParserFeatureProfile {
98 ParserFeatureProfile {
99 pure_rust: self.pure_rust,
100 tree_sitter_standard: self.tree_sitter_standard,
101 tree_sitter_c2rust: self.tree_sitter_c2rust,
102 glr: self.glr,
103 }
104 }
105
106 #[must_use]
108 pub fn from_env() -> Self {
109 Self {
110 pure_rust: env_flag(&["CARGO_FEATURE_PURE_RUST", "ADZE_USE_PURE_RUST"]),
111 tree_sitter_standard: env_flag(&["CARGO_FEATURE_TREE_SITTER_STANDARD"]),
112 tree_sitter_c2rust: env_flag(&["CARGO_FEATURE_TREE_SITTER_C2RUST"]),
113 glr: env_flag(&["CARGO_FEATURE_GLR"]),
114 }
115 }
116
117 #[must_use]
119 pub const fn non_conflict_backend(self) -> &'static str {
120 if self.glr {
121 ParserBackend::GLR.name()
122 } else if self.pure_rust {
123 ParserBackend::PureRust.name()
124 } else {
125 ParserBackend::TreeSitter.name()
126 }
127 }
128
129 #[must_use]
131 pub const fn resolve_non_conflict_backend(self) -> ParserBackend {
132 self.as_profile().resolve_backend(false)
133 }
134
135 #[must_use]
137 pub const fn resolve_conflict_backend(self) -> ParserBackend {
138 self.as_profile().resolve_backend(true)
139 }
140}
141
142fn env_flag(names: &[&str]) -> bool {
143 names.iter().any(|name| std::env::var(name).is_ok())
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
159pub struct GovernanceMetadata {
160 pub phase: String,
162 pub implemented: usize,
164 pub total: usize,
166 pub status_line: String,
168}
169
170impl GovernanceMetadata {
171 #[must_use]
185 pub fn is_complete(&self) -> bool {
186 self.total > 0 && self.implemented == self.total
187 }
188
189 #[must_use]
201 pub fn with_counts(
202 phase: impl Into<String>,
203 implemented: usize,
204 total: usize,
205 status_line: impl Into<String>,
206 ) -> Self {
207 Self {
208 phase: phase.into(),
209 implemented,
210 total,
211 status_line: status_line.into(),
212 }
213 }
214
215 #[must_use]
217 pub fn for_grid(
218 phase: BddPhase,
219 scenarios: &[BddScenario],
220 profile: ParserFeatureProfile,
221 ) -> Self {
222 let (implemented, total) = bdd_progress(phase, scenarios);
223 Self {
224 phase: phase_name(phase).to_string(),
225 implemented,
226 total,
227 status_line: status_line(phase, implemented, total, profile),
228 }
229 }
230}
231
232impl Default for GovernanceMetadata {
233 fn default() -> Self {
234 Self {
235 phase: "runtime".to_string(),
236 implemented: 0,
237 total: 0,
238 status_line: "runtime:0/0".to_string(),
239 }
240 }
241}
242
243fn phase_name(phase: BddPhase) -> &'static str {
244 match phase {
245 BddPhase::Core => "core",
246 BddPhase::Runtime => "runtime",
247 }
248}
249
250fn status_line(
251 phase: BddPhase,
252 implemented: usize,
253 total: usize,
254 profile: ParserFeatureProfile,
255) -> String {
256 let backend = profile.resolve_backend(false).name();
257 let phase_label = phase_name(phase);
258 format!("{phase_label}:{implemented}/{total}:{backend}:{profile}")
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn profile_snapshot_new() {
267 let snap = ParserFeatureProfileSnapshot::new(true, false, true, false);
268 assert!(snap.pure_rust);
269 assert!(!snap.tree_sitter_standard);
270 assert!(snap.tree_sitter_c2rust);
271 assert!(!snap.glr);
272 }
273
274 #[test]
275 fn profile_snapshot_roundtrip_via_profile() {
276 let snap = ParserFeatureProfileSnapshot::new(false, true, false, true);
277 let profile = snap.as_profile();
278 let snap2 = ParserFeatureProfileSnapshot::from_profile(profile);
279 assert_eq!(snap, snap2);
280 }
281
282 #[test]
283 fn profile_snapshot_serde_roundtrip() {
284 let snap = ParserFeatureProfileSnapshot::new(true, false, true, true);
285 let json = serde_json::to_string(&snap).unwrap();
286 let deserialized: ParserFeatureProfileSnapshot = serde_json::from_str(&json).unwrap();
287 assert_eq!(snap, deserialized);
288 }
289
290 #[test]
291 fn non_conflict_backend_name_is_non_empty() {
292 let snap = ParserFeatureProfileSnapshot::new(false, false, false, false);
293 assert!(!snap.non_conflict_backend().is_empty());
294 }
295
296 #[test]
297 fn governance_metadata_default() {
298 let meta = GovernanceMetadata::default();
299 assert_eq!(meta.phase, "runtime");
300 assert_eq!(meta.implemented, 0);
301 assert_eq!(meta.total, 0);
302 assert!(!meta.is_complete());
303 }
304
305 #[test]
306 fn governance_metadata_with_counts() {
307 let meta = GovernanceMetadata::with_counts("core", 5, 10, "core:5/10");
308 assert_eq!(meta.implemented, 5);
309 assert_eq!(meta.total, 10);
310 assert!(!meta.is_complete());
311 }
312
313 #[test]
314 fn governance_metadata_complete() {
315 let meta = GovernanceMetadata::with_counts("core", 8, 8, "done");
316 assert!(meta.is_complete());
317 }
318
319 #[test]
320 fn governance_metadata_serde_roundtrip() {
321 let meta = GovernanceMetadata::with_counts("runtime", 3, 7, "runtime:3/7");
322 let json = serde_json::to_string(&meta).unwrap();
323 let deserialized: GovernanceMetadata = serde_json::from_str(&json).unwrap();
324 assert_eq!(meta, deserialized);
325 }
326
327 #[test]
328 fn governance_metadata_for_grid() {
329 use adze_bdd_grid_core::BddScenarioStatus;
330 let scenarios = [BddScenario {
331 id: 1,
332 title: "test",
333 reference: "T-1",
334 core_status: BddScenarioStatus::Implemented,
335 runtime_status: BddScenarioStatus::Deferred { reason: "wip" },
336 }];
337 let profile = ParserFeatureProfile::current();
338 let meta = GovernanceMetadata::for_grid(BddPhase::Core, &scenarios, profile);
339 assert_eq!(meta.phase, "core");
340 assert_eq!(meta.total, 1);
341 assert_eq!(meta.implemented, 1);
342 assert!(!meta.status_line.is_empty());
343 }
344
345 #[test]
346 fn non_conflict_backend_glr_profile() {
347 let snap = ParserFeatureProfileSnapshot::new(true, false, false, true);
348 assert_eq!(snap.non_conflict_backend(), ParserBackend::GLR.name());
349 }
350
351 #[test]
352 fn non_conflict_backend_pure_rust_profile() {
353 let snap = ParserFeatureProfileSnapshot::new(true, false, false, false);
354 assert_eq!(snap.non_conflict_backend(), ParserBackend::PureRust.name());
355 }
356
357 #[test]
358 fn non_conflict_backend_tree_sitter_fallback() {
359 let snap = ParserFeatureProfileSnapshot::new(false, true, false, false);
360 assert_eq!(
361 snap.non_conflict_backend(),
362 ParserBackend::TreeSitter.name()
363 );
364 }
365
366 #[test]
367 fn resolve_non_conflict_and_conflict_backend() {
368 let snap = ParserFeatureProfileSnapshot::new(true, false, false, true);
369 let non_conflict = snap.resolve_non_conflict_backend();
370 let conflict = snap.resolve_conflict_backend();
371 assert_eq!(non_conflict, snap.as_profile().resolve_backend(false));
372 assert_eq!(conflict, snap.as_profile().resolve_backend(true));
373 }
374
375 #[test]
376 fn profile_snapshot_debug_format() {
377 let snap = ParserFeatureProfileSnapshot::new(true, false, true, false);
378 let debug = format!("{:?}", snap);
379 assert!(debug.contains("ParserFeatureProfileSnapshot"));
380 assert!(debug.contains("pure_rust: true"));
381 }
382
383 #[test]
384 fn profile_snapshot_hash_consistency() {
385 use std::collections::HashSet;
386 let a = ParserFeatureProfileSnapshot::new(true, false, true, false);
387 let b = ParserFeatureProfileSnapshot::new(true, false, true, false);
388 let c = ParserFeatureProfileSnapshot::new(false, false, true, false);
389 let mut set = HashSet::new();
390 set.insert(a);
391 set.insert(b);
392 set.insert(c);
393 assert_eq!(set.len(), 2);
394 }
395
396 #[test]
397 fn profile_snapshot_copy_semantics() {
398 let snap = ParserFeatureProfileSnapshot::new(true, false, true, false);
399 let copied = snap;
400 assert_eq!(snap, copied);
401 }
402
403 #[test]
404 fn from_env_with_no_vars() {
405 let snap = ParserFeatureProfileSnapshot::from_env();
408 let _ = snap.non_conflict_backend();
409 }
411
412 #[test]
413 fn governance_metadata_clone_eq() {
414 let meta = GovernanceMetadata::with_counts("core", 3, 5, "core:3/5");
415 let cloned = meta.clone();
416 assert_eq!(meta, cloned);
417 }
418
419 #[test]
420 fn governance_metadata_for_grid_runtime_phase() {
421 use adze_bdd_grid_core::BddScenarioStatus;
422 let scenarios = [BddScenario {
423 id: 1,
424 title: "test",
425 reference: "T-1",
426 core_status: BddScenarioStatus::Deferred { reason: "later" },
427 runtime_status: BddScenarioStatus::Implemented,
428 }];
429 let profile = ParserFeatureProfile::current();
430 let meta = GovernanceMetadata::for_grid(BddPhase::Runtime, &scenarios, profile);
431 assert_eq!(meta.phase, "runtime");
432 assert_eq!(meta.implemented, 1);
433 assert_eq!(meta.total, 1);
434 }
435
436 #[test]
437 fn governance_metadata_for_grid_empty_scenarios() {
438 let profile = ParserFeatureProfile::current();
439 let meta = GovernanceMetadata::for_grid(BddPhase::Core, &[], profile);
440 assert_eq!(meta.implemented, 0);
441 assert_eq!(meta.total, 0);
442 }
443
444 #[test]
445 fn governance_metadata_debug_format() {
446 let meta = GovernanceMetadata::with_counts("core", 1, 2, "core:1/2");
447 let debug = format!("{:?}", meta);
448 assert!(debug.contains("GovernanceMetadata"));
449 assert!(debug.contains("core"));
450 }
451}