Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,103 changes: 558 additions & 545 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 6 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "evidenceangel"
description = "Library and executables to work with EvidenceAngel evidence packages (*.evp)."
version = "1.5.0-rc.8"
version = "1.6.0-alpha.1"
edition = "2024"
license = "GPL-3.0-or-later"
authors = [
Expand Down Expand Up @@ -40,10 +40,8 @@ exporter-zip-of-files = []
ui = [
"dep:clap",
"dep:directories",
"dep:fluent",
"dep:fluent-templates",
"dep:glib-build-tools",
"dep:once_cell",
"dep:open",
"dep:parse_datetime",
"dep:parking_lot",
Expand All @@ -69,23 +67,21 @@ clap-verbosity-flag = { version = "3.0.2", default-features = false, features =
clap_complete = { version = "4.5.2", optional = true }
colored = { version = "3.0.0", optional = true }
directories = { version = "6.0.0", optional = true }
fluent = { version = "0.16.1", optional = true }
fluent-templates = { version = "0.13.0", optional = true }
getset = "0.1.2"
html-escape = { version = "0.2.13", optional = true }
infer = "0.19.0"
jsonschema = "0.30.0"
once_cell = { version = "1.19.0", optional = true }
open = { version = "5.3.0", optional = true }
parking_lot = { version = "0.12.3", optional = true }
parse_datetime = { version = "0.9.0", optional = true }
parse_datetime = { version = "0.10.0", optional = true }
relm4 = { version = "0.9.0", features = [
"libadwaita",
"gnome_46",
], optional = true }
relm4-icons = { version = "0.9.0", optional = true }
rust_xlsxwriter = { version = "0.86.1", features = ["chrono"], optional = true }
schemars = { version = "0.8.21", features = ["chrono"], optional = true }
rust_xlsxwriter = { version = "0.89.1", features = ["chrono"], optional = true }
schemars = { version = "1.0.4", features = ["chrono04"], optional = true }
serde = { version = "1.0.200", features = ["derive"] }
serde_json = "1.0.116"
sha256 = "1.5.0"
Expand All @@ -97,7 +93,7 @@ tracing-panic = { version = "0.1.2", optional = true }
tracing-subscriber = { version = "0.3.19", optional = true }
tracing-subscriber-multi = { version = "0.1.0", optional = true }
uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] }
zip = "2.4.1"
zip = "4.3.0"

[target.'cfg(windows)'.dependencies]
winapi = "0.3.9"
Expand All @@ -107,5 +103,5 @@ winresource = "0.1"
ico-builder = "0.1"

[build-dependencies]
glib-build-tools = { version = "0.20.0", optional = true }
glib-build-tools = { version = "0.21.0", optional = true }
mdbook = { version = "0.4.48", default-features = false, features = ["search"] }
44 changes: 36 additions & 8 deletions schemas/draft-hopkins-evp-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ submissionType = "independent"

[seriesInfo]
name = "Internet-Draft"
value = "draft-hopkins-evp-spec-01"
value = "draft-hopkins-evp-spec-04"
stream = "independent"
status = "informational"

date = 2025-05-05T00:00:00Z
date = 2025-07-27T00:00:00Z

[[author]]
initials="L."
Expand Down Expand Up @@ -63,9 +63,23 @@ The format does not attempt to:
## Intended Audience

This specification is intended for those who might wish to write their
own implementation of the evidence package format.

## Changes from Previous Verisons
own implementation of the evidence package format. There are a number of
situations where writing an implementation may be desirable:

* in an automation tool that runs a number of operations to
automatically test something, to produce an evidence package
containing the results of the automated testing;
* in a manual evidence collection tool, where a user might want to
collect evidence in a single, easy to manage place for later
processing or sharing;
* in an analysis tool, to view, annotate, share and understand the
evidence from previous testing;
* in a viewer, to view evidence that has been shared, for example from a
testing team to a customer, or;
* any other situation where it may be desirable to collect test evidence
and bundle it together for later.

## Changes from Previous Versions

This document forms the original specification.

Expand All @@ -79,8 +93,22 @@ when, and only when, they appear in all capitals, as shown here.

# Specification

An evidence package is a structured ZIP archive. It **MUST** contain the
file "manifest.json", and the directories "media" and "testcases".
An evidence package is a structured ZIP archive [@!zip]. It **MUST**
contain the file "manifest.json", and the directories "media" and
"testcases" internally within the ZIP archive. This structure does not
need to be represented outside of the ZIP archive and as such the
internal structure does not need to be understood by an end-user of any
tool that works with evidence packages.

<reference anchor="zip" target="https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT">
<front>
<title>.ZIP File Format Specification</title>
<author>
<organization>PKWARE, Inc.</organization>
</author>
<date year="2022" month="November" day="01"/>
</front>
</reference>

See (#example-archive) for an example of the file's internal structure.

Expand Down Expand Up @@ -306,7 +334,7 @@ When an implementor loads a file with fields it cannot understand, it

# IANA Considerations

This document acts as the specification for the requested media type
This document acts as the specification for the media type
application/vnd.angel.evidence-package.

# Security Considerations
Expand Down
22 changes: 20 additions & 2 deletions src/evidenceangel-cli/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl fmt::Display for CliPackage {
.custom_test_case_metadata_fields
.iter()
.collect::<Vec<_>>();
sorted_custom_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
sorted_custom_fields.sort_by(|(_, a), (_, b)| a.cmp(b));
for (idx, (key, field)) in sorted_custom_fields.iter().enumerate() {
let ch = if idx == sorted_custom_fields.len() - 1 {
"╰"
Expand Down Expand Up @@ -176,7 +176,7 @@ impl fmt::Display for CliPackage {
}

/// A custom metadata field within a package
#[derive(Serialize, JsonSchema)]
#[derive(Serialize, JsonSchema, PartialEq, Eq)]
struct CliCustomMetadataField {
/// The field's ID
key: String,
Expand Down Expand Up @@ -205,6 +205,24 @@ impl fmt::Display for CliCustomMetadataField {
}
}

impl PartialOrd for CliCustomMetadataField {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for CliCustomMetadataField {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.primary && !other.primary {
return std::cmp::Ordering::Less;
}
if !self.primary && other.primary {
return std::cmp::Ordering::Greater;
}
self.name.cmp(&other.name)
}
}

/// A test case within a package
#[derive(Serialize, JsonSchema)]
struct PackageTestCase {
Expand Down
9 changes: 6 additions & 3 deletions src/evidenceangel-cli/test_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,12 @@ impl fmt::Display for CliTestCase {
}
};

rich_text.push_str(&format!(
// SAFETY: This won't fail as there is no I/O
write!(
rich_text,
"{padding_left}{formatted}{padding_right} | "
));
)
.unwrap();
}
rich_text.push('\n');
}
Expand Down Expand Up @@ -882,7 +885,7 @@ pub fn process(path: PathBuf, command: &TestCasesSubcommand) -> CliData {
new_order.insert(new_pos, case_id);
}
Err(e) => return CliError::FailedToReadPackage(Rc::new(e)).into(),
};
}

// Update order
if let Err(e) = package.set_test_case_order(new_order) {
Expand Down
4 changes: 2 additions & 2 deletions src/evidenceangel-ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,7 +1229,7 @@ impl Component for AppModel {
.expect("Cannot navigate to metadata when no package is open");
if let Some(fields) = &pkg_fields {
let mut fields: Vec<_> = fields.iter().collect();
fields.sort_by(|(a, _), (b, _)| a.cmp(b));
fields.sort_by(|(_, a), (_, b)| a.cmp(b));
for (key, field) in fields {
custom_fields.push_back(CustomMetadataEditorFactoryInit {
root: root.clone(),
Expand Down Expand Up @@ -1306,7 +1306,7 @@ impl Component for AppModel {
pkg.read().metadata().custom_test_case_metadata()
{
let mut fields: Vec<_> = fields.iter().collect();
fields.sort_by(|(a, _), (b, _)| a.cmp(b));
fields.sort_by(|(_, a), (_, b)| a.cmp(b));
for (key, field) in fields {
custom_metadata.push_back(CustomMetadataFactoryInit {
key: key.clone(),
Expand Down
4 changes: 4 additions & 0 deletions src/evidenceangel-ui/evidence_factory/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ impl Component for ComponentModel {

gtk::ScrolledWindow {
set_hexpand: true,
set_vscrollbar_policy: gtk::PolicyType::Never,

gtk::TextView {
add_css_class: "monospace",
set_left_margin: 8,
set_right_margin: 8,
set_top_margin: 8,
set_bottom_margin: 8,
set_height_request: super::EVIDENCE_INNER_HEIGHT_REQUEST,
set_halign: gtk::Align::Fill,
set_valign: gtk::Align::Fill,

Expand All @@ -80,13 +82,15 @@ impl Component for ComponentModel {

gtk::ScrolledWindow {
set_hexpand: true,
set_vscrollbar_policy: gtk::PolicyType::Never,

gtk::TextView {
add_css_class: "monospace",
set_left_margin: 8,
set_right_margin: 8,
set_top_margin: 8,
set_bottom_margin: 8,
set_height_request: super::EVIDENCE_INNER_HEIGHT_REQUEST,
set_halign: gtk::Align::Fill,
set_valign: gtk::Align::Fill,

Expand Down
3 changes: 2 additions & 1 deletion src/evidenceangel-ui/evidence_factory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod rich_text;
mod text;

const EVIDENCE_HEIGHT_REQUEST: i32 = 300;
const EVIDENCE_INNER_HEIGHT_REQUEST: i32 = 100;
const HTTP_SEPARATOR: char = '\x1e';

pub struct EvidenceFactoryModel {
Expand Down Expand Up @@ -271,7 +272,7 @@ impl FactoryComponent for EvidenceFactoryModel {
widgets.evidence_child.set_child(Some(component.widget()));
self.sub_component = Box::new(component);
}
};
}

widgets
}
Expand Down
60 changes: 28 additions & 32 deletions src/evidenceangel-ui/evidence_factory/rich_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,40 +117,36 @@ impl Component for ComponentModel {
// Text
#[name = "frame"]
gtk::Frame {
gtk::ScrolledWindow {
set_height_request: 200,
set_hexpand: true,

#[name = "text_view"]
gtk::TextView {
set_left_margin: 8,
set_right_margin: 8,
set_top_margin: 8,
set_bottom_margin: 8,
set_wrap_mode: gtk::WrapMode::Word,

#[name = "text_buffer"]
#[wrap(Some)]
set_buffer = &gtk::TextBuffer {
set_text: &init.text,
connect_changed => ComponentInput::Internal(ComponentInputInternal::TextChanged) @signal_text_changed,
},
#[name = "text_view"]
gtk::TextView {
set_left_margin: 8,
set_right_margin: 8,
set_top_margin: 8,
set_bottom_margin: 8,
set_height_request: super::EVIDENCE_INNER_HEIGHT_REQUEST,
set_wrap_mode: gtk::WrapMode::Word,

#[name = "text_buffer"]
#[wrap(Some)]
set_buffer = &gtk::TextBuffer {
set_text: &init.text,
connect_changed => ComponentInput::Internal(ComponentInputInternal::TextChanged) @signal_text_changed,
},

add_controller = gtk::ShortcutController {
add_shortcut = gtk::Shortcut {
#[wrap(Some)]
set_trigger = gtk::ShortcutTrigger::parse_string("<primary>B").unwrap(),
#[wrap(Some)]
set_action = gtk::ShortcutAction::parse_string("action(rich-text-editor.bold)").unwrap(),
},
add_shortcut = gtk::Shortcut {
#[wrap(Some)]
set_trigger = gtk::ShortcutTrigger::parse_string("<primary>I").unwrap(),
#[wrap(Some)]
set_action = gtk::ShortcutAction::parse_string("action(rich-text-editor.italic)").unwrap(),
},
add_controller = gtk::ShortcutController {
add_shortcut = gtk::Shortcut {
#[wrap(Some)]
set_trigger = gtk::ShortcutTrigger::parse_string("<primary>B").unwrap(),
#[wrap(Some)]
set_action = gtk::ShortcutAction::parse_string("action(rich-text-editor.bold)").unwrap(),
},
}
add_shortcut = gtk::Shortcut {
#[wrap(Some)]
set_trigger = gtk::ShortcutTrigger::parse_string("<primary>I").unwrap(),
#[wrap(Some)]
set_action = gtk::ShortcutAction::parse_string("action(rich-text-editor.italic)").unwrap(),
},
},
}
},
}
Expand Down
26 changes: 11 additions & 15 deletions src/evidenceangel-ui/evidence_factory/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,18 @@ impl Component for ComponentModel {
view! {
#[root]
gtk::Frame {
gtk::ScrolledWindow {
set_height_request: 100,
set_hexpand: true,
gtk::TextView {
set_left_margin: 8,
set_right_margin: 8,
set_top_margin: 8,
set_bottom_margin: 8,
set_height_request: super::EVIDENCE_INNER_HEIGHT_REQUEST,

gtk::TextView {
set_left_margin: 8,
set_right_margin: 8,
set_top_margin: 8,
set_bottom_margin: 8,

#[name = "text_buffer"]
#[wrap(Some)]
set_buffer = &gtk::TextBuffer {
set_text: &init.text,
connect_changed => ComponentInput::Internal(ComponentInputInternal::TextChanged),
}
#[name = "text_buffer"]
#[wrap(Some)]
set_buffer = &gtk::TextBuffer {
set_text: &init.text,
connect_changed => ComponentInput::Internal(ComponentInputInternal::TextChanged),
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/evidenceangel-ui/lang.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::{borrow::Cow, collections::HashMap, fmt::Display};
use std::{borrow::Cow, collections::HashMap, fmt::Display, sync::LazyLock};

use fluent::FluentValue;
use fluent_templates::{LanguageIdentifier, Loader};
use once_cell::sync::Lazy;
use fluent_templates::{LanguageIdentifier, Loader, fluent_bundle::types::FluentValue};
use parking_lot::Mutex;

fluent_templates::static_loader! {
Expand All @@ -11,7 +9,7 @@ fluent_templates::static_loader! {
fallback_language: "en",
};
}
static USE_LOCALE: Lazy<Mutex<Option<LanguageIdentifier>>> = Lazy::new(|| Mutex::new(None));
static USE_LOCALE: LazyLock<Mutex<Option<LanguageIdentifier>>> = LazyLock::new(|| Mutex::new(None));

/// Initialises i18n and returned the locale identifier
pub fn initialise_i18n() -> LanguageIdentifier {
Expand Down
Loading