Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
trivernis | ce20464337 | 1 year ago |
trivernis | 3c22c723ad | 1 year ago |
@ -0,0 +1,56 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use async_walkdir::{Filtering, WalkDir};
|
||||||
|
use futures::{future, StreamExt};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct TemplateName(String);
|
||||||
|
|
||||||
|
impl AsRef<str> for TemplateName {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Page {
|
||||||
|
template: TemplateName,
|
||||||
|
#[serde(flatten)]
|
||||||
|
data: HashMap<String, toml::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ContentLoader {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentLoader {
|
||||||
|
pub fn new(path: PathBuf) -> Self {
|
||||||
|
Self { path }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_pages(&self) -> Vec<Page> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_files(&self) -> Vec<PathBuf> {
|
||||||
|
WalkDir::new(&self.path)
|
||||||
|
.filter(|e| async move {
|
||||||
|
e.path()
|
||||||
|
.extension()
|
||||||
|
.map(|e| {
|
||||||
|
if e == "toml" {
|
||||||
|
Filtering::Continue
|
||||||
|
} else {
|
||||||
|
Filtering::Ignore
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(Filtering::Ignore)
|
||||||
|
})
|
||||||
|
.map(|e| e.expect("failed to read dir").path())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_page(path: PathBuf) {}
|
@ -1,8 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
pub struct Context {
|
|
||||||
pub content_dir: PathBuf,
|
|
||||||
pub template_dir: PathBuf,
|
|
||||||
pub stylesheet_dir: PathBuf,
|
|
||||||
pub output_dir: PathBuf,
|
|
||||||
}
|
|
@ -1,124 +0,0 @@
|
|||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use async_walkdir::WalkDir;
|
|
||||||
use futures::StreamExt;
|
|
||||||
use globset::{Glob, GlobSetBuilder};
|
|
||||||
use miette::{Context, IntoDiagnostic, Result};
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use super::IndexData;
|
|
||||||
|
|
||||||
/// loads directory data
|
|
||||||
pub struct DirLoader {
|
|
||||||
base_path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct FolderData {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub index: IndexData,
|
|
||||||
pub pages: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirLoader {
|
|
||||||
pub fn new(base_path: PathBuf) -> Self {
|
|
||||||
Self { base_path }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asynchronously reads all the entries at the given content location
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
|
||||||
pub async fn read_content(&self) -> Result<Vec<FolderData>> {
|
|
||||||
let mut entries = WalkDir::new(&self.base_path);
|
|
||||||
let mut paths = Vec::new();
|
|
||||||
paths.push(self.base_path.to_owned());
|
|
||||||
|
|
||||||
while let Some(res) = entries.next().await {
|
|
||||||
match res {
|
|
||||||
Ok(entry) => {
|
|
||||||
let entry_path = entry.path();
|
|
||||||
if entry_path.is_dir() {
|
|
||||||
paths.push(entry_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e).into_diagnostic(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let folder_data =
|
|
||||||
futures::future::try_join_all(paths.into_iter().map(|p| self.read_dir(p)))
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|f| f)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(folder_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self))]
|
|
||||||
async fn read_dir(&self, path: PathBuf) -> Result<Option<FolderData>> {
|
|
||||||
let index_path = path.join("_index.toml");
|
|
||||||
|
|
||||||
if !index_path.exists() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let index_data = read_index_data(&index_path).await?;
|
|
||||||
let pages = find_pages(&path, &index_data).await?;
|
|
||||||
|
|
||||||
Ok(Some(FolderData {
|
|
||||||
path,
|
|
||||||
index: index_data,
|
|
||||||
pages,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace")]
|
|
||||||
async fn read_index_data(path: &Path) -> Result<IndexData> {
|
|
||||||
let index_str = fs::read_to_string(path)
|
|
||||||
.await
|
|
||||||
.into_diagnostic()
|
|
||||||
.context("reading index file")?;
|
|
||||||
toml::from_str(&index_str).into_diagnostic()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace")]
|
|
||||||
async fn find_pages(dir: &Path, index_data: &IndexData) -> Result<Vec<PathBuf>> {
|
|
||||||
let include_set = build_glob_set(&index_data.include_files)
|
|
||||||
.build()
|
|
||||||
.into_diagnostic()?;
|
|
||||||
let excluded_set = build_glob_set(&index_data.excluded_files)
|
|
||||||
.build()
|
|
||||||
.into_diagnostic()?;
|
|
||||||
|
|
||||||
let mut read_dir = fs::read_dir(dir).await.into_diagnostic()?;
|
|
||||||
let mut pages = Vec::new();
|
|
||||||
|
|
||||||
while let Some(entry) = read_dir.next_entry().await.into_diagnostic()? {
|
|
||||||
let entry_path = entry.path();
|
|
||||||
|
|
||||||
if entry_path.is_file()
|
|
||||||
&& !entry_path
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.starts_with("_")
|
|
||||||
&& include_set.is_match(&entry_path)
|
|
||||||
&& !excluded_set.is_match(&entry_path)
|
|
||||||
{
|
|
||||||
pages.push(entry_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(pages)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace")]
|
|
||||||
fn build_glob_set(globs: &Vec<String>) -> GlobSetBuilder {
|
|
||||||
let mut builder = GlobSetBuilder::new();
|
|
||||||
globs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|pattern| Glob::new(pattern).ok())
|
|
||||||
.fold(&mut builder, |b, g| b.add(g));
|
|
||||||
|
|
||||||
builder
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct IndexData {
|
|
||||||
/// the default template that is used for rendering
|
|
||||||
pub default_template: Option<String>,
|
|
||||||
|
|
||||||
/// files that are included for rendering
|
|
||||||
#[serde(default)]
|
|
||||||
pub include_files: Vec<String>,
|
|
||||||
|
|
||||||
/// files that are explicitly excluded from rendering
|
|
||||||
#[serde(default)]
|
|
||||||
pub excluded_files: Vec<String>,
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
mod dir_loader;
|
|
||||||
mod index;
|
|
||||||
mod page;
|
|
||||||
mod page_loader;
|
|
||||||
|
|
||||||
pub use dir_loader::*;
|
|
||||||
pub use index::*;
|
|
||||||
pub use page::*;
|
|
||||||
pub use page_loader::*;
|
|
@ -1,25 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
pub enum Page {
|
|
||||||
Data(PageData),
|
|
||||||
Content(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
pub struct PageData {
|
|
||||||
/// Metadata for this page
|
|
||||||
#[serde(default)]
|
|
||||||
pub metadata: PageMetadata,
|
|
||||||
|
|
||||||
/// remaining data of this page
|
|
||||||
/// passed to the templates when rendering
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub data: toml::Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Deserialize, Serialize)]
|
|
||||||
pub struct PageMetadata {
|
|
||||||
/// template used to render this page
|
|
||||||
pub template: Option<String>,
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use miette::{Context, IntoDiagnostic, Result};
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use super::Page;
|
|
||||||
|
|
||||||
pub struct PageLoader;
|
|
||||||
|
|
||||||
/// loads a page and parses the data depending on the extension
|
|
||||||
#[tracing::instrument(level = "trace")]
|
|
||||||
pub async fn load_page(path: &Path) -> Result<Page> {
|
|
||||||
let string_content = load_string_content(path).await?;
|
|
||||||
|
|
||||||
if let Some(extension) = path.extension() {
|
|
||||||
let extension_lower = extension.to_string_lossy().to_lowercase();
|
|
||||||
match extension_lower.as_str() {
|
|
||||||
"toml" => Ok(Page::Data(
|
|
||||||
toml::from_str(&string_content).into_diagnostic()?,
|
|
||||||
)),
|
|
||||||
_ => Ok(Page::Content(string_content)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Page::Content(string_content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace")]
|
|
||||||
async fn load_string_content(path: &Path) -> Result<String> {
|
|
||||||
fs::read_to_string(path)
|
|
||||||
.await
|
|
||||||
.into_diagnostic()
|
|
||||||
.context("reading page content")
|
|
||||||
}
|
|
@ -0,0 +1,33 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use config::ConfigLoader;
|
||||||
|
|
||||||
|
use miette::Result;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod content_loader;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Paths {
|
||||||
|
pub config: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Viki {
|
||||||
|
config_loader: ConfigLoader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Viki {
|
||||||
|
#[tracing::instrument(level = "trace")]
|
||||||
|
pub async fn load(paths: Paths) -> Result<Self> {
|
||||||
|
let config_loader = ConfigLoader::load(paths.config).await?;
|
||||||
|
|
||||||
|
Ok(Self { config_loader })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
|
pub async fn reload(&mut self) -> Result<()> {
|
||||||
|
self.config_loader.reload().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
use comrak::ComrakOptions;
|
|
||||||
use tera::{try_get_value, Filter};
|
|
||||||
|
|
||||||
pub struct Markdown;
|
|
||||||
|
|
||||||
impl Filter for Markdown {
|
|
||||||
fn filter(
|
|
||||||
&self,
|
|
||||||
value: &tera::Value,
|
|
||||||
_args: &std::collections::HashMap<String, tera::Value>,
|
|
||||||
) -> tera::Result<tera::Value> {
|
|
||||||
let string_content = try_get_value!("markdown", "value", String, value);
|
|
||||||
let html = comrak::markdown_to_html(&string_content, &ComrakOptions::default());
|
|
||||||
|
|
||||||
Ok(tera::Value::String(html))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
use tera::Tera;
|
|
||||||
mod markdown;
|
|
||||||
|
|
||||||
pub fn register_all(tera: &mut Tera) {
|
|
||||||
tera_text_filters::register_all(tera);
|
|
||||||
tera.register_filter("markdown", markdown::Markdown);
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
use tera::Tera;
|
|
||||||
|
|
||||||
mod filters;
|
|
||||||
|
|
||||||
pub fn register_all(tera: &mut Tera) {
|
|
||||||
filters::register_all(tera);
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
use std::{path::PathBuf, sync::Arc};
|
|
||||||
|
|
||||||
use futures::future;
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
|
||||||
use tera::{Context as TeraContext, Tera};
|
|
||||||
use tokio::{fs, sync::Mutex};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
context::Context,
|
|
||||||
data::{load_page, FolderData},
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::style::{load_stylesheets, Stylesheets};
|
|
||||||
|
|
||||||
mod style;
|
|
||||||
|
|
||||||
// renders content using the given template folder
|
|
||||||
pub struct ContentRenderer {
|
|
||||||
template_glob: String,
|
|
||||||
ctx: Arc<Context>,
|
|
||||||
styles: Arc<Mutex<Stylesheets>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentRenderer {
|
|
||||||
pub async fn new(ctx: Arc<Context>) -> Result<Self> {
|
|
||||||
let template_glob = format!("{}/**/*", ctx.template_dir.to_string_lossy());
|
|
||||||
let styles = load_stylesheets(&ctx.stylesheet_dir).await?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
template_glob,
|
|
||||||
ctx,
|
|
||||||
styles: Arc::new(Mutex::new(styles)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
|
||||||
pub async fn render_all(&self, dirs: Vec<FolderData>) -> Result<()> {
|
|
||||||
if self.ctx.output_dir.exists() {
|
|
||||||
fs::remove_dir_all(&self.ctx.output_dir)
|
|
||||||
.await
|
|
||||||
.into_diagnostic()?;
|
|
||||||
}
|
|
||||||
let mut tera = Tera::new(&self.template_glob).into_diagnostic()?;
|
|
||||||
super::processors::register_all(&mut tera);
|
|
||||||
future::try_join_all(dirs.into_iter().map(|data| self.render_folder(&tera, data))).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
|
||||||
async fn render_folder(&self, tera: &Tera, data: FolderData) -> Result<()> {
|
|
||||||
let dir_name = data
|
|
||||||
.path
|
|
||||||
.components()
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.as_os_str()
|
|
||||||
.to_string_lossy();
|
|
||||||
let default_template = data
|
|
||||||
.index
|
|
||||||
.default_template
|
|
||||||
.to_owned()
|
|
||||||
.unwrap_or(dir_name.into());
|
|
||||||
|
|
||||||
future::try_join_all(
|
|
||||||
data.pages
|
|
||||||
.into_iter()
|
|
||||||
.map(|page| self.render_page(tera, default_template.clone(), page)),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
|
||||||
async fn render_page(
|
|
||||||
&self,
|
|
||||||
tera: &Tera,
|
|
||||||
default_template: String,
|
|
||||||
page_path: PathBuf,
|
|
||||||
) -> Result<()> {
|
|
||||||
tracing::debug!("Rendering {page_path:?}");
|
|
||||||
|
|
||||||
let page = load_page(&page_path).await?;
|
|
||||||
let mut context = TeraContext::new();
|
|
||||||
let mut template_name = default_template;
|
|
||||||
let mut style_name = template_name.to_owned();
|
|
||||||
|
|
||||||
match page {
|
|
||||||
crate::data::Page::Data(data) => {
|
|
||||||
if let Some(tmpl) = data.metadata.template {
|
|
||||||
template_name = tmpl.to_owned();
|
|
||||||
style_name = tmpl;
|
|
||||||
}
|
|
||||||
context.insert("data", &data.data);
|
|
||||||
}
|
|
||||||
crate::data::Page::Content(content) => context.insert("content", &content),
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut styles = self.styles.lock().await;
|
|
||||||
let style_embed = styles
|
|
||||||
.get_style_embed(&style_name, &self.ctx.output_dir)
|
|
||||||
.await?;
|
|
||||||
context.insert("style", &style_embed);
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("context = {context:?}");
|
|
||||||
|
|
||||||
let html = tera
|
|
||||||
.render(&format!("{template_name}.html"), &context)
|
|
||||||
.into_diagnostic()?;
|
|
||||||
let rel_path = page_path
|
|
||||||
.strip_prefix(&self.ctx.content_dir)
|
|
||||||
.into_diagnostic()?;
|
|
||||||
let mut out_path = self.ctx.output_dir.join(rel_path);
|
|
||||||
out_path.set_extension("html");
|
|
||||||
let parent = out_path.parent().unwrap();
|
|
||||||
|
|
||||||
if !parent.exists() {
|
|
||||||
fs::create_dir_all(parent).await.into_diagnostic()?;
|
|
||||||
}
|
|
||||||
fs::write(out_path, html).await.into_diagnostic()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use async_walkdir::WalkDir;
|
|
||||||
use futures::StreamExt;
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
|
||||||
use rsass::output::Format;
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
const DEFAULT_SHEET_NAME: &str = "style";
|
|
||||||
const EMBED_THRESHOLD: usize = 512;
|
|
||||||
|
|
||||||
pub struct Stylesheets {
|
|
||||||
page_styles: HashMap<String, PathBuf>,
|
|
||||||
processed_styles: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace")]
|
|
||||||
pub async fn load_stylesheets(base_dir: &PathBuf) -> Result<Stylesheets> {
|
|
||||||
let mut entries = WalkDir::new(base_dir);
|
|
||||||
let mut page_styles = HashMap::new();
|
|
||||||
let empty_path = PathBuf::new();
|
|
||||||
|
|
||||||
while let Some(res) = entries.next().await {
|
|
||||||
match res {
|
|
||||||
Ok(entry) => {
|
|
||||||
let entry_path = entry.path();
|
|
||||||
if entry_path.is_file() {
|
|
||||||
let rel_path = entry_path.strip_prefix(base_dir).into_diagnostic()?;
|
|
||||||
|
|
||||||
if let Some(file_name) = entry_path.file_stem() {
|
|
||||||
let file_name = rel_path.parent().unwrap_or(&empty_path).join(file_name);
|
|
||||||
let file_name = file_name.to_string_lossy().into_owned();
|
|
||||||
page_styles.insert(file_name, entry_path.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e).into_diagnostic(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tracing::debug!("Styles {page_styles:?}");
|
|
||||||
|
|
||||||
Ok(Stylesheets {
|
|
||||||
page_styles,
|
|
||||||
processed_styles: HashMap::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stylesheets {
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, out_dir))]
|
|
||||||
pub async fn get_style_embed(&mut self, name: &str, out_dir: &Path) -> Result<String> {
|
|
||||||
let mut styles: Vec<String> = Vec::with_capacity(2);
|
|
||||||
|
|
||||||
if let Some(default_style) = self
|
|
||||||
.get_processed_style(DEFAULT_SHEET_NAME, out_dir)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
styles.push(default_style);
|
|
||||||
}
|
|
||||||
if let Some(style) = self.get_processed_style(name, out_dir).await? {
|
|
||||||
styles.push(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(styles.join(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, out_dir))]
|
|
||||||
async fn get_processed_style(&mut self, name: &str, out_dir: &Path) -> Result<Option<String>> {
|
|
||||||
if let Some(processed) = self.processed_styles.get(name) {
|
|
||||||
Ok(Some(processed.to_owned()))
|
|
||||||
} else if let Some(source) = self.page_styles.get(name) {
|
|
||||||
let format = Format {
|
|
||||||
style: rsass::output::Style::Compressed,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let style_contents = rsass::compile_scss_path(source, format).into_diagnostic()?;
|
|
||||||
let style_html = if style_contents.len() < EMBED_THRESHOLD {
|
|
||||||
let utf_contents = String::from_utf8(style_contents).into_diagnostic()?;
|
|
||||||
|
|
||||||
format!(r#"<style type="text/css">{utf_contents}</style>"#)
|
|
||||||
} else {
|
|
||||||
let output_path = out_dir.join(name).with_extension("css");
|
|
||||||
let parent = output_path.parent().unwrap();
|
|
||||||
if !parent.exists() {
|
|
||||||
fs::create_dir_all(parent).await.into_diagnostic()?;
|
|
||||||
}
|
|
||||||
fs::write(output_path, style_contents)
|
|
||||||
.await
|
|
||||||
.into_diagnostic()?;
|
|
||||||
|
|
||||||
format!(r#"<link rel="stylesheet" href="/{name}.css">"#)
|
|
||||||
};
|
|
||||||
self.processed_styles
|
|
||||||
.insert(name.to_owned(), style_html.to_owned());
|
|
||||||
|
|
||||||
Ok(Some(style_html))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue