diff options
author | gennyble <gen@nyble.dev> | 2024-12-18 00:27:34 -0600 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2024-12-18 00:27:34 -0600 |
commit | 5ea6a13ead2a5cab0f578c8af5f0e0be88c3a08c (patch) | |
tree | f8ab97a0abeb03142da4c7a8ed859d6a408cd16a | |
parent | f6b441fe53dd75af5933c4456d92070ccb7bd8af (diff) | |
download | cutie-main.tar.gz cutie-main.zip |
-rwxr-xr-x[-rw-r--r--] | .gitignore | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | .rustfmt.toml | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | Cargo.lock | 15 | ||||
-rwxr-xr-x[-rw-r--r--] | Cargo.toml | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | LICENSE | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | converge/Cargo.toml | 3 | ||||
-rwxr-xr-x | converge/example.html | 185 | ||||
-rwxr-xr-x[-rw-r--r--] | converge/readme.md | 7 | ||||
-rwxr-xr-x[-rw-r--r--] | converge/src/main.rs | 39 | ||||
-rwxr-xr-x | converge/src/timedb.rs | 97 | ||||
-rwxr-xr-x[-rw-r--r--] | readme.md | 6 | ||||
-rwxr-xr-x[-rw-r--r--] | src/lib.rs | 29 | ||||
-rwxr-xr-x[-rw-r--r--] | src/query.rs | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/tag.rs | 53 | ||||
-rwxr-xr-x[-rw-r--r--] | tests/nyble.html | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | tests/nyble_pages.rs | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | tests/touching_grass.html | 0 |
17 files changed, 394 insertions, 44 deletions
diff --git a/.gitignore b/.gitignore index ea8c4bf..ea8c4bf 100644..100755 --- a/.gitignore +++ b/.gitignore diff --git a/.rustfmt.toml b/.rustfmt.toml index 218e203..218e203 100644..100755 --- a/.rustfmt.toml +++ b/.rustfmt.toml diff --git a/Cargo.lock b/Cargo.lock index 4c4488d..5aa59d6 100644..100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,21 +3,6 @@ version = 3 [[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" - -[[package]] -name = "converge" -version = "0.1.0" -dependencies = [ - "camino", - "cutie", - "thiserror", -] - -[[package]] name = "cutie" version = "0.1.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index d62ac1d..d35799a 100644..100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,5 @@ license = "ISC" [dependencies] thiserror = "1.0.52" -[workspace] -members = ["converge"] +#[workspace] +#members = ["converge"] diff --git a/LICENSE b/LICENSE index 8a55383..8a55383 100644..100755 --- a/LICENSE +++ b/LICENSE diff --git a/converge/Cargo.toml b/converge/Cargo.toml index 4dc9e4b..98481f6 100644..100755 --- a/converge/Cargo.toml +++ b/converge/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bempline = "0.8.1" camino = "1.1.6" cutie = { path = "../", version = "0.1.0" } thiserror = "1.0.52" +time = { version = "0.3.31", features = ["formatting", "macros"] } +scurvy = { path = "../../scurvy" } diff --git a/converge/example.html b/converge/example.html new file mode 100755 index 0000000..970cc06 --- /dev/null +++ b/converge/example.html @@ -0,0 +1,185 @@ +/Users/gen/src/inf/served/touching-grass.html +/Users/gen/src/inf/fixtures/post.html +/Users/gen/src/inf/fixtures/base.html +Looking for html +Looking for head +Looking for title + closed title + closed head +Looking for body +Looking for ul +Looking for li +Looking for a + closed a + closed li + closed ul +Looking for main + closed main +Looking for footer +Looking for ul +Looking for li + closed li +Looking for li +Looking for abbr + closed abbr + closed li +Looking for li +Looking for abbr + closed abbr + closed li + closed ul + closed footer + closed body + closed html +Looking for nav +Looking for a + closed a + closed nav +BEFORE +Looking for main + closed main +Looking for section +Looking for video + closed video +Looking for ul +Looking for li + closed li +Looking for li +Looking for a + closed a + closed li +Looking for li +Looking for a + closed a + closed li +Looking for li + closed li +Looking for li +Looking for input +Looking for label + closed label + closed input + closed li + closed ul + closed section +Looking for script + parse special +Looking for p + closed p +Looking for p + closed p +Looking for p +Looking for span + closed span +Looking for i + closed i +Looking for span + closed span + closed p +Looking for p + closed p +<html> + +<head> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <meta charset="utf-8" /> + + <link rel="icon" type="image/png" href="/3directions.png" /> + <link rel="icon" type="image/svg+xml" href="/3directions.svg" /> + + <title></title> + + <link rel="stylesheet" href="/styles/common.css" /> +</head> + +<body> + <ul id="nav-access"> + <li><a href="#content">Skip to main content</a></li> + </ul> + + <nav class="sized"> + <a id="home" href="/" alt="home">← home</a> +</nav><main id="content" class="writing sized"><section style="width: 100%; text-align: right; padding: 8px 0"> + <video id="grass" controls width="100%" poster="grass_poster.jpg" preload="metadata" loop> + <source src="grass_720p.mp4" type="video/mp4" /> + <track default src="grass.vtt" /> + </video> + <ul> + <li class="small">download</li> + <li><a href="grass.mp4" download>[1080p | 7.1MB]</a></li> + <li><a href="grass_720p.mp4" download>[720p | 1.9MB]</a></li> + <li id="long"></li> + <li><input type="checkbox" checked id="loop"><label> loop video?</label></input></li> + </ul> +</section> + +<script> + document.addEventListener("DOMContentLoaded", setup); + + const video = document.getElementById("grass"); + function setup() { + let marks = document.getElementsByClassName("mark"); + for (let i = 0; i < marks.length; ++i) { + marks[i].addEventListener('click', markClick) + } + console.log(marks); + + let loop = document.getElementById('loop'); + loop.addEventListener('change', loopChanged) + + // Fire once with a fake event so we aren't desynced + loopChanged({ 'target': loop }); + } + + function markClick(event) { + let target = event.target; + let time = parseFloat(target.getAttribute('data-time')); + console.log(`seeking to ${time}`) + video.currentTime = time; + } + + function loopChanged(event) { + let target = event.target; + let checked = target.checked; + console.log(`video looping: ${checked}`); + video.loop = checked; + } +</script> + +<p> + I took this short video to send to a friend one day and accidentally + made a 106 frame masterpiece. Well, that's hyperbolic. But there's a + lot I enjoy about it. +</p> + +<p> + The contrast between my orange painted nails and the surprisingly + green grass is pretty pleasing. I think the gentle brown of the brick + in the bottom-left corner helps keep from an overwhelming greenery. + Thanks bricks :) +</p> + +<p> + The accidental camera movement is nice, too. I like the angle the camera is at and the way it wobbles. + It's clearly hand-held. At <span class="mark" data-time="1.3">1.3-ish-seconds</span> + you can see I gently sway forward, but seem to be less wobbly. <i>I'm braced; + I'm stable! I have-three-points-of-contact-with-the-ground!</i> Then, at roughly + <span class="mark" data-time="1.9">2 seconds</span> when the grass finally + yields, I get rocked back and wobbles resume. +</p> + +<p> + The sound! And the sound. The ripping of the grass. That planty matter finally giving way. The fibrous tearing. + I just like it; I enjoy it! I greatly appreciate it. +</p></main> + + <footer class="sized"> + <ul id="dates"> + <li id="dates-title"> times</li> + <li><abbr title="creation time">c</abbr> {created}</li> + <li><abbr title="modification time">m</abbr> {modified}</li> + </ul> + </footer> +</body> + +</html> diff --git a/converge/readme.md b/converge/readme.md index 5a778c3..7fcecb5 100644..100755 --- a/converge/readme.md +++ b/converge/readme.md @@ -6,4 +6,9 @@ Setup: <path> {directions} content -{directions} content \ No newline at end of file +{directions} content + +**variables converge tries to fill** + +- `{file_time_created}`: time the file was created +- `{file_time_modified}`: time the file was modified \ No newline at end of file diff --git a/converge/src/main.rs b/converge/src/main.rs index 20f2683..74abe67 100644..100755 --- a/converge/src/main.rs +++ b/converge/src/main.rs @@ -1,32 +1,30 @@ use std::str::FromStr; use camino::Utf8PathBuf; +use scurvy::Argument; fn main() { - let mut args = std::env::args().skip(1); - let argc = args.len(); - if argc == 0 { - eprintln!("usage: converge <content file> [supporting files ...]"); - return; + let arguments = [ + Argument::arg("timdb", "path").help("time database generated with whenwasit"), + Argument::arg("part", "path").help("part converge is supposed to build"), + ]; + let cli = scurvy::parse(&arguments); + + let content: Utf8PathBuf = cli.parse_opt_or_die("part"); + let supporting: Vec<Utf8PathBuf> = cli.free_opts().into_iter().map(Utf8PathBuf::from).collect(); + + let time = std::time::Instant::now(); + for idx in 0..100 { + let supporting = supporting.clone(); + let html = process(content.clone(), supporting); } + println!("{}ms for 100", time.elapsed().as_millis()); - let content = match args.next() { - None => { - eprintln!("usage: converge <content file> [supporting files ...]"); - return; - } - Some(path) => Utf8PathBuf::from(path), - }; - - let supporting = args.map(Utf8PathBuf::from).collect(); - - let html = process(content, supporting); - println!("{html}") + //println!("{html}") } fn process(content_file: Utf8PathBuf, mut supporting: Vec<Utf8PathBuf>) -> cutie::Html { - println!("{content_file}"); - let raw = std::fs::read_to_string(&content_file).unwrap(); + let raw = std::fs::read_to_string(content_file).unwrap(); match Part::from_str(&raw) { Err(PartError::NoSetup) => cutie::Html::parse(raw), @@ -73,7 +71,7 @@ fn process(content_file: Utf8PathBuf, mut supporting: Vec<Utf8PathBuf>) -> cutie html: &'a mut cutie::Html, ident: &str, ) -> &'a mut cutie::Tag { - match html.get_parent_that_contains_tag_name_mut(&ident) { + match html.get_parent_that_contains_tag_name_mut(ident) { None => { eprintln!("error processing file"); eprintln!("failed to find element with tag {ident}"); @@ -93,7 +91,6 @@ fn process(content_file: Utf8PathBuf, mut supporting: Vec<Utf8PathBuf>) -> cutie tag.children.extend(content_html.nodes); } Opcode::Before => { - println!("BEFORE"); let predicate = |node: &cutie::Node| -> bool { if let cutie::Node::Tag(tag) = node { if tag.name == ident { diff --git a/converge/src/timedb.rs b/converge/src/timedb.rs new file mode 100755 index 0000000..718115d --- /dev/null +++ b/converge/src/timedb.rs @@ -0,0 +1,97 @@ +use std::path::Path; + +use time::OffsetDateTime; + +#[derive(Debug)] +pub struct TimeDb { + data: Vec<TimedFile>, +} + +impl TimeDb { + pub fn load<P: AsRef<Path>>(path: P) -> Self { + let file = std::fs::read_to_string(path).unwrap(); + + let mut data = vec![]; + for line in file.lines() { + let it = TimedFile::parse_line(line); + data.push(it); + } + + Self { data } + } + + pub fn get_times(&self, path: &str) -> Option<&TimedFile> { + for file in &self.data { + if &file.path == path { + return Some(file); + } + } + + None + } +} + +#[derive(Debug)] +pub struct TimedFile { + path: String, + pub creation: Option<OffsetDateTime>, + pub modification: Option<OffsetDateTime>, + pub access: Option<OffsetDateTime>, +} + +impl TimedFile { + pub fn parse_line<S: AsRef<str>>(raw: S) -> Self { + let mut values = raw.as_ref().rsplitn(4, ",").collect::<Vec<&str>>(); + values.reverse(); + + let to_odt = |str: &&str| -> Option<OffsetDateTime> { + str.parse::<u64>() + .ok() + .map(|t| OffsetDateTime::from_unix_timestamp(t as i64).unwrap()) + }; + + let path = unescape(values[0]); + let creation = values.get(1).map(to_odt).flatten(); + let modification = values.get(2).map(to_odt).flatten(); + let access = values.get(3).map(to_odt).flatten(); + + Self { + path, + creation, + modification, + access, + } + } +} + +// Permissive unescape. Everything that's not \\ or \, is passed +// unchanged, while those get their slash removed +fn unescape<S: AsRef<str>>(raw: S) -> String { + let raw = raw.as_ref(); + + if !raw.contains('\\') { + return raw.to_owned(); + } + + let mut unescape = String::with_capacity(raw.len()); + let mut escaped = false; + for ch in raw.chars() { + match (escaped, ch) { + (false, '\\') => { + escaped = true; + } + (false, c) => unescape.push(c), + (true, '\\') | (true, ',') => { + unescape.push(ch); + escaped = false; + } + (true, c) => { + unescape.push('\\'); + unescape.push(c); + escaped = false; + } + } + } + + unescape +} diff --git a/readme.md b/readme.md index 49cdf8a..252c728 100644..100755 --- a/readme.md +++ b/readme.md @@ -3,7 +3,11 @@ Not particularly fast, probably. - all tags must close *(even `<br/>`, `<meta/>`, `<link/>`)* - tag names must be separated from the tag-body *(where the attributes go)* by a space character *(` `, 0x20)* -- self-closing tags must have the closing `/` at the ver y end of the body *(directly before the `>`)* +- attributes must use `"` as their quoting character AND attributes must be + quoted *(no `src=image.png` nonsense. is that- does that ever even happen? i hope not)* +- attribute keys, if they have a value, must have the `=` directly following the + key *(good: `src="image.png"`, bad: `src = "image.png`)* +- self-closing tags must have the closing `/` at the very end of the body *(directly before the `>`)* - no > in tags except at the end (not even in attributes) - inline `<script>` and `<style>` must have their closing-tag be first-of-line *(excluding whitespace)* \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 8b5c47d..bc8b629 100644..100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,31 @@ impl Html { None } + pub fn get_by_id(&self, id: &str) -> Option<&Tag> { + // depth first + fn find_node<'a>(tag: &'a Tag, id: &str) -> Option<&'a Tag> { + if tag.id().unwrap_or_default() == id { + return Some(tag); + } + + for child in tag.child_tags() { + if let Some(tag) = find_node(child, id) { + return Some(tag); + } + } + + None + } + + for child in self.child_tags() { + if let Some(tag) = find_node(child, id) { + return Some(tag); + } + } + + None + } + fn parse_node(raw: &str) -> Consumed { match Self::is_tag(raw) { Some(_) => { @@ -133,14 +158,11 @@ impl Html { }; } - println!("Looking for {}", root_tag.name); - loop { // Special case <script> and <style> if root_tag.name == "script" && tag.get_attribute("src").is_none() || root_tag.name == "style" { - println!("\tparse special"); let special = Self::special_parse(rest.unwrap(), root_tag.name); match special { @@ -166,7 +188,6 @@ impl Html { // Find the closing end of out root_tag if let Some((parsed, remaining)) = Self::is_tag(rest.unwrap()) { if parsed.closing && parsed.name == root_tag.name { - println!("\tclosed {}", parsed.name); break Consumed { node: Node::Tag(tag), remaining, diff --git a/src/query.rs b/src/query.rs index 26b02aa..26b02aa 100644..100755 --- a/src/query.rs +++ b/src/query.rs diff --git a/src/tag.rs b/src/tag.rs index 0a97061..43c4e0c 100644..100755 --- a/src/tag.rs +++ b/src/tag.rs @@ -1,4 +1,5 @@ use core::fmt; +use std::{iter::Peekable, str::CharIndices}; use crate::Node; @@ -208,6 +209,8 @@ impl<'a> Iterator for TagIteratorMut<'a> { mod test { use crate::Tag; + use super::peek_skip_while; + #[test] fn tag_finds_boolen_attribute() { let tag = Tag { @@ -240,4 +243,54 @@ mod test { }; assert!(tag.get_attribute("contenteditable").is_some()); } + + // yes, this is a bad name. + #[test] + fn peek_skip_while_works() { + let str = "\t no whitespace"; + let mut chari = str.char_indices().peekable(); + + peek_skip_while(&mut chari, |c| c.is_whitespace()); + + let next_idx = chari.next().unwrap().0; + assert_eq!(&str[next_idx..], "no whitespace"); + } +} + +struct Attribute<'body> { + name: &'body str, + content: Option<&'body str>, + start: usize, + len: usize, +} + +fn find_attribute<'body>(body: &'body str, attribute: &str) -> Option<Attribute<'body>> { + let mut chari = body.char_indices().peekable(); + + 'big: loop { + // skip whitespace + peek_skip_while(&mut chari, |c| c.is_whitespace()); + + let key_start = chari.next().unwrap(); + // find end of key + peek_skip_while(&mut chari, |c| c.is_alphanumeric()); + + let key_after = chari.next().unwrap(); + } + + todo!() +} + +fn peek_skip_while<P>(iter: &mut Peekable<CharIndices>, mut predicate: P) +where + P: FnMut(&char) -> bool, +{ + loop { + match iter.peek() { + Some((_, c)) if predicate(c) => { + iter.next(); + } + _ => break, + } + } } diff --git a/tests/nyble.html b/tests/nyble.html index 41614ab..41614ab 100644..100755 --- a/tests/nyble.html +++ b/tests/nyble.html diff --git a/tests/nyble_pages.rs b/tests/nyble_pages.rs index 2a8fc75..2a8fc75 100644..100755 --- a/tests/nyble_pages.rs +++ b/tests/nyble_pages.rs diff --git a/tests/touching_grass.html b/tests/touching_grass.html index de90798..de90798 100644..100755 --- a/tests/touching_grass.html +++ b/tests/touching_grass.html |