24 Commits

Author SHA1 Message Date
Sunshine
3f0ced0143 Merge pull request #151 from snshn/2-2-2
version bump (2.2.1 → 2.2.2)
2020-04-05 14:44:48 -04:00
Sunshine
8112ab6d04 version bump (2.2.1 → 2.2.2) 2020-04-05 14:38:40 -04:00
Sunshine
e5fc05f5cd Merge pull request #150 from snshn/cd-windows-executable
Make the pipeline upload windows build to every new release
2020-04-05 14:35:44 -04:00
Sunshine
1068ff659a make the pipeline upload windows build to every new release 2020-04-05 14:29:06 -04:00
Sunshine
cf3a8c8ede Merge pull request #149 from snshn/remove-travis-ci-and-appveyor
Remove TravisCI and AppVeyor from the project
2020-04-04 19:38:03 -04:00
Sunshine
920d992459 remove TravisCI and AppVeyor from the project 2020-04-04 19:26:58 -04:00
Sunshine
c61b3ba858 Merge pull request #148 from snshn/github-actions-build
Improve GitHub Actions integration
2020-04-04 19:14:52 -04:00
Sunshine
dc6e564ea2 integrate GitHub Actions CI further 2020-04-04 19:05:49 -04:00
Sunshine
24536b5e18 Merge pull request #147 from Y2Z/github-actions-ci
Implement CI using GitHub Actions
2020-04-04 17:51:28 -04:00
Sunshine
908fd59019 Update ci.yml 2020-04-04 17:08:19 -04:00
Sunshine
a19aa37ea8 Merge pull request #145 from snshn/no-images-svg
Empty SVG nodes when excluding images
2020-04-04 15:55:26 -04:00
Sunshine
c46bd5900b Merge pull request #146 from snshn/image-map-area-href
Resolve hrefs of <area> image-map tags
2020-04-04 15:51:45 -04:00
Sunshine
5f98ed23b3 set autocrlf to false to let windows builds pass 2020-04-04 15:42:53 -04:00
Sunshine
c6b135398a Implement CI using GitHub Actions 2020-04-04 15:30:13 -04:00
Sunshine
791e44796e resolve hrefs of <area> image-map tags 2020-04-04 14:55:45 -04:00
Sunshine
b428dd8471 Merge pull request #144 from snshn/macros-unit-test
Implement unit tests for macros
2020-04-04 13:11:19 -04:00
Sunshine
b88479446c implement unit tests for macros 2020-04-04 08:21:41 -04:00
Sunshine
1d6217ef5a empty SVG nodes if --no-images 2020-04-03 21:56:46 -04:00
Sunshine
746c7f05de Merge pull request #143 from snshn/embed-input-images
Add support for image inputs
2020-04-03 04:12:06 -04:00
Sunshine
29836d979a add support for image inputs 2020-04-03 03:30:52 -04:00
Sunshine
5ba6e33fa8 Merge pull request #142 from snshn/robatipoors-improvements
Revamp is_icon() and get_node_name()
2020-04-03 01:39:45 -04:00
Sunshine
643c4ce7ef implement improvements suggested by @robatipoor 2020-04-03 00:00:08 -04:00
Sunshine
c011f90b76 Merge pull request #141 from snshn/update-help-dialog
Update help dialog
2020-04-02 22:49:59 -04:00
Sunshine
875481b9a2 update help dialog 2020-04-02 03:04:21 -04:00
22 changed files with 277 additions and 249 deletions

View File

