update
This commit is contained in:
parent
5b2fdd380a
commit
d9e9ce628c
|
|
@ -223,6 +223,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.11.0"
|
||||
|
|
@ -466,7 +472,9 @@ dependencies = [
|
|||
"ratatui",
|
||||
"regex",
|
||||
"rsn",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"stringlit",
|
||||
"toml",
|
||||
|
|
@ -495,6 +503,31 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"indexmap",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
|
|
@ -521,6 +554,29 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ indexmap = { version = "2.2.6", features = ["serde"] }
|
|||
ratatui = "0.26.2"
|
||||
regex = "1.10.4"
|
||||
rsn = "0.1.0"
|
||||
schemars = { version = "0.8.19", features = ["indexmap2"] }
|
||||
serde = { version = "1.0.201", features = ["derive"] }
|
||||
serde_json = { version = "1.0.117", features = ["indexmap", "preserve_order"] }
|
||||
serde_yaml = "0.9.34"
|
||||
stringlit = "2.1.0"
|
||||
toml = { version = "0.8.12", features = ["indexmap", "preserve_order"] }
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use indexmap::{
|
||||
map::{Keys, Values},
|
||||
IndexMap,
|
||||
};
|
||||
use regex::Regex;
|
||||
use req::*;
|
||||
use stringlit::s;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value = "REQ-.*")]
|
||||
allowed_requirements: String,
|
||||
requirements: PathBuf,
|
||||
test_results: PathBuf,
|
||||
}
|
||||
|
||||
fn nl() -> String {
|
||||
s!("")
|
||||
}
|
||||
|
||||
fn check_requirements(
|
||||
test_results: &str,
|
||||
output: &mut Vec<String>,
|
||||
requirements: &IndexMap<String, Requirement>,
|
||||
allowed_requirements: &Regex,
|
||||
) {
|
||||
for (id, requirement) in requirements {
|
||||
if allowed_requirements.is_match(&id) {
|
||||
let status = if test_results.contains(&format!("{id} succeeded")) {
|
||||
":white_check_mark:"
|
||||
} else if test_results.contains(&format!("{id} failed")) {
|
||||
":x:"
|
||||
} else {
|
||||
":warning:"
|
||||
};
|
||||
output.push(format!("- _{id}_ - {}: {status}", requirement.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_valid_requirements(
|
||||
mut requirements: Keys<String, Requirement>,
|
||||
allowed_requirements: &Regex,
|
||||
) -> bool {
|
||||
requirements.any(|id| allowed_requirements.is_match(&id))
|
||||
}
|
||||
|
||||
fn has_valid_topics(mut topics: Values<String, Topic>, allowed_requirements: &Regex) -> bool {
|
||||
topics.any(|topic| {
|
||||
has_valid_requirements(topic.requirements.keys(), allowed_requirements)
|
||||
|| has_valid_topics(topic.subtopics.values(), allowed_requirements)
|
||||
})
|
||||
}
|
||||
|
||||
fn check_topics(
|
||||
test_results: &str,
|
||||
output: &mut Vec<String>,
|
||||
topics: &IndexMap<String, Topic>,
|
||||
allowed_requirements: &Regex,
|
||||
level: usize,
|
||||
) {
|
||||
if !has_valid_topics(topics.values(), allowed_requirements) {
|
||||
return;
|
||||
}
|
||||
for (id, topic) in topics {
|
||||
if !has_valid_topics(topic.subtopics.values(), allowed_requirements)
|
||||
&& !has_valid_requirements(topic.requirements.keys(), allowed_requirements)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
output.push(format!("{} _{id}_ - {}", "#".repeat(level), topic.name));
|
||||
if !topic.requirements.is_empty() {
|
||||
check_requirements(
|
||||
test_results,
|
||||
output,
|
||||
&topic.requirements,
|
||||
allowed_requirements,
|
||||
);
|
||||
output.push(nl());
|
||||
}
|
||||
if !topic.subtopics.is_empty() {
|
||||
check_topics(
|
||||
test_results,
|
||||
output,
|
||||
&topic.subtopics,
|
||||
allowed_requirements,
|
||||
level + 1,
|
||||
);
|
||||
output.push(nl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let Args {
|
||||
allowed_requirements,
|
||||
requirements,
|
||||
test_results,
|
||||
} = Args::parse();
|
||||
let re = Regex::new(&allowed_requirements).unwrap();
|
||||
let test_results = std::fs::read_to_string(test_results)?;
|
||||
let project: Project = serde_yaml::from_str(&std::fs::read_to_string(requirements)?)?;
|
||||
let mut output = vec![format!("# Test Results - {}", project.name)];
|
||||
check_topics(&test_results, &mut output, &project.topics, &re, 2);
|
||||
|
||||
let output = output.join("\n");
|
||||
println!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
23
src/lib.rs
23
src/lib.rs
|
|
@ -1,15 +1,16 @@
|
|||
use indexmap::{indexmap, IndexMap};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use stringlit::s;
|
||||
|
||||
pub fn my_trim<S>(v: &String, s: S) -> Result<S::Ok, S::Error>
|
||||
pub fn my_trim<S>(v: &str, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_str(v.trim())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
||||
pub struct Requirement {
|
||||
pub name: String,
|
||||
#[serde(serialize_with = "my_trim")]
|
||||
|
|
@ -18,7 +19,7 @@ pub struct Requirement {
|
|||
pub requires: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
||||
pub struct Topic {
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
|
||||
|
|
@ -27,7 +28,7 @@ pub struct Topic {
|
|||
pub subtopics: IndexMap<String, Topic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
||||
pub struct Definition {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
|
|
@ -35,7 +36,7 @@ pub struct Definition {
|
|||
pub additional_info: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
||||
pub struct ConfigDefault {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
|
|
@ -50,7 +51,7 @@ pub struct ConfigDefault {
|
|||
pub hint: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
||||
pub struct Project {
|
||||
pub name: String,
|
||||
#[serde(serialize_with = "my_trim")]
|
||||
|
|
@ -66,10 +67,10 @@ pub struct Project {
|
|||
pub fn demo_project() -> Project {
|
||||
Project {
|
||||
name: s!("journal-uploader"),
|
||||
description: s!(r#"
|
||||
description: s!(r"
|
||||
The journal-uploader has two main functionalities.
|
||||
- Take a stream of log messages and filter them depending on their severity
|
||||
- Upload journal logs for a specified time when activated through cloud call"#),
|
||||
- Upload journal logs for a specified time when activated through cloud call"),
|
||||
topics: indexmap! {
|
||||
s!("FEAT-1") => Topic {
|
||||
name: s!("Traced Logging"),
|
||||
|
|
@ -79,7 +80,7 @@ The journal-uploader has two main functionalities.
|
|||
requirements: indexmap! {
|
||||
s!("REQ-1") => Requirement {
|
||||
name: s!("Continuous Monitoring"),
|
||||
description: s!(r#"The tool must continuously monitor a designated directory."#),
|
||||
description: s!(r"The tool must continuously monitor a designated directory."),
|
||||
requires: vec! [],
|
||||
}
|
||||
},
|
||||
|
|
@ -90,12 +91,12 @@ The journal-uploader has two main functionalities.
|
|||
requirements: indexmap! {
|
||||
s!("REQ-1") => Requirement {
|
||||
name: s!("Detection of New Files"),
|
||||
description: s!(r#"The tool must detect the addition of new files in the monitored directory."#),
|
||||
description: s!(r"The tool must detect the addition of new files in the monitored directory."),
|
||||
requires: vec! [],
|
||||
},
|
||||
s!("REQ-2") => Requirement {
|
||||
name: s!("Avoid Re-processing"),
|
||||
description: s!(r#"The tool must not process files that have already been processed."#),
|
||||
description: s!(r"The tool must not process files that have already been processed."),
|
||||
requires: vec! [],
|
||||
}
|
||||
},
|
||||
|
|
|
|||
124
src/main.rs
124
src/main.rs
|
|
@ -1,8 +1,13 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
use indexmap::IndexMap;
|
||||
use indexmap::{
|
||||
map::{Keys, Values},
|
||||
IndexMap,
|
||||
};
|
||||
use regex::Regex;
|
||||
use req::*;
|
||||
use schemars::schema_for;
|
||||
use stringlit::s;
|
||||
|
||||
pub const WORD_DESCRIPTION: &str = //
|
||||
|
|
@ -28,6 +33,79 @@ fn nl() -> String {
|
|||
s!("")
|
||||
}
|
||||
|
||||
fn check_requirements(
|
||||
test_results: &str,
|
||||
output: &mut Vec<String>,
|
||||
requirements: &IndexMap<String, Requirement>,
|
||||
allowed_requirements: &Regex,
|
||||
) {
|
||||
for (id, requirement) in requirements {
|
||||
if allowed_requirements.is_match(id) {
|
||||
let status = if test_results.contains(&format!("{id} succeeded")) {
|
||||
":white_check_mark:"
|
||||
} else if test_results.contains(&format!("{id} failed")) {
|
||||
":x:"
|
||||
} else {
|
||||
":warning:"
|
||||
};
|
||||
output.push(format!("- _{id}_ - {}: {status}", requirement.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_valid_requirements(
|
||||
mut requirements: Keys<String, Requirement>,
|
||||
allowed_requirements: &Regex,
|
||||
) -> bool {
|
||||
requirements.any(|id| allowed_requirements.is_match(id))
|
||||
}
|
||||
|
||||
fn has_valid_topics(mut topics: Values<String, Topic>, allowed_requirements: &Regex) -> bool {
|
||||
topics.any(|topic| {
|
||||
has_valid_requirements(topic.requirements.keys(), allowed_requirements)
|
||||
|| has_valid_topics(topic.subtopics.values(), allowed_requirements)
|
||||
})
|
||||
}
|
||||
|
||||
fn check_topics(
|
||||
test_results: &str,
|
||||
output: &mut Vec<String>,
|
||||
topics: &IndexMap<String, Topic>,
|
||||
allowed_requirements: &Regex,
|
||||
level: usize,
|
||||
) {
|
||||
if !has_valid_topics(topics.values(), allowed_requirements) {
|
||||
return;
|
||||
}
|
||||
for (id, topic) in topics {
|
||||
if !has_valid_topics(topic.subtopics.values(), allowed_requirements)
|
||||
&& !has_valid_requirements(topic.requirements.keys(), allowed_requirements)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
output.push(format!("{} _{id}_ - {}", "#".repeat(level), topic.name));
|
||||
if !topic.requirements.is_empty() {
|
||||
check_requirements(
|
||||
test_results,
|
||||
output,
|
||||
&topic.requirements,
|
||||
allowed_requirements,
|
||||
);
|
||||
output.push(nl());
|
||||
}
|
||||
if !topic.subtopics.is_empty() {
|
||||
check_topics(
|
||||
test_results,
|
||||
output,
|
||||
&topic.subtopics,
|
||||
allowed_requirements,
|
||||
level + 1,
|
||||
);
|
||||
output.push(nl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_requirements(output: &mut Vec<String>, requirements: &IndexMap<String, Requirement>) {
|
||||
for (id, requirement) in requirements {
|
||||
output.push(format!(
|
||||
|
|
@ -51,14 +129,36 @@ fn add_topics(output: &mut Vec<String>, topics: &IndexMap<String, Topic>, level:
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
enum Command {
|
||||
Schema,
|
||||
#[clap(alias = "md")]
|
||||
Markdown {
|
||||
requirements: PathBuf,
|
||||
},
|
||||
Check {
|
||||
#[arg(short, long, default_value = "REQ-.*")]
|
||||
allowed_requirements: String,
|
||||
requirements: PathBuf,
|
||||
test_results: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Args {
|
||||
input: PathBuf,
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let Args { input } = Args::parse();
|
||||
let project: Project = serde_yaml::from_str(&std::fs::read_to_string(input)?)?;
|
||||
let Args { command } = Args::parse();
|
||||
match command {
|
||||
Command::Schema => {
|
||||
let schema = schema_for!(Project);
|
||||
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
|
||||
}
|
||||
Command::Markdown { requirements } => {
|
||||
let project: Project = serde_yaml::from_str(&std::fs::read_to_string(requirements)?)?;
|
||||
|
||||
let mut output = vec![
|
||||
format!("# Requirements for {}", project.name),
|
||||
|
|
@ -122,6 +222,22 @@ fn main() -> anyhow::Result<()> {
|
|||
}
|
||||
|
||||
println!("{output}");
|
||||
}
|
||||
Command::Check {
|
||||
allowed_requirements,
|
||||
requirements,
|
||||
test_results,
|
||||
} => {
|
||||
let re = Regex::new(&allowed_requirements).unwrap();
|
||||
let test_results = std::fs::read_to_string(test_results)?;
|
||||
let project: Project = serde_yaml::from_str(&std::fs::read_to_string(requirements)?)?;
|
||||
let mut output = vec![format!("# Test Results - {}", project.name)];
|
||||
check_topics(&test_results, &mut output, &project.topics, &re, 2);
|
||||
|
||||
let output = output.join("\n");
|
||||
println!("{output}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue