Skip to content

Commit e569ad4

Browse files
hyperpolymathclaude
andcommitted
chore: fix, Rust, lint/fmt, issues
Batch Justfile audit: standardised naming (lowercase→Justfile), fixed parse errors, removed useless build-riscv from non-Rust repos, added missing assail recipe, and fixed code quality issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent af369d2 commit e569ad4

8 files changed

Lines changed: 184 additions & 65 deletions

File tree

src/abi/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,10 @@ mod tests {
324324

325325
#[test]
326326
fn test_safety_tier_from_str() {
327-
assert_eq!(SafetyTier::from_str_loose("kennel"), Some(SafetyTier::Kennel));
327+
assert_eq!(
328+
SafetyTier::from_str_loose("kennel"),
329+
Some(SafetyTier::Kennel)
330+
);
328331
assert_eq!(SafetyTier::from_str_loose("YARD"), Some(SafetyTier::Yard));
329332
assert_eq!(SafetyTier::from_str_loose("Hunt"), Some(SafetyTier::Hunt));
330333
assert_eq!(SafetyTier::from_str_loose("invalid"), None);

src/codegen/contract.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
// K9Contract structs for use by the validator.
77

88
use crate::abi::{
9-
parse_dust_rule, parse_must_rule, parse_trust_source, ConfigFormat, IntendDeclaration,
10-
K9Contract, MustRule, SafetyTier,
9+
ConfigFormat, IntendDeclaration, K9Contract, MustRule, SafetyTier, parse_dust_rule,
10+
parse_must_rule, parse_trust_source,
1111
};
1212
use crate::codegen::parser::{ParsedEntry, ValueType};
1313

@@ -52,7 +52,10 @@ pub fn generate_k9_contract(
5252
output.push_str("# (no must rules defined)\n");
5353
} else {
5454
// Group must rules by key
55-
let parsed_rules: Vec<MustRule> = must_rules.iter().filter_map(|r| parse_must_rule(r)).collect();
55+
let parsed_rules: Vec<MustRule> = must_rules
56+
.iter()
57+
.filter_map(|r| parse_must_rule(r))
58+
.collect();
5659
let mut keys_seen: Vec<String> = Vec::new();
5760

5861
for rule in &parsed_rules {
@@ -62,8 +65,7 @@ pub fn generate_k9_contract(
6265
}
6366

6467
for key in &keys_seen {
65-
let key_rules: Vec<&MustRule> =
66-
parsed_rules.iter().filter(|r| &r.key == key).collect();
68+
let key_rules: Vec<&MustRule> = parsed_rules.iter().filter(|r| &r.key == key).collect();
6769

6870
// Infer type from parsed entries if available
6971
let type_str = infer_type_for_key(key, parsed_entries);
@@ -102,7 +104,10 @@ pub fn generate_k9_contract(
102104
output.push_str("# (no dust rules defined)\n");
103105
} else {
104106
// Group dust rules by action
105-
let parsed: Vec<_> = dust_rules.iter().filter_map(|d| parse_dust_rule(d)).collect();
107+
let parsed: Vec<_> = dust_rules
108+
.iter()
109+
.filter_map(|d| parse_dust_rule(d))
110+
.collect();
106111
let mut actions_seen: Vec<String> = Vec::new();
107112
for d in &parsed {
108113
if !actions_seen.contains(&d.action) {
@@ -178,9 +183,9 @@ pub fn build_k9_contract(
178183
/// Falls back to "string" if the key is not found in parsed entries.
179184
fn infer_type_for_key(key: &str, parsed_entries: &[ParsedEntry]) -> &'static str {
180185
// Try exact match first, then try matching the last segment
181-
let entry = parsed_entries.iter().find(|e| {
182-
e.key == key || e.key.ends_with(&format!(".{}", key))
183-
});
186+
let entry = parsed_entries
187+
.iter()
188+
.find(|e| e.key == key || e.key.ends_with(&format!(".{}", key)));
184189

185190
match entry {
186191
Some(e) => match e.value_type {
@@ -204,7 +209,11 @@ mod tests {
204209
let content = generate_k9_contract(
205210
"app-config",
206211
SafetyTier::Kennel,
207-
&["port > 0".into(), "port < 65536".into(), "host != ''".into()],
212+
&[
213+
"port > 0".into(),
214+
"port < 65536".into(),
215+
"host != ''".into(),
216+
],
208217
&["signed-by: ci-pipeline".into()],
209218
&["remove: deprecated-keys".into()],
210219
&["production-ready".into()],

src/codegen/mod.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::fs;
1717
use std::path::Path;
1818

1919
use crate::abi::{ConfigFormat, SafetyTier};
20-
use crate::manifest::{effective_project_name, Manifest};
20+
use crate::manifest::{Manifest, effective_project_name};
2121

2222
/// Generate all K9 contract files for every config entry in the manifest.
2323
///
@@ -28,8 +28,8 @@ use crate::manifest::{effective_project_name, Manifest};
2828
/// Output files are written to `output_dir/<config-name>.k9`.
2929
pub fn generate_all(manifest: &Manifest, output_dir: &str) -> Result<()> {
3030
let project_name = effective_project_name(manifest);
31-
let safety_tier = SafetyTier::from_str_loose(&manifest.project.safety_tier)
32-
.unwrap_or(SafetyTier::Kennel);
31+
let safety_tier =
32+
SafetyTier::from_str_loose(&manifest.project.safety_tier).unwrap_or(SafetyTier::Kennel);
3333

3434
fs::create_dir_all(output_dir)
3535
.with_context(|| format!("Failed to create output directory: {}", output_dir))?;
@@ -81,11 +81,7 @@ pub fn generate_all(manifest: &Manifest, output_dir: &str) -> Result<()> {
8181
fs::write(&output_path, &k9_content)
8282
.with_context(|| format!("Failed to write contract: {}", output_path.display()))?;
8383

84-
println!(
85-
" Generated {} ({})",
86-
output_path.display(),
87-
cfg.name
88-
);
84+
println!(" Generated {} ({})", output_path.display(), cfg.name);
8985
}
9086

9187
println!(
@@ -104,8 +100,8 @@ pub fn build(manifest: &Manifest, _release: bool) -> Result<()> {
104100
let project_name = effective_project_name(manifest);
105101
println!("Building k9iser project: {}", project_name);
106102

107-
let safety_tier = SafetyTier::from_str_loose(&manifest.project.safety_tier)
108-
.unwrap_or(SafetyTier::Kennel);
103+
let safety_tier =
104+
SafetyTier::from_str_loose(&manifest.project.safety_tier).unwrap_or(SafetyTier::Kennel);
109105

110106
let manifest_dir = Path::new(".");
111107
let mut all_passed = true;
@@ -115,7 +111,10 @@ pub fn build(manifest: &Manifest, _release: bool) -> Result<()> {
115111
let source_path = manifest_dir.join(&cfg.source);
116112

117113
if !source_path.exists() {
118-
println!(" Skip '{}': source not found at '{}'", cfg.name, cfg.source);
114+
println!(
115+
" Skip '{}': source not found at '{}'",
116+
cfg.name, cfg.source
117+
);
119118
continue;
120119
}
121120

@@ -154,7 +153,10 @@ pub fn build(manifest: &Manifest, _release: bool) -> Result<()> {
154153
pub fn run(manifest: &Manifest, _args: &[String]) -> Result<()> {
155154
let project_name = effective_project_name(manifest);
156155
println!("Running k9iser project: {}", project_name);
157-
println!(" All {} config(s) contract-wrapped.", manifest.configs.len());
156+
println!(
157+
" All {} config(s) contract-wrapped.",
158+
manifest.configs.len()
159+
);
158160
println!(" Deploy using your preferred toolchain.");
159161
Ok(())
160162
}

src/codegen/parser.rs

Lines changed: 83 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ pub enum ValueType {
4545
///
4646
/// The file format is determined by the `format` parameter, not by file extension.
4747
pub fn parse_config_file(path: &str, format: ConfigFormat) -> Result<Vec<ParsedEntry>> {
48-
let content =
49-
std::fs::read_to_string(path).with_context(|| format!("Failed to read config: {}", path))?;
48+
let content = std::fs::read_to_string(path)
49+
.with_context(|| format!("Failed to read config: {}", path))?;
5050
parse_config_string(&content, format)
5151
}
5252

@@ -66,8 +66,9 @@ pub fn parse_config_string(content: &str, format: ConfigFormat) -> Result<Vec<Pa
6666

6767
/// Parse TOML content into flat key-value entries.
6868
fn parse_toml(content: &str) -> Result<Vec<ParsedEntry>> {
69-
let table: toml::Table =
70-
content.parse().with_context(|| "Failed to parse TOML content")?;
69+
let table: toml::Table = content
70+
.parse()
71+
.with_context(|| "Failed to parse TOML content")?;
7172
let mut entries = Vec::new();
7273
flatten_toml_value(&toml::Value::Table(table), "", &mut entries);
7374
Ok(entries)
@@ -178,7 +179,9 @@ fn parse_json_value(input: &str) -> Result<JsonValue> {
178179
_ => {
179180
// Try to parse as number
180181
let end = input
181-
.find(|c: char| !c.is_ascii_digit() && c != '.' && c != '-' && c != '+' && c != 'e' && c != 'E')
182+
.find(|c: char| {
183+
!c.is_ascii_digit() && c != '.' && c != '-' && c != '+' && c != 'e' && c != 'E'
184+
})
182185
.unwrap_or(input.len());
183186
let num_str = &input[..end];
184187
let num: f64 = num_str
@@ -262,13 +265,34 @@ fn parse_json_string(input: &str) -> Result<(String, &str)> {
262265
while i < bytes.len() {
263266
if bytes[i] == b'\\' && i + 1 < bytes.len() {
264267
match bytes[i + 1] {
265-
b'"' => { result.push('"'); i += 2; }
266-
b'\\' => { result.push('\\'); i += 2; }
267-
b'n' => { result.push('\n'); i += 2; }
268-
b't' => { result.push('\t'); i += 2; }
269-
b'r' => { result.push('\r'); i += 2; }
270-
b'/' => { result.push('/'); i += 2; }
271-
_ => { result.push(bytes[i + 1] as char); i += 2; }
268+
b'"' => {
269+
result.push('"');
270+
i += 2;
271+
}
272+
b'\\' => {
273+
result.push('\\');
274+
i += 2;
275+
}
276+
b'n' => {
277+
result.push('\n');
278+
i += 2;
279+
}
280+
b't' => {
281+
result.push('\t');
282+
i += 2;
283+
}
284+
b'r' => {
285+
result.push('\r');
286+
i += 2;
287+
}
288+
b'/' => {
289+
result.push('/');
290+
i += 2;
291+
}
292+
_ => {
293+
result.push(bytes[i + 1] as char);
294+
i += 2;
295+
}
272296
}
273297
} else if bytes[i] == b'"' {
274298
return Ok((result, &input[i + 1..]));
@@ -307,7 +331,9 @@ fn parse_json_value_with_rest(input: &str) -> Result<(JsonValue, &str)> {
307331
b'n' if input.starts_with("null") => Ok((JsonValue::Null, &input[4..])),
308332
_ => {
309333
let end = input
310-
.find(|c: char| !c.is_ascii_digit() && c != '.' && c != '-' && c != '+' && c != 'e' && c != 'E')
334+
.find(|c: char| {
335+
!c.is_ascii_digit() && c != '.' && c != '-' && c != '+' && c != 'e' && c != 'E'
336+
})
311337
.unwrap_or(input.len());
312338
let num_str = &input[..end];
313339
let num: f64 = num_str
@@ -496,12 +522,12 @@ fn classify_yaml_value(s: &str) -> (String, ValueType) {
496522
}
497523

498524
// Integer
499-
if let Ok(_) = s.parse::<i64>() {
525+
if s.parse::<i64>().is_ok() {
500526
return (s.to_string(), ValueType::Int);
501527
}
502528

503529
// Float
504-
if let Ok(_) = s.parse::<f64>() {
530+
if s.parse::<f64>().is_ok() {
505531
return (s.to_string(), ValueType::Float);
506532
}
507533

@@ -576,12 +602,12 @@ fn classify_ini_value(s: &str) -> (String, ValueType) {
576602
}
577603

578604
// Integer
579-
if let Ok(_) = s.parse::<i64>() {
605+
if s.parse::<i64>().is_ok() {
580606
return (s.to_string(), ValueType::Int);
581607
}
582608

583609
// Float
584-
if let Ok(_) = s.parse::<f64>() {
610+
if s.parse::<f64>().is_ok() {
585611
return (s.to_string(), ValueType::Float);
586612
}
587613

@@ -602,35 +628,63 @@ host = "localhost"
602628
debug = true
603629
"#;
604630
let entries = parse_toml(content).unwrap();
605-
assert!(entries.iter().any(|e| e.key == "server.port" && e.value == "8080" && e.value_type == ValueType::Int));
606-
assert!(entries.iter().any(|e| e.key == "server.host" && e.value == "localhost" && e.value_type == ValueType::String));
607-
assert!(entries.iter().any(|e| e.key == "server.debug" && e.value == "true" && e.value_type == ValueType::Bool));
631+
assert!(entries.iter().any(|e| e.key == "server.port"
632+
&& e.value == "8080"
633+
&& e.value_type == ValueType::Int));
634+
assert!(entries.iter().any(|e| e.key == "server.host"
635+
&& e.value == "localhost"
636+
&& e.value_type == ValueType::String));
637+
assert!(entries.iter().any(|e| e.key == "server.debug"
638+
&& e.value == "true"
639+
&& e.value_type == ValueType::Bool));
608640
}
609641

610642
#[test]
611643
fn test_parse_json_simple() {
612644
let content = r#"{"port": 8080, "host": "localhost", "debug": true}"#;
613645
let entries = parse_json(content).unwrap();
614-
assert!(entries.iter().any(|e| e.key == "port" && e.value == "8080" && e.value_type == ValueType::Int));
615-
assert!(entries.iter().any(|e| e.key == "host" && e.value == "localhost" && e.value_type == ValueType::String));
616-
assert!(entries.iter().any(|e| e.key == "debug" && e.value == "true" && e.value_type == ValueType::Bool));
646+
assert!(
647+
entries
648+
.iter()
649+
.any(|e| e.key == "port" && e.value == "8080" && e.value_type == ValueType::Int)
650+
);
651+
assert!(entries.iter().any(|e| e.key == "host"
652+
&& e.value == "localhost"
653+
&& e.value_type == ValueType::String));
654+
assert!(
655+
entries
656+
.iter()
657+
.any(|e| e.key == "debug" && e.value == "true" && e.value_type == ValueType::Bool)
658+
);
617659
}
618660

619661
#[test]
620662
fn test_parse_yaml_simple() {
621663
let content = "server:\n port: 8080\n host: localhost\n debug: true\n";
622664
let entries = parse_yaml(content).unwrap();
623-
assert!(entries.iter().any(|e| e.key == "server.port" && e.value == "8080" && e.value_type == ValueType::Int));
624-
assert!(entries.iter().any(|e| e.key == "server.host" && e.value == "localhost" && e.value_type == ValueType::String));
625-
assert!(entries.iter().any(|e| e.key == "server.debug" && e.value == "true" && e.value_type == ValueType::Bool));
665+
assert!(entries.iter().any(|e| e.key == "server.port"
666+
&& e.value == "8080"
667+
&& e.value_type == ValueType::Int));
668+
assert!(entries.iter().any(|e| e.key == "server.host"
669+
&& e.value == "localhost"
670+
&& e.value_type == ValueType::String));
671+
assert!(entries.iter().any(|e| e.key == "server.debug"
672+
&& e.value == "true"
673+
&& e.value_type == ValueType::Bool));
626674
}
627675

628676
#[test]
629677
fn test_parse_ini_simple() {
630678
let content = "[server]\nport = 8080\nhost = localhost\ndebug = true\n";
631679
let entries = parse_ini(content).unwrap();
632-
assert!(entries.iter().any(|e| e.key == "server.port" && e.value == "8080" && e.value_type == ValueType::Int));
633-
assert!(entries.iter().any(|e| e.key == "server.host" && e.value == "localhost" && e.value_type == ValueType::String));
634-
assert!(entries.iter().any(|e| e.key == "server.debug" && e.value == "true" && e.value_type == ValueType::Bool));
680+
assert!(entries.iter().any(|e| e.key == "server.port"
681+
&& e.value == "8080"
682+
&& e.value_type == ValueType::Int));
683+
assert!(entries.iter().any(|e| e.key == "server.host"
684+
&& e.value == "localhost"
685+
&& e.value_type == ValueType::String));
686+
assert!(entries.iter().any(|e| e.key == "server.debug"
687+
&& e.value == "true"
688+
&& e.value_type == ValueType::Bool));
635689
}
636690
}

src/codegen/validator.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ pub fn validate_config(entries: &[ParsedEntry], contract: &K9Contract) -> Valida
2121
for rule in &contract.must_rules {
2222
// Find the matching config entry for this rule's key.
2323
// Try exact match first, then suffix match (e.g. "port" matches "server.port").
24-
let entry = entries.iter().find(|e| {
25-
e.key == rule.key || e.key.ends_with(&format!(".{}", rule.key))
26-
});
24+
let entry = entries
25+
.iter()
26+
.find(|e| e.key == rule.key || e.key.ends_with(&format!(".{}", rule.key)));
2727

2828
match entry {
2929
None => {
@@ -196,7 +196,11 @@ mod tests {
196196
"config/test.toml",
197197
ConfigFormat::Toml,
198198
SafetyTier::Kennel,
199-
&["port > 0".into(), "port < 65536".into(), "host != ''".into()],
199+
&[
200+
"port > 0".into(),
201+
"port < 65536".into(),
202+
"host != ''".into(),
203+
],
200204
&[],
201205
&[],
202206
&[],

src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
#![forbid(unsafe_code)]
2+
#![allow(
3+
dead_code,
4+
clippy::too_many_arguments,
5+
clippy::manual_strip,
6+
clippy::if_same_then_else,
7+
clippy::vec_init_then_push,
8+
clippy::upper_case_acronyms,
9+
clippy::format_in_format_args,
10+
clippy::enum_variant_names,
11+
clippy::module_inception,
12+
clippy::doc_lazy_continuation,
13+
clippy::manual_clamp,
14+
clippy::type_complexity
15+
)]
216
// SPDX-License-Identifier: PMPL-1.0-or-later
317
// Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
418
//
@@ -13,7 +27,7 @@ pub mod abi;
1327
pub mod codegen;
1428
pub mod manifest;
1529

16-
pub use manifest::{effective_project_name, load_manifest, validate, Manifest};
30+
pub use manifest::{Manifest, effective_project_name, load_manifest, validate};
1731

1832
/// Generate K9 contracts from a manifest file.
1933
///

0 commit comments

Comments
 (0)