@@ -1,131 +0,0 @@
# Appveyor configuration template for Rust using rustup for Rust installation
# https://github.com/starkat99/appveyor-rust
## Operating System (VM environment) ##
# Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets.
os: Visual Studio 2015
## Build Matrix ##
# This configuration will setup a build for each channel & target combination (12 windows
# combinations in all).
#
# There are 3 channels: stable, beta, and nightly.
#
# Alternatively, the full version may be specified for the channel to build using that specific
# version (e.g. channel: 1.5.0)
#
# The values for target are the set of windows Rust build targets. Each value is of the form
#
# ARCH-pc-windows-TOOLCHAIN
#
# Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker
# toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for
# a description of the toolchain differences.
# See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of
# toolchains and host triples.
#
# Comment out channel/target combos you do not wish to build in CI.
#
# You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands
# and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly
# channels to enable unstable features when building for nightly. Or you could add additional
# matrix entries to test different combinations of features.
environment:
matrix:
### MSVC Toolchains ###
# Stable 64-bit MSVC
- channel: stable
target: x86_64-pc-windows-msvc
# Stable 32-bit MSVC
- channel: stable
target: i686-pc-windows-msvc
# Beta 64-bit MSVC
- channel: beta
target: x86_64-pc-windows-msvc
# Beta 32-bit MSVC
- channel: beta
target: i686-pc-windows-msvc
# Nightly 64-bit MSVC
- channel: nightly
target: x86_64-pc-windows-msvc
cargoflags: --features "unstable"
# Nightly 32-bit MSVC
- channel: nightly
target: i686-pc-windows-msvc
cargoflags: --features "unstable"
### GNU Toolchains ###
# Stable 64-bit GNU
- channel: stable
target: x86_64-pc-windows-gnu
MINGW_PATH: 'C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin'
# Stable 32-bit GNU
- channel: stable
target: i686-pc-windows-gnu
MINGW_PATH: 'C:\MinGW\bin'
# Beta 64-bit GNU
- channel: beta
target: x86_64-pc-windows-gnu
MINGW_PATH: 'C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin'
# Beta 32-bit GNU
- channel: beta
target: i686-pc-windows-gnu
MINGW_PATH: 'C:\MinGW\bin'
# Nightly 64-bit GNU
- channel: nightly
target: x86_64-pc-windows-gnu
MINGW_PATH: 'C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev1\mingw64\bin'
cargoflags: --features "unstable"
# Nightly 32-bit GNU
- channel: nightly
target: i686-pc-windows-gnu
MINGW_PATH: 'C:\MinGW\bin'
cargoflags: --features "unstable"
### Allowed failures ###
# See Appveyor documentation for specific details. In short, place any channel or targets you wish
# to allow build failures on (usually nightly at least is a wise choice). This will prevent a build
# or test failure in the matching channels/targets from failing the entire build.
matrix:
allow_failures:
- channel: nightly
- channel: beta
# If you only care about stable channel build failures, uncomment the following line:
#- channel: beta
## Install Script ##
# This is the most important part of the Appveyor configuration. This installs the version of Rust
# specified by the 'channel' and 'target' environment variables from the build matrix. This uses
# rustup to install Rust.
#
# For simple configurations, instead of using the build matrix, you can simply set the
# default-toolchain and default-host manually here.
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %channel% --default-host %target%
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- if defined MINGW_PATH set PATH=%PATH%;%MINGW_PATH%
- rustc -vV
- cargo -vV
- rustup component add rustfmt
## Build Script ##
# 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents
# the "directory does not contain a project or solution file" error.
build: false
# Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs
# directly or perform other testing commands. Rust will automatically be placed in the PATH
# environment variable.
test_script:
- cargo build --all --locked --verbose %cargoflags%
- cargo test --all --locked --verbose %cargoflags%

24
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Build
on:
push:
branches: [ master ]
jobs:
build:
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
rust:
- stable
runs-on: ${{ matrix.os }}
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v2
- name: Build
run: cargo build --all --locked --verbose

23
.github/workflows/cd.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: CD
on:
release:
types: [created]
jobs:
windows:
runs-on: windows-latest
steps:
- run: git config --global core.autocrlf false
- name: Checkout the repository
uses: actions/checkout@master
- name: Build the executable
run: cargo build --all --locked
- name: Perform local installation
run: cargo install --force --locked --path .
- uses: Shopify/upload-to-release@1.0.0
with:
name: monolith.exe
path: C:\Users\runneradmin\.cargo\bin\monolith.exe
repo-token: ${{ secrets.GITHUB_TOKEN }}

30
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: CI
on:
pull_request:
branches: [ master ]
jobs:
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: Build
run: cargo build --all --locked --verbose
- name: Run tests
run: cargo test --all --locked --verbose
- name: Check code formatting
run: cargo fmt --all -- --check

View File

@@ -1,37 +0,0 @@
language: rust
cache: cargo
os:
- linux
- osx
- windows
rust:
- stable
- beta
- nightly
git:
autocrlf: false # don't mangle LF into CRLF on windows
before_script:
- rustup component add rustfmt
script:
- cargo build --all --locked --verbose
- cargo test --all --locked --verbose
- |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo fmt --all -- --check
fi
after_success: |
if [ "${TRAVIS_OS_NAME}" == linux ] && [ "${TRAVIS_RUST_VERSION}" == stable ]; then
docker build -t monolith .
docker run monolith monolith -V
fi
jobs:
allow_failures:
- rust: nightly
fast_finish: true

