4 Commits

Author SHA1 Message Date
Sunshine
4a5f85ba97 add support for parsing multiple link type (rel) attribute values 2024-12-02 11:34:44 -10:00
Sunshine
2a8d5d7916 add NetBSD CI job 2024-09-25 19:15:45 -04:00
Sunshine
95645fcff4 clean up and format code 2024-09-25 19:15:45 -04:00
Sunshine
b6b358b3bc correct distro codename in apt sources list 2024-09-05 13:56:49 -10:00
12 changed files with 142 additions and 47 deletions

View File

@@ -1 +0,0 @@
docs/arch

View File

@@ -6,9 +6,7 @@ on:
paths-ignore:
- 'assets/'
- 'dist/'
- 'docs/'
- 'snap/'
- '.adr-dir'
- 'Dockerfile'
- 'LICENSE'
- 'Makefile'

View File

@@ -6,9 +6,7 @@ on:
paths-ignore:
- 'assets/'
- 'dist/'
- 'docs/'
- 'snap/'
- '.adr-dir'
- 'Dockerfile'
- 'LICENSE'
- 'Makefile'

View File

@@ -6,9 +6,7 @@ on:
paths-ignore:
- 'assets/'
- 'dist/'
- 'docs/'
- 'snap/'
- '.adr-dir'
- 'Dockerfile'
- 'LICENSE'
- 'Makefile'

View File

@@ -36,7 +36,7 @@ jobs:
run: |
sudo mkdir /cross-build
sudo touch /etc/apt/sources.list.d/armhf.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ bionic main" | sudo tee -a /etc/apt/sources.list.d/armhf.list
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ focal main" | sudo tee -a /etc/apt/sources.list.d/armhf.list
sudo apt-get update
sudo apt-get install -y gcc-arm-linux-gnueabihf libc6-armhf-cross libc6-dev-armhf-cross
sudo apt-get download libssl1.1:armhf libssl-dev:armhf
@@ -69,7 +69,7 @@ jobs:
run: |
sudo mkdir /cross-build
sudo touch /etc/apt/sources.list.d/arm64.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic main" | sudo tee -a /etc/apt/sources.list.d/arm64.list
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ focal main" | sudo tee -a /etc/apt/sources.list.d/arm64.list
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu libc6-arm64-cross libc6-dev-arm64-cross
sudo apt-get download libssl1.1:arm64 libssl-dev:arm64

34
.github/workflows/ci-netbsd.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
# CI NetBSD GitHub Actions workflow for monolith
name: CI
on:
pull_request:
branches: [ master ]
paths-ignore:
- 'assets/'
- 'dist/'
- 'snap/'
- 'Dockerfile'
- 'LICENSE'
- 'Makefile'
- 'monolith.nuspec'
- 'README.md'
jobs:
build_and_test:
runs-on: ubuntu-latest
name: Build and test (netbsd)
steps:
- name: "Checkout repository"
uses: actions/checkout@v4
- name: Test in NetBSD
uses: vmactions/netbsd-vm@v1
with:
usesh: true
prepare: |
/usr/sbin/pkg_add rust mktools gmake pkgconf cwrappers
run: |
cargo build --all --locked --verbose --no-default-features
cargo test --all --locked --verbose --no-default-features

View File

@@ -8,9 +8,7 @@ on:
paths-ignore:
- 'assets/'
- 'dist/'
- 'docs/'
- 'snap/'
- '.adr-dir'
- 'Dockerfile'
- 'LICENSE'
- 'Makefile'
@@ -19,23 +17,19 @@ on:
jobs:
build_and_test:
name: Build and test
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
rust:
- stable
- beta
- nightly
runs-on: ${{ matrix.os }}
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v2
- name: "Checkout repository"
uses: actions/checkout@v4
- name: Build
run: cargo build --all --locked --verbose

View File

@@ -51,7 +51,7 @@ scoop install main/monolith
#### Via [Winget](https://winstall.app/apps/Y2Z.Monolith) (Windows)
```console
winget install --id=Y2Z.Monolith -e
winget install --id=Y2Z.Monolith -e
```
#### Via [MacPorts](https://ports.macports.org/port/monolith/summary) (macOS)

View File

@@ -1,10 +1,10 @@
#!/bin/sh
DOCKER=docker
PROG_NAME=monolith
if which podman 2>&1 > /dev/null; then
DOCKER=podman
fi
ORG_NAME=y2z
PROG_NAME=monolith
$DOCKER run --rm y2z/$PROG_NAME "$@"
$DOCKER run --rm $ORG_NAME/$PROG_NAME "$@"

View File

