This commit is contained in:
parent
9207ff7ec8
commit
cbfd5575d0
103
Cargo.lock
generated
103
Cargo.lock
generated
|
@ -513,6 +513,38 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "camino"
|
||||||
|
version = "1.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cargo-platform"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cargo_metadata"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
|
||||||
|
dependencies = [
|
||||||
|
"camino",
|
||||||
|
"cargo-platform",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.6"
|
version = "1.1.6"
|
||||||
|
@ -727,6 +759,12 @@ dependencies = [
|
||||||
"syn 2.0.72",
|
"syn 2.0.72",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -754,6 +792,40 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embed-licensing"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f380b9d6229ab32bddd61aa8117da94bfbf6552e085cb0ae4eb81bb5844a0e5d"
|
||||||
|
dependencies = [
|
||||||
|
"embed-licensing-core",
|
||||||
|
"embed-licensing-macros",
|
||||||
|
"spdx",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embed-licensing-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21ce8d1f31d4e1f4e2c76702247f971a57bcc44a2c52cb4912d00258ae0e19b6"
|
||||||
|
dependencies = [
|
||||||
|
"cargo_metadata",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"spdx",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embed-licensing-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9275a4ab52ca953913ad4d7bc3458856ac01e201310e0f9652d1b4ccb9267dff"
|
||||||
|
dependencies = [
|
||||||
|
"embed-licensing-core",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.34"
|
version = "0.8.34"
|
||||||
|
@ -1234,6 +1306,7 @@ dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"clap",
|
"clap",
|
||||||
"datamatrix",
|
"datamatrix",
|
||||||
|
"embed-licensing",
|
||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"env_logger 0.11.5",
|
"env_logger 0.11.5",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -1242,6 +1315,7 @@ dependencies = [
|
||||||
"maud",
|
"maud",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
"pretty_assertions",
|
||||||
"printpdf",
|
"printpdf",
|
||||||
"quickcheck",
|
"quickcheck",
|
||||||
"quickcheck_macros",
|
"quickcheck_macros",
|
||||||
|
@ -1249,6 +1323,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"serde_variant",
|
"serde_variant",
|
||||||
|
"spdx",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
@ -1650,6 +1725,16 @@ version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||||
|
dependencies = [
|
||||||
|
"diff",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "printpdf"
|
name = "printpdf"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1918,6 +2003,9 @@ name = "semver"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
|
@ -2037,6 +2125,15 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spdx"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
|
@ -2819,6 +2916,12 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.7.35"
|
||||||
|
|
|
@ -8,6 +8,7 @@ version = "0.0.0"
|
||||||
authors = ["Simon Bruder <simon@sbruder.de>"]
|
authors = ["Simon Bruder <simon@sbruder.de>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
|
repository = "https://git.sbruder.de/simon/li7y"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-identity = "0.7.1"
|
actix-identity = "0.7.1"
|
||||||
|
@ -21,6 +22,7 @@ enum-iterator = "2.1.0"
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
|
embed-licensing = "0.1.0"
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
maud = { version = "0.26.0", features = ["actix-web"] }
|
maud = { version = "0.26.0", features = ["actix-web"] }
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
|
@ -30,6 +32,7 @@ rust-embed = { version = "8.5.0", features = ["actix"] }
|
||||||
serde = { version = "1.0.203", features = ["serde_derive"] }
|
serde = { version = "1.0.203", features = ["serde_derive"] }
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
serde_variant = "0.1.3"
|
serde_variant = "0.1.3"
|
||||||
|
spdx = { version = "0.10.6", features = ["text"] }
|
||||||
sqlx = { version = "0.7.4", features = ["runtime-tokio", "postgres", "uuid", "time"] }
|
sqlx = { version = "0.7.4", features = ["runtime-tokio", "postgres", "uuid", "time"] }
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
time = { version = "0.3.36", features = ["parsing", "serde"] }
|
time = { version = "0.3.36", features = ["parsing", "serde"] }
|
||||||
|
@ -37,6 +40,7 @@ uuid = { version = "1.9.0", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = "3.8.0"
|
actix-http = "3.8.0"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
|
||||||
|
|
461
src/frontend/licensing.rs
Normal file
461
src/frontend/licensing.rs
Normal file
|
@ -0,0 +1,461 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use std::collections::{BTreeSet, VecDeque};
|
||||||
|
|
||||||
|
use actix_identity::Identity;
|
||||||
|
use actix_web::{get, web, Responder};
|
||||||
|
use embed_licensing::CrateLicense;
|
||||||
|
use maud::{html, Markup, Render};
|
||||||
|
use spdx::expression::ExprNode;
|
||||||
|
use spdx::LicenseReq;
|
||||||
|
|
||||||
|
use super::templates;
|
||||||
|
|
||||||
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
|
cfg.service(licensing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Ord)]
|
||||||
|
enum SpdxExpression {
|
||||||
|
Req(LicenseReq),
|
||||||
|
And(BTreeSet<SpdxExpression>),
|
||||||
|
Or(BTreeSet<SpdxExpression>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_separated_list(list: impl IntoIterator<Item: Render>, separator: &str) -> Markup {
|
||||||
|
let mut iter = list.into_iter();
|
||||||
|
html! {
|
||||||
|
"("
|
||||||
|
(iter.next().unwrap())
|
||||||
|
@for item in iter {
|
||||||
|
small .font-monospace { " " (separator) " " }
|
||||||
|
(item)
|
||||||
|
}
|
||||||
|
")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove the outermost parentheses
|
||||||
|
impl Render for SpdxExpression {
|
||||||
|
fn render(&self) -> Markup {
|
||||||
|
match self {
|
||||||
|
Self::Req(req) => html! {
|
||||||
|
@let license = req.license.id().expect("only SPDX license identifiers supported");
|
||||||
|
a .font-monospace href={ "#license-" (license.name) } { (license.name) }
|
||||||
|
@if let Some(exception) = req.exception {
|
||||||
|
small .font-monospace { " WITH " }
|
||||||
|
a .font-monospace href={ "#exception-" (exception.name) } { (exception.name) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::And(items) => render_separated_list(items, "AND"),
|
||||||
|
Self::Or(items) => render_separated_list(items, "OR"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<spdx::Expression> for SpdxExpression {
|
||||||
|
fn from(value: spdx::Expression) -> Self {
|
||||||
|
let mut stack = VecDeque::new();
|
||||||
|
let mut expr = None;
|
||||||
|
|
||||||
|
for node in value.iter() {
|
||||||
|
match node {
|
||||||
|
ExprNode::Op(op) => {
|
||||||
|
let last = expr.unwrap_or_else(|| stack.pop_back().unwrap());
|
||||||
|
expr = Some(match op {
|
||||||
|
spdx::expression::Operator::Or => {
|
||||||
|
SpdxExpression::Or(BTreeSet::from([last, stack.pop_back().unwrap()]))
|
||||||
|
}
|
||||||
|
spdx::expression::Operator::And => {
|
||||||
|
SpdxExpression::And(BTreeSet::from([last, stack.pop_back().unwrap()]))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ExprNode::Req(req) => {
|
||||||
|
stack.push_back(SpdxExpression::Req(req.req.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case for single req
|
||||||
|
if expr.is_none() && stack.len() == 1 {
|
||||||
|
return stack.pop_back().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
expr.expect("empty expression not possible").simplify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! spdx_expression_simplify_impl {
|
||||||
|
( $variant:ident, $items:expr ) => {{
|
||||||
|
let mut changed = false;
|
||||||
|
for item in $items.clone().into_iter() {
|
||||||
|
// if is split to avoid referencing a moved value
|
||||||
|
if let Self::$variant(_) = item {
|
||||||
|
$items.remove(&item);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if let Self::$variant(mut inner_items) = item {
|
||||||
|
$items.append(&mut inner_items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
Self::$variant($items).simplify()
|
||||||
|
} else {
|
||||||
|
Self::$variant($items.into_iter().map(|it| it.simplify()).collect())
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpdxExpression {
|
||||||
|
fn simplify(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Req(_) => self,
|
||||||
|
Self::And(mut items) => spdx_expression_simplify_impl!(And, items),
|
||||||
|
Self::Or(mut items) => spdx_expression_simplify_impl!(Or, items),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Package {
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
fn version(&self) -> Option<&str>;
|
||||||
|
fn authors(&self) -> &[String];
|
||||||
|
fn website(&self) -> &str;
|
||||||
|
fn license(&self) -> &CrateLicense;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for dyn Package {
|
||||||
|
fn render(&self) -> Markup {
|
||||||
|
html! {
|
||||||
|
div .col-3 {
|
||||||
|
div .card {
|
||||||
|
div .card-body {
|
||||||
|
h4 .card-title {
|
||||||
|
a href=(self.website()) { (self.name()) }
|
||||||
|
@if let Some(version) = self.version() {
|
||||||
|
" " (version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul .list-group .list-group-flush {
|
||||||
|
@if self.authors().is_empty() {
|
||||||
|
li .list-group-item { em { "no authors specified in Cargo manifest" } }
|
||||||
|
}
|
||||||
|
@for author in self.authors() {
|
||||||
|
li .list-group-item { (author) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div .card-body.align-content-end {
|
||||||
|
@match self.license() {
|
||||||
|
CrateLicense::SpdxExpression(expr) => {
|
||||||
|
(SpdxExpression::from(*expr.clone()))
|
||||||
|
},
|
||||||
|
CrateLicense::Other(content) => {
|
||||||
|
@let modal_id = format!("license-modal-{}-{}", self.name(), self.version().unwrap_or(""));
|
||||||
|
button
|
||||||
|
.btn.btn-primary
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target={ "#" (modal_id) }
|
||||||
|
{ "custom license" }
|
||||||
|
div .modal.modal-lg #(modal_id) {
|
||||||
|
div .modal-dialog {
|
||||||
|
div .modal-content {
|
||||||
|
div.modal-header {
|
||||||
|
h1 .modal-title.fs-5 {
|
||||||
|
"custom license of "
|
||||||
|
(self.name())
|
||||||
|
@if let Some(version) = self.version() {
|
||||||
|
" " (version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button .btn-close type="button" data-bs-dismiss="modal";
|
||||||
|
}
|
||||||
|
div .modal-body {
|
||||||
|
pre style="text-wrap: wrap" { (content) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Package for embed_licensing::Crate {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> Option<&str> {
|
||||||
|
Some(&self.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authors(&self) -> &[String] {
|
||||||
|
&self.authors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn website(&self) -> &str {
|
||||||
|
&self.website
|
||||||
|
}
|
||||||
|
|
||||||
|
fn license(&self) -> &CrateLicense {
|
||||||
|
&self.license
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OtherPackage {
|
||||||
|
name: String,
|
||||||
|
website: String,
|
||||||
|
authors: Vec<String>,
|
||||||
|
license: CrateLicense,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Package for OtherPackage {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> Option<&str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn authors(&self) -> &[String] {
|
||||||
|
&self.authors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn website(&self) -> &str {
|
||||||
|
&self.website
|
||||||
|
}
|
||||||
|
|
||||||
|
fn license(&self) -> &CrateLicense {
|
||||||
|
&self.license
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/licensing")]
|
||||||
|
async fn licensing(user: Option<Identity>) -> impl Responder {
|
||||||
|
let cargo_licensing = embed_licensing::collect!();
|
||||||
|
|
||||||
|
let other_packages = [OtherPackage {
|
||||||
|
name: "Bootstrap".to_string(),
|
||||||
|
website: "https://getbootstrap.com/".to_string(),
|
||||||
|
authors: vec!["The Bootstrap Authors".to_string()],
|
||||||
|
license: CrateLicense::SpdxExpression(Box::new(spdx::Expression::parse("MIT").unwrap())),
|
||||||
|
}];
|
||||||
|
|
||||||
|
let mut licenses = cargo_licensing.licenses;
|
||||||
|
let mut exceptions = cargo_licensing.exceptions;
|
||||||
|
|
||||||
|
for other_package in &other_packages {
|
||||||
|
if let CrateLicense::SpdxExpression(expr) = &other_package.license {
|
||||||
|
for node in expr.iter() {
|
||||||
|
if let spdx::expression::ExprNode::Req(spdx::expression::ExpressionReq {
|
||||||
|
req,
|
||||||
|
..
|
||||||
|
}) = node
|
||||||
|
{
|
||||||
|
let license = req
|
||||||
|
.license
|
||||||
|
.id()
|
||||||
|
.expect("only SPDX license identifiers supported");
|
||||||
|
|
||||||
|
if !licenses.contains(&license) {
|
||||||
|
licenses.push(license)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(exception) = req.exception {
|
||||||
|
if !exceptions.contains(&exception) {
|
||||||
|
exceptions.push(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses.sort_unstable();
|
||||||
|
exceptions.sort_unstable();
|
||||||
|
|
||||||
|
templates::base(
|
||||||
|
templates::TemplateConfig {
|
||||||
|
path: "/licensing",
|
||||||
|
title: Some("Licensing"),
|
||||||
|
page_title: Some(Box::new("Licensing")),
|
||||||
|
user,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
html! {
|
||||||
|
h3 .mt-4 { "Rust Packages" }
|
||||||
|
|
||||||
|
div .row.g-2 {
|
||||||
|
@for package in cargo_licensing.packages {
|
||||||
|
(&package as &dyn Package)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 .mt-4 { "Other Packages" }
|
||||||
|
|
||||||
|
div .row.g-2 {
|
||||||
|
@for package in other_packages {
|
||||||
|
(&package as &dyn Package)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 .mt-4 { "Licenses" }
|
||||||
|
|
||||||
|
@for license in licenses {
|
||||||
|
h4 #{ "license-" (license.name) } .mt-3 { (license.full_name) " (" span .font-monospace { (license.name) } ")" }
|
||||||
|
|
||||||
|
pre style="text-wrap: wrap" { (license.text()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 .mt-4 { "Exceptions" }
|
||||||
|
|
||||||
|
@for exception in exceptions {
|
||||||
|
h4 #{ "exception-" (exception.name) } .mt-3.font-monospace { (exception.name) }
|
||||||
|
|
||||||
|
pre style="text-wrap: wrap" { (exception.text()) }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use spdx::{exception_id, license_id, LicenseItem, LicenseReq};
|
||||||
|
|
||||||
|
use super::SpdxExpression;
|
||||||
|
|
||||||
|
fn req(id: &str, or_later: bool, exception: Option<&str>) -> SpdxExpression {
|
||||||
|
SpdxExpression::Req(LicenseReq {
|
||||||
|
license: LicenseItem::Spdx {
|
||||||
|
id: license_id(id).unwrap(),
|
||||||
|
or_later,
|
||||||
|
},
|
||||||
|
exception: exception.map(exception_id).map(Option::unwrap),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spdx_expression_from_simple() {
|
||||||
|
assert_eq!(
|
||||||
|
SpdxExpression::from(spdx::Expression::parse("MIT").unwrap()),
|
||||||
|
req("MIT", false, None)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spdx_expression_from_complex() {
|
||||||
|
assert_eq!(
|
||||||
|
SpdxExpression::from(
|
||||||
|
spdx::Expression::parse("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT OR (CC0-1.0 AND Unlicense)")
|
||||||
|
.unwrap()
|
||||||
|
),
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
req("Apache-2.0", false, None),
|
||||||
|
req("MIT", false, None),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("CC0-1.0", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
]))
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spdx_expression_simplify_or() {
|
||||||
|
assert_eq!(
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
SpdxExpression::Or(BTreeSet::from([])),
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
req("MIT", false, None),
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
])),
|
||||||
|
])),
|
||||||
|
req("LGPL-2.1", true, None),
|
||||||
|
]))
|
||||||
|
.simplify(),
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
req("LGPL-2.1", true, None),
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spdx_expression_simplify_and() {
|
||||||
|
assert_eq!(
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
SpdxExpression::And(BTreeSet::from([])),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
])),
|
||||||
|
])),
|
||||||
|
req("LGPL-2.1", true, None),
|
||||||
|
]))
|
||||||
|
.simplify(),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
req("LGPL-2.1", true, None),
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_spdx_expression_simplify_mixed() {
|
||||||
|
assert_eq!(
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
])),
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
])),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("CC0-1.0", false, None),
|
||||||
|
req("GPL-3.0", false, None),
|
||||||
|
])),
|
||||||
|
req("LGPL-2.1", true, None),
|
||||||
|
]))
|
||||||
|
.simplify(),
|
||||||
|
SpdxExpression::Or(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Unlicense", false, None),
|
||||||
|
req("LGPL-2.1", true, None),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("MIT", false, None),
|
||||||
|
req("Apache-2.0", false, Some("LLVM-exception")),
|
||||||
|
])),
|
||||||
|
SpdxExpression::And(BTreeSet::from([
|
||||||
|
req("CC0-1.0", false, None),
|
||||||
|
req("GPL-3.0", false, None),
|
||||||
|
])),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ mod item;
|
||||||
mod item_class;
|
mod item_class;
|
||||||
mod jump;
|
mod jump;
|
||||||
mod labels;
|
mod labels;
|
||||||
|
mod licensing;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
|
|
||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
|
@ -19,7 +20,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
.configure(item::config)
|
.configure(item::config)
|
||||||
.configure(item_class::config)
|
.configure(item_class::config)
|
||||||
.configure(jump::config)
|
.configure(jump::config)
|
||||||
.configure(labels::config);
|
.configure(labels::config)
|
||||||
|
.configure(licensing::config);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
|
|
|
@ -59,7 +59,13 @@ fn footer() -> Markup {
|
||||||
html! {
|
html! {
|
||||||
footer .mt-auto.py-3.bg-body-tertiary.text-body-tertiary {
|
footer .mt-auto.py-3.bg-body-tertiary.text-body-tertiary {
|
||||||
div .container {
|
div .container {
|
||||||
p .mb-0 { "li7y is free software, released under the terms of the AGPL v3" }
|
p .mb-0 {
|
||||||
|
"li7y is free software, released under the terms of the AGPL v3 ("
|
||||||
|
a href="https://git.sbruder.de/simon/li7y" { "source code" }
|
||||||
|
", "
|
||||||
|
a href="/licensing" { "licensing of all dependencies" }
|
||||||
|
")"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
49
tests/licensing.rs
Normal file
49
tests/licensing.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
use actix_web::{body::MessageBody, test};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures("default"))]
|
||||||
|
async fn is_linked(pool: PgPool) {
|
||||||
|
let srv = test::init_service(li7y::app(&common::config(), &pool)).await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::get().uri("/login").to_request();
|
||||||
|
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
|
let body = String::from_utf8(res.into_body().try_into_bytes().unwrap().to_vec()).unwrap();
|
||||||
|
|
||||||
|
assert!(body.contains(r#"<a href="/licensing">"#));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sqlx::test(fixtures("default"))]
|
||||||
|
async fn contains_basic_information(pool: PgPool) {
|
||||||
|
let srv = test::init_service(li7y::app(&common::config(), &pool)).await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::get().uri("/licensing").to_request();
|
||||||
|
|
||||||
|
let res = test::call_service(&srv, req).await;
|
||||||
|
|
||||||
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
|
let body = String::from_utf8(res.into_body().try_into_bytes().unwrap().to_vec()).unwrap();
|
||||||
|
|
||||||
|
// crate names
|
||||||
|
assert!(body.contains(">actix-web<"));
|
||||||
|
assert!(body.contains(">sqlx-postgres<"));
|
||||||
|
|
||||||
|
// author names (only me and entries without email address)
|
||||||
|
assert!(body.contains(">Simon Bruder <simon@sbruder.de><"));
|
||||||
|
assert!(body.contains(">RustCrypto Developers<"));
|
||||||
|
|
||||||
|
// license links (only partial, as I don’t want to check class names)
|
||||||
|
assert!(body.contains(r##"href="#license-Apache-2.0">Apache-2.0<"##));
|
||||||
|
assert!(body.contains(r##"href="#license-MIT">MIT<"##));
|
||||||
|
}
|
Loading…
Reference in a new issue