145 lines
3.5 KiB
Rust
145 lines
3.5 KiB
Rust
#![forbid(unsafe_code)]
|
|
#![warn(missing_docs, clippy::missing_docs_in_private_items)]
|
|
|
|
//! # https://bauke.xyz/userstyles
|
|
|
|
use serde::Deserialize;
|
|
|
|
/// All potential errors that could occur.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum Error {
|
|
/// [`std::io::Error`]
|
|
#[error(transparent)]
|
|
Io(#[from] std::io::Error),
|
|
/// [`serde_json::Error`]
|
|
#[error(transparent)]
|
|
Json(#[from] serde_json::Error),
|
|
}
|
|
|
|
/// All available userstyles as a const, to easily iterate over them.
|
|
pub const ALL_USERSTYLES: &[Userstyles] =
|
|
&[Userstyles::TildesBaukula, Userstyles::TildesCompact];
|
|
|
|
/// All available userstyles.
|
|
#[derive(Debug)]
|
|
pub enum Userstyles {
|
|
/// Tildes Baukula
|
|
TildesBaukula,
|
|
/// Tildes Compact
|
|
TildesCompact,
|
|
}
|
|
|
|
/// Userstyle data.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct Userstyle {
|
|
/// Compiled CSS.
|
|
pub css: String,
|
|
/// An optional image.
|
|
pub image: Option<Vec<u8>>,
|
|
/// Metadata of the userstyle.
|
|
pub metadata: UserstyleMetadata,
|
|
}
|
|
|
|
/// Userstyle metadata.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct UserstyleMetadata {
|
|
/// The name.
|
|
pub name: String,
|
|
/// The namespace.
|
|
pub namespace: String,
|
|
/// The version.
|
|
pub version: String,
|
|
/// The author.
|
|
pub author: String,
|
|
/// The description.
|
|
pub description: String,
|
|
/// The homepage URL.
|
|
#[serde(rename = "homepageURL")]
|
|
pub homepage_url: String,
|
|
/// The update URL.
|
|
#[serde(rename = "updateURL")]
|
|
pub update_url: String,
|
|
/// The license.
|
|
pub license: String,
|
|
/// The domain for `@-moz-document domain`.
|
|
pub domain: String,
|
|
}
|
|
|
|
impl Userstyle {
|
|
/// Formats a [`Userstyle`] in the expected `.user.css` format.
|
|
pub fn format(&self) -> String {
|
|
let mut css = String::new();
|
|
for line in self.css.lines() {
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
css += &format!(" {}\n", line);
|
|
}
|
|
|
|
format!(
|
|
r#"/* ==UserStyle==
|
|
@name {name}
|
|
@namespace {namespace}
|
|
@version {version}
|
|
@author {author}
|
|
@description {description}
|
|
@homepageURL {homepage_url}
|
|
@updateURL {update_url}
|
|
@license {license}
|
|
==/UserStyle== */
|
|
|
|
@-moz-document domain("{domain}") {{
|
|
{css}
|
|
}}
|
|
"#,
|
|
name = self.metadata.name,
|
|
namespace = self.metadata.namespace,
|
|
version = self.metadata.version,
|
|
author = self.metadata.author,
|
|
description = self.metadata.description,
|
|
homepage_url = self.metadata.homepage_url,
|
|
update_url = self.metadata.update_url,
|
|
license = self.metadata.license,
|
|
domain = self.metadata.domain,
|
|
css = css.trim_end(),
|
|
)
|
|
}
|
|
|
|
/// Returns a [`Userstyle`] from a given `target` [`Userstyles`].
|
|
pub fn load(target: &Userstyles) -> Result<Self, Error> {
|
|
let (css, image, metadata) = match target {
|
|
Userstyles::TildesBaukula => (
|
|
include_str!("../target/userstyles/tildes-baukula.css"),
|
|
Some(
|
|
include_bytes!("tildes-baukula/images/tildes-baukula-logo.png")
|
|
.to_vec(),
|
|
),
|
|
include_str!("tildes-baukula/metadata.json"),
|
|
),
|
|
Userstyles::TildesCompact => (
|
|
include_str!("../target/userstyles/tildes-compact.css"),
|
|
Some(
|
|
include_bytes!("tildes-compact/images/tildes-compact-logo.png")
|
|
.to_vec(),
|
|
),
|
|
include_str!("tildes-compact/metadata.json"),
|
|
),
|
|
};
|
|
|
|
Ok(Self {
|
|
css: css.to_string(),
|
|
image,
|
|
metadata: serde_json::from_str(metadata)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Assert that all metadata.json files parse successfully.
|
|
#[test]
|
|
fn test_metadata_parsing() {
|
|
for target in ALL_USERSTYLES {
|
|
assert!(Userstyle::load(target).is_ok());
|
|
}
|
|
}
|