@@ -23,6 +23,15 @@ use crate::url::{
};
use crate::utils::{parse_content_type, retrieve_asset};
#[derive(PartialEq, Eq)]
pub enum LinkType {
Alternate,
DnsPrefetch,
Icon,
Preload,
Stylesheet,
}
struct SrcSetItem<'a> {
path: &'a str,
descriptor: &'a str,
@@ -141,26 +150,6 @@ pub fn create_metadata_tag(url: &Url) -> String {
)
}
pub fn determine_link_node_type(node: &Handle) -> &str {
let mut link_type: &str = "unknown";
if let Some(link_attr_rel_value) = get_node_attr(node, "rel") {
if is_icon(&link_attr_rel_value) {
link_type = "icon";
} else if link_attr_rel_value.eq_ignore_ascii_case("stylesheet")
|| link_attr_rel_value.eq_ignore_ascii_case("alternate stylesheet")
{
link_type = "stylesheet";
} else if link_attr_rel_value.eq_ignore_ascii_case("preload") {
link_type = "preload";
} else if link_attr_rel_value.eq_ignore_ascii_case("dns-prefetch") {
link_type = "dns-prefetch";
}
}
link_type
}
pub fn embed_srcset(
cache: &mut HashMap<String, Vec<u8>>,
client: &Client,
@@ -454,6 +443,26 @@ pub fn is_icon(attr_value: &str) -> bool {
ICON_VALUES.contains(&attr_value.to_lowercase().as_str())
}
pub fn parse_link_type(link_attr_rel_value: &str) -> Vec<LinkType> {
let mut types: Vec<LinkType> = vec![];
for link_attr_rel_type in link_attr_rel_value.split_whitespace() {
if link_attr_rel_type.eq_ignore_ascii_case("alternate") {
types.push(LinkType::Alternate);
} else if link_attr_rel_type.eq_ignore_ascii_case("dns-prefetch") {
types.push(LinkType::DnsPrefetch);
} else if link_attr_rel_type.eq_ignore_ascii_case("preload") {
types.push(LinkType::Preload);
} else if link_attr_rel_type.eq_ignore_ascii_case("stylesheet") {
types.push(LinkType::Stylesheet);
} else if is_icon(&link_attr_rel_type) {
types.push(LinkType::Icon);
}
}
types
}
pub fn set_base_url(document: &Handle, desired_base_href: String) -> RcDom {
let mut buf: Vec<u8> = Vec::new();
serialize(
@@ -665,7 +674,10 @@ pub fn retrieve_and_embed_asset(
s = String::from_utf8_lossy(&data).to_string();
}
if node_name == "link" && determine_link_node_type(node) == "stylesheet" {
if node_name == "link"
&& parse_link_type(&get_node_attr(node, "rel").unwrap_or(String::from("")))
.contains(&LinkType::Stylesheet)
{
// Stylesheet LINK elements require special treatment
let css: String = embed_css(cache, client, &final_url, &s, options);
@@ -757,9 +769,10 @@ pub fn walk_and_embed_assets(
}
}
"link" => {
let link_type: &str = determine_link_node_type(node);
let link_node_types: Vec<LinkType> =
parse_link_type(&get_node_attr(node, "rel").unwrap_or(String::from("")));
if link_type == "icon" {
if link_node_types.contains(&LinkType::Icon) {
// Find and resolve LINK's href attribute
if let Some(link_attr_href_value) = get_node_attr(node, "href") {
if !options.no_images && !link_attr_href_value.is_empty() {
@@ -776,7 +789,7 @@ pub fn walk_and_embed_assets(
set_node_attr(node, "href", None);
}
}
} else if link_type == "stylesheet" {
} else if link_node_types.contains(&LinkType::Stylesheet) {
// Resolve LINK's href attribute
if let Some(link_attr_href_value) = get_node_attr(node, "href") {
if options.no_css {
@@ -797,7 +810,9 @@ pub fn walk_and_embed_assets(
}
}
}
} else if link_type == "preload" || link_type == "dns-prefetch" {
} else if link_node_types.contains(&LinkType::Preload)
|| link_node_types.contains(&LinkType::DnsPrefetch)
{
// Since all resources are embedded as data URLs, preloading and prefetching are not necessary
set_node_attr(node, "rel", None);
} else {

View File

@@ -9,6 +9,7 @@ mod get_node_attr;
mod get_node_name;
mod has_favicon;
mod is_icon;
mod parse_link_type;
mod serialize_document;
mod set_node_attr;
mod walk_and_embed_assets;

View File

@@ -0,0 +1,58 @@
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
#[cfg(test)]
mod passing {
use monolith::html;
#[test]
fn icon() {
assert!(html::parse_link_type("icon").contains(&html::LinkType::Icon));
}
#[test]
fn shortcut_icon_capitalized() {
assert!(html::parse_link_type("Shortcut Icon").contains(&html::LinkType::Icon));
}
#[test]
fn stylesheet() {
assert!(html::parse_link_type("stylesheet").contains(&html::LinkType::Stylesheet));
}
#[test]
fn preload_stylesheet() {
assert!(html::parse_link_type("preload stylesheet").contains(&html::LinkType::Stylesheet));
}
}
// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗
// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝
// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗
// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
#[cfg(test)]
mod failing {
use monolith::html;
#[test]
fn mask_icon() {
assert!(html::parse_link_type("mask-icon").is_empty());
}
#[test]
fn fluid_icon() {
assert!(html::parse_link_type("fluid-icon").is_empty());
}
#[test]
fn empty_string() {
assert!(html::parse_link_type("").is_empty());
}
}