feat(engine): support AUDITCTL system variable and .adt log generation (gaps F4) (#1504)

* feat(engine): support AUDITCTL system variable and .adt log generation (gaps F4)

* audit: write the .adt log only beside a saved drawing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
aljormon84
2026-10-19 02:53:04 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent 148091aa93
commit 9f7a2422ea
7 changed files with 68 additions and 3 deletions
+6
View File
@@ -82,6 +82,12 @@ impl Report {
"message": self.message(),
})
}
/// Format this report for an AUDITCTL `.adt` log file.
pub fn format_adt(&self, title: &str, path: Option<&str>) -> String {
let name = path.unwrap_or(title);
format!("Audit of drawing: {name}\n\n{}\n", self.message())
}
}
/// Audit `d` without changing it.
+26 -1
View File
@@ -84,7 +84,11 @@ fn run_audit(s: &mut Session, p: &Value) -> Result<Value> {
if fix && !issues.is_empty() {
s.state_mut()?.doc = Arc::new(copy);
}
Ok(crate::audit::Report { issues, fixed: fix }.to_json())
let report = crate::audit::Report { issues, fixed: fix };
if s.settings.auditctl {
write_audit_log(s, &report);
}
Ok(report.to_json())
}
/// Open a drawing file as tolerantly as the readers allow, then fix what AUDIT finds. The
@@ -119,12 +123,33 @@ fn run_recover(s: &mut Session, p: &Value) -> Result<Value> {
st.saved = Arc::new((*st.doc).clone());
}
let uid = st.uid;
if s.settings.auditctl {
write_audit_log(s, &report);
}
let mut r = report.to_json();
r["index"] = json!(i);
r["uid"] = json!(uid);
Ok(r)
}
#[cfg(not(target_arch = "wasm32"))]
fn write_audit_log(s: &Session, report: &crate::audit::Report) {
let (path_str, title) = match s.state() {
Ok(st) => (st.path.clone(), st.title.clone()),
Err(_) => (None, "Drawing".to_string()),
};
// An unsaved drawing has no folder of its own to hold the log.
let Some(path) = &path_str else { return };
let adt_path = std::path::PathBuf::from(path).with_extension("adt");
let content = report.format_adt(&title, path_str.as_deref());
if let Err(e) = std::fs::write(&adt_path, content) {
log::warn!("AUDITCTL: could not write audit log {}: {e}", adt_path.display());
}
}
#[cfg(target_arch = "wasm32")]
fn write_audit_log(_: &Session, _: &crate::audit::Report) {}
#[cfg(not(target_arch = "wasm32"))]
fn read_path(cmd: &str, path: &str) -> Result<Vec<u8>> {
std::fs::read(path).map_err(|e| bad(cmd, format!("{path}: {e}")))
+3
View File
@@ -447,6 +447,8 @@ pub struct Settings {
pub polarsnap: bool,
/// POLARDIST: the PolarSnap step; 0 uses the snap X spacing.
pub polardist: f64,
/// AUDITCTL: 1 creates an audit report (.adt) file when AUDIT is run (default: 0).
pub auditctl: bool,
}
impl Default for Settings {
@@ -506,6 +508,7 @@ impl Default for Settings {
polaraddang: Vec::new(),
polarsnap: false,
polardist: 0.0,
auditctl: false,
}
}
}
+1
View File
@@ -7,6 +7,7 @@ use crate::{EngineError, Result, Session};
mod table;
const SESSION_VARS: &[&str] = &[
"AUDITCTL",
"OSMODE",
"OSNAPHATCH",
"ANNOAUTOSCALE",
+2 -1
View File
@@ -81,7 +81,8 @@ const LINEWEIGHTS: [i64; 24] = [0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53,
/// On/off session settings: name, read, write.
type Toggle = (&'static str, fn(&Settings) -> bool, fn(&mut Settings, bool));
const SETTINGS: &[Toggle] = &[("TRANSPARENCYDISPLAY", |s| s.transparency_display, |s, v| s.transparency_display = v)];
const SETTINGS: &[Toggle] =
&[("AUDITCTL", |s| s.auditctl, |s, v| s.auditctl = v), ("TRANSPARENCYDISPLAY", |s| s.transparency_display, |s, v| s.transparency_display = v)];
fn header_var(name: &str) -> Option<&'static (&'static str, Kind, Def)> {
HEADER.iter().find(|(n, _, _)| *n == name)
+28
View File
@@ -300,3 +300,31 @@ fn recover_opens_the_file_with_the_fixes_applied() {
assert_eq!(s.docs.len(), docs);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn auditctl_generates_adt_report_file() {
let mut s = Session::new();
// Default is 0 (off)
assert_eq!(s.execute("getvar", &json!({"name": "AUDITCTL"})).unwrap()["value"], json!(0));
s.execute("setvar", &json!({"name": "AUDITCTL", "value": 1})).unwrap();
assert_eq!(s.execute("getvar", &json!({"name": "AUDITCTL"})).unwrap()["value"], json!(1));
let dir = std::env::temp_dir().join(format!("cadcraft-auditctl-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let dxf_path = dir.join("drawing_test.dxf");
std::fs::write(&dxf_path, b"dummy").unwrap();
s.state_mut().unwrap().path = Some(dxf_path.to_string_lossy().to_string());
s.state_mut().unwrap().title = "drawing_test.dxf".to_string();
let r = s.execute("audit", &json!({"fix": false})).unwrap();
assert_eq!(r["errors"], 0);
let adt_path = dxf_path.with_extension("adt");
assert!(adt_path.exists(), "AUDITCTL 1 must generate .adt log file");
let content = std::fs::read_to_string(&adt_path).unwrap();
assert!(content.contains("Audit of drawing:"), "{content}");
assert!(content.contains("Audit: 0 errors found"), "{content}");
let _ = std::fs::remove_dir_all(&dir);
}
+2 -1
View File
@@ -27,7 +27,7 @@ do them first.
| F1 | **[alpha blocker]** Prompt options that still print "not available yet": -ATTEDIT one-at-a-time Position, Height, Angle and Style, colour books in -ATTEDIT Color, -RENAME of materials, plot styles and viewport configurations. SPLINEDIT Join, Edit vertex Add and Elevate order (#559), PEDIT Edit vertex and the other SPLINEDIT options (#558) done: Edit vertex Tangent directions last for the PEDIT session only (LWPOLYLINE stores none). MTEXT Dynamic/Static columns done (#547). SPLINE, MTEXT and MLEADER options done (#408): SPLINE Object fits the polyline's vertices (no spline frame is kept) (STRETCH polygon, CP and WP, and fence selection done; TRIM/EXTEND, PLINE arc mode and Length, XLINE done (#397); OFFSET, ROTATE and FILLET options done earlier) | `cmd/blocks/attedit.rs`, `cmd/props/rename.rs` | Low: the most-used commands are done | 1–3 |
| F2 | XREF editing in place and clipping: XCLIP is missing. REFEDIT, REFSET and REFCLOSE edit a block reference in place (working set, faded rest of the block, everything else locked, save or discard; #813); left: editing an xref in place (its source drawing is not written back; XOPEN opens it), picking nested references, a dimmed look for the locked drawing and a banner with Save and Discard, attribute sync of other references (ATTSYNC). XATTACH, the External References palette, -XREF (Bind/Detach/Path/Unload/Reload/Overlay), XBIND, XOPEN, overlays, relative paths and loading at open are done (#525). Unloaded state and xref-layer overrides last only for the session | `cmd/blocks/xref.rs` | Medium: xrefs can't be edited in place or clipped | 15–25 |
| F3 | **[alpha blocker]** (dynamic-block authoring is beta) No dynamic-block authoring: parameters and actions can't be added in the Block Editor. Dynamic blocks from other files are read and their references edited (visibility, distances, stretch and move, lookups, grips). BEDIT (with the Edit Block Definition dialog, which also starts new blocks), BSAVE, BSAVEAS and BCLOSE edit a static block definition, and every reference updates; REFEDIT edits a reference in place; no authoring palettes yet | issue #185 offers help | High: standard libraries are dynamic | 40–65 |
| F4 | Autosave leftovers: no autosave in the web build (browser storage); autosave files are DXF, not DWG; no AUDITCTL `.adt` log and no separate RECOVER mode in the file readers (SAVETIME, Drawing Recovery, AUDIT and RECOVER landed with #406) | `engine/src/autosave.rs`, `engine/src/audit.rs` | Low-medium (web users) | 3–5 |
| F4 | Autosave leftovers: no autosave in the web build (browser storage); autosave files are DXF, not DWG; no separate RECOVER mode in the file readers (SAVETIME, Drawing Recovery, AUDIT and RECOVER landed with #406; AUDITCTL `.adt` log landed) | `engine/src/autosave.rs`, `engine/src/audit.rs` | Low-medium (web users) | 2–4 |
| F5 | Associative arrays (ASsociative, Levels, polar ROWs, ARRAYEDIT, ARRAYCLOSE) landed (#448); left: grip editing of arrays (count/spacing grips), ARRAYPATH is not associative, ARRAYEDIT has no item deletion (CTRL+click) and no Key point base points, levels only shift Z (no 3D view) | `cmd/array.rs`, `cmd/array/edit.rs` | Medium | 4–6 |
| F7 | Dimensions: no DIMSTYLE manager dialog; no smart DIM; DIMTFILL/DIMTFILLCLR, DIMALTTD/DIMALTTZ, DIMUPT and DIMTXTDIRECTION not modelled; jog height and break size not style settings; DIMBREAK breaks don't follow moved objects (style variables, DIMJOGGED, DIMJOGLINE, DIMBREAK, DIMEDIT, DIMINSPECT and QDIM modes done, #507) | `doc/src/tables.rs`, `cmd/annotate_dimedit.rs`, `cmd/annotate_qdim.rs` | Medium | 10–15 |
| F8 | TOLERANCE leftovers (feature control frames landed, #420: model, our own symbol drawing, DXF, command-line prompts, explode, grips): no Geometric Tolerance dialog, no LEADER/QLEADER tolerance annotation, no in-place editing of a frame's contents | `cmd/tolerance.rs` | Low-medium (mechanical) | 2–4 |
@@ -156,6 +156,7 @@ Detail: [localization-parity.md](localization-parity.md).
| 2026-10-12 | minor | Property-based and hostile-input tests for geometry numerics landed (deterministic xorshift generator, no new dependency; known answers at 1e6–1e7 coordinates): S6 removed |
| 2026-10-12 | trivial | F13: HATCHEDIT refuses a bad scale or angle before editing (#724) |
| 2026-10-12 | minor | F3: dynamic block references show their grips (distance arrows, position squares, visibility/lookup lists, per visibility state) and drag them (`blocks.reference.grips`, `blocks.reference.grip`) |
| 2026-10-17 | minor | AUDITCTL system variable and .adt log generation in AUDIT/RECOVER landed: F4 narrowed (no .adt log removed) |
| 2026-10-12 | minor | Multiline attributes read, drawn and saved (they lost their text before): F17 narrowed to creating them |
| 2026-10-13 | minor | F3: opening a dynamic block's own drawing (authoring elements in model space) offers the Block Editor on `<Current Drawing>`, as AutoCAD does |
| 2026-10-12 | minor | Edit Block Definition dialog (block list, preview, description; a new name starts a new block): F3 text updated, hours unchanged |