Skip to content

Commit

Permalink
Add minify macro and filetype config
Browse files Browse the repository at this point in the history
  • Loading branch information
Bright-Shard committed Oct 28, 2024
1 parent b07b49b commit fd2c805
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 30 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Macros can be combined, like this:

- `#!INCLUDE(path/to/file)`: Webby will compile the given file, then embed it at the macro's location. The file must contain valid UTF-8 text.
- `#!BASE64(text)`: Base64-encode the given text.
- `#!MINIFY(format, text)`: Run `text` through the minifier for `format`. For example, `#!MINIMISE(html, <body> <p >hello!</p></body>)` will output `<body><p>hello!</p></body>`.
- `#!INCLUDE_BASE64(path/to/file)`: Base64-encode the given file. This differs from `#!BASE64(#!INCLUDE(path/to/file))` because it can also base64-encode binary files.

# minifiers
Expand Down Expand Up @@ -87,6 +88,12 @@ output = "file.out.html"
# If this isn't specified, webby will infer if it should compile or copy the
# file based on the file's ending.
mode = "compile"
# (Optional) Override the file type
# By default webby will treat files differently based on their file type. Files
# ending in .html will be run through the HTML minifier, while files ending in
# .css will be run through the CSS minifier. This setting will make webby treat
# the file as what's given here instead of the file's file extension.
filetype = "html"
```

# todo
Expand All @@ -101,3 +108,4 @@ mode = "compile"
- [x] Gemtext translator
- [ ] Markdown translator
- [ ] Redo macro compiler... it's old and has bugs
- [ ] Replace any instances of `panic!()` with returning an error string
70 changes: 48 additions & 22 deletions src/compiler.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use {
crate::{line_number_of_offset, Cow},
crate::{line_number_of_offset, minifier, Cow},
base64::{engine::general_purpose::STANDARD, Engine},
std::{fs, path::Path},
};

pub fn compile_macros<'a>(original: &'a str, source_path: &'a Path) -> Cow<'a> {
pub fn compile_macros<'a>(original: &'a str, source_path: &'a Path) -> Result<Cow<'a>, String> {
let mut output = String::default();
let mut offset = 0;

Expand All @@ -26,25 +26,37 @@ pub fn compile_macros<'a>(original: &'a str, source_path: &'a Path) -> Cow<'a> {
offset += start_idx;

let macro_src = &original[offset..];
let paren_open = macro_src.find('(').unwrap_or_else(|| {
panic!(
let Some(paren_open) = macro_src.find('(') else {
return Err(format!(
"Expected ( in macro invocation at {source_path:?}:{}",
line_number_of_offset(original, offset)
)
});
let mut paren_close = macro_src.find(')').unwrap_or_else(|| {
panic!(
"Expected ) to end macro invocation at {source_path:?}:{}",
line_number_of_offset(original, offset)
)
});
while macro_src.as_bytes().get(paren_close + 1).copied() == Some(b')') {
paren_close += 1;
));
};
let mut paren_depth = 0u8;
let mut bytes = macro_src[paren_open..].bytes().enumerate();
let paren_close: usize;
loop {
let Some((idx, byte)) = bytes.next() else {
return Err(format!(
"Expected ) to end macro invocation at {source_path:?}:{}",
line_number_of_offset(original, offset)
));
};

if byte == b'(' {
paren_depth += 1;
} else if byte == b')' {
paren_depth -= 1;
if paren_depth == 0 {
paren_close = idx + paren_open;
break;
}
}
}

let macro_name = &macro_src[2..paren_open];
let macro_args = &macro_src[paren_open + 1..paren_close];
let macro_args = compile_macros(macro_args, source_path);
let macro_args = compile_macros(macro_args, source_path)?;
let macro_args = macro_args.as_ref();

match macro_name {
Expand All @@ -56,7 +68,7 @@ pub fn compile_macros<'a>(original: &'a str, source_path: &'a Path) -> Cow<'a> {
line_number_of_offset(original, offset)
)
});
let compiled = compile_macros(&src, &path);
let compiled = compile_macros(&src, &path)?;
output += compiled.as_ref();
}
"BASE64" => {
Expand All @@ -72,20 +84,34 @@ pub fn compile_macros<'a>(original: &'a str, source_path: &'a Path) -> Cow<'a> {
});
output += STANDARD.encode(&src).as_str();
}
other => panic!(
"Unknown macro '{other}' in macro invocation at {source_path:?}:{}",
line_number_of_offset(original, offset)
),
"MINIFY" => {
let mut split = macro_args.split(',');
let Some(file_type) = split.next() else {
return Err(format!("Expected two arguments (file type and code) in {macro_name} at {source_path:?}:{}", line_number_of_offset(original, offset)));
};
let remaining = &macro_args[file_type.len() + 1..];
match file_type {
"html" => output += &minifier::minify_html(source_path.to_str().unwrap(), remaining, original)?,
"css" => output += &minifier::minify_css(remaining),
_ => return Err(format!("Unknown file type given in {macro_name} macro at {source_path:?}:{} - file type was `{file_type}`, but can only be html or css", line_number_of_offset(original, offset)))
}
}
other => {
return Err(format!(
"Unknown macro '{other}' in macro invocation at {source_path:?}:{}",
line_number_of_offset(original, offset)
))
}
}

offset += paren_close + 1;
}

if output.is_empty() {
Cow::Borrowed(original)
Ok(Cow::Borrowed(original))
} else {
output += &original[offset..];
Cow::Owned(output)
Ok(Cow::Owned(output))
}
}

Expand Down
20 changes: 14 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ pub enum Mode {
Link,
}

pub enum FileType {
Html,
Css,
Gemtext,
Unknown,
}

pub struct Target {
pub path: PathBuf,
pub output: PathBuf,
pub mode: Mode,
pub file_type: FileType,
}

pub fn build_target(target: Target) -> Result<(), Cow<'static>> {
Expand All @@ -42,20 +50,20 @@ pub fn build_target(target: Target) -> Result<(), Cow<'static>> {
&target.path
)
});
let compiled_macros = compiler::compile_macros(&original, &target.path);
let compiled_macros = compiler::compile_macros(&original, &target.path)?;

let output = match target.path.extension().and_then(|val| val.to_str()) {
Some("gmi") => Cow::Owned(translator::translate_gemtext(
let output = match target.file_type {
FileType::Gemtext => Cow::Owned(translator::translate_gemtext(
&target.path,
compiled_macros.as_ref(),
)?),
Some("html") => Cow::Owned(minifier::minify_html(
FileType::Html => Cow::Owned(minifier::minify_html(
target.path.to_str().unwrap(),
&compiled_macros,
&original,
)?),
Some("css") => Cow::Owned(minifier::minify_css(&compiled_macros)),
_ => compiled_macros,
FileType::Css => Cow::Owned(minifier::minify_css(&compiled_macros)),
FileType::Unknown => compiled_macros,
};

fs::write(&target.output, output.as_ref())
Expand Down
24 changes: 22 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
boml::{table::TomlGetError, Toml},
std::{borrow::Cow, env, fs, thread},
webby::{build_target, Mode, Target},
webby::{build_target, FileType, Mode, Target},
};

pub fn main() -> Result<(), Cow<'static, str>> {
Expand Down Expand Up @@ -72,8 +72,28 @@ pub fn main() -> Result<(), Cow<'static, str>> {
} else {
output_dir.join(path.file_name().unwrap())
};
let file_type = if let Ok(file_type) = table.get_string("filetype") {
match file_type {
"html" => FileType::Html,
"css" => FileType::Css,
"gemtext" => FileType::Gemtext,
_ => return Err(Cow::Owned(format!("Target `{path:?}` had an unexpected filetype: {file_type}\n`filetype` must be one of html, css, or gemtext")))
}
} else {
match path.extension().and_then(|str| str.to_str()) {
Some("html") => FileType::Html,
Some("css") => FileType::Css,
Some("gmi") | Some("gemtext") => FileType::Gemtext,
_ => FileType::Unknown,
}
};

let target = Target { path, output, mode };
let target = Target {
path,
output,
mode,
file_type,
};
let worker = thread::spawn(move || build_target(target));
tasks.push(worker);
}
Expand Down
12 changes: 12 additions & 0 deletions src/minifier/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub fn minify_html(
/// Whitespace that isn't in a tag property's value will also be stripped.
/// Any nested tags inside that content tag will be re-run through this
/// minifier.
/// 8. If the tag is a <![CDATA[]]> tag, it will not be minimised. Content
/// should be explicitly minimised with the #!MINIMISE macro.
///
/// # TODO
/// - Find a decent JS minifier, add it as a dep, and feature flag it. JS is
Expand Down Expand Up @@ -137,6 +139,16 @@ fn handle_tag<'a>(
let tag_name = &source[1..tag_name_end];
log!("Parsing tag `{tag_name}`");

if tag_name == "![CDATA[" {
let Some(end) = source[tag_name_end..].find("]]>") else {
return Err(Cow::Owned(format!(
"Unclosed CDATA tag at {source_path:?}{}",
line_number_of_offset(error_meta.0, error_meta.1 + tag_name_end)
)));
};
return Ok((Cow::Borrowed(&source[..end + "]]>".len()]), end));
}

if tag_closed {
log!(" Opening tag closed w/o properties");
output.push('>');
Expand Down

0 comments on commit fd2c805

Please sign in to comment.