update
This commit is contained in:
parent
26ea87b295
commit
eb246b7b29
|
|
@ -145,6 +145,47 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete"
|
||||||
|
version = "4.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete_command"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
|
"clap_complete_fig",
|
||||||
|
"clap_complete_nushell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete_fig"
|
||||||
|
version = "4.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54b3e65f91fabdd23cac3d57d39d5d938b4daabd070c335c006dccb866a61110"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete_nushell"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d02bc8b1a18ee47c4d2eec3fb5ac034dc68ebea6125b1509e9ccdffcddce66e"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.4"
|
version = "4.5.4"
|
||||||
|
|
@ -476,6 +517,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_complete_command",
|
||||||
"crossterm 0.27.0",
|
"crossterm 0.27.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"markdown",
|
"markdown",
|
||||||
|
|
|
||||||
13
Cargo.toml
13
Cargo.toml
|
|
@ -1,18 +1,21 @@
|
||||||
[package]
|
[package]
|
||||||
name = "req"
|
name = "req"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "req"
|
default-run = "req"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[profile.release]
|
||||||
|
strip = "symbols"
|
||||||
|
lto = "fat"
|
||||||
|
split-debuginfo = "true"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.83"
|
anyhow = "1.0.83"
|
||||||
clap = { version = "4.5.4", features = ["derive"] }
|
clap = { version = "4.5.4", features = ["derive"] }
|
||||||
crossterm = "0.27.0"
|
clap_complete_command = "0.5.1"
|
||||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
indexmap = { version = "2.2.6", features = ["serde"] }
|
||||||
markdown = "1.0.0-alpha.17"
|
markdown = "1.0.0-alpha.17"
|
||||||
ratatui = "0.26.2"
|
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
rsn = "0.1.0"
|
rsn = "0.1.0"
|
||||||
schemars = { version = "0.8.19", features = ["indexmap2"] }
|
schemars = { version = "0.8.19", features = ["indexmap2"] }
|
||||||
|
|
@ -21,5 +24,3 @@ serde_json = { version = "1.0.117", features = ["indexmap", "preserve_order"] }
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
stringlit = "2.1.0"
|
stringlit = "2.1.0"
|
||||||
toml = { version = "0.8.12", features = ["indexmap", "preserve_order"] }
|
toml = { version = "0.8.12", features = ["indexmap", "preserve_order"] }
|
||||||
tui = "0.19.0"
|
|
||||||
version_operators = "0.0.1"
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ mkdir -p out
|
||||||
cargo build
|
cargo build
|
||||||
cargo run -q -- schema > out/schema.json
|
cargo run -q -- schema > out/schema.json
|
||||||
cargo run -q -- demo > out/demo.yml
|
cargo run -q -- demo > out/demo.yml
|
||||||
cargo run -q -- md req.yml > out/requirements.md
|
cargo run -q -- md requirements.yml > out/requirements.md
|
||||||
cargo run -q -- html req.yml > out/requirements.html
|
cargo run -q -- html requirements.yml > out/requirements.html
|
||||||
cargo run -q -- check req.yml test_result.txt > out/text_result.md
|
cargo run -q -- check requirements.yml test_result.txt > out/test_result.md
|
||||||
|
|
|
||||||
177
src/bin/tui.rs
177
src/bin/tui.rs
|
|
@ -1,177 +0,0 @@
|
||||||
use crossterm::{
|
|
||||||
event::{self, Event as CEvent, KeyCode},
|
|
||||||
execute,
|
|
||||||
style::Color,
|
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
|
||||||
};
|
|
||||||
use ratatui::{
|
|
||||||
backend::CrosstermBackend,
|
|
||||||
layout::{Constraint, Direction, Layout},
|
|
||||||
prelude::Style,
|
|
||||||
widgets::{Block, Borders, List, ListItem, ListState},
|
|
||||||
Terminal,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::thread;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use req::*;
|
|
||||||
|
|
||||||
enum Event<I> {
|
|
||||||
Input(I),
|
|
||||||
Tick,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct App {
|
|
||||||
project: Project,
|
|
||||||
topics_list_state: ListState,
|
|
||||||
requirements_list_state: ListState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
fn new(project: Project) -> App {
|
|
||||||
let mut topics_list_state = ListState::default();
|
|
||||||
topics_list_state.select(Some(0));
|
|
||||||
|
|
||||||
let mut requirements_list_state = ListState::default();
|
|
||||||
requirements_list_state.select(Some(0));
|
|
||||||
|
|
||||||
App {
|
|
||||||
project,
|
|
||||||
topics_list_state,
|
|
||||||
requirements_list_state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self, f: &mut ratatui::Frame) {
|
|
||||||
let chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.margin(1)
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Percentage(10), // Project Title
|
|
||||||
Constraint::Percentage(45), // Topics List
|
|
||||||
Constraint::Percentage(45), // Requirements List
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(f.size());
|
|
||||||
|
|
||||||
let project_title = Block::default()
|
|
||||||
.title(self.project.name.clone())
|
|
||||||
.borders(Borders::ALL);
|
|
||||||
f.render_widget(project_title, chunks[0]);
|
|
||||||
|
|
||||||
let topics: Vec<ListItem> = self
|
|
||||||
.project
|
|
||||||
.topics
|
|
||||||
.iter()
|
|
||||||
.map(|(name, _)| ListItem::new(name.clone()))
|
|
||||||
.collect();
|
|
||||||
let topics_list = List::new(topics)
|
|
||||||
.block(Block::default().borders(Borders::ALL).title("Topics"))
|
|
||||||
.highlight_style(Style::default().bg(Color::Blue.into()));
|
|
||||||
f.render_stateful_widget(topics_list, chunks[1], &mut self.topics_list_state);
|
|
||||||
|
|
||||||
let requirements: Vec<ListItem> = self
|
|
||||||
.project
|
|
||||||
.topics
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.1
|
|
||||||
.requirements
|
|
||||||
.iter()
|
|
||||||
.map(|(name, _)| ListItem::new(name.clone()))
|
|
||||||
.collect();
|
|
||||||
let requirements_list = List::new(requirements)
|
|
||||||
.block(Block::default().borders(Borders::ALL).title("Requirements"))
|
|
||||||
.highlight_style(Style::default().bg(Color::Yellow.into()));
|
|
||||||
f.render_stateful_widget(
|
|
||||||
requirements_list,
|
|
||||||
chunks[2],
|
|
||||||
&mut self.requirements_list_state,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_topic(&mut self) {
|
|
||||||
let n = self.project.topics.len();
|
|
||||||
if let Some(i) = self.topics_list_state.selected() {
|
|
||||||
if i >= n - 1 {
|
|
||||||
self.topics_list_state.select(Some(0));
|
|
||||||
} else {
|
|
||||||
self.topics_list_state.select(Some(i + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn previous_topic(&mut self) {
|
|
||||||
let n = self.project.topics.len();
|
|
||||||
if let Some(i) = self.topics_list_state.selected() {
|
|
||||||
if i == 0 {
|
|
||||||
self.topics_list_state.select(Some(n - 1));
|
|
||||||
} else {
|
|
||||||
self.topics_list_state.select(Some(i - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
enable_raw_mode()?;
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
execute!(stdout, EnterAlternateScreen)?;
|
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
|
||||||
let mut terminal = Terminal::new(backend)?;
|
|
||||||
|
|
||||||
let project = demo_project();
|
|
||||||
|
|
||||||
let mut app = App::new(project);
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
let tick_rate = Duration::from_millis(200);
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut last_tick = Instant::now();
|
|
||||||
loop {
|
|
||||||
let timeout = tick_rate
|
|
||||||
.checked_sub(last_tick.elapsed())
|
|
||||||
.unwrap_or_else(|| Duration::from_secs(0));
|
|
||||||
if event::poll(timeout).expect("poll works") {
|
|
||||||
if let CEvent::Key(key) = event::read().expect("read works") {
|
|
||||||
tx.send(Event::Input(key)).expect("send works");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if last_tick.elapsed() >= tick_rate {
|
|
||||||
tx.send(Event::Tick).expect("tick works");
|
|
||||||
last_tick = Instant::now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loop {
|
|
||||||
terminal.draw(|f| app.draw(f))?;
|
|
||||||
|
|
||||||
match rx.recv()? {
|
|
||||||
Event::Input(event) => match event.code {
|
|
||||||
KeyCode::Char('q') => {
|
|
||||||
disable_raw_mode()?;
|
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
|
||||||
terminal.show_cursor()?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Down => {
|
|
||||||
app.next_topic();
|
|
||||||
}
|
|
||||||
KeyCode::Up => {
|
|
||||||
app.previous_topic();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Event::Tick => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -125,6 +125,7 @@ pub struct Project {
|
||||||
serialize_with = "serialize_version",
|
serialize_with = "serialize_version",
|
||||||
deserialize_with = "deserialize_version"
|
deserialize_with = "deserialize_version"
|
||||||
)]
|
)]
|
||||||
|
#[schemars(with = "String", regex(pattern = r"^\d\.\d\.\d$"))]
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
#[serde(serialize_with = "my_trim")]
|
#[serde(serialize_with = "my_trim")]
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
@ -138,5 +139,5 @@ pub struct Project {
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn demo_project() -> Project {
|
pub fn demo_project() -> Project {
|
||||||
serde_yaml::from_str(include_str!("../req.yml")).expect("Should never happen!")
|
serde_yaml::from_str(include_str!("../requirements.yml")).expect("Should never happen!")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
src/main.rs
71
src/main.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::{CommandFactory, Parser, Subcommand};
|
||||||
use indexmap::{
|
use indexmap::{
|
||||||
map::{Keys, Values},
|
map::{Keys, Values},
|
||||||
IndexMap,
|
IndexMap,
|
||||||
|
|
@ -35,16 +35,28 @@ fn nl() -> String {
|
||||||
|
|
||||||
fn check_requirements(
|
fn check_requirements(
|
||||||
test_results: &str,
|
test_results: &str,
|
||||||
output: &mut IndexMap<String, bool>,
|
output: &mut IndexMap<String, (bool, Vec<String>)>,
|
||||||
requirements: &IndexMap<String, Requirement>,
|
requirements: &IndexMap<String, Requirement>,
|
||||||
allowed_requirements: &Regex,
|
allowed_requirements: &[Regex],
|
||||||
) {
|
) {
|
||||||
for (id, _) in dbg!(requirements) {
|
for (id, _) in requirements {
|
||||||
if allowed_requirements.is_match(id) {
|
if allowed_requirements.iter().any(|r| r.is_match(id)) {
|
||||||
if test_results.contains(&format!("{}: failed", id.trim())) {
|
let search_string = format!("{}: failed", id.trim());
|
||||||
output.insert(id.trim().to_string(), false);
|
if test_results.contains(&search_string) {
|
||||||
|
let errors = test_results.lines().filter_map(|l| {
|
||||||
|
if l.starts_with(&search_string) {
|
||||||
|
l.split_once(":")
|
||||||
|
.map(|(_, txt)| txt)
|
||||||
|
.and_then(|txt| txt.split_once("-").map(|(_, err)| err.to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output.insert(id.trim().to_string(), (false, errors.collect()));
|
||||||
} else if test_results.contains(&format!("{}: passed", id.trim())) {
|
} else if test_results.contains(&format!("{}: passed", id.trim())) {
|
||||||
output.entry(id.trim().to_string()).or_insert(true);
|
output
|
||||||
|
.entry(id.trim().to_string())
|
||||||
|
.or_insert((true, Vec::new()));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,12 +64,12 @@ fn check_requirements(
|
||||||
|
|
||||||
fn has_valid_requirements(
|
fn has_valid_requirements(
|
||||||
mut requirements: Keys<String, Requirement>,
|
mut requirements: Keys<String, Requirement>,
|
||||||
allowed_requirements: &Regex,
|
allowed_requirements: &[Regex],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
requirements.any(|id| allowed_requirements.is_match(id))
|
requirements.any(|id| allowed_requirements.iter().any(|r| r.is_match(id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_valid_topics(mut topics: Values<String, Topic>, allowed_requirements: &Regex) -> bool {
|
fn has_valid_topics(mut topics: Values<String, Topic>, allowed_requirements: &[Regex]) -> bool {
|
||||||
topics.any(|topic| {
|
topics.any(|topic| {
|
||||||
has_valid_requirements(topic.requirements.keys(), allowed_requirements)
|
has_valid_requirements(topic.requirements.keys(), allowed_requirements)
|
||||||
|| has_valid_topics(topic.subtopics.values(), allowed_requirements)
|
|| has_valid_topics(topic.subtopics.values(), allowed_requirements)
|
||||||
|
|
@ -68,7 +80,7 @@ fn check_topics(
|
||||||
test_results: &[PathBuf],
|
test_results: &[PathBuf],
|
||||||
output: &mut Vec<String>,
|
output: &mut Vec<String>,
|
||||||
topics: &IndexMap<String, Topic>,
|
topics: &IndexMap<String, Topic>,
|
||||||
allowed_requirements: &Regex,
|
allowed_requirements: &[Regex],
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if !has_valid_topics(topics.values(), allowed_requirements) {
|
if !has_valid_topics(topics.values(), allowed_requirements) {
|
||||||
|
|
@ -102,16 +114,19 @@ fn check_topics(
|
||||||
|
|
||||||
if !topic.requirements.is_empty() {
|
if !topic.requirements.is_empty() {
|
||||||
for (id, req) in &topic.requirements {
|
for (id, req) in &topic.requirements {
|
||||||
let status = if let Some(status) = test_status.get(id) {
|
let (status, errors) = if let Some((status, errors)) = test_status.get(id) {
|
||||||
if *status {
|
if *status {
|
||||||
":white_check_mark:"
|
(":white_check_mark:", errors.to_owned())
|
||||||
} else {
|
} else {
|
||||||
":x:"
|
(":x:", errors.to_owned())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
":warning:"
|
(":warning:", Vec::new())
|
||||||
};
|
};
|
||||||
output.push(format!("- _{}_ - {}: {status}", id.trim(), req.name));
|
output.push(format!("- _{}_ - {}: {status}", id.trim(), req.name));
|
||||||
|
for err in errors {
|
||||||
|
output.push(format!(" - {}", err.trim()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output.push(nl());
|
output.push(nl());
|
||||||
|
|
@ -163,7 +178,7 @@ fn add_topics(output: &mut Vec<String>, topics: &IndexMap<String, Topic>, level:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Subcommand)]
|
||||||
enum Command {
|
enum Command {
|
||||||
/// Outputs the JSON schema for the input data
|
/// Outputs the JSON schema for the input data
|
||||||
Schema,
|
Schema,
|
||||||
|
|
@ -184,13 +199,19 @@ enum Command {
|
||||||
Check {
|
Check {
|
||||||
#[arg(short, long, default_value = "REQ-.*")]
|
#[arg(short, long, default_value = "REQ-.*")]
|
||||||
/// Regex to select which requirements should be checked
|
/// Regex to select which requirements should be checked
|
||||||
allowed_requirements: String,
|
allowed_requirements: Vec<String>,
|
||||||
/// The path to the requirements file
|
/// The path to the requirements file
|
||||||
requirements: PathBuf,
|
requirements: PathBuf,
|
||||||
/// The path to the test output files
|
/// The path to the test output files
|
||||||
#[arg(required=true, num_args=1..)]
|
#[arg(required=true, num_args=1..)]
|
||||||
test_results: Vec<PathBuf>,
|
test_results: Vec<PathBuf>,
|
||||||
},
|
},
|
||||||
|
/// Generate shell completions
|
||||||
|
Completions {
|
||||||
|
/// The shell to generate the completions for
|
||||||
|
#[arg(value_enum)]
|
||||||
|
shell: clap_complete_command::Shell,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|
@ -295,10 +316,14 @@ fn main() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
Command::Html { requirements } => {
|
Command::Html { requirements } => {
|
||||||
let output = to_markdown(requirements, false)?;
|
let output = to_markdown(requirements, false)?;
|
||||||
|
let template = include_str!("../template.html");
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
markdown::to_html_with_options(&output, &markdown::Options::gfm())
|
template.replace(
|
||||||
|
"{{content}}",
|
||||||
|
&markdown::to_html_with_options(&output, &markdown::Options::gfm())
|
||||||
.map_err(|e| anyhow::anyhow!("{e}"))?
|
.map_err(|e| anyhow::anyhow!("{e}"))?
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::Schema => {
|
Command::Schema => {
|
||||||
|
|
@ -314,7 +339,10 @@ fn main() -> anyhow::Result<()> {
|
||||||
requirements,
|
requirements,
|
||||||
test_results,
|
test_results,
|
||||||
} => {
|
} => {
|
||||||
let re = Regex::new(&allowed_requirements).unwrap();
|
let re = allowed_requirements
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| Regex::new(&r).expect("Invalid regex!"));
|
||||||
|
let re: Vec<_> = re.collect();
|
||||||
let project: Project = parse(&std::fs::read_to_string(requirements)?)?;
|
let project: Project = parse(&std::fs::read_to_string(requirements)?)?;
|
||||||
let mut output = vec![format!("# Test Results - {}", project.name)];
|
let mut output = vec![format!("# Test Results - {}", project.name)];
|
||||||
check_topics(&test_results, &mut output, &project.topics, &re, 2)?;
|
check_topics(&test_results, &mut output, &project.topics, &re, 2)?;
|
||||||
|
|
@ -322,6 +350,9 @@ fn main() -> anyhow::Result<()> {
|
||||||
let output = output.join("\n");
|
let output = output.join("\n");
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
}
|
}
|
||||||
|
Command::Completions { shell } => {
|
||||||
|
shell.generate(&mut Args::command(), &mut std::io::stdout());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
/* General body styling */
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Heading styles */
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
color: #0056b3;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
border-bottom: 2px solid #eee;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #004494;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #003073;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
color: #002652;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1em;
|
||||||
|
/* Ensures that subtopic headings don't scale down too much */
|
||||||
|
font-weight: bold;
|
||||||
|
/* Adds emphasis to make subtopic headings stand out */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC keywords styling */
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong em {
|
||||||
|
color: #d63447;
|
||||||
|
/* Bright red for MUST, SHOULD, etc. */
|
||||||
|
font-style: normal;
|
||||||
|
/* Override italic style from em */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link styling */
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List styling for better readability */
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul>li:not(:has(> p))::before {
|
||||||
|
content: "• ";
|
||||||
|
color: #007bff;
|
||||||
|
/* Matching the link color */
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul>li:not(:has(> ul))>p::before {
|
||||||
|
content: "• ";
|
||||||
|
color: #007bff;
|
||||||
|
/* Matching the link color */
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nested lists to indicate hierarchy */
|
||||||
|
ul ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Version and requirements emphasis */
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Special styling for configuration sections */
|
||||||
|
h2 {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Config and Definitions section styling */
|
||||||
|
ul ul li {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detailed requirement items */
|
||||||
|
li strong em {
|
||||||
|
display: inline-block;
|
||||||
|
/* Ensures consistent alignment */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{content}}
|
||||||
|
</body>
|
||||||
Loading…
Reference in New Issue