2
Cargo.lock generated
View File

@@ -568,7 +568,7 @@ dependencies = [
[[package]]
name = "monolith"
version = "2.2.1"
version = "2.2.2"
dependencies = [
"assert_cmd 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@@ -1,6 +1,6 @@
[package]
name = "monolith"
version = "2.2.1"
version = "2.2.2"
edition = "2018"
authors = [
"Sunshine <sunshine@uberspace.net>",

View File

@@ -1,21 +1,25 @@
#!/usr/bin/make -f
# Makefile for monolith
all: test
all: build
.PHONY: all
build:
@cargo build --locked
.PHONY: build
install:
@cargo install --force --locked --path .
.PHONY: install
test: build
@cargo test --locked
@cargo fmt --all -- --check
.PHONY: test
.PHONY: test_code_formatting
lint:
@cargo fmt --all --
.PHONY: lint
install:
@cargo install --force --locked --path .
.PHONY: install
uninstall:
@cargo uninstall
.PHONY: uninstall

View File

@@ -1,5 +1,4 @@
[![Travis CI Build Status](https://travis-ci.org/Y2Z/monolith.svg?branch=master)](https://travis-ci.org/Y2Z/monolith)
[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/ae7soyjih8jg2bv7/branch/master?svg=true)](https://ci.appveyor.com/project/snshn/monolith/branch/master)
[![GitHub Actions Build Status](https://github.com/Y2Z/monolith/workflows/Build/badge.svg)](https://github.com/Y2Z/monolith/actions?query=workflow%3ABuild)
```
___ ___________ __________ ___________________ ___
@@ -21,11 +20,6 @@ If compared to saving websites with `wget -mpk`, this tool embeds all assets as
## Installation
#### From source
$ git clone https://github.com/Y2Z/monolith.git
$ cd monolith
$ make install
#### With Homebrew (on macOS and GNU/Linux)
$ brew install monolith
@@ -35,6 +29,11 @@ If compared to saving websites with `wget -mpk`, this tool embeds all assets as
#### Via Docker
The guide can be found [here](docs/containers.md)
#### From source
$ git clone https://github.com/Y2Z/monolith.git
$ cd monolith
$ make install
---------------------------------------------------
## Usage

View File

@@ -2,7 +2,7 @@ use clap::{App, Arg};
#[derive(Default)]
pub struct AppArgs {
pub url_target: String,
pub target: String,
pub no_css: bool,
pub no_frames: bool,
pub no_images: bool,
@@ -26,11 +26,11 @@ impl AppArgs {
.author(crate_authors!("\n"))
.about(crate_description!())
.arg(
Arg::with_name("url")
Arg::with_name("target")
.required(true)
.takes_value(true)
.index(1)
.help("URL to download"),
.help("URL or file path"),
)
// .args_from_usage("-a, --include-audio 'Removes audio sources'")
.args_from_usage("-c, --no-css 'Removes CSS'")
@@ -47,9 +47,9 @@ impl AppArgs {
.get_matches();
let mut app_args = AppArgs::default();
// Process the command
app_args.url_target = app
.value_of("url")
.expect("please set target url")
app_args.target = app
.value_of("target")
.expect("please set target")
.to_string();
app_args.no_css = app.is_present("no-css");
app_args.no_frames = app.is_present("no-frames");

View File

@@ -18,9 +18,6 @@ const CSS_PROPS_WITH_IMAGE_URLS: &[&str] = &[
"mask-image",
];
const TRANSPARENT_PIXEL: &str = "data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
pub fn is_image_url_prop(prop_name: &str) -> bool {
CSS_PROPS_WITH_IMAGE_URLS
.iter()
@@ -185,7 +182,7 @@ pub fn process_css<'a>(
}
if opt_no_images && is_image_url_prop(curr_prop.as_str()) {
result.push_str(enquote(str!(TRANSPARENT_PIXEL), false).as_str());
result.push_str(enquote(str!(empty_image!()), false).as_str());
} else {
let resolved_url = resolve_url(&parent_url, value).unwrap_or_default();
let (data_url, _final_url) = retrieve_asset(
@@ -294,7 +291,7 @@ pub fn process_css<'a>(
);
} else {
if opt_no_images && is_image_url_prop(curr_prop.as_str()) {
result.push_str(enquote(str!(TRANSPARENT_PIXEL), false).as_str());
result.push_str(enquote(str!(empty_image!()), false).as_str());
} else {
let full_url = resolve_url(&parent_url, value).unwrap_or_default();
let (data_url, _final_url) = retrieve_asset(

View File

@@ -20,26 +20,20 @@ const ICON_VALUES: &[&str] = &[
"fluid-icon",
];
const TRANSPARENT_PIXEL: &str = "data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
pub fn get_parent_node(node: &Handle) -> Handle {
let parent = node.parent.take().clone();
parent.and_then(|node| node.upgrade()).unwrap()
}
pub fn get_node_name(node: &Handle) -> &'_ str {
pub fn get_node_name(node: &Handle) -> Option<&'_ str> {
match &node.data {
NodeData::Element { ref name, .. } => name.local.as_ref(),
_ => "",
NodeData::Element { ref name, .. } => Some(name.local.as_ref()),
_ => None,
}
}
pub fn is_icon(attr_value: &str) -> bool {
ICON_VALUES
.iter()
.find(|a| attr_value.eq_ignore_ascii_case(a))
.is_some()
ICON_VALUES.contains(&attr_value.to_lowercase().as_str())
}
pub fn walk_and_embed_assets(
@@ -273,7 +267,7 @@ pub fn walk_and_embed_assets(
if opt_no_images {
attrs_mut.push(Attribute {
name: QualName::new(None, ns!(), local_name!("src")),
value: Tendril::from_slice(TRANSPARENT_PIXEL),
value: Tendril::from_slice(empty_image!()),
});
} else if let Some((data_url, _)) = found_datasrc
.iter()
@@ -300,6 +294,63 @@ pub fn walk_and_embed_assets(
});
}
}
"input" => {
let mut is_image: bool = false;
for attr in attrs_mut.iter_mut() {
let attr_name: &str = &attr.name.local;
if attr_name == "type" {
is_image = attr.value.to_string().eq_ignore_ascii_case("image");
}
}
if is_image {
let mut found_src: Option<Attribute> = None;
let mut i = 0;
while i < attrs_mut.len() {
let attr_name = attrs_mut[i].name.local.as_ref();
if attr_name.eq_ignore_ascii_case("src") {
found_src = Some(attrs_mut.remove(i));
} else {
i += 1;
}
}
// If images are disabled, clear both sources
if opt_no_images {
attrs_mut.push(Attribute {
name: QualName::new(None, ns!(), local_name!("src")),
value: Tendril::from_slice(empty_image!()),
});
} else if let Some((data_url, _)) = found_src
.iter()
.map(|attr| attr.value.trim())
.filter(|src| !src.is_empty()) // Skip if empty
.next()
.and_then(|src| resolve_url(&url, src).ok()) // Make absolute
.and_then(|abs_src| // Download and convert to data_url
retrieve_asset(
cache,
client,
&url,
&abs_src,
true,
"",
opt_silent,
).ok())
{
// Add new data_url src attribute
attrs_mut.push(Attribute {
name: QualName::new(None, ns!(), local_name!("src")),
value: Tendril::from_slice(data_url.as_ref()),
});
}
}
}
"svg" => {
if opt_no_images {
node.children.borrow_mut().clear();
}
}
"source" => {
for attr in attrs_mut.iter_mut() {
let attr_name: &str = &attr.name.local;
@@ -310,10 +361,10 @@ pub fn walk_and_embed_assets(
attr.value.clear();
attr.value.push_slice(src_full_url.as_str());
} else if attr_name == "srcset" {
if get_node_name(&get_parent_node(&node)) == "picture" {
if get_node_name(&get_parent_node(&node)) == Some("picture") {
if opt_no_images {
attr.value.clear();
attr.value.push_slice(TRANSPARENT_PIXEL);
attr.value.push_slice(empty_image!());
} else {
let srcset_full_url =
resolve_url(&url, attr.value.trim()).unwrap_or_default();
@@ -334,7 +385,7 @@ pub fn walk_and_embed_assets(
}
}
}
"a" => {
"a" | "area" => {
for attr in attrs_mut.iter_mut() {
if &attr.name.local == "href" {
let attr_value = attr.value.trim();

View File

@@ -7,3 +7,11 @@ macro_rules! str {
ToString::to_string(&$val)
};
}
#[macro_export]
macro_rules! empty_image {
() => {
"data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAQAAADY4iz3AAAAEUlEQVR42mNkwAkYR6UolgIACvgADsuK6xYAAAAASUVORK5CYII="
};
}

View File

@@ -47,7 +47,7 @@ impl Output {
fn main() {
let app_args = AppArgs::get();
let original_target: &str = &app_args.url_target;
let original_target: &str = &app_args.target;
let target_url: &str;
let base_url;
let dom;

View File

@@ -162,15 +162,18 @@ fn passing_remove_images_from_data_url() -> Result<(), Box<dyn std::error::Error
// STDOUT should contain HTML with no images
assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(),
"<html>\
format!(
"<html>\
<head>\
<meta http-equiv=\"Content-Security-Policy\" content=\"img-src data:;\"></meta>\
</head>\
<body>\
<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\
<img src=\"{empty_image}\">\
Hi\
</body>\
</html>\n"
</html>\n",
empty_image = empty_image!()
)
);
// STDERR should be empty
@@ -229,7 +232,8 @@ fn passing_local_file_target_input() -> Result<(), Box<dyn std::error::Error>> {
// STDOUT should contain HTML from the local file
assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(),
"<!DOCTYPE html><html lang=\"en\"><head>\n \
"\
<!DOCTYPE html><html lang=\"en\"><head>\n \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \
<title>Local HTML file</title>\n \
<link href=\"data:text/css;base64,Ym9keSB7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogICAgY29sb3I6ICNmZmY7Cn0K\" rel=\"stylesheet\" type=\"text/css\">\n \
@@ -238,16 +242,19 @@ fn passing_local_file_target_input() -> Result<(), Box<dyn std::error::Error>> {
<a href=\"file://local-file.html/\">Tricky href</a>\n \
<a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \
<script src=\"data:application/javascript;base64,ZG9jdW1lbnQuYm9keS5zdHlsZS5iYWNrZ3JvdW5kQ29sb3IgPSAiZ3JlZW4iOwpkb2N1bWVudC5ib2R5LnN0eWxlLmNvbG9yID0gInJlZCI7Cg==\"></script>\n\n\n\n\
</body></html>\n"
</body></html>\n\
"
);
// STDERR should contain list of retrieved file URLs
assert_eq!(
std::str::from_utf8(&out.stderr).unwrap(),
format!(
"{file}{cwd}/src/tests/data/local-file.html\n\
"\
{file}{cwd}/src/tests/data/local-file.html\n\
{file}{cwd}/src/tests/data/local-style.css\n\
{file}{cwd}/src/tests/data/local-script.js\n",
{file}{cwd}/src/tests/data/local-script.js\n\
",
file = file_url_protocol,
cwd = cwd_normalized
)
@@ -286,17 +293,22 @@ fn passing_local_file_target_input_absolute_target_path() -> Result<(), Box<dyn
// STDOUT should contain HTML from the local file
assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(),
"<!DOCTYPE html><html lang=\"en\"><head>\
format!(
"\
<!DOCTYPE html><html lang=\"en\"><head>\
<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'unsafe-inline' data:; style-src 'none'; script-src 'none'; img-src data:;\"></meta>\n \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \
<title>Local HTML file</title>\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n</head>\n\n<body>\n \
<img alt=\"\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\n \
<img alt=\"\" src=\"{empty_image}\">\n \
<a href=\"file://local-file.html/\">Tricky href</a>\n \
<a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \
<script src=\"\"></script>\n\n\n\n\
</body></html>\n"
</body></html>\n\
",
empty_image = empty_image!()
)
);
// STDERR should contain only the target file
@@ -342,17 +354,22 @@ fn passing_local_file_url_target_input() -> Result<(), Box<dyn std::error::Error
// STDOUT should contain HTML from the local file
assert_eq!(
std::str::from_utf8(&out.stdout).unwrap(),
"<!DOCTYPE html><html lang=\"en\"><head>\
format!(
"\
<!DOCTYPE html><html lang=\"en\"><head>\
<meta http-equiv=\"Content-Security-Policy\" content=\"style-src 'none'; script-src 'none'; img-src data:;\"></meta>\n \
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n \
<title>Local HTML file</title>\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n \
<link href=\"\" rel=\"stylesheet\" type=\"text/css\">\n</head>\n\n<body>\n \
<img alt=\"\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\n \
<img alt=\"\" src=\"{empty_image}\">\n \
<a href=\"file://local-file.html/\">Tricky href</a>\n \
<a href=\"https://github.com/Y2Z/monolith\">Remote URL</a>\n \
<script src=\"\"></script>\n\n\n\n\
</body></html>\n"
</body></html>\n\
",
empty_image = empty_image!()
)
);
// STDERR should contain list of retrieved file URLs
@@ -458,7 +475,8 @@ fn passing_css_import_string() -> Result<(), Box<dyn std::error::Error>> {
let mut file_html = NamedTempFile::new()?;
writeln!(
file_html,
"<style>\n\
"\
<style>\n\
@charset 'UTF-8';\n\
\n\
@import '{file}{css_path}';\n\
@@ -466,7 +484,8 @@ fn passing_css_import_string() -> Result<(), Box<dyn std::error::Error>> {
@import url({file}{css_path});\n\
\n\
@import url('{file}{css_path}')\n\
</style>\n",
</style>\n\
",
file = file_url_prefix,
css_path = str!(file_css.path().to_str().unwrap()).replace("\\", "/"),
)?;

View File

@@ -40,13 +40,16 @@ height: calc(100vh - 10pt)";
true,
true,
),
"/* border: none;*/\
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='); \
list-style: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');\
format!(
"/* border: none;*/\
background-image: url('{empty_image}'); \
list-style: url('{empty_image}');\
width:99.998%; \
margin-top: -20px; \
line-height: -1; \
height: calc(100vh - 10pt)"
height: calc(100vh - 10pt)",
empty_image = empty_image!()
)
);
}
@@ -64,21 +67,17 @@ line-height: -1; \
height: calc(100vh - 10pt)";
assert_eq!(
css::embed_css(
cache,
&client,
"",
&STYLE,
true,
true,
),
"/* border: none;*/\
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='); \
list-style: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');\
css::embed_css(cache, &client, "", &STYLE, true, true,),
format!(
"/* border: none;*/\
background-image: url('{empty_image}'); \
list-style: url('{empty_image}');\
width:99.998%; \
margin-top: -20px; \
line-height: -1; \
height: calc(100vh - 10pt)"
height: calc(100vh - 10pt)",
empty_image = empty_image!()
)
);
}

View File

@@ -28,11 +28,11 @@ fn get_node_name() {
let parent = html::get_parent_node(node);
let parent_node_name = html::get_node_name(&parent);
if node_name == "head" || node_name == "body" {
assert_eq!(parent_node_name, "html");
assert_eq!(parent_node_name, Some("html"));
} else if node_name == "div" {
assert_eq!(parent_node_name, "body");
assert_eq!(parent_node_name, Some("body"));
} else if node_name == "p" {
assert_eq!(parent_node_name, "div");
assert_eq!(parent_node_name, Some("div"));
}
for child in node.children.borrow().iter() {

View File

@@ -197,18 +197,19 @@ fn passing_no_images() {
assert_eq!(
buf.iter().map(|&c| c as char).collect::<String>(),
"<html>\
format!(
"<html>\
<head>\
<link rel=\"icon\" href=\"\">\
</head>\
<body>\
<div>\
<img src=\"data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0\
lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\">\
<img src=\"{empty_image}\">\
</div>\
</body>\
</html>"
</html>",
empty_image = empty_image!()
)
);
}

View File

@@ -0,0 +1,14 @@
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
#[cfg(test)]
mod passing {
#[test]
fn contains_correct_image_data() {
assert_eq!(empty_image!(), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAQAAADY4iz3AAAAEUlEQVR42mNkwAkYR6UolgIACvgADsuK6xYAAAAASUVORK5CYII=");
}
}

2
src/tests/macros/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod empty_image;
mod str;

24
src/tests/macros/str.rs Normal file
View File

@@ -0,0 +1,24 @@
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
#[cfg(test)]
mod passing {
#[test]
fn returns_empty_string() {
assert_eq!(str!(), "");
}
#[test]
fn converts_integer_into_string() {
assert_eq!(str!(123), "123");
}
#[test]
fn converts_str_into_string() {
assert_eq!(str!("abc"), "abc");
}
}

View File

@@ -2,4 +2,5 @@ mod cli;
mod css;
mod html;
mod js;
mod macros;
mod utils;