1
Fork 0
userstyles/source/lib.rs

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());
}
}