about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2024-12-18 00:27:34 -0600
committergennyble <gen@nyble.dev>2024-12-18 00:27:34 -0600
commit5ea6a13ead2a5cab0f578c8af5f0e0be88c3a08c (patch)
treef8ab97a0abeb03142da4c7a8ed859d6a408cd16a
parentf6b441fe53dd75af5933c4456d92070ccb7bd8af (diff)
downloadcutie-5ea6a13ead2a5cab0f578c8af5f0e0be88c3a08c.tar.gz
cutie-5ea6a13ead2a5cab0f578c8af5f0e0be88c3a08c.zip
Ahhhhh HEAD main
-rwxr-xr-x[-rw-r--r--].gitignore0
-rwxr-xr-x[-rw-r--r--].rustfmt.toml0
-rwxr-xr-x[-rw-r--r--]Cargo.lock15
-rwxr-xr-x[-rw-r--r--]Cargo.toml4
-rwxr-xr-x[-rw-r--r--]LICENSE0
-rwxr-xr-x[-rw-r--r--]converge/Cargo.toml3
-rwxr-xr-xconverge/example.html185
-rwxr-xr-x[-rw-r--r--]converge/readme.md7
-rwxr-xr-x[-rw-r--r--]converge/src/main.rs39
-rwxr-xr-xconverge/src/timedb.rs97
-rwxr-xr-x[-rw-r--r--]readme.md6
-rwxr-xr-x[-rw-r--r--]src/lib.rs29
-rwxr-xr-x[-rw-r--r--]src/query.rs0
-rwxr-xr-x[-rw-r--r--]src/tag.rs53
-rwxr-xr-x[-rw-r--r--]tests/nyble.html0
-rwxr-xr-x[-rw-r--r--]tests/nyble_pages.rs0
-rwxr-xr-x[-rw-r--r--]tests/touching_grass.html0
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">&larr; 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&nbsp;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">&nbsp;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