Rewrote and optimized
395
Cargo.lock
generated
|
@ -3,55 +3,45 @@
|
|||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
|
@ -59,13 +49,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
name = "getrandom"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -78,41 +69,38 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
|||
name = "html"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"html_parser",
|
||||
"toml",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html_parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"inkjet",
|
||||
"v_htmlescape",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.5"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
name = "inkjet"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
checksum = "b44a14d77fc5879e5137be9beee1401ca24038be727ca1b4b68bc81705f98b40"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"cc",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml 0.7.8",
|
||||
"toml 0.8.12",
|
||||
"tree-sitter",
|
||||
"tree-sitter-highlight",
|
||||
"v_htmlescape",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
|
@ -122,9 +110,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
|
@ -132,115 +120,73 @@ version = "1.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.17"
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
name = "regex-automata"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.5"
|
||||
|
@ -250,22 +196,11 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.51"
|
||||
version = "2.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -274,18 +209,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -294,14 +229,26 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.10"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
|
||||
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
"toml_edit 0.19.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -315,28 +262,50 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.6"
|
||||
version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
name = "toml_edit"
|
||||
version = "0.22.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.6"
|
||||
name = "tree-sitter"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||
checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-highlight"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"thiserror",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
|
@ -344,6 +313,12 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "v_htmlescape"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -351,51 +326,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.5"
|
||||
version = "0.5.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
@ -6,6 +6,8 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
html_parser = "0.7"
|
||||
toml = "0.8"
|
||||
walkdir = "2.5"
|
||||
inkjet = "0.10"
|
||||
v_htmlescape = "0.15"
|
||||
|
||||
[profiles.dev.package.inkjet]
|
||||
opt-level = 3
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<A>
|
||||
<a href=@href>
|
||||
<bold>
|
||||
<tp:children />
|
||||
</bold>
|
||||
<a>
|
||||
<@children />
|
||||
<@children />
|
||||
</a>
|
||||
</A>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[compile]
|
||||
output="dist"
|
||||
templates="tp"
|
||||
source="src"
|
||||
minify=false
|
35
hyper-build/home.html
Normal file
88
hyper-build/html-templating.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><link type="image/x-icon" rel="icon" href="resources/favicon.svg"/><!-- --><!-- --></head><body><div class="pure-g"><div class="home-menu pure-menu pure-menu-horizontal"><ul class="pure-menu-list"><li class="pure-menu-item"><a class="pure-menu-link" href="/home"> Home </a></li><li class="pure-menu-item"><a href="/projects" class="pure-menu-link"> Projects </a></li><li class="pure-menu-item"><a href="/blog" class="pure-menu-link"> Blog </a></li><li class="pure-menu-item"><a class="pure-menu-link" href="/about"> About </a></li></ul></div><div class="pure-u-1-1"><div class="header"><h1> Rust HTML Templating Engine </h1><h2> Front End - Parser Design </h2></div><div class="content"><h2 class="distinct"> Motivation </h2>
|
||||
HTML is a powerful tool for creating static web pages, but
|
||||
is not easily modular or scalable. Front end frameworks like
|
||||
React provide reusable components. I want to use components
|
||||
without the overhead of a front-end framework. I want the final
|
||||
output to be statically generated rather than generated on the fly
|
||||
by the server or the client, minified and with inlined CSS and
|
||||
JavaScript.
|
||||
<h2 class="distinct"> Approach </h2>
|
||||
After looking at some of the HTML parsing libraries, I was
|
||||
unsatisfied with the approaches most developers took. The DOM
|
||||
is represented as a tree structure, where each node keeps a
|
||||
pointer to its children.
|
||||
<div class="code-block">
|
||||
<pre><code>
|
||||
<span class="comment">// From the html_parser crate</span>
|
||||
<span class="keyword">pub</span> <span class="keyword storage type">enum</span> <span class="type">Node</span> <span class="punctuation bracket">{</span>
|
||||
<span class="type enum variant">Text</span><span class="punctuation bracket">(</span><span class="type">String</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Element</span><span class="punctuation bracket">(</span><span class="type">Element</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Comment</span><span class="punctuation bracket">(</span><span class="type">String</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
|
||||
<span class="keyword">pub</span> <span class="keyword storage type">struct</span> <span class="type">Element</span> <span class="punctuation bracket">{</span>
|
||||
<span class="keyword">pub</span> <span class="variable other member">name</span>: <span class="type">String</span><span class="punctuation delimiter">,</span>
|
||||
<span class="keyword">pub</span> <span class="variable other member">attributes</span>: <span class="type">HashMap</span><span class="punctuation bracket"><</span><span class="type">String</span><span class="punctuation delimiter">,</span> <span class="type">Option</span><span class="punctuation bracket"><</span><span class="type">String</span><span class="punctuation bracket">></span><span class="punctuation bracket">></span><span class="punctuation delimiter">,</span>
|
||||
<span class="keyword">pub</span> <span class="variable other member">children</span>: <span class="type">Vec</span><span class="punctuation bracket"><</span><span class="type">Node</span><span class="punctuation bracket">></span><span class="punctuation delimiter">,</span>
|
||||
<span class="comment">// other fields omitted</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
</code></pre>
|
||||
</div>
|
||||
This is bad for cache locality and effectively forces the use
|
||||
of recursion in order to traverse the DOM. Recursion is undesirable
|
||||
for performance and robustness reasons. My approach is to create a
|
||||
flat array of elements.
|
||||
<div class="code-block">
|
||||
<pre><code>
|
||||
<span class="comment">// My implementation (fields omitted)</span>
|
||||
<span class="keyword">pub</span> <span class="keyword storage type">enum</span> <span class="type">HtmlElement</span> <span class="punctuation bracket">{</span>
|
||||
<span class="type enum variant">DocType</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Comment</span><span class="punctuation bracket">(</span><span class="comment">/* */</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">OpenTag</span> <span class="punctuation bracket">{</span> <span class="comment">/* */</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">CloseTag</span> <span class="punctuation bracket">{</span> <span class="comment">/* */</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Text</span><span class="punctuation bracket">(</span> <span class="comment">/* */</span> <span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Script</span> <span class="punctuation bracket">{</span> <span class="comment">/* */</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Style</span> <span class="punctuation bracket">{</span> <span class="comment">/* */</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
|
||||
<span class="type enum variant">Directive</span> <span class="punctuation bracket">{</span> <span class="comment">/* */</span> <span class="punctuation bracket">}</span><span class="punctuation delimiter">,</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
</code></pre>
|
||||
</div>
|
||||
This approach lends itself to linear iterative algorithms. An
|
||||
element's children can be represented as a slice of the DOM array,
|
||||
from the opening tag to its close tag.
|
||||
<h2 class="distinct"> Templates </h2>
|
||||
I define a "template" as a user defined element which expands into
|
||||
a larger piece of HTML. Templates can pass on children and attributes
|
||||
to the final output. Some special templates can perform other functions,
|
||||
like inlining a CSS file or syntax highlighting a code block.
|
||||
<div class="code-block">
|
||||
<pre><code>
|
||||
<span class="comment"><!-- Here is an example of a template definition --></span>
|
||||
<span class="punctuation bracket"><</span><span class="tag">Echo</span><span class="punctuation bracket">></span>
|
||||
<span class="comment"><!--
|
||||
This means to inherit the 'class' attribute from the invocation
|
||||
--></span>
|
||||
<span class="punctuation bracket"><</span><span class="tag">h1</span> <span class="attribute">class</span><span class="punctuation delimiter">=</span><span class="string">@class</span><span class="punctuation bracket">></span>
|
||||
<span class="comment"><!--
|
||||
This means place the children of the invocation here
|
||||
--></span>
|
||||
<span class="punctuation bracket"><</span>@children<span class="punctuation bracket">/></span>
|
||||
<span class="punctuation bracket"></</span><span class="tag">h1</span><span class="punctuation bracket">></span>
|
||||
<span class="punctuation bracket"><</span><span class="tag">h2</span><span class="punctuation bracket">></span>
|
||||
<span class="comment"><!-- This means to inherit the 'href' attribute from the invocation --></span>
|
||||
<span class="punctuation bracket"><</span><span class="tag">a</span> <span class="attribute">href</span><span class="punctuation delimiter">=</span><span class="string">"</span><span class="string">@href</span><span class="string">"</span><span class="punctuation bracket">></span>
|
||||
<span class="comment"><!-- Children can be duplicated any number of times --></span>
|
||||
<span class="punctuation bracket"><</span>@children<span class="punctuation bracket">/></span>
|
||||
<span class="punctuation bracket"></</span><span class="tag">a</span><span class="punctuation bracket">></span>
|
||||
<span class="punctuation bracket"></</span><span class="tag">h2</span><span class="punctuation bracket">></span>
|
||||
<span class="comment"><!--
|
||||
Attributes can also be duplciated, and boolean attributes
|
||||
work exactly as expected
|
||||
--></span>
|
||||
<span class="punctuation bracket"><</span><span class="tag">h3</span> <span class="attribute">class</span><span class="punctuation delimiter">=</span><span class="string">@class</span> <span class="attribute">hidden</span><span class="punctuation delimiter">=</span><span class="string">@hidden</span><span class="punctuation bracket">></span>
|
||||
<span class="punctuation bracket"><</span>@children<span class="punctuation bracket">/></span>
|
||||
<span class="punctuation bracket"></</span><span class="tag">h3</span><span class="punctuation bracket">></span>
|
||||
<span class="punctuation bracket"></</span><span class="tag">Echo</span><span class="punctuation bracket">></span>
|
||||
<span class="comment"><!-- End of template definition --></span>
|
||||
</code></pre>
|
||||
</div></div></div></div></body></html>
|
56
hyper-build/http-server.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta content="width=device-width, initial-scale=1" name="viewport"/><link type="image/x-icon" href="resources/favicon.svg" rel="icon"/><!-- --><!-- --></head><body><div class="pure-g"><div class="home-menu pure-menu pure-menu-horizontal"><ul class="pure-menu-list"><li class="pure-menu-item"><a href="/home" class="pure-menu-link"> Home </a></li><li class="pure-menu-item"><a href="/projects" class="pure-menu-link"> Projects </a></li><li class="pure-menu-item"><a href="/blog" class="pure-menu-link"> Blog </a></li><li class="pure-menu-item"><a href="/about" class="pure-menu-link"> About </a></li></ul></div><div class="pure-u-1-1"><div class="header"><h1> Rust HTTP Server </h1><h2> Keywords: Back End - TCP - SSL </h2></div><div class="content"><h2 class="distinct"> Motivation </h2>
|
||||
As a systems programmer, I want to understand the technologies
|
||||
I use at a very low level. Modern web stacks abstract away the
|
||||
process of handling TCP connections, serving SSL certificates,
|
||||
and parsing HTTP requests. The best way to understand a technology
|
||||
is to implement it from the ground up, I studied the HTTP 1.1
|
||||
specification and wrote a server to host my website.
|
||||
<h2 class="distinct"> Architecture </h2>
|
||||
At the most basic level, the server will translate the request URL
|
||||
into a local file path, and respond with the file matching that path
|
||||
if it exists. Request URLs are sanitized to prevent accessing files
|
||||
outside of the server's directory. Two tables are used for rewriting
|
||||
and routing URLs. This makes handling URLs predictable and robust.
|
||||
Because I route all traffic through Cloudflare, I decided to use its
|
||||
caching feature rather than implement a cache locally.
|
||||
<div class="code-block">
|
||||
<pre><code>
|
||||
<span class="comment">// Snippet from the request handling code</span>
|
||||
<span class="keyword">pub</span> <span class="keyword">async</span> <span class="keyword function">fn</span> <span class="function">construct_response</span><span class="punctuation bracket">(</span><span class="keyword storage modifier">&</span><span class="variable builtin">self</span><span class="punctuation delimiter">,</span> <span class="variable parameter">request_url</span>: <span class="keyword storage modifier">&</span><span class="type builtin">str</span><span class="punctuation bracket">)</span> <span class="operator">-></span> <span class="type">Response</span> <span class="punctuation bracket">{</span>
|
||||
<span class="keyword storage">let</span> <span class="variable">sanitized_url</span> <span class="operator">=</span> <span class="function">rewrite_url</span><span class="punctuation bracket">(</span><span class="variable parameter">request_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">;</span>
|
||||
<span class="keyword control conditional">if</span> <span class="variable">sanitized_url</span> <span class="operator">!=</span> <span class="variable parameter">request_url</span> <span class="punctuation bracket">{</span>
|
||||
<span class="keyword control return">return</span> <span class="type">Self</span><span class="punctuation delimiter">::</span><span class="function">redirect</span><span class="punctuation bracket">(</span><span class="operator">&</span><span class="variable">sanitized_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">;</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
|
||||
<span class="keyword storage">let</span> <span class="variable">routed_url</span> <span class="operator">=</span> <span class="variable builtin">self</span><span class="punctuation delimiter">.</span><span class="function">perform_routing</span><span class="punctuation bracket">(</span><span class="operator">&</span><span class="variable">sanitized_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">.</span><span class="keyword control return">await</span><span class="punctuation delimiter">;</span>
|
||||
<span class="keyword control conditional">if</span> <span class="keyword storage">let</span> <span class="constructor">Some</span><span class="punctuation bracket">(</span><span class="variable">response</span><span class="punctuation bracket">)</span> <span class="operator">=</span>
|
||||
<span class="variable builtin">self</span><span class="punctuation delimiter">.</span><span class="function">perform_redirecting</span><span class="punctuation bracket">(</span><span class="operator">&</span><span class="variable">routed_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">.</span><span class="keyword control return">await</span> <span class="punctuation bracket">{</span>
|
||||
<span class="keyword control return">return</span> <span class="variable">response</span><span class="punctuation delimiter">;</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
<span class="keyword control conditional">match</span> <span class="variable builtin">self</span><span class="punctuation delimiter">.</span><span class="function">get_resource</span><span class="punctuation bracket">(</span><span class="operator">&</span><span class="variable">routed_url</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">.</span><span class="keyword control return">await</span> <span class="punctuation bracket">{</span>
|
||||
<span class="constructor">Some</span><span class="punctuation bracket">(</span><span class="variable">bytes</span><span class="punctuation bracket">)</span> <span class="operator">=></span> <span class="type">Response</span><span class="punctuation delimiter">::</span><span class="function">from_data</span><span class="punctuation bracket">(</span><span class="variable">bytes</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="constructor">None</span> <span class="operator">=></span> <span class="type">Self</span><span class="punctuation delimiter">::</span><span class="function">not_found</span><span class="punctuation bracket">(</span><span class="punctuation bracket">)</span><span class="punctuation delimiter">,</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
<span class="punctuation bracket">}</span>
|
||||
</code></pre>
|
||||
</div><h2 class="distinct"> Dependencies </h2>
|
||||
My goal with this project was to provide a functional server using as few
|
||||
libraries as possible. The final project directly depends on four crates:
|
||||
<div class="code-block">
|
||||
<pre><code>
|
||||
// Snippet from Cargo<span class="punctuation delimiter">.</span>toml
|
||||
<span class="punctuation bracket">[</span><span class="type">dependencies</span><span class="punctuation bracket">]</span>
|
||||
<span class="variable other member">log</span> <span class="operator">=</span> <span class="string">"0.4"</span>
|
||||
<span class="variable other member">log4rs</span> <span class="operator">=</span> <span class="string">"1.3"</span>
|
||||
<span class="variable other member">tiny_http</span> <span class="operator">=</span> <span class="string">"0.12"</span>
|
||||
<span class="variable other member">tokio</span> <span class="operator">=</span> <span class="punctuation bracket">{</span><span class="variable other member">version</span> <span class="operator">=</span> <span class="string">"1"</span><span class="punctuation delimiter">,</span> <span class="variable other member">features</span> <span class="operator">=</span> <span class="punctuation bracket">[</span><span class="string">"full"</span><span class="punctuation bracket">]</span><span class="punctuation bracket">}</span>
|
||||
</code></pre>
|
||||
</div>
|
||||
The first two crates, log and log4rs, provide logging functions which aren't critical to the
|
||||
server's functionality. The tiny_http crate provides a simple wrapper for the standard
|
||||
library TCP stream, and performs SSL encryption. It is used as the backbone
|
||||
for many Rust web frameworks. While I could implement these features myself, I
|
||||
decided against it for security and browser compatibility reasons. Finally there is
|
||||
tokio, an async runtime. This is necessary because Rust does not provide a runtime
|
||||
by default, and building one is out of scope.
|
||||
</div></div></div></body></html>
|
BIN
hyper-build/resources/CascadiaMonoPL.woff2
Normal file
4
hyper-build/resources/award.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-award" viewBox="0 0 16 16">
|
||||
<path d="M9.669.864 8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68zm1.196 1.193.684 1.365 1.086 1.072L12.387 6l.248 1.506-1.086 1.072-.684 1.365-1.51.229L8 10.874l-1.355-.702-1.51-.229-.684-1.365-1.086-1.072L3.614 6l-.25-1.506 1.087-1.072.684-1.365 1.51-.229L8 1.126l1.356.702z"/>
|
||||
<path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 620 B |
1
hyper-build/resources/cloudflare-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFF" d="m115.679 69.288-15.591-8.94-2.689-1.163-63.781.436v32.381h82.061z"/><path fill="#F38020" d="M87.295 89.022c.763-2.617.472-5.015-.8-6.796-1.163-1.635-3.125-2.58-5.488-2.689l-44.737-.581c-.291 0-.545-.145-.691-.363s-.182-.509-.109-.8c.145-.436.581-.763 1.054-.8l45.137-.581c5.342-.254 11.157-4.579 13.192-9.885l2.58-6.723c.109-.291.145-.581.073-.872-2.906-13.158-14.644-22.97-28.672-22.97-12.938 0-23.913 8.359-27.838 19.952a13.35 13.35 0 0 0-9.267-2.58c-6.215.618-11.193 5.597-11.811 11.811-.145 1.599-.036 3.162.327 4.615C10.104 70.051 2 78.337 2 88.549c0 .909.073 1.817.182 2.726a.895.895 0 0 0 .872.763h82.57c.472 0 .909-.327 1.054-.8l.617-2.216z"/><path fill="#FAAE40" d="M101.542 60.275c-.4 0-.836 0-1.236.036-.291 0-.545.218-.654.509l-1.744 6.069c-.763 2.617-.472 5.015.8 6.796 1.163 1.635 3.125 2.58 5.488 2.689l9.522.581c.291 0 .545.145.691.363.145.218.182.545.109.8-.145.436-.581.763-1.054.8l-9.924.582c-5.379.254-11.157 4.579-13.192 9.885l-.727 1.853c-.145.363.109.727.509.727h34.089c.4 0 .763-.254.872-.654.581-2.108.909-4.325.909-6.614 0-13.447-10.975-24.422-24.458-24.422"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
4
hyper-build/resources/favicon.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-ethernet" viewBox="0 0 16 16">
|
||||
<path d="M14 13.5v-7a.5.5 0 0 0-.5-.5H12V4.5a.5.5 0 0 0-.5-.5h-1v-.5A.5.5 0 0 0 10 3H6a.5.5 0 0 0-.5.5V4h-1a.5.5 0 0 0-.5.5V6H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5M3.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m2 0h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25zM9.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25z"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 2a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 993 B |
1
hyper-build/resources/github-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#181616"><path fill-rule="evenodd" clip-rule="evenodd" d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"/><path d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"/></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
hyper-build/resources/godot-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M114.906 84.145s-.172-1.04-.27-1.028l-18.823 1.817a3.062 3.062 0 00-2.77 2.84l-.516 7.413-14.566 1.04-.988-6.72a3.089 3.089 0 00-3.04-2.62H54.067a3.089 3.089 0 00-3.039 2.62l-.988 6.72-14.566-1.04-.516-7.414a3.058 3.058 0 00-2.77-2.84l-18.835-1.816c-.094-.012-.168 1.028-.266 1.028l-.024 4.074 15.954 2.574.52 7.477a3.084 3.084 0 002.843 2.847l20.059 1.434c.078.004.152.008.226.008a3.087 3.087 0 003.031-2.621l1.02-6.915h14.57l1.02 6.915a3.088 3.088 0 003.254 2.613l20.062-1.434a3.084 3.084 0 002.844-2.847l.52-7.477 15.945-2.586zm0 0" fill="#fff"/><path d="M13.086 53.422v30.723c.059 0 .113.003.168.007L32.09 85.97a2.027 2.027 0 011.828 1.875l.582 8.316 16.426 1.172 1.133-7.672a2.03 2.03 0 012.007-1.734h19.868a2.03 2.03 0 012.007 1.734l1.133 7.672 16.43-1.172.578-8.316a2.027 2.027 0 011.828-1.875l18.828-1.817c.055-.004.11-.007.168-.007V81.69h.008V53.42c2.652-3.335 5.16-7.019 7.086-10.116-2.941-5.008-6.543-9.48-10.395-13.625a101.543 101.543 0 00-10.316 6.004c-1.64-1.633-3.484-2.965-5.3-4.36-1.782-1.43-3.79-2.48-5.696-3.703.566-4.223.848-8.379.96-12.719-4.913-2.476-10.155-4.113-15.456-5.293-2.117 3.559-4.055 7.41-5.738 11.176-2-.332-4.008-.457-6.02-.48V20.3c-.016 0-.027.004-.039.004s-.023-.004-.04-.004v.004c-2.01.023-4.019.148-6.019.48-1.683-3.765-3.62-7.617-5.738-11.176-5.3 1.18-10.543 2.817-15.457 5.293.113 4.34.395 8.496.961 12.72-1.906 1.222-3.914 2.273-5.695 3.702-1.813 1.395-3.66 2.727-5.301 4.36a101.543 101.543 0 00-10.316-6.004C12.543 33.824 8.94 38.297 6 43.305c2.313 3.629 4.793 7.273 7.086 10.117zm0 0" fill="#478cbf"/><path d="M98.008 89.84l-.582 8.36a2.024 2.024 0 01-1.88 1.878l-20.062 1.434c-.046.004-.097.004-.144.004-.996 0-1.86-.73-2.004-1.73l-1.152-7.806H55.816l-1.152 7.805a2.026 2.026 0 01-2.148 1.727l-20.063-1.434a2.024 2.024 0 01-1.879-1.879l-.582-8.36-16.937-1.632c.008 1.82.03 3.816.03 4.211 0 17.887 22.692 26.484 50.88 26.582h.07c28.188-.098 50.871-8.695 50.871-26.582 0-.402.024-2.39.031-4.211zm0 0" fill="#478cbf"/><path d="M48.652 65.895c0 6.27-5.082 11.351-11.351 11.351-6.266 0-11.348-5.082-11.348-11.351 0-6.266 5.082-11.344 11.348-11.344 6.27 0 11.351 5.078 11.351 11.344" fill="#fff"/><path d="M45.922 66.566a7.531 7.531 0 01-7.535 7.532 7.534 7.534 0 01-7.535-7.532 7.534 7.534 0 017.535-7.53 7.531 7.531 0 017.535 7.53" fill="#414042"/><path d="M64 78.277c-2.02 0-3.652-1.488-3.652-3.32v-10.45c0-1.831 1.632-3.32 3.652-3.32 2.016 0 3.656 1.489 3.656 3.32v10.45c0 1.832-1.64 3.32-3.656 3.32m15.348-12.382c0 6.27 5.082 11.351 11.351 11.351 6.266 0 11.348-5.082 11.348-11.351 0-6.266-5.082-11.344-11.348-11.344-6.27 0-11.351 5.078-11.351 11.344" fill="#fff"/><path d="M82.078 66.566a7.53 7.53 0 007.531 7.532 7.531 7.531 0 100-15.063 7.53 7.53 0 00-7.53 7.531" fill="#414042"/></svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
hyper-build/resources/html5-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#E44D26" d="M19.037 113.876L9.032 1.661h109.936l-10.016 112.198-45.019 12.48z"/><path fill="#F16529" d="M64 116.8l36.378-10.086 8.559-95.878H64z"/><path fill="#EBEBEB" d="M64 52.455H45.788L44.53 38.361H64V24.599H29.489l.33 3.692 3.382 37.927H64zm0 35.743l-.061.017-15.327-4.14-.979-10.975H33.816l1.928 21.609 28.193 7.826.063-.017z"/><path fill="#fff" d="M63.952 52.455v13.763h16.947l-1.597 17.849-15.35 4.143v14.319l28.215-7.82.207-2.325 3.234-36.233.335-3.696h-3.708zm0-27.856v13.762h33.244l.276-3.092.628-6.978.329-3.692z"/></svg>
|
After Width: | Height: | Size: 607 B |
1
hyper-build/resources/java-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#0074BD" d="M52.581 67.817s-3.284 1.911 2.341 2.557c6.814.778 10.297.666 17.805-.753 0 0 1.979 1.237 4.735 2.309-16.836 7.213-38.104-.418-24.881-4.113zm-2.059-9.415s-3.684 2.729 1.945 3.311c7.28.751 13.027.813 22.979-1.103 0 0 1.373 1.396 3.536 2.157-20.352 5.954-43.021.469-28.46-4.365z"/><path fill="#EA2D2E" d="M67.865 42.431c4.151 4.778-1.088 9.074-1.088 9.074s10.533-5.437 5.696-12.248c-4.519-6.349-7.982-9.502 10.771-20.378.001 0-29.438 7.35-15.379 23.552z"/><path fill="#0074BD" d="M90.132 74.781s2.432 2.005-2.678 3.555c-9.716 2.943-40.444 3.831-48.979.117-3.066-1.335 2.687-3.187 4.496-3.576 1.887-.409 2.965-.334 2.965-.334-3.412-2.403-22.055 4.719-9.469 6.762 34.324 5.563 62.567-2.506 53.665-6.524zm-35.97-26.134s-15.629 3.713-5.534 5.063c4.264.57 12.758.439 20.676-.225 6.469-.543 12.961-1.704 12.961-1.704s-2.279.978-3.93 2.104c-15.874 4.175-46.533 2.23-37.706-2.038 7.463-3.611 13.533-3.2 13.533-3.2zM82.2 64.317c16.135-8.382 8.674-16.438 3.467-15.353-1.273.266-1.845.496-1.845.496s.475-.744 1.378-1.063c10.302-3.62 18.223 10.681-3.322 16.345 0 0 .247-.224.322-.425z"/><path fill="#EA2D2E" d="M72.474 1.313s8.935 8.939-8.476 22.682c-13.962 11.027-3.184 17.313-.006 24.498-8.15-7.354-14.128-13.828-10.118-19.852 5.889-8.842 22.204-13.131 18.6-27.328z"/><path fill="#0074BD" d="M55.749 87.039c15.484.99 39.269-.551 39.832-7.878 0 0-1.082 2.777-12.799 4.981-13.218 2.488-29.523 2.199-39.191.603 0 0 1.98 1.64 12.158 2.294z"/><path fill="#EA2D2E" d="M94.866 100.181h-.472v-.264h1.27v.264h-.47v1.317h-.329l.001-1.317zm2.535.066h-.006l-.468 1.251h-.216l-.465-1.251h-.005v1.251h-.312v-1.581h.457l.431 1.119.432-1.119h.454v1.581h-.302v-1.251zm-44.19 14.79c-1.46 1.266-3.004 1.978-4.391 1.978-1.974 0-3.045-1.186-3.045-3.085 0-2.055 1.146-3.56 5.738-3.56h1.697v4.667h.001zm4.031 4.548v-14.077c0-3.599-2.053-5.973-6.997-5.973-2.886 0-5.416.714-7.473 1.622l.592 2.493c1.62-.595 3.715-1.147 5.771-1.147 2.85 0 4.075 1.147 4.075 3.521v1.779h-1.424c-6.921 0-10.044 2.685-10.044 6.723 0 3.479 2.058 5.456 5.933 5.456 2.49 0 4.351-1.028 6.088-2.533l.316 2.137h3.163v-.001zm13.452 0h-5.027l-6.051-19.689h4.391l3.756 12.099.835 3.635c1.896-5.258 3.24-10.596 3.912-15.733h4.271c-1.143 6.481-3.203 13.598-6.087 19.688zm19.288-4.548c-1.465 1.266-3.01 1.978-4.392 1.978-1.976 0-3.046-1.186-3.046-3.085 0-2.055 1.149-3.56 5.736-3.56h1.701v4.667h.001zm4.033 4.548v-14.077c0-3.599-2.059-5.973-6.999-5.973-2.889 0-5.418.714-7.475 1.622l.593 2.493c1.62-.595 3.718-1.147 5.774-1.147 2.846 0 4.074 1.147 4.074 3.521v1.779h-1.424c-6.923 0-10.045 2.685-10.045 6.723 0 3.479 2.056 5.456 5.93 5.456 2.491 0 4.349-1.028 6.091-2.533l.318 2.137h3.163v-.001zm-56.693 3.346c-1.147 1.679-3.005 3.008-5.037 3.757l-1.989-2.345c1.547-.794 2.872-2.075 3.489-3.269.532-1.063.753-2.43.753-5.701V92.891h4.284v22.173c0 4.375-.348 6.144-1.5 7.867z"/></svg>
|
After Width: | Height: | Size: 2.8 KiB |
BIN
hyper-build/resources/mona lisa.gif
Normal file
After Width: | Height: | Size: 6.4 MiB |
1
hyper-build/resources/python-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFD845" d="M49.33 62h29.159C86.606 62 93 55.132 93 46.981V19.183c0-7.912-6.632-13.856-14.555-15.176-5.014-.835-10.195-1.215-15.187-1.191-4.99.023-9.612.448-13.805 1.191C37.098 6.188 35 10.758 35 19.183V30h29v4H23.776c-8.484 0-15.914 5.108-18.237 14.811-2.681 11.12-2.8 17.919 0 29.53C7.614 86.983 12.569 93 21.054 93H31V79.952C31 70.315 39.428 62 49.33 62zm-1.838-39.11c-3.026 0-5.478-2.479-5.478-5.545 0-3.079 2.451-5.581 5.478-5.581 3.015 0 5.479 2.502 5.479 5.581-.001 3.066-2.465 5.545-5.479 5.545zm74.789 25.921C120.183 40.363 116.178 34 107.682 34H97v12.981C97 57.031 88.206 65 78.489 65H49.33C41.342 65 35 72.326 35 80.326v27.8c0 7.91 6.745 12.564 14.462 14.834 9.242 2.717 17.994 3.208 29.051 0C85.862 120.831 93 116.549 93 108.126V97H64v-4h43.682c8.484 0 11.647-5.776 14.599-14.66 3.047-9.145 2.916-17.799 0-29.529zm-41.955 55.606c3.027 0 5.479 2.479 5.479 5.547 0 3.076-2.451 5.579-5.479 5.579-3.015 0-5.478-2.502-5.478-5.579 0-3.068 2.463-5.547 5.478-5.547z"/></svg>
|
After Width: | Height: | Size: 1 KiB |
1
hyper-build/resources/rust-icon.svg
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
hyper-build/resources/starry night.gif
Normal file
After Width: | Height: | Size: 7 MiB |
BIN
hyper-build/resources/wanderer.gif
Normal file
After Width: | Height: | Size: 6.6 MiB |
5
hyper-build/resources/web-assembly-icon.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 107.62 107.62"><defs><style>
|
||||
.cls-1 {
|
||||
fill: #654ff0;
|
||||
}
|
||||
</style></defs><title>web-assembly-icon</title><g id="Layer_2" data-name="Layer 2"><g id="Notch_-_Purple" data-name="Notch - Purple"><g id="icon"><path class="cls-1" d="M66.12,0c0,.19,0,.38,0,.58a12.34,12.34,0,1,1-24.68,0c0-.2,0-.39,0-.58H0V107.62H107.62V0ZM51.38,96.1,46.14,70.17H46L40.39,96.1H33.18L25,58h7.13L37,83.93h.09L42.94,58h6.67L54.9,84.25H55L60.55,58h7L58.46,96.1Zm39.26,0-2.43-8.48H75.4L73.53,96.1H66.36L75.59,58H86.83L98,96.1Z"/><polygon class="cls-1" points="79.87 67.39 76.76 81.37 86.44 81.37 82.87 67.39 79.87 67.39"/></g></g></g></svg>
|
After Width: | Height: | Size: 685 B |
231
hyper-build/styles/extend.css
Normal file
|
@ -0,0 +1,231 @@
|
|||
@font-face {
|
||||
font-family: Cascadia;
|
||||
src: url("resources/CascadiaMonoPL.woff2");
|
||||
}
|
||||
|
||||
body {
|
||||
color: #444;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.pure-img-responsive { max-width: 100%; height: auto;
|
||||
}
|
||||
|
||||
.cascadia {
|
||||
font-family: Cascadia;
|
||||
}
|
||||
|
||||
.home-menu {
|
||||
padding: 0.5em;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
|
||||
}
|
||||
.home-menu {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.home-menu a {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.home-menu li a:hover,
|
||||
.home-menu li a:focus {
|
||||
animation: menu-hover-anim 0.25s ease-out;
|
||||
animation-fill-mode: forwards;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.link {
|
||||
color: #00f;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.video {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.video iframe {
|
||||
width: 32em;
|
||||
height: 18em;
|
||||
frameborder: "0";
|
||||
}
|
||||
|
||||
/*
|
||||
Add transition to containers so they can push in and out.
|
||||
*/
|
||||
#layout,
|
||||
#menu,
|
||||
.menu-link {
|
||||
-webkit-transition: all 0.2s ease-out;
|
||||
-moz-transition: all 0.2s ease-out;
|
||||
-ms-transition: all 0.2s ease-out;
|
||||
-o-transition: all 0.2s ease-out;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
/*
|
||||
This is the parent `<div>` that contains the menu and the content area.
|
||||
*/
|
||||
#layout {
|
||||
position: relative;
|
||||
left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
#layout.active #menu {
|
||||
left: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#layout.active .menu-link {
|
||||
left: 150px;
|
||||
}
|
||||
/*
|
||||
The content `<div>` is where all your content goes.
|
||||
*/
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
padding: 0 2em;
|
||||
max-width: 800px;
|
||||
margin-bottom: 50px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
padding: 2.5em 4em 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0.2em 0;
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.header h2 {
|
||||
font-weight: 300;
|
||||
color: #888;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.distinct {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
}
|
||||
|
||||
@keyframes card-hover-anim {
|
||||
to {background-color: #ccf;}
|
||||
}
|
||||
@keyframes card-text-hover-anim {
|
||||
to {color: #222;}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #eee;
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
margin: 1em 0em 1em 0em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card h1 {
|
||||
color: #444;
|
||||
}
|
||||
.card h2 {
|
||||
color: #777;
|
||||
}
|
||||
.card h3 {
|
||||
color: #999;
|
||||
}
|
||||
.card a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
animation: card-hover-anim 0.25s ease-out;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
|
||||
/* .card:hover h3 { */
|
||||
/* animation: card-text-hover-anim 0.25s ease-out; */
|
||||
/* animation-fill-mode: forwards; */
|
||||
/* } */
|
||||
|
||||
.card img {
|
||||
vertical-align: bottom;
|
||||
float: right;
|
||||
padding: 0em 0.1em 0em 0.1em;
|
||||
width: 60px;
|
||||
}
|
||||
.card svg {
|
||||
vertical-align: bottom;
|
||||
float: right;
|
||||
padding: 0em 0.1em 0em 0.1em;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
|
||||
.content-subhead {
|
||||
margin: 50px 0 20px 0;
|
||||
font-weight: 300;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.code-block {
|
||||
background-color: #fdf6e3;
|
||||
color: #002b36;
|
||||
font-family: Cascadia;
|
||||
padding: 0em 0.5em 0em 0.5em;
|
||||
margin: 1em 0em 1em 0em;
|
||||
page-break-inside: avoid;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
.code-block pre {
|
||||
display: block;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.code-block pre code {
|
||||
font-family: Cascadia;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.code-block span {
|
||||
background-color: #fdf6e3;
|
||||
}
|
||||
.keyword {
|
||||
color: #6c71c4;
|
||||
}
|
||||
.constant {
|
||||
color: #cb4b16;
|
||||
}
|
||||
.function {
|
||||
color: #268bd2;
|
||||
}
|
||||
.operator {
|
||||
color: #6c71c4;
|
||||
}
|
||||
.punctuation {
|
||||
color: #586e75;
|
||||
}
|
||||
.string {
|
||||
color: #859900;
|
||||
}
|
||||
.type {
|
||||
color: #2aa198;
|
||||
}
|
||||
.property {
|
||||
color: #586e75;
|
||||
}
|
||||
|
11
hyper-build/styles/main.css
Normal file
101
hyper-src/home.html
Normal file
|
@ -0,0 +1,101 @@
|
|||
<Container>
|
||||
<div class="header">
|
||||
<h1 id="demo" />
|
||||
<h2> Completed Projects </h2>
|
||||
</div>
|
||||
<script>
|
||||
var i = 0;
|
||||
var txt = 'Welcome to my home page';
|
||||
var speed = 30;
|
||||
|
||||
function typeWriter() {
|
||||
if (i < txt.length) {
|
||||
document.getElementById("demo").innerHTML += txt.charAt(i);
|
||||
i++;
|
||||
setTimeout(typeWriter, speed + Math.random() * 25.0);
|
||||
}
|
||||
}
|
||||
typeWriter()
|
||||
</script>
|
||||
|
||||
<div class="content">
|
||||
<Card href="http-server.html">
|
||||
<RustIcon />
|
||||
<CloudflareIcon />
|
||||
<h1> HTTP Server </h1>
|
||||
<h3> Back End - TCP - SSL </h3>
|
||||
<h2>
|
||||
Currently serving you this website
|
||||
</h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<RustIcon />
|
||||
<HtmlIcon />
|
||||
<h1> HTML Templating Engine </h1>
|
||||
<h3> Front End - Parser Design </h3>
|
||||
<h2>
|
||||
Used to create this website
|
||||
</h2>
|
||||
</Card>
|
||||
<Card href="html-templating.html">
|
||||
<RustIcon />
|
||||
<WasmIcon />
|
||||
<h1> Forte Assembly Language </h1>
|
||||
<h3> Programming Language - Hackathon </h3>
|
||||
<h2>
|
||||
Radically different machine code. A creative-coding endeavor
|
||||
</h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<RustIcon />
|
||||
<WasmIcon />
|
||||
<h1> Fishbowl </h1>
|
||||
<h3> Image Encoding - Hardware Rendering </h3>
|
||||
<h2> Kinematic image processing with GPU acceleration </h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<RustIcon />
|
||||
<WasmIcon />
|
||||
<h1> Math Interpreter </h1>
|
||||
<h3> Parser Design </h3>
|
||||
<h2>
|
||||
Interpret and evaluate plain-text math expressions
|
||||
</h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<RustIcon />
|
||||
<h1> nd-range </h1>
|
||||
<h3> Vector Math - Standard Library </h3>
|
||||
<h2>
|
||||
An extension of Rust's 'Range' type
|
||||
using the Cartesian Product Algorithm
|
||||
</h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<RustIcon />
|
||||
<h1> Fractal Explorer </h1>
|
||||
<h3> Parallel Algorithms - Optimization - Hackathon </h3>
|
||||
<h2>
|
||||
A Mandelbrot Fractal viewer using CPU parallelism
|
||||
and the derivative bail algorithm
|
||||
</h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<PythonIcon />
|
||||
<h1> Pokédex </h1>
|
||||
<h3> TKinter - Web APIs - Native UI </h3>
|
||||
<h2>
|
||||
A TKinter app for viewing the original Pokédex, with
|
||||
stats scraped from online sources
|
||||
</h2>
|
||||
</Card>
|
||||
<Card>
|
||||
<PythonIcon />
|
||||
<h1> Stock Trading A.I. </h1>
|
||||
<h3> Command Line App - Web APIs </h3>
|
||||
<h2>
|
||||
A simple heuristic trading algorithm
|
||||
</h2>
|
||||
</Card>
|
||||
</div>
|
||||
</Container>
|
90
hyper-src/html-templating.html
Normal file
|
@ -0,0 +1,90 @@
|
|||
<Container>
|
||||
<div class="header">
|
||||
<h1> Rust HTML Templating Engine </h1>
|
||||
<h2> Front End - Parser Design </h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2 class="distinct"> Motivation </h2>
|
||||
HTML is a powerful tool for creating static web pages, but
|
||||
is not easily modular or scalable. Front end frameworks like
|
||||
React provide reusable components. I want to use components
|
||||
without the overhead of a front-end framework. I want the final
|
||||
output to be statically generated rather than generated on the fly
|
||||
by the server or the client, minified and with inlined CSS and
|
||||
JavaScript.
|
||||
<h2 class="distinct"> Approach </h2>
|
||||
After looking at some of the HTML parsing libraries, I was
|
||||
unsatisfied with the approaches most developers took. The DOM
|
||||
is represented as a tree structure, where each node keeps a
|
||||
pointer to its children.
|
||||
<@code lang="rust">
|
||||
// From the html_parser crate
|
||||
pub enum Node {
|
||||
Text(String),
|
||||
Element(Element),
|
||||
Comment(String),
|
||||
}
|
||||
|
||||
pub struct Element {
|
||||
pub name: String,
|
||||
pub attributes: HashMap<String, Option<String>>,
|
||||
pub children: Vec<Node>,
|
||||
// other fields omitted
|
||||
}
|
||||
</@code>
|
||||
This is bad for cache locality and effectively forces the use
|
||||
of recursion in order to traverse the DOM. Recursion is undesirable
|
||||
for performance and robustness reasons. My approach is to create a
|
||||
flat array of elements.
|
||||
<@code lang="rust">
|
||||
// My implementation (fields omitted)
|
||||
pub enum HtmlElement {
|
||||
DocType,
|
||||
Comment(/* */),
|
||||
OpenTag { /* */ },
|
||||
CloseTag { /* */ },
|
||||
Text( /* */ ),
|
||||
Script { /* */ },
|
||||
Style { /* */ },
|
||||
Directive { /* */ },
|
||||
}
|
||||
</@code>
|
||||
This approach lends itself to linear iterative algorithms. An
|
||||
element's children can be represented as a slice of the DOM array,
|
||||
from the opening tag to its close tag.
|
||||
<h2 class="distinct"> Templates </h2>
|
||||
I define a "template" as a user defined element which expands into
|
||||
a larger piece of HTML. Templates can pass on children and attributes
|
||||
to the final output. Some special templates can perform other functions,
|
||||
like inlining a CSS file or syntax highlighting a code block.
|
||||
<@code lang="html">
|
||||
<!-- Here is an example of a template definition -->
|
||||
<Echo>
|
||||
<!--
|
||||
This means to inherit the 'class' attribute from the invocation
|
||||
-->
|
||||
<h1 class=@class>
|
||||
<!--
|
||||
This means place the children of the invocation here
|
||||
-->
|
||||
<@children/>
|
||||
</h1>
|
||||
<h2>
|
||||
<!-- This means to inherit the 'href' attribute from the invocation -->
|
||||
<a href="@href">
|
||||
<!-- Children can be duplicated any number of times -->
|
||||
<@children/>
|
||||
</a>
|
||||
</h2>
|
||||
<!--
|
||||
Attributes can also be duplciated, and boolean attributes
|
||||
work exactly as expected
|
||||
-->
|
||||
<h3 class=@class hidden=@hidden>
|
||||
<@children/>
|
||||
</h3>
|
||||
</Echo>
|
||||
<!-- End of template definition -->
|
||||
</@code>
|
||||
</div>
|
||||
</Container>
|
60
hyper-src/http-server.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<Container>
|
||||
<div class="header">
|
||||
<h1> Rust HTTP Server </h1>
|
||||
<h2> Keywords: Back End - TCP - SSL </h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2 class="distinct"> Motivation </h2>
|
||||
As a systems programmer, I want to understand the technologies
|
||||
I use at a very low level. Modern web stacks abstract away the
|
||||
process of handling TCP connections, serving SSL certificates,
|
||||
and parsing HTTP requests. The best way to understand a technology
|
||||
is to implement it from the ground up, I studied the HTTP 1.1
|
||||
specification and wrote a server to host my website.
|
||||
<h2 class="distinct"> Architecture </h2>
|
||||
At the most basic level, the server will translate the request URL
|
||||
into a local file path, and respond with the file matching that path
|
||||
if it exists. Request URLs are sanitized to prevent accessing files
|
||||
outside of the server's directory. Two tables are used for rewriting
|
||||
and routing URLs. This makes handling URLs predictable and robust.
|
||||
Because I route all traffic through Cloudflare, I decided to use its
|
||||
caching feature rather than implement a cache locally.
|
||||
<@code lang="rust">
|
||||
// Snippet from the request handling code
|
||||
pub async fn construct_response(&self, request_url: &str) -> Response {
|
||||
let sanitized_url = rewrite_url(request_url);
|
||||
if sanitized_url != request_url {
|
||||
return Self::redirect(&sanitized_url);
|
||||
}
|
||||
|
||||
let routed_url = self.perform_routing(&sanitized_url).await;
|
||||
if let Some(response) =
|
||||
self.perform_redirecting(&routed_url).await {
|
||||
return response;
|
||||
}
|
||||
match self.get_resource(&routed_url).await {
|
||||
Some(bytes) => Response::from_data(bytes),
|
||||
None => Self::not_found(),
|
||||
}
|
||||
}
|
||||
</@code>
|
||||
<h2 class="distinct"> Dependencies </h2>
|
||||
My goal with this project was to provide a functional server using as few
|
||||
libraries as possible. The final project directly depends on four crates:
|
||||
<@code lang=toml>
|
||||
// Snippet from Cargo.toml
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
log4rs = "1.3"
|
||||
tiny_http = "0.12"
|
||||
tokio = {version = "1", features = ["full"]}
|
||||
</@code>
|
||||
The first two crates, log and log4rs, provide logging functions which aren't critical to the
|
||||
server's functionality. The tiny_http crate provides a simple wrapper for the standard
|
||||
library TCP stream, and performs SSL encryption. It is used as the backbone
|
||||
for many Rust web frameworks. While I could implement these features myself, I
|
||||
decided against it for security and browser compatibility reasons. Finally there is
|
||||
tokio, an async runtime. This is necessary because Rust does not provide a runtime
|
||||
by default, and building one is out of scope.
|
||||
</div>
|
||||
</Container>
|
BIN
hyper-src/resources/CascadiaMonoPL.woff2
Normal file
4
hyper-src/resources/award.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-award" viewBox="0 0 16 16">
|
||||
<path d="M9.669.864 8 0 6.331.864l-1.858.282-.842 1.68-1.337 1.32L2.6 6l-.306 1.854 1.337 1.32.842 1.68 1.858.282L8 12l1.669-.864 1.858-.282.842-1.68 1.337-1.32L13.4 6l.306-1.854-1.337-1.32-.842-1.68zm1.196 1.193.684 1.365 1.086 1.072L12.387 6l.248 1.506-1.086 1.072-.684 1.365-1.51.229L8 10.874l-1.355-.702-1.51-.229-.684-1.365-1.086-1.072L3.614 6l-.25-1.506 1.087-1.072.684-1.365 1.51-.229L8 1.126l1.356.702z"/>
|
||||
<path d="M4 11.794V16l4-1 4 1v-4.206l-2.018.306L8 13.126 6.018 12.1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 620 B |
1
hyper-src/resources/cloudflare-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFF" d="m115.679 69.288-15.591-8.94-2.689-1.163-63.781.436v32.381h82.061z"/><path fill="#F38020" d="M87.295 89.022c.763-2.617.472-5.015-.8-6.796-1.163-1.635-3.125-2.58-5.488-2.689l-44.737-.581c-.291 0-.545-.145-.691-.363s-.182-.509-.109-.8c.145-.436.581-.763 1.054-.8l45.137-.581c5.342-.254 11.157-4.579 13.192-9.885l2.58-6.723c.109-.291.145-.581.073-.872-2.906-13.158-14.644-22.97-28.672-22.97-12.938 0-23.913 8.359-27.838 19.952a13.35 13.35 0 0 0-9.267-2.58c-6.215.618-11.193 5.597-11.811 11.811-.145 1.599-.036 3.162.327 4.615C10.104 70.051 2 78.337 2 88.549c0 .909.073 1.817.182 2.726a.895.895 0 0 0 .872.763h82.57c.472 0 .909-.327 1.054-.8l.617-2.216z"/><path fill="#FAAE40" d="M101.542 60.275c-.4 0-.836 0-1.236.036-.291 0-.545.218-.654.509l-1.744 6.069c-.763 2.617-.472 5.015.8 6.796 1.163 1.635 3.125 2.58 5.488 2.689l9.522.581c.291 0 .545.145.691.363.145.218.182.545.109.8-.145.436-.581.763-1.054.8l-9.924.582c-5.379.254-11.157 4.579-13.192 9.885l-.727 1.853c-.145.363.109.727.509.727h34.089c.4 0 .763-.254.872-.654.581-2.108.909-4.325.909-6.614 0-13.447-10.975-24.422-24.458-24.422"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
4
hyper-src/resources/favicon.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-ethernet" viewBox="0 0 16 16">
|
||||
<path d="M14 13.5v-7a.5.5 0 0 0-.5-.5H12V4.5a.5.5 0 0 0-.5-.5h-1v-.5A.5.5 0 0 0 10 3H6a.5.5 0 0 0-.5.5V4h-1a.5.5 0 0 0-.5.5V6H2.5a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5M3.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m2 0h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25zM9.75 11h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25v-1.5a.25.25 0 0 1 .25-.25m1.75.25a.25.25 0 0 1 .25-.25h.5a.25.25 0 0 1 .25.25v1.5a.25.25 0 0 1-.25.25h-.5a.25.25 0 0 1-.25-.25z"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 2a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 993 B |
1
hyper-src/resources/github-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#181616"><path fill-rule="evenodd" clip-rule="evenodd" d="M64 5.103c-33.347 0-60.388 27.035-60.388 60.388 0 26.682 17.303 49.317 41.297 57.303 3.017.56 4.125-1.31 4.125-2.905 0-1.44-.056-6.197-.082-11.243-16.8 3.653-20.345-7.125-20.345-7.125-2.747-6.98-6.705-8.836-6.705-8.836-5.48-3.748.413-3.67.413-3.67 6.063.425 9.257 6.223 9.257 6.223 5.386 9.23 14.127 6.562 17.573 5.02.542-3.903 2.107-6.568 3.834-8.076-13.413-1.525-27.514-6.704-27.514-29.843 0-6.593 2.36-11.98 6.223-16.21-.628-1.52-2.695-7.662.584-15.98 0 0 5.07-1.623 16.61 6.19C53.7 35 58.867 34.327 64 34.304c5.13.023 10.3.694 15.127 2.033 11.526-7.813 16.59-6.19 16.59-6.19 3.287 8.317 1.22 14.46.593 15.98 3.872 4.23 6.215 9.617 6.215 16.21 0 23.194-14.127 28.3-27.574 29.796 2.167 1.874 4.097 5.55 4.097 11.183 0 8.08-.07 14.583-.07 16.572 0 1.607 1.088 3.49 4.148 2.897 23.98-7.994 41.263-30.622 41.263-57.294C124.388 32.14 97.35 5.104 64 5.104z"/><path d="M26.484 91.806c-.133.3-.605.39-1.035.185-.44-.196-.685-.605-.543-.906.13-.31.603-.395 1.04-.188.44.197.69.61.537.91zm2.446 2.729c-.287.267-.85.143-1.232-.28-.396-.42-.47-.983-.177-1.254.298-.266.844-.14 1.24.28.394.426.472.984.17 1.255zM31.312 98.012c-.37.258-.976.017-1.35-.52-.37-.538-.37-1.183.01-1.44.373-.258.97-.025 1.35.507.368.545.368 1.19-.01 1.452zm3.261 3.361c-.33.365-1.036.267-1.552-.23-.527-.487-.674-1.18-.343-1.544.336-.366 1.045-.264 1.564.23.527.486.686 1.18.333 1.543zm4.5 1.951c-.147.473-.825.688-1.51.486-.683-.207-1.13-.76-.99-1.238.14-.477.823-.7 1.512-.485.683.206 1.13.756.988 1.237zm4.943.361c.017.498-.563.91-1.28.92-.723.017-1.308-.387-1.315-.877 0-.503.568-.91 1.29-.924.717-.013 1.306.387 1.306.88zm4.598-.782c.086.485-.413.984-1.126 1.117-.7.13-1.35-.172-1.44-.653-.086-.498.422-.997 1.122-1.126.714-.123 1.354.17 1.444.663zm0 0"/></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
hyper-src/resources/godot-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M114.906 84.145s-.172-1.04-.27-1.028l-18.823 1.817a3.062 3.062 0 00-2.77 2.84l-.516 7.413-14.566 1.04-.988-6.72a3.089 3.089 0 00-3.04-2.62H54.067a3.089 3.089 0 00-3.039 2.62l-.988 6.72-14.566-1.04-.516-7.414a3.058 3.058 0 00-2.77-2.84l-18.835-1.816c-.094-.012-.168 1.028-.266 1.028l-.024 4.074 15.954 2.574.52 7.477a3.084 3.084 0 002.843 2.847l20.059 1.434c.078.004.152.008.226.008a3.087 3.087 0 003.031-2.621l1.02-6.915h14.57l1.02 6.915a3.088 3.088 0 003.254 2.613l20.062-1.434a3.084 3.084 0 002.844-2.847l.52-7.477 15.945-2.586zm0 0" fill="#fff"/><path d="M13.086 53.422v30.723c.059 0 .113.003.168.007L32.09 85.97a2.027 2.027 0 011.828 1.875l.582 8.316 16.426 1.172 1.133-7.672a2.03 2.03 0 012.007-1.734h19.868a2.03 2.03 0 012.007 1.734l1.133 7.672 16.43-1.172.578-8.316a2.027 2.027 0 011.828-1.875l18.828-1.817c.055-.004.11-.007.168-.007V81.69h.008V53.42c2.652-3.335 5.16-7.019 7.086-10.116-2.941-5.008-6.543-9.48-10.395-13.625a101.543 101.543 0 00-10.316 6.004c-1.64-1.633-3.484-2.965-5.3-4.36-1.782-1.43-3.79-2.48-5.696-3.703.566-4.223.848-8.379.96-12.719-4.913-2.476-10.155-4.113-15.456-5.293-2.117 3.559-4.055 7.41-5.738 11.176-2-.332-4.008-.457-6.02-.48V20.3c-.016 0-.027.004-.039.004s-.023-.004-.04-.004v.004c-2.01.023-4.019.148-6.019.48-1.683-3.765-3.62-7.617-5.738-11.176-5.3 1.18-10.543 2.817-15.457 5.293.113 4.34.395 8.496.961 12.72-1.906 1.222-3.914 2.273-5.695 3.702-1.813 1.395-3.66 2.727-5.301 4.36a101.543 101.543 0 00-10.316-6.004C12.543 33.824 8.94 38.297 6 43.305c2.313 3.629 4.793 7.273 7.086 10.117zm0 0" fill="#478cbf"/><path d="M98.008 89.84l-.582 8.36a2.024 2.024 0 01-1.88 1.878l-20.062 1.434c-.046.004-.097.004-.144.004-.996 0-1.86-.73-2.004-1.73l-1.152-7.806H55.816l-1.152 7.805a2.026 2.026 0 01-2.148 1.727l-20.063-1.434a2.024 2.024 0 01-1.879-1.879l-.582-8.36-16.937-1.632c.008 1.82.03 3.816.03 4.211 0 17.887 22.692 26.484 50.88 26.582h.07c28.188-.098 50.871-8.695 50.871-26.582 0-.402.024-2.39.031-4.211zm0 0" fill="#478cbf"/><path d="M48.652 65.895c0 6.27-5.082 11.351-11.351 11.351-6.266 0-11.348-5.082-11.348-11.351 0-6.266 5.082-11.344 11.348-11.344 6.27 0 11.351 5.078 11.351 11.344" fill="#fff"/><path d="M45.922 66.566a7.531 7.531 0 01-7.535 7.532 7.534 7.534 0 01-7.535-7.532 7.534 7.534 0 017.535-7.53 7.531 7.531 0 017.535 7.53" fill="#414042"/><path d="M64 78.277c-2.02 0-3.652-1.488-3.652-3.32v-10.45c0-1.831 1.632-3.32 3.652-3.32 2.016 0 3.656 1.489 3.656 3.32v10.45c0 1.832-1.64 3.32-3.656 3.32m15.348-12.382c0 6.27 5.082 11.351 11.351 11.351 6.266 0 11.348-5.082 11.348-11.351 0-6.266-5.082-11.344-11.348-11.344-6.27 0-11.351 5.078-11.351 11.344" fill="#fff"/><path d="M82.078 66.566a7.53 7.53 0 007.531 7.532 7.531 7.531 0 100-15.063 7.53 7.53 0 00-7.53 7.531" fill="#414042"/></svg>
|
After Width: | Height: | Size: 2.7 KiB |
1
hyper-src/resources/html5-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#E44D26" d="M19.037 113.876L9.032 1.661h109.936l-10.016 112.198-45.019 12.48z"/><path fill="#F16529" d="M64 116.8l36.378-10.086 8.559-95.878H64z"/><path fill="#EBEBEB" d="M64 52.455H45.788L44.53 38.361H64V24.599H29.489l.33 3.692 3.382 37.927H64zm0 35.743l-.061.017-15.327-4.14-.979-10.975H33.816l1.928 21.609 28.193 7.826.063-.017z"/><path fill="#fff" d="M63.952 52.455v13.763h16.947l-1.597 17.849-15.35 4.143v14.319l28.215-7.82.207-2.325 3.234-36.233.335-3.696h-3.708zm0-27.856v13.762h33.244l.276-3.092.628-6.978.329-3.692z"/></svg>
|
After Width: | Height: | Size: 607 B |
1
hyper-src/resources/java-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#0074BD" d="M52.581 67.817s-3.284 1.911 2.341 2.557c6.814.778 10.297.666 17.805-.753 0 0 1.979 1.237 4.735 2.309-16.836 7.213-38.104-.418-24.881-4.113zm-2.059-9.415s-3.684 2.729 1.945 3.311c7.28.751 13.027.813 22.979-1.103 0 0 1.373 1.396 3.536 2.157-20.352 5.954-43.021.469-28.46-4.365z"/><path fill="#EA2D2E" d="M67.865 42.431c4.151 4.778-1.088 9.074-1.088 9.074s10.533-5.437 5.696-12.248c-4.519-6.349-7.982-9.502 10.771-20.378.001 0-29.438 7.35-15.379 23.552z"/><path fill="#0074BD" d="M90.132 74.781s2.432 2.005-2.678 3.555c-9.716 2.943-40.444 3.831-48.979.117-3.066-1.335 2.687-3.187 4.496-3.576 1.887-.409 2.965-.334 2.965-.334-3.412-2.403-22.055 4.719-9.469 6.762 34.324 5.563 62.567-2.506 53.665-6.524zm-35.97-26.134s-15.629 3.713-5.534 5.063c4.264.57 12.758.439 20.676-.225 6.469-.543 12.961-1.704 12.961-1.704s-2.279.978-3.93 2.104c-15.874 4.175-46.533 2.23-37.706-2.038 7.463-3.611 13.533-3.2 13.533-3.2zM82.2 64.317c16.135-8.382 8.674-16.438 3.467-15.353-1.273.266-1.845.496-1.845.496s.475-.744 1.378-1.063c10.302-3.62 18.223 10.681-3.322 16.345 0 0 .247-.224.322-.425z"/><path fill="#EA2D2E" d="M72.474 1.313s8.935 8.939-8.476 22.682c-13.962 11.027-3.184 17.313-.006 24.498-8.15-7.354-14.128-13.828-10.118-19.852 5.889-8.842 22.204-13.131 18.6-27.328z"/><path fill="#0074BD" d="M55.749 87.039c15.484.99 39.269-.551 39.832-7.878 0 0-1.082 2.777-12.799 4.981-13.218 2.488-29.523 2.199-39.191.603 0 0 1.98 1.64 12.158 2.294z"/><path fill="#EA2D2E" d="M94.866 100.181h-.472v-.264h1.27v.264h-.47v1.317h-.329l.001-1.317zm2.535.066h-.006l-.468 1.251h-.216l-.465-1.251h-.005v1.251h-.312v-1.581h.457l.431 1.119.432-1.119h.454v1.581h-.302v-1.251zm-44.19 14.79c-1.46 1.266-3.004 1.978-4.391 1.978-1.974 0-3.045-1.186-3.045-3.085 0-2.055 1.146-3.56 5.738-3.56h1.697v4.667h.001zm4.031 4.548v-14.077c0-3.599-2.053-5.973-6.997-5.973-2.886 0-5.416.714-7.473 1.622l.592 2.493c1.62-.595 3.715-1.147 5.771-1.147 2.85 0 4.075 1.147 4.075 3.521v1.779h-1.424c-6.921 0-10.044 2.685-10.044 6.723 0 3.479 2.058 5.456 5.933 5.456 2.49 0 4.351-1.028 6.088-2.533l.316 2.137h3.163v-.001zm13.452 0h-5.027l-6.051-19.689h4.391l3.756 12.099.835 3.635c1.896-5.258 3.24-10.596 3.912-15.733h4.271c-1.143 6.481-3.203 13.598-6.087 19.688zm19.288-4.548c-1.465 1.266-3.01 1.978-4.392 1.978-1.976 0-3.046-1.186-3.046-3.085 0-2.055 1.149-3.56 5.736-3.56h1.701v4.667h.001zm4.033 4.548v-14.077c0-3.599-2.059-5.973-6.999-5.973-2.889 0-5.418.714-7.475 1.622l.593 2.493c1.62-.595 3.718-1.147 5.774-1.147 2.846 0 4.074 1.147 4.074 3.521v1.779h-1.424c-6.923 0-10.045 2.685-10.045 6.723 0 3.479 2.056 5.456 5.93 5.456 2.491 0 4.349-1.028 6.091-2.533l.318 2.137h3.163v-.001zm-56.693 3.346c-1.147 1.679-3.005 3.008-5.037 3.757l-1.989-2.345c1.547-.794 2.872-2.075 3.489-3.269.532-1.063.753-2.43.753-5.701V92.891h4.284v22.173c0 4.375-.348 6.144-1.5 7.867z"/></svg>
|
After Width: | Height: | Size: 2.8 KiB |
BIN
hyper-src/resources/mona lisa.gif
Normal file
After Width: | Height: | Size: 6.4 MiB |
1
hyper-src/resources/python-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#FFD845" d="M49.33 62h29.159C86.606 62 93 55.132 93 46.981V19.183c0-7.912-6.632-13.856-14.555-15.176-5.014-.835-10.195-1.215-15.187-1.191-4.99.023-9.612.448-13.805 1.191C37.098 6.188 35 10.758 35 19.183V30h29v4H23.776c-8.484 0-15.914 5.108-18.237 14.811-2.681 11.12-2.8 17.919 0 29.53C7.614 86.983 12.569 93 21.054 93H31V79.952C31 70.315 39.428 62 49.33 62zm-1.838-39.11c-3.026 0-5.478-2.479-5.478-5.545 0-3.079 2.451-5.581 5.478-5.581 3.015 0 5.479 2.502 5.479 5.581-.001 3.066-2.465 5.545-5.479 5.545zm74.789 25.921C120.183 40.363 116.178 34 107.682 34H97v12.981C97 57.031 88.206 65 78.489 65H49.33C41.342 65 35 72.326 35 80.326v27.8c0 7.91 6.745 12.564 14.462 14.834 9.242 2.717 17.994 3.208 29.051 0C85.862 120.831 93 116.549 93 108.126V97H64v-4h43.682c8.484 0 11.647-5.776 14.599-14.66 3.047-9.145 2.916-17.799 0-29.529zm-41.955 55.606c3.027 0 5.479 2.479 5.479 5.547 0 3.076-2.451 5.579-5.479 5.579-3.015 0-5.478-2.502-5.478-5.579 0-3.068 2.463-5.547 5.478-5.547z"/></svg>
|
After Width: | Height: | Size: 1 KiB |
1
hyper-src/resources/rust-icon.svg
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
hyper-src/resources/starry night.gif
Normal file
After Width: | Height: | Size: 7 MiB |
BIN
hyper-src/resources/wanderer.gif
Normal file
After Width: | Height: | Size: 6.6 MiB |
5
hyper-src/resources/web-assembly-icon.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 107.62 107.62"><defs><style>
|
||||
.cls-1 {
|
||||
fill: #654ff0;
|
||||
}
|
||||
</style></defs><title>web-assembly-icon</title><g id="Layer_2" data-name="Layer 2"><g id="Notch_-_Purple" data-name="Notch - Purple"><g id="icon"><path class="cls-1" d="M66.12,0c0,.19,0,.38,0,.58a12.34,12.34,0,1,1-24.68,0c0-.2,0-.39,0-.58H0V107.62H107.62V0ZM51.38,96.1,46.14,70.17H46L40.39,96.1H33.18L25,58h7.13L37,83.93h.09L42.94,58h6.67L54.9,84.25H55L60.55,58h7L58.46,96.1Zm39.26,0-2.43-8.48H75.4L73.53,96.1H66.36L75.59,58H86.83L98,96.1Z"/><polygon class="cls-1" points="79.87 67.39 76.76 81.37 86.44 81.37 82.87 67.39 79.87 67.39"/></g></g></g></svg>
|
After Width: | Height: | Size: 685 B |
231
hyper-src/styles/extend.css
Normal file
|
@ -0,0 +1,231 @@
|
|||
@font-face {
|
||||
font-family: Cascadia;
|
||||
src: url("resources/CascadiaMonoPL.woff2");
|
||||
}
|
||||
|
||||
body {
|
||||
color: #444;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.pure-img-responsive { max-width: 100%; height: auto;
|
||||
}
|
||||
|
||||
.cascadia {
|
||||
font-family: Cascadia;
|
||||
}
|
||||
|
||||
.home-menu {
|
||||
padding: 0.5em;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0, 0.10);
|
||||
}
|
||||
.home-menu {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.home-menu a {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.home-menu li a:hover,
|
||||
.home-menu li a:focus {
|
||||
animation: menu-hover-anim 0.25s ease-out;
|
||||
animation-fill-mode: forwards;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.link {
|
||||
color: #00f;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.video {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.video iframe {
|
||||
width: 32em;
|
||||
height: 18em;
|
||||
frameborder: "0";
|
||||
}
|
||||
|
||||
/*
|
||||
Add transition to containers so they can push in and out.
|
||||
*/
|
||||
#layout,
|
||||
#menu,
|
||||
.menu-link {
|
||||
-webkit-transition: all 0.2s ease-out;
|
||||
-moz-transition: all 0.2s ease-out;
|
||||
-ms-transition: all 0.2s ease-out;
|
||||
-o-transition: all 0.2s ease-out;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
/*
|
||||
This is the parent `<div>` that contains the menu and the content area.
|
||||
*/
|
||||
#layout {
|
||||
position: relative;
|
||||
left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
#layout.active #menu {
|
||||
left: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#layout.active .menu-link {
|
||||
left: 150px;
|
||||
}
|
||||
/*
|
||||
The content `<div>` is where all your content goes.
|
||||
*/
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
padding: 0 2em;
|
||||
max-width: 800px;
|
||||
margin-bottom: 50px;
|
||||
line-height: 1.6em;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin: 0;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
padding: 2.5em 4em 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0.2em 0;
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.header h2 {
|
||||
font-weight: 300;
|
||||
color: #888;
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.distinct {
|
||||
background-color: #444;
|
||||
color: #fff;
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
}
|
||||
|
||||
@keyframes card-hover-anim {
|
||||
to {background-color: #ccf;}
|
||||
}
|
||||
@keyframes card-text-hover-anim {
|
||||
to {color: #222;}
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #eee;
|
||||
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||
margin: 1em 0em 1em 0em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card h1 {
|
||||
color: #444;
|
||||
}
|
||||
.card h2 {
|
||||
color: #777;
|
||||
}
|
||||
.card h3 {
|
||||
color: #999;
|
||||
}
|
||||
.card a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
animation: card-hover-anim 0.25s ease-out;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
|
||||
/* .card:hover h3 { */
|
||||
/* animation: card-text-hover-anim 0.25s ease-out; */
|
||||
/* animation-fill-mode: forwards; */
|
||||
/* } */
|
||||
|
||||
.card img {
|
||||
vertical-align: bottom;
|
||||
float: right;
|
||||
padding: 0em 0.1em 0em 0.1em;
|
||||
width: 60px;
|
||||
}
|
||||
.card svg {
|
||||
vertical-align: bottom;
|
||||
float: right;
|
||||
padding: 0em 0.1em 0em 0.1em;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
|
||||
.content-subhead {
|
||||
margin: 50px 0 20px 0;
|
||||
font-weight: 300;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.code-block {
|
||||
background-color: #fdf6e3;
|
||||
color: #002b36;
|
||||
font-family: Cascadia;
|
||||
padding: 0em 0.5em 0em 0.5em;
|
||||
margin: 1em 0em 1em 0em;
|
||||
page-break-inside: avoid;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
.code-block pre {
|
||||
display: block;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.code-block pre code {
|
||||
font-family: Cascadia;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.code-block span {
|
||||
background-color: #fdf6e3;
|
||||
}
|
||||
.keyword {
|
||||
color: #6c71c4;
|
||||
}
|
||||
.constant {
|
||||
color: #cb4b16;
|
||||
}
|
||||
.function {
|
||||
color: #268bd2;
|
||||
}
|
||||
.operator {
|
||||
color: #6c71c4;
|
||||
}
|
||||
.punctuation {
|
||||
color: #586e75;
|
||||
}
|
||||
.string {
|
||||
color: #859900;
|
||||
}
|
||||
.type {
|
||||
color: #2aa198;
|
||||
}
|
||||
.property {
|
||||
color: #586e75;
|
||||
}
|
||||
|
11
hyper-src/styles/main.css
Normal file
13
simple.html
|
@ -1,13 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link "asdf">
|
||||
<lg:include rel='css' href="./style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<A href="fdfs"> asdf </A>
|
||||
</body>
|
||||
</html>
|
||||
|
354
src/compile.rs
Normal file
|
@ -0,0 +1,354 @@
|
|||
use crate::trace::*;
|
||||
use std::{collections::HashMap, io::Write, path::Path, rc::Rc};
|
||||
|
||||
use crate::parse::*;
|
||||
|
||||
type Lexeme = HtmlElement;
|
||||
|
||||
pub const RECURSION_LIMIT: usize = 256;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Element {
|
||||
attributes: Attributes,
|
||||
child_span: Vec<Lexeme>,
|
||||
}
|
||||
pub type Templates = HashMap<String, Element>;
|
||||
|
||||
fn parse_element<'a>(tail: &'a [Lexeme]) -> Option<(Element, &'a [Lexeme])> {
|
||||
// Parse first element
|
||||
let (_, attributes) = match tail.first() {
|
||||
Some(lm) => match lm {
|
||||
Lexeme::OpenTag {
|
||||
attributes,
|
||||
is_empty: true,
|
||||
..
|
||||
} => {
|
||||
return Some((
|
||||
Element {
|
||||
attributes: attributes.clone(),
|
||||
child_span: vec![],
|
||||
},
|
||||
&tail[1..],
|
||||
))
|
||||
},
|
||||
Lexeme::OpenTag {
|
||||
name,
|
||||
attributes,
|
||||
is_empty: false,
|
||||
} => (name, attributes),
|
||||
_ => return None,
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let mut depth = 0;
|
||||
let mut end = 1;
|
||||
|
||||
for lm in tail {
|
||||
match lm {
|
||||
Lexeme::OpenTag { is_empty, .. } => {
|
||||
if !is_empty {
|
||||
depth += 1;
|
||||
}
|
||||
},
|
||||
Lexeme::CloseTag { .. } => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if depth == 0 {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
}
|
||||
end += 1;
|
||||
}
|
||||
Some((
|
||||
Element {
|
||||
attributes: attributes.clone(),
|
||||
child_span: tail[1..end - 1].to_vec(),
|
||||
},
|
||||
&tail[end..],
|
||||
))
|
||||
}
|
||||
|
||||
pub fn parse_templates(source: impl AsRef<str>) -> Result<Templates> {
|
||||
let mut tail: &[Lexeme] = &parse_html(source.as_ref())?;
|
||||
let mut new_templates: Templates = Default::default();
|
||||
while let Some(lm) = tail.first() {
|
||||
match lm {
|
||||
Lexeme::OpenTag {
|
||||
name,
|
||||
is_empty: false,
|
||||
..
|
||||
} => {
|
||||
let (head, new_tail) = parse_element(tail)
|
||||
.ctx(format!("at template definition {}", name))?;
|
||||
|
||||
new_templates.insert(name.clone(), head);
|
||||
tail = new_tail;
|
||||
},
|
||||
// Ignore comments
|
||||
Lexeme::Comment(_) => {
|
||||
tail = &tail[1..];
|
||||
},
|
||||
_ => {
|
||||
eprintln!("[WARNING] Unexpected root node: {}", lm.serialize());
|
||||
tail = &tail[1..];
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(new_templates)
|
||||
}
|
||||
|
||||
pub fn parse_templates_file(path: impl AsRef<Path>) -> Result<Templates> {
|
||||
let file = read_file(path.as_ref()).ctx(format!("opening templates file"))?;
|
||||
parse_templates(file).ctx(format!("in file {}", path.as_ref().display()))
|
||||
}
|
||||
|
||||
fn expand_template(
|
||||
base: &Element,
|
||||
template: &Element,
|
||||
output: &mut Vec<Lexeme>,
|
||||
) {
|
||||
for element in &template.child_span {
|
||||
match element {
|
||||
Lexeme::OpenTag {
|
||||
name,
|
||||
is_empty,
|
||||
attributes,
|
||||
} => {
|
||||
let mut new_attributes = HashMap::new();
|
||||
|
||||
for (key, value) in attributes {
|
||||
if let Some(at_key) = value.strip_prefix('@') {
|
||||
if let Some(at_value) = base.attributes.get(at_key) {
|
||||
new_attributes.insert(at_key.to_string(), at_value.clone());
|
||||
}
|
||||
} else {
|
||||
new_attributes.insert(key.into(), value.into());
|
||||
}
|
||||
}
|
||||
|
||||
// for (key, value) in attributes {
|
||||
// // If value is non-null
|
||||
// if let Some(id) = value {
|
||||
// // If value is '@' prefixed
|
||||
// if let Some(variable_name) = id.strip_prefix('@') {
|
||||
// // If key exists in base
|
||||
// if let Some(new_value) =
|
||||
// base.attributes.get(variable_name) {
|
||||
// new_attributes.insert(key.clone(),
|
||||
// new_value.clone()); }
|
||||
// // Otherwise discard variable
|
||||
// }
|
||||
// // Insert non-null non-variable value
|
||||
// else {
|
||||
// new_attributes.insert(key.clone(), value.clone());
|
||||
// }
|
||||
// } else {
|
||||
// // Insert null non-variable value
|
||||
// new_attributes.insert(key.clone(), value.clone());
|
||||
// }
|
||||
// }
|
||||
|
||||
output.push(Lexeme::OpenTag {
|
||||
name: name.clone(),
|
||||
attributes: new_attributes,
|
||||
is_empty: *is_empty,
|
||||
});
|
||||
},
|
||||
Lexeme::Directive { name, .. } => {
|
||||
if name == "children" {
|
||||
output.append(&mut base.child_span.to_vec());
|
||||
} else {
|
||||
output.push(element.clone());
|
||||
}
|
||||
},
|
||||
_ => output.push(element.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compilation_pass(
|
||||
mut source: &[Lexeme],
|
||||
templates: &Templates,
|
||||
) -> Result<(Vec<Lexeme>, usize)> {
|
||||
let mut output: Vec<Lexeme> = vec![];
|
||||
let mut num_expanded = 0;
|
||||
while let Some(lm) = source.first() {
|
||||
match lm {
|
||||
Lexeme::OpenTag { name, .. } => {
|
||||
if let Some(tmp) = templates.get(name) {
|
||||
num_expanded += 1;
|
||||
let (base, new_tail) =
|
||||
parse_element(source).ctx(format!("at template usage {}", name))?;
|
||||
expand_template(&base, tmp, &mut output);
|
||||
source = new_tail;
|
||||
continue;
|
||||
} else {
|
||||
output.push(lm.clone());
|
||||
}
|
||||
},
|
||||
_ => output.push(lm.clone()),
|
||||
}
|
||||
source = &source[1..];
|
||||
}
|
||||
Ok((output, num_expanded))
|
||||
}
|
||||
|
||||
pub fn compile_source(
|
||||
source: impl AsRef<str>,
|
||||
templates: &Templates,
|
||||
) -> Result<Vec<Lexeme>> {
|
||||
let mut source: Vec<Lexeme> = parse_html(source.as_ref())?;
|
||||
for i in 1..=RECURSION_LIMIT {
|
||||
let (new_source, num_expanded) = compilation_pass(&source, templates)?;
|
||||
source = new_source;
|
||||
if num_expanded == 0 {
|
||||
break;
|
||||
}
|
||||
if source.len() >= LEXEME_MEMORY_LIMIT {
|
||||
return Err(compile_error("reached memory limit expanding templates"));
|
||||
}
|
||||
if i == RECURSION_LIMIT {
|
||||
return Err(compile_error("reached recursion limit expanding templates"));
|
||||
}
|
||||
}
|
||||
Ok(source.to_vec())
|
||||
}
|
||||
|
||||
pub fn compile_source_file(
|
||||
path: impl AsRef<Path>,
|
||||
templates: &Templates,
|
||||
) -> Result<Vec<Lexeme>> {
|
||||
let file = read_file(&path)?;
|
||||
compile_source(file, templates)
|
||||
.ctx(format!("while compiling file {}", path.as_ref().display()))
|
||||
}
|
||||
|
||||
pub fn serialize(output: &Vec<Lexeme>) -> String {
|
||||
output
|
||||
.iter()
|
||||
.map(|lm| lm.serialize())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
pub fn serialize_mini(output: &Vec<Lexeme>) -> String {
|
||||
output
|
||||
.iter()
|
||||
.map(|lm| lm.serialize())
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
pub struct Compiler {
|
||||
templates: HashMap<String, Element>,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
templates: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_template_file(
|
||||
&mut self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> Result<&mut Self> {
|
||||
let templates = parse_templates_file(path)?;
|
||||
self.templates.extend(templates);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_src(
|
||||
&mut self,
|
||||
from: impl AsRef<Path>,
|
||||
to: impl AsRef<Path>,
|
||||
) -> Result<&mut Self> {
|
||||
let source = compile_source_file(from, &self.templates)?;
|
||||
let serial = serialize(&source);
|
||||
let mut new_file = std::fs::File::create(to)?;
|
||||
new_file.write_all(serial.as_bytes())?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_template_folder(
|
||||
&mut self,
|
||||
from: impl AsRef<Path>,
|
||||
) -> Result<&mut Self> {
|
||||
for file in std::fs::read_dir(&from)? {
|
||||
let file =
|
||||
file.ctx(format!("reading directory {}", from.as_ref().display()))?;
|
||||
let path = file.path();
|
||||
let last = path.components().last().ctx("empty path encountered")?;
|
||||
let ft = match file.file_type() {
|
||||
Ok(ft) => ft,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if ft.is_dir() {
|
||||
self.with_template_folder(&from.as_ref().join(&last))?;
|
||||
continue;
|
||||
}
|
||||
let ext = match path.extension() {
|
||||
Some(ext) => ext,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
if ext != "html" {
|
||||
continue;
|
||||
}
|
||||
self.with_template_file(&path)?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_src_folder(
|
||||
&mut self,
|
||||
from: impl AsRef<Path>,
|
||||
to: impl AsRef<Path>,
|
||||
) -> Result<&mut Self> {
|
||||
let _ = std::fs::remove_dir_all(&to);
|
||||
let _ = std::fs::create_dir_all(&to);
|
||||
for file in std::fs::read_dir(&from)? {
|
||||
let file =
|
||||
file.ctx(format!("reading directory {}", from.as_ref().display()))?;
|
||||
let path = file.path();
|
||||
let last = path.components().last().ctx("empty path encountered")?;
|
||||
let destination = to.as_ref().join(&last);
|
||||
let ft = match file.file_type() {
|
||||
Ok(ft) => ft,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if ft.is_dir() {
|
||||
self.with_src_folder(
|
||||
&from.as_ref().join(&last),
|
||||
&to.as_ref().join(&last),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
let ext = match path.extension() {
|
||||
Some(ext) => ext,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Copy file but do not compile
|
||||
if ext != "html" {
|
||||
let file = std::fs::read(&path)
|
||||
.ctx(format!("opening file to read: {}", path.display()))?;
|
||||
let mut new_file = std::fs::File::create(&destination)
|
||||
.ctx(format!("opening file to write: {}", destination.display()))?;
|
||||
new_file
|
||||
.write_all(&file)
|
||||
.ctx(format!("writing to file: {}", destination.display()))?;
|
||||
continue;
|
||||
}
|
||||
self.with_src(&path, destination)?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
278
src/compiler.rs
|
@ -1,278 +0,0 @@
|
|||
use html_parser::{Dom, DomVariant, Element, ElementVariant, Node};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use crate::{
|
||||
macros::*,
|
||||
trace::{WithContext, *},
|
||||
};
|
||||
|
||||
/// Replaces @variables in template with appropriate
|
||||
/// values, or removes if not provided
|
||||
fn init_vars(
|
||||
node: Node,
|
||||
attributes: &HashMap<String, Option<String>>,
|
||||
children: &Vec<Node>,
|
||||
) -> Node {
|
||||
let mut element = match node {
|
||||
Node::Text(_) | Node::Comment(_) => return node,
|
||||
Node::Element(e) => e,
|
||||
};
|
||||
|
||||
let mut new_classes = Vec::with_capacity(element.classes.len());
|
||||
|
||||
for cls in &mut element.classes {
|
||||
if !cls.starts_with('@') {
|
||||
new_classes.push(cls.clone());
|
||||
continue;
|
||||
}
|
||||
let name = &cls[1..];
|
||||
// Attributes in class must not be null
|
||||
if let Some(Some(k)) = attributes.get(name) {
|
||||
new_classes.push(k.clone());
|
||||
}
|
||||
// Otherwise discard variable from classes
|
||||
}
|
||||
element.classes = new_classes;
|
||||
|
||||
// Attributes in ID must not be null
|
||||
if let Some(id) = &element.id {
|
||||
if let Some(name) = id.strip_prefix('@') {
|
||||
element.id = attributes.get(name).unwrap_or(&None).clone();
|
||||
} else {
|
||||
element.id = None;
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_attributes = HashMap::new();
|
||||
|
||||
for (key, value) in &element.attributes {
|
||||
if let Some(id) = value {
|
||||
if let Some(variable_name) = id.strip_prefix('@') {
|
||||
if let Some(value) = attributes.get(variable_name) {
|
||||
// Insert null and non-null variables if key exists
|
||||
new_attributes.insert(key.clone(), value.clone());
|
||||
}
|
||||
// Otherwise discard variable
|
||||
}
|
||||
// Insert non-null non-variable value
|
||||
else {
|
||||
new_attributes.insert(key.clone(), value.clone());
|
||||
}
|
||||
} else {
|
||||
// Insert null non-variable value
|
||||
new_attributes.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
element.attributes = new_attributes;
|
||||
let mut new_children = Vec::with_capacity(element.children.len());
|
||||
for child in &element.children {
|
||||
let name = child.element().map(|c| c.name.as_str()).unwrap_or("");
|
||||
if "tp:children" == name {
|
||||
for child in children {
|
||||
new_children.push(child.clone());
|
||||
}
|
||||
} else {
|
||||
let child = init_vars(child.clone(), attributes, children);
|
||||
new_children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
element.children = new_children;
|
||||
|
||||
Node::Element(element)
|
||||
}
|
||||
|
||||
/// Swaps out a template invocation for its definition
|
||||
fn expand_templates(
|
||||
invocation: Element,
|
||||
templates: &Element,
|
||||
) -> Result<Vec<Node>> {
|
||||
for child in &invocation.children {
|
||||
if child
|
||||
.element()
|
||||
.map(|c| c.name.as_str().strip_prefix("tp:").is_some())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(Error::new(
|
||||
ErrorKind::Compilation,
|
||||
"Illegal use of tp namespace",
|
||||
));
|
||||
}
|
||||
for subchild in child {
|
||||
if subchild
|
||||
.element()
|
||||
.map(|c| c.name.as_str().strip_prefix("tp:").is_some())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(Error::new(
|
||||
ErrorKind::Compilation,
|
||||
"Illegal use of tp namespace",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect params
|
||||
let mut attributes = invocation.attributes;
|
||||
let classes = invocation.classes.join(" ");
|
||||
if classes.len() != 0 {
|
||||
attributes.insert("class".into(), Some(classes));
|
||||
}
|
||||
if let Some(id) = invocation.id {
|
||||
attributes.insert("id".into(), Some(id));
|
||||
}
|
||||
// Swap params
|
||||
let expanded = init_vars(
|
||||
Node::Element(templates.clone()),
|
||||
&attributes,
|
||||
&invocation.children,
|
||||
);
|
||||
Ok(expanded.element().unwrap().children.clone())
|
||||
}
|
||||
|
||||
/// Serializes HTML node
|
||||
/// * `node` - Node to serialize
|
||||
/// * `templates` - Map of templates by their names
|
||||
fn node_to_string(
|
||||
node: &Node,
|
||||
templates: &HashMap<String, Element>,
|
||||
macros: &HashMap<String, Macro>,
|
||||
) -> Result<String> {
|
||||
const OPEN: bool = false;
|
||||
const CLOSE: bool = true;
|
||||
let mut stack: Vec<(Node, bool)> = vec![(node.clone(), OPEN)];
|
||||
let mut buf = String::new();
|
||||
while let Some((current, closing)) = stack.pop() {
|
||||
if closing == OPEN {
|
||||
stack.push((current.clone(), CLOSE));
|
||||
match current {
|
||||
Node::Text(t) => {
|
||||
buf.push_str(&t);
|
||||
},
|
||||
Node::Element(e) => {
|
||||
// Expand if macro
|
||||
if let Some(mc) = macros.get(&e.name) {
|
||||
stack.pop().unwrap();
|
||||
let expanded =
|
||||
mc(&e.attributes).ctx(format!("Expanding macro: {}", e.name))?;
|
||||
buf.push_str(&expanded);
|
||||
continue;
|
||||
}
|
||||
// Expand if template
|
||||
if let Some(tp) = templates.get(&e.name) {
|
||||
let _ = stack.pop();
|
||||
let elements = expand_templates(e.clone(), tp)
|
||||
.ctx(format!("Expanding template: {}", e.name))?;
|
||||
for element in elements.into_iter().rev() {
|
||||
stack.push((element, OPEN));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// <
|
||||
buf.push('<');
|
||||
// Tag name
|
||||
buf.push_str(&e.name);
|
||||
// Classes
|
||||
if !e.classes.is_empty() {
|
||||
buf.push_str(&format!(" class='{}'", e.classes.join(" ")));
|
||||
}
|
||||
// ID
|
||||
if let Some(id) = &e.id {
|
||||
buf.push_str(&format!(" id='{}'", id));
|
||||
}
|
||||
// Attributes
|
||||
for (k, v) in &e.attributes {
|
||||
match v {
|
||||
Some(v) => buf.push_str(&format!(" {}='{}'", k, v)),
|
||||
None => buf.push_str(&format!(" {}", k)),
|
||||
}
|
||||
}
|
||||
match e.variant {
|
||||
ElementVariant::Normal => {
|
||||
buf.push_str(">\n");
|
||||
for child in e.children.iter().rev() {
|
||||
stack.push((child.clone(), OPEN));
|
||||
}
|
||||
},
|
||||
ElementVariant::Void => {
|
||||
buf.push_str("/>\n");
|
||||
let _ = stack.pop();
|
||||
},
|
||||
}
|
||||
},
|
||||
Node::Comment(_) => {},
|
||||
}
|
||||
} else {
|
||||
match current {
|
||||
Node::Text(_) => buf.push('\n'),
|
||||
Node::Comment(_) => {},
|
||||
Node::Element(e) => {
|
||||
buf.push_str(&format!("</{}>\n", e.name));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub struct Compiler {
|
||||
templates: HashMap<String, Element>,
|
||||
macros: HashMap<String, Macro>,
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
templates: HashMap::new(),
|
||||
macros: default_macros(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_templates_file(&mut self, path: impl AsRef<Path>) -> Result<()> {
|
||||
let file = read_file(&path)?;
|
||||
self.parse_templates(&file).ctx(format!(
|
||||
"Parsing templates file: {}",
|
||||
path.as_ref().to_string_lossy()
|
||||
))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_templates(&mut self, html: &str) -> Result<()> {
|
||||
let tps: HashMap<String, Element> = Dom::parse(html)?
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|i| i.element().cloned())
|
||||
.flatten()
|
||||
.map(|i| (i.name.clone(), i))
|
||||
.collect();
|
||||
if tps.is_empty() {
|
||||
return Err(Error::new(ErrorKind::Parsing, "No blueprints found"));
|
||||
}
|
||||
self.templates.extend(tps);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_source_file(&self, path: impl AsRef<Path>) -> Result<String> {
|
||||
let file = read_file(&path)?;
|
||||
self.compile_source(&file).ctx(format!(
|
||||
"Compiling source file: {}",
|
||||
path.as_ref().to_string_lossy()
|
||||
))
|
||||
}
|
||||
|
||||
pub fn compile_source(&self, html: &str) -> Result<String> {
|
||||
let dom = Dom::parse(html)?;
|
||||
if !dom.errors.is_empty() {}
|
||||
if dom.tree_type == DomVariant::Empty {
|
||||
return Err(Error::new(ErrorKind::Parsing, "Empty DOM"));
|
||||
}
|
||||
if dom.tree_type != DomVariant::Document {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Parsing,
|
||||
"DOM must exactly have 1 root node",
|
||||
));
|
||||
}
|
||||
let tree = &dom.children[0];
|
||||
node_to_string(&tree, &self.templates, &self.macros)
|
||||
}
|
||||
}
|
106
src/directives.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use crate::parse::Attributes;
|
||||
|
||||
pub fn expand_directive(
|
||||
name: &str,
|
||||
attributes: &Attributes,
|
||||
contents: &str,
|
||||
) -> String {
|
||||
match name {
|
||||
"style" => style_dir(attributes).unwrap_or("<!-- -->".to_string()),
|
||||
"script" => script_dir(attributes).unwrap_or("<!-- -->".to_string()),
|
||||
"code" => code(attributes, contents).unwrap_or("<!-- -->".to_string()),
|
||||
_ => "<!-- -->".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn style_dir(attributes: &Attributes) -> Option<String> {
|
||||
let path = attributes.get("href")?;
|
||||
let file = std::fs::read_to_string(path).ok()?;
|
||||
Some(format!("<style>\n{}\n</style>", file.trim()))
|
||||
}
|
||||
|
||||
fn script_dir(attributes: &Attributes) -> Option<String> {
|
||||
let path = attributes.get("href")?;
|
||||
let file = std::fs::read_to_string(path).ok()?;
|
||||
Some(format!("<script>\n{}\n</script>", file.trim()))
|
||||
}
|
||||
|
||||
fn code(attributes: &Attributes, contents: &str) -> Option<String> {
|
||||
use inkjet::*;
|
||||
let minimum_indent = contents
|
||||
.trim()
|
||||
.lines()
|
||||
.map(|line| line.len() - line.trim_start().len())
|
||||
.min()
|
||||
.unwrap_or(0);
|
||||
|
||||
let normalized_string: String = contents
|
||||
.lines()
|
||||
.map(|line| line.replacen(" ", "", minimum_indent))
|
||||
.map(|line| line + "\n")
|
||||
.collect();
|
||||
|
||||
let language = parse_language(attributes.get("lang")?)?;
|
||||
|
||||
let mut hl = Highlighter::new();
|
||||
let buffer = hl
|
||||
.highlight_to_string(language, &MyFormatter(), normalized_string)
|
||||
.ok()?;
|
||||
|
||||
let buffer = format!(
|
||||
"<div class=\"code-block\">\n<pre><code>{}</code></pre>\n</div>",
|
||||
buffer
|
||||
);
|
||||
|
||||
Some(buffer)
|
||||
}
|
||||
|
||||
struct MyFormatter();
|
||||
use inkjet::{
|
||||
constants::HIGHLIGHT_CLASS_NAMES, formatter::*,
|
||||
tree_sitter_highlight::HighlightEvent,
|
||||
};
|
||||
|
||||
impl Formatter for MyFormatter {
|
||||
fn write<W>(
|
||||
&self,
|
||||
source: &str,
|
||||
writer: &mut W,
|
||||
event: inkjet::tree_sitter_highlight::HighlightEvent,
|
||||
) -> inkjet::Result<()>
|
||||
where
|
||||
W: std::fmt::Write,
|
||||
{
|
||||
match event {
|
||||
HighlightEvent::Source { start, end } => {
|
||||
let span = source
|
||||
.get(start..end)
|
||||
.expect("Source bounds should be in bounds!");
|
||||
let span = v_htmlescape::escape(span).to_string();
|
||||
writer.write_str(&span)?;
|
||||
},
|
||||
HighlightEvent::HighlightStart(idx) => {
|
||||
let name = HIGHLIGHT_CLASS_NAMES[idx.0];
|
||||
write!(writer, "<span class=\"{}\">", name)?;
|
||||
},
|
||||
HighlightEvent::HighlightEnd => {
|
||||
writer.write_str("</span>")?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_language(lang: &str) -> Option<inkjet::Language> {
|
||||
use inkjet::*;
|
||||
Some(match lang {
|
||||
"rust" => Language::Rust,
|
||||
"python" => Language::Python,
|
||||
"javascript" => Language::Javascript,
|
||||
"html" => Language::Html,
|
||||
"css" => Language::Css,
|
||||
"toml" => Language::Toml,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
use crate::trace::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn default_macros() -> HashMap<String, Macro> {
|
||||
let mut hm: HashMap<String, Macro> = HashMap::new();
|
||||
hm.insert("lg:include".into(), INCLUDE_MACRO);
|
||||
hm
|
||||
}
|
||||
|
||||
pub type Macro = fn(&HashMap<String, Option<String>>) -> Result<String>;
|
||||
|
||||
const INCLUDE_MACRO: Macro = |input| {
|
||||
let href = input
|
||||
.get("href")
|
||||
.ok_or(compile_error("'href' attribute missing"))?
|
||||
.as_ref()
|
||||
.ok_or(compile_error("'href' attribute is empty"))?;
|
||||
|
||||
let rel = input
|
||||
.get("rel")
|
||||
.ok_or(compile_error("'rel' attribute missing"))?
|
||||
.as_ref()
|
||||
.ok_or(compile_error("'rel' attribute is empty"))?;
|
||||
|
||||
let r = match rel.as_str() {
|
||||
"css" => format!("<style>\n{}\n</style>\n", read_file(href)?),
|
||||
"js" => format!("<script>\n{}\n</script>\n", read_file(href)?),
|
||||
"inline" => read_file(href)?,
|
||||
_ => return Err(compile_error(format!("Invalid 'rel' value: {}", rel))),
|
||||
};
|
||||
Ok(r)
|
||||
};
|
84
src/main.rs
|
@ -1,81 +1,11 @@
|
|||
mod compiler;
|
||||
mod macros;
|
||||
mod parser;
|
||||
mod compile;
|
||||
mod directives;
|
||||
mod parse;
|
||||
mod trace;
|
||||
use compiler::*;
|
||||
use trace::*;
|
||||
|
||||
fn run_compiler() -> Result<()> {
|
||||
// Read config file
|
||||
let default_config = include_str!("../default.toml")
|
||||
.parse::<toml::Table>()
|
||||
.unwrap();
|
||||
let user_config = std::fs::read_to_string("lg.toml")
|
||||
.ctx("Reading config file")?
|
||||
.parse::<toml::Table>()
|
||||
.ctx("Parsing config file")?;
|
||||
let mut config = default_config.clone();
|
||||
config.extend(user_config);
|
||||
|
||||
let mut c = Compiler::new();
|
||||
let compile_table = config["compile"]
|
||||
.as_table()
|
||||
.ctx("Reading compile options")?;
|
||||
|
||||
// Parse all templates
|
||||
let templates_path = compile_table["templates"]
|
||||
.as_str()
|
||||
.ctx("Reading templates path")?;
|
||||
let dirs = walkdir::WalkDir::new(templates_path)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|v| {
|
||||
if v.file_name().to_str().unwrap_or("").ends_with(".html")
|
||||
&& v.file_type().is_file()
|
||||
{
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
for file in dirs {
|
||||
c.parse_templates_file(file.path())?;
|
||||
}
|
||||
// Compile all source files
|
||||
let source_path = compile_table["source"]
|
||||
.as_str()
|
||||
.ctx("Reading templates path")?;
|
||||
let dirs = walkdir::WalkDir::new(source_path)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|v| {
|
||||
if v.file_name().to_str().unwrap_or("").ends_with(".html")
|
||||
&& v.file_type().is_file()
|
||||
{
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
for file in dirs {}
|
||||
|
||||
// println!("{}", s);
|
||||
fn main() -> Result<(), trace::Error> {
|
||||
let mut c = compile::Compiler::new();
|
||||
c.with_template_folder("templates/")?
|
||||
.with_src_folder("hyper-src/", "hyper-build/")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let test = include_str!("../simple.html").to_string();
|
||||
// let test = " <as> </as> ".to_string();
|
||||
let r = parser::parse_html(&test).unwrap();
|
||||
for l in r {
|
||||
println!("{l:?}");
|
||||
}
|
||||
// match run_compiler() {
|
||||
// Ok(_) => {},
|
||||
// Err(e) => {
|
||||
// eprintln!("{}", e);
|
||||
// std::process::exit(1);
|
||||
// },
|
||||
// }
|
||||
}
|
||||
|
|
497
src/parse.rs
Normal file
|
@ -0,0 +1,497 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::directives::expand_directive;
|
||||
|
||||
pub type Attributes = HashMap<String, String>;
|
||||
pub type Offset = usize;
|
||||
pub type Parse<'a, T> = (T, &'a str, Offset);
|
||||
pub type MaybeParse<'a, T> = Option<Parse<'a, T>>;
|
||||
|
||||
#[inline]
|
||||
fn parse_until(i: &str, condition: impl Fn(char) -> bool) -> Parse<&str> {
|
||||
match i.chars().position(condition) {
|
||||
Some(pos) => (&i[..pos], &i[pos..], pos),
|
||||
None => (&i, "", i.len()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_until_str<'a>(
|
||||
tail: &'a str,
|
||||
to_match: &'static str,
|
||||
) -> MaybeParse<'a, &'a str> {
|
||||
for i in 0..tail.len() {
|
||||
let substr = &tail[0..i];
|
||||
if substr.ends_with(to_match) {
|
||||
let end = i - to_match.len();
|
||||
return Some((&tail[0..end], &tail[end..], end));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse_str<'a>(i: &'a str, matches: &str) -> MaybeParse<'a, &'a str> {
|
||||
let length = matches.len();
|
||||
match i.get(0..matches.len()) {
|
||||
Some(s) => {
|
||||
if s.eq_ignore_ascii_case(matches) {
|
||||
Some((s, &i[length..], length))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_char(i: &str, matches: char) -> MaybeParse<char> {
|
||||
if let Some(c) = i.chars().next() {
|
||||
if c == matches {
|
||||
return Some((c, &i[1..], 1));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_delimited(i: &str, delim: char) -> MaybeParse<&str> {
|
||||
let (_start, i, o1) = parse_char(i, delim)?;
|
||||
let (contents, i, o2) = parse_until(i, |c| c == delim);
|
||||
let (_end, i, o3) = parse_char(i, delim)?;
|
||||
Some((contents, i, o1 + o2 + o3))
|
||||
}
|
||||
|
||||
fn index_to_rc(input: &str, index: usize) -> (usize, usize) {
|
||||
let (mut row, mut col) = (1, 1);
|
||||
for c in input[0..index].chars() {
|
||||
if c == '\n' {
|
||||
row += 1;
|
||||
col = 1;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
}
|
||||
(row, col)
|
||||
}
|
||||
|
||||
pub const LEXEME_MEMORY_LIMIT: usize = 65535;
|
||||
|
||||
// Tags that are implicitly self closing, ending in /> is
|
||||
// optional
|
||||
const VOID_ELEMENTS: [&str; 16] = [
|
||||
"area", "base", "br", "col", "command", "embed", "hr", "img", "input",
|
||||
"keygen", "link", "meta", "param", "source", "track", "wbr",
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// Encountered illegal sequence
|
||||
Illegal,
|
||||
/// Closing tag does not match previous opening tag
|
||||
UnbalancedTags,
|
||||
/// Tried to parse `N > 65535` elements
|
||||
MemoryLimit,
|
||||
}
|
||||
|
||||
impl From<Error> for crate::trace::Error {
|
||||
fn from(value: Error) -> Self {
|
||||
Self {
|
||||
kind: crate::trace::ErrorKind::Parsing,
|
||||
reason: match value.kind {
|
||||
ErrorKind::Illegal => "Illegal character encountered",
|
||||
ErrorKind::UnbalancedTags => "Unbalanced open and close tags",
|
||||
ErrorKind::MemoryLimit => "Ran out of memory",
|
||||
}
|
||||
.to_string(),
|
||||
backtrace: vec![format!("at line {} column {}", value.row, value.column)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Error {
|
||||
pub kind: ErrorKind,
|
||||
pub char_index: usize,
|
||||
pub row: usize,
|
||||
pub column: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HtmlElement {
|
||||
/// The required `<!DOCTYPE HTML>` preamble
|
||||
DocType,
|
||||
/// Text inside of a `<!-- comment -->`
|
||||
Comment(String),
|
||||
/// Any opening tag, including <empty/> tags
|
||||
OpenTag {
|
||||
/// The name of the tag
|
||||
name: String,
|
||||
/// Attribute names and their values if present
|
||||
attributes: Attributes,
|
||||
/// Whether the tag closes itself. Tags that are
|
||||
/// implicitly empty are:
|
||||
/// `area, base, br, col, command, embed, hr, img, input
|
||||
/// keygen, link, meta, param, source, track, wbr`
|
||||
is_empty: bool,
|
||||
},
|
||||
/// Any closing tag that is not empty. Closing implicitly
|
||||
/// empty tags is an error
|
||||
CloseTag { name: String },
|
||||
/// Any inner text that is not entirely whitespace
|
||||
Text(String),
|
||||
/// A `<script>` tag and its contents
|
||||
Script {
|
||||
/// Attribute names and their values if present
|
||||
attributes: Attributes,
|
||||
/// The raw text between the open and close `<script>`
|
||||
/// tags
|
||||
contents: String,
|
||||
},
|
||||
/// A `<style>` tag and its contents
|
||||
Style {
|
||||
/// Attribute names and their values if present
|
||||
attributes: Attributes,
|
||||
/// The raw text between the open and close `<style>`
|
||||
/// tags
|
||||
contents: String,
|
||||
},
|
||||
Directive {
|
||||
name: String,
|
||||
attributes: Attributes,
|
||||
contents: String,
|
||||
},
|
||||
}
|
||||
fn serialize_attributes(attr: &Attributes) -> String {
|
||||
attr
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
if v.is_empty() {
|
||||
format!(" {k}")
|
||||
} else {
|
||||
format!(" {k}=\"{v}\"")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
impl HtmlElement {
|
||||
pub fn serialize(&self) -> String {
|
||||
match self {
|
||||
Self::DocType => "<!DOCTYPE html>".into(),
|
||||
Self::Comment(_) => format!("<!---->"),
|
||||
Self::OpenTag {
|
||||
name,
|
||||
attributes,
|
||||
is_empty: false,
|
||||
} => format!("<{}{}>", name, serialize_attributes(&attributes)),
|
||||
Self::OpenTag {
|
||||
name,
|
||||
attributes,
|
||||
is_empty: true,
|
||||
} => format!("<{}{}/>", name, serialize_attributes(&attributes)),
|
||||
Self::CloseTag { name } => format!("</{}>", name),
|
||||
Self::Style {
|
||||
attributes,
|
||||
contents,
|
||||
} => {
|
||||
format!(
|
||||
"<style{}>\n{}\n</style>",
|
||||
serialize_attributes(attributes),
|
||||
contents
|
||||
)
|
||||
},
|
||||
Self::Script {
|
||||
attributes,
|
||||
contents,
|
||||
} => {
|
||||
format!(
|
||||
"<script{}>\n{}\n</script>",
|
||||
serialize_attributes(attributes),
|
||||
contents
|
||||
)
|
||||
},
|
||||
Self::Text(t) => t.clone(),
|
||||
Self::Directive {
|
||||
name,
|
||||
attributes,
|
||||
contents,
|
||||
} => expand_directive(&name, &attributes, &contents),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used as the `condition` argument for `parse_until` to
|
||||
/// parse names of things
|
||||
const NAME_REGEX: fn(char) -> bool =
|
||||
|c| !(c.is_ascii_alphanumeric() || [':', '_', '-', '@'].contains(&c));
|
||||
|
||||
/// Used as the `condition` argument for `parse_until` to
|
||||
/// arbitrary whitespace
|
||||
const WS_REGEX: fn(char) -> bool = |c| !c.is_whitespace();
|
||||
|
||||
fn parse_doctype(i: &str) -> MaybeParse<HtmlElement> {
|
||||
let (_, i, o1) = parse_str(i, "<!doctype")?;
|
||||
let (_, i, o2) = parse_until(i, WS_REGEX);
|
||||
// Minimum of 1 whitespace
|
||||
if o2 == 0 {
|
||||
return None;
|
||||
}
|
||||
let (_, i, o3) = parse_str(i, "html")?;
|
||||
let (_, i, o4) = parse_until(i, WS_REGEX);
|
||||
let (_, i, o5) = parse_str(i, ">")?;
|
||||
Some((HtmlElement::DocType, i, o1 + o2 + o3 + o4 + o5))
|
||||
}
|
||||
|
||||
fn parse_comment<'a>(tail: &'a str) -> MaybeParse<HtmlElement> {
|
||||
let (_, tail, o1) = parse_str(tail, "<!--")?;
|
||||
let (comment, tail, o2) = parse_until_str(tail, "-->")?;
|
||||
let (_, tail, o3) = parse_str(tail, "-->")?;
|
||||
Some((HtmlElement::Comment(comment.into()), tail, o1 + o2 + o3))
|
||||
}
|
||||
|
||||
fn parse_raw_text(i: &str) -> MaybeParse<(String, Attributes, String)> {
|
||||
let (open, mut i, o1) = parse_open_tag(i)?;
|
||||
let (open_name, attributes, is_empty) = match open {
|
||||
HtmlElement::OpenTag {
|
||||
name,
|
||||
attributes,
|
||||
is_empty,
|
||||
} => (name, attributes, is_empty),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let (close_name, contents, i, o2) = if is_empty {
|
||||
(open_name.clone(), "".to_string(), i, 0)
|
||||
} else {
|
||||
let mut contents = String::new();
|
||||
let mut o2 = 0;
|
||||
while !i.is_empty() {
|
||||
let (text, new_i, new_off) = parse_text(i);
|
||||
contents.push_str(&text);
|
||||
o2 += new_off;
|
||||
i = new_i;
|
||||
if i.starts_with("</") {
|
||||
if let Some((HtmlElement::CloseTag { name }, _, _)) = parse_close_tag(i)
|
||||
{
|
||||
if name == open_name {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if text.is_empty() {
|
||||
i = &i[1..];
|
||||
o2 += 1;
|
||||
contents.push('<');
|
||||
}
|
||||
}
|
||||
let (close, i, o3) = parse_close_tag(i)?;
|
||||
let close_name = match close {
|
||||
HtmlElement::CloseTag { name } => name,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
(close_name, contents, i, o2 + o3)
|
||||
};
|
||||
if open_name != close_name {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(((open_name, attributes, contents), i, o1 + o2))
|
||||
}
|
||||
|
||||
fn parse_style(i: &str) -> MaybeParse<HtmlElement> {
|
||||
let ((name, attributes, contents), i, o) = parse_raw_text(i)?;
|
||||
if name != "style" {
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
HtmlElement::Style {
|
||||
attributes,
|
||||
contents,
|
||||
},
|
||||
i,
|
||||
o,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_script(i: &str) -> MaybeParse<HtmlElement> {
|
||||
let ((name, attributes, contents), i, o) = parse_raw_text(i)?;
|
||||
if name != "script" {
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
HtmlElement::Script {
|
||||
attributes,
|
||||
contents,
|
||||
},
|
||||
i,
|
||||
o,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_directive(i: &str) -> MaybeParse<HtmlElement> {
|
||||
let ((name, attributes, contents), i, o) = parse_raw_text(i)?;
|
||||
if let Some(name) = name.strip_prefix('@') {
|
||||
Some((
|
||||
HtmlElement::Directive {
|
||||
name: name.to_string(),
|
||||
attributes,
|
||||
contents,
|
||||
},
|
||||
i,
|
||||
o,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_open_tag(i: &str) -> MaybeParse<HtmlElement> {
|
||||
let (_, i, o1) = parse_str(i, "<")?;
|
||||
let (name, i, o2) = parse_until(i, NAME_REGEX);
|
||||
let mut attributes = HashMap::new();
|
||||
let mut i = i;
|
||||
let mut o3 = 0;
|
||||
while let Some(((key, value), new_i, new_o)) = parse_attribute(i) {
|
||||
attributes.insert(key, value);
|
||||
i = new_i;
|
||||
o3 += new_o;
|
||||
}
|
||||
let (_, i, o4) = parse_until(i, WS_REGEX);
|
||||
// Find all attributes
|
||||
let (is_empty, i, o5) = match parse_str(i, "/") {
|
||||
Some((_, i, o5)) => (true, i, o5),
|
||||
None => (false, i, 0),
|
||||
};
|
||||
|
||||
let is_empty = is_empty || VOID_ELEMENTS.contains(&name);
|
||||
let (_, i, o6) = parse_char(i, '>')?;
|
||||
Some((
|
||||
HtmlElement::OpenTag {
|
||||
name: name.to_string(),
|
||||
attributes,
|
||||
is_empty,
|
||||
},
|
||||
i,
|
||||
o1 + o2 + o3 + o4 + o5 + o6,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_attribute(i: &str) -> MaybeParse<(String, String)> {
|
||||
let (_, i, o1) = parse_until(i, WS_REGEX);
|
||||
if o1 == 0 {
|
||||
return None;
|
||||
}
|
||||
let (key, i, o2) = parse_until(i, NAME_REGEX);
|
||||
if o2 == 0 {
|
||||
return None;
|
||||
}
|
||||
let get_value = || -> Option<(&str, &str, Offset)> {
|
||||
let (_, i, o1) = parse_until(i, WS_REGEX);
|
||||
let (_, i, o2) = parse_str(i, "=")?;
|
||||
let (_, i, o3) = parse_until(i, WS_REGEX);
|
||||
let (value, i, o4) = parse_delimited(i, '\"')
|
||||
.or_else(|| parse_delimited(i, '\''))
|
||||
.unwrap_or_else(|| parse_until(i, NAME_REGEX));
|
||||
Some((value, i, o1 + o2 + o3 + o4))
|
||||
};
|
||||
let (value, i, o3) = get_value().unwrap_or(("", i, 0));
|
||||
Some(((key.to_string(), value.to_string()), i, o1 + o2 + o3))
|
||||
}
|
||||
|
||||
fn parse_close_tag(i: &str) -> MaybeParse<HtmlElement> {
|
||||
let (_, i, o1) = parse_str(i, "</")?;
|
||||
let (name, i, o2) = parse_until(i, NAME_REGEX);
|
||||
if o2 == 0 {
|
||||
return None;
|
||||
}
|
||||
let (_, i, o3) = parse_until(i, WS_REGEX);
|
||||
let (_, i, o4) = parse_str(i, ">")?;
|
||||
Some((
|
||||
HtmlElement::CloseTag {
|
||||
name: name.to_string(),
|
||||
},
|
||||
i,
|
||||
o1 + o2 + o3 + o4,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_text(i: &str) -> Parse<String> {
|
||||
let (text, i, o1) = parse_until(i, |c| c == '<');
|
||||
(text.to_string(), i, o1)
|
||||
}
|
||||
|
||||
pub fn parse_html(
|
||||
input: &str,
|
||||
) -> Result<Vec<HtmlElement>, crate::trace::Error> {
|
||||
let mut output = vec![];
|
||||
let mut validation_stack = vec![];
|
||||
let mut i = input;
|
||||
let mut offset = 0;
|
||||
let throw_err = |kind, offset| {
|
||||
let (row, column) = index_to_rc(input, offset);
|
||||
Error {
|
||||
kind,
|
||||
char_index: offset,
|
||||
row,
|
||||
column,
|
||||
}
|
||||
.into()
|
||||
};
|
||||
while i.len() > 0 {
|
||||
let (lm, new_i, new_off) = if i.starts_with("<!--") {
|
||||
parse_comment(i)
|
||||
} else if i.starts_with("<!") {
|
||||
parse_doctype(i)
|
||||
} else if i.starts_with("<style") {
|
||||
parse_style(i)
|
||||
} else if i.starts_with("<script") {
|
||||
parse_script(i)
|
||||
} else if i.starts_with("</") {
|
||||
parse_close_tag(i)
|
||||
} else if i.starts_with("<@") {
|
||||
parse_directive(i)
|
||||
} else if i.starts_with("<") {
|
||||
parse_open_tag(i)
|
||||
} else {
|
||||
let (text, new_i, new_off) = parse_text(i);
|
||||
// skip if text is all whitespace
|
||||
if text.trim().is_empty() {
|
||||
i = new_i;
|
||||
offset += new_off;
|
||||
continue;
|
||||
}
|
||||
Some((HtmlElement::Text(text), new_i, new_off))
|
||||
}
|
||||
.ok_or_else(|| throw_err(ErrorKind::Illegal, offset))?;
|
||||
|
||||
if output.len() >= LEXEME_MEMORY_LIMIT {
|
||||
return Err(throw_err(ErrorKind::MemoryLimit, offset));
|
||||
}
|
||||
|
||||
match &lm {
|
||||
HtmlElement::OpenTag {
|
||||
name,
|
||||
is_empty: false,
|
||||
..
|
||||
} => validation_stack.push(name.clone()),
|
||||
HtmlElement::CloseTag { name } => {
|
||||
let top = validation_stack.pop();
|
||||
if top.map_or(true, |top| &top != name) {
|
||||
return Err(throw_err(ErrorKind::UnbalancedTags, offset));
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
output.push(lm);
|
||||
i = new_i;
|
||||
offset += new_off;
|
||||
}
|
||||
if validation_stack.is_empty() {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(throw_err(ErrorKind::UnbalancedTags, offset))
|
||||
}
|
||||
}
|
474
src/parser.rs
|
@ -1,474 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ParseError {
|
||||
InvalidTag,
|
||||
MismatchedClosing { expected: String, found: String },
|
||||
UnmatchedOpen(String),
|
||||
UnmatchedClose(String),
|
||||
VoidClosingTag(String),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
use crate::trace::{self, WithContext};
|
||||
|
||||
impl From<ParseError> for trace::Error {
|
||||
fn from(value: ParseError) -> Self {
|
||||
let msg = match value {
|
||||
ParseError::InvalidTag => "Failed to parse a tag".into(),
|
||||
ParseError::MismatchedClosing { expected, found } => {
|
||||
format!(
|
||||
"Found closing tag '{}' where '{}' was expected",
|
||||
found, expected
|
||||
)
|
||||
},
|
||||
ParseError::UnmatchedOpen(s) => {
|
||||
format!("The tag '{}' is opened, but never closed", s)
|
||||
},
|
||||
ParseError::UnmatchedClose(s) => {
|
||||
format!("The tag '{}' is closed, but never opened", s)
|
||||
},
|
||||
ParseError::VoidClosingTag(s) => {
|
||||
format!("The tag '{}' should not have a closing tag", s)
|
||||
},
|
||||
ParseError::Unknown => {
|
||||
return trace::Error::new(
|
||||
trace::ErrorKind::Unknown,
|
||||
"Unknown error while parsing",
|
||||
)
|
||||
},
|
||||
};
|
||||
trace::Error::new(trace::ErrorKind::Parsing, msg)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Lexeme<'a> {
|
||||
OpenTag {
|
||||
name: &'a str,
|
||||
attributes: HashMap<&'a str, Option<&'a str>>,
|
||||
is_void: bool,
|
||||
},
|
||||
CloseTag {
|
||||
name: &'a str,
|
||||
},
|
||||
Text(&'a str),
|
||||
Doctype,
|
||||
Comment,
|
||||
}
|
||||
|
||||
fn normalize_whitespace(mut tail: &str) -> String {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
|
||||
let mut _index = 0;
|
||||
let mut buffer = String::with_capacity(tail.len());
|
||||
while !tail.is_empty() {
|
||||
match parse_whitespace_min(tail, 1, &mut _index) {
|
||||
Some((_, new_tail)) => {
|
||||
buffer.push(' ');
|
||||
tail = new_tail;
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
let (chars, new_tail) =
|
||||
parse_while(tail, |c| !c.is_whitespace(), &mut _index);
|
||||
buffer.push_str(chars);
|
||||
tail = new_tail
|
||||
}
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Try parsing single specific character ignoring case
|
||||
fn parse_char<'a>(
|
||||
tail: &'a str,
|
||||
c: char,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
if !tail.is_empty() && tail[0..1].eq_ignore_ascii_case(&c.to_string()) {
|
||||
*index += 1;
|
||||
Some((&tail[0..1], &tail[1..]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_str<'a>(
|
||||
tail: &'a str,
|
||||
to_match: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
if tail.len() < to_match.len() {
|
||||
return None;
|
||||
}
|
||||
if tail[0..to_match.len()].eq_ignore_ascii_case(to_match) {
|
||||
*index += to_match.len();
|
||||
Some((&tail[0..to_match.len()], &tail[to_match.len()..]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_until_str<'a>(
|
||||
tail: &'a str,
|
||||
to_match: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
for i in 0..tail.len() {
|
||||
let substr = &tail[0..i];
|
||||
if substr.ends_with(to_match) {
|
||||
*index += i;
|
||||
return Some((&tail[0..i], &tail[i..]));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Parse until condition is not true for next character
|
||||
fn parse_while<'a>(
|
||||
tail: &'a str,
|
||||
condition: impl Fn(char) -> bool,
|
||||
index: &mut usize,
|
||||
) -> (&'a str, &'a str) {
|
||||
let mut end;
|
||||
let mut it = tail.char_indices();
|
||||
'outer: loop {
|
||||
match it.next() {
|
||||
Some((i, c)) => {
|
||||
end = i;
|
||||
if !condition(c) {
|
||||
break 'outer;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
// Reached end of input
|
||||
return (&tail, "");
|
||||
},
|
||||
};
|
||||
}
|
||||
*index += end;
|
||||
(&tail[0..end], &tail[end..])
|
||||
}
|
||||
|
||||
fn parse_whitespace<'a>(i: &'a str, index: &mut usize) -> (&'a str, &'a str) {
|
||||
parse_while(i, |c| c.is_whitespace(), index)
|
||||
}
|
||||
|
||||
fn parse_whitespace_min<'a>(
|
||||
tail: &'a str,
|
||||
min: usize,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
let mut new_index = 0;
|
||||
let (ws, tail) = parse_whitespace(tail, &mut new_index);
|
||||
if ws.len() < min {
|
||||
None
|
||||
} else {
|
||||
*index += new_index;
|
||||
Some((ws, tail))
|
||||
}
|
||||
}
|
||||
|
||||
/// Try parsing all characters between two delimiter
|
||||
/// characters
|
||||
fn parse_delimited<'a>(
|
||||
i: &'a str,
|
||||
delimiter: char,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
let mut new_index = 0;
|
||||
let (_, tail) = parse_char(i, delimiter, &mut new_index)?;
|
||||
let (value, tail) = parse_while(tail, |c| c != delimiter, &mut new_index);
|
||||
let (_, tail) = parse_char(tail, delimiter, &mut new_index)?;
|
||||
*index += new_index;
|
||||
Some((value, tail))
|
||||
}
|
||||
|
||||
fn parse_tag_name<'a>(
|
||||
i: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
let mut new_index = 0;
|
||||
let (value, tail) = parse_while(
|
||||
i,
|
||||
|c| c.is_ascii_alphanumeric() || [':', '_', '-'].contains(&c),
|
||||
&mut new_index,
|
||||
);
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
*index += new_index;
|
||||
Some((value, tail))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attribute_key<'a>(
|
||||
i: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
let mut new_index = 0;
|
||||
let (value, tail) = parse_while(
|
||||
i,
|
||||
|c| {
|
||||
!(['"', '\'', '>', '/', '='].contains(&c)
|
||||
|| c.is_control()
|
||||
|| c.is_whitespace())
|
||||
},
|
||||
&mut new_index,
|
||||
);
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
*index += new_index;
|
||||
Some((value, tail))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_attribute_val<'a>(
|
||||
i: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Option<(&'a str, &'a str)> {
|
||||
const SINGLE_QUOTE: char = '\'';
|
||||
const DOUBLE_QUOTE: char = '"';
|
||||
let mut new_index = 0;
|
||||
let (value, tail) =
|
||||
parse_delimited(i, SINGLE_QUOTE, &mut new_index) // Single quote delimit
|
||||
.or_else(|| parse_delimited(i, DOUBLE_QUOTE, &mut new_index)) // Double quote delimit
|
||||
.or_else(|| { // Unquoted
|
||||
Some(parse_while(i, |c| {
|
||||
!(c.is_whitespace()
|
||||
|| [SINGLE_QUOTE, DOUBLE_QUOTE, '=', '<', '>', '`'].contains(&c))
|
||||
}, &mut new_index))
|
||||
})?;
|
||||
*index += new_index;
|
||||
Some((value, tail))
|
||||
}
|
||||
|
||||
/// Returns Option<((key, value), tail)>
|
||||
fn parse_key_val<'a>(
|
||||
tail: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Option<((&'a str, Option<&'a str>), &'a str)> {
|
||||
let mut new_index = 0;
|
||||
// Require whitespace
|
||||
let (_, tail) = parse_whitespace_min(tail, 1, &mut new_index)?;
|
||||
// Fail when no key found
|
||||
let (key, tail) = parse_attribute_key(tail, &mut new_index)?;
|
||||
if let Some((_, tail)) = parse_char(
|
||||
parse_whitespace(tail, &mut new_index).1,
|
||||
'=',
|
||||
&mut new_index,
|
||||
) {
|
||||
let (_, tail) = parse_whitespace(tail, &mut new_index);
|
||||
// Fail when = is not followed by value
|
||||
let (val, tail) = parse_attribute_val(tail, &mut new_index)?;
|
||||
let val = if val.is_empty() { None } else { Some(val) };
|
||||
*index += new_index;
|
||||
Some(((key, val), tail))
|
||||
} else {
|
||||
*index += new_index;
|
||||
Some(((key, None), tail))
|
||||
}
|
||||
}
|
||||
|
||||
// Tags that are implicitly self closing, ending in /> is optional
|
||||
const VOID_ELEMENTS: [&str; 16] = [
|
||||
"area", "base", "br", "col", "command", "embed", "hr", "img", "input",
|
||||
"keygen", "link", "meta", "param", "source", "track", "wbr",
|
||||
];
|
||||
|
||||
fn parse_open_tag<'a>(
|
||||
tail: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
|
||||
let mut new_index = 0;
|
||||
// <
|
||||
let (_, tail) =
|
||||
parse_char(tail, '<', &mut new_index).ok_or(ParseError::Unknown)?;
|
||||
// tag name
|
||||
let (name, mut tail) =
|
||||
parse_tag_name(tail, &mut new_index).ok_or(ParseError::InvalidTag)?;
|
||||
// attributes
|
||||
let mut attributes: HashMap<&str, Option<&str>> = HashMap::new();
|
||||
while let Some((kv, new_tail)) = parse_key_val(tail, &mut new_index) {
|
||||
attributes.insert(kv.0, kv.1);
|
||||
tail = new_tail;
|
||||
}
|
||||
let (_, tail) = parse_whitespace(tail, &mut new_index);
|
||||
let (is_void, tail) =
|
||||
parse_char(tail, '/', &mut new_index).unwrap_or(("", tail));
|
||||
let is_void = !is_void.is_empty() || VOID_ELEMENTS.contains(&name);
|
||||
let (_, tail) = match parse_char(tail, '>', &mut new_index) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(ParseError::InvalidTag);
|
||||
},
|
||||
};
|
||||
*index += new_index;
|
||||
Ok((
|
||||
Lexeme::OpenTag {
|
||||
name,
|
||||
attributes,
|
||||
is_void,
|
||||
},
|
||||
tail,
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_close_tag<'a>(
|
||||
tail: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
|
||||
let mut new_index = 0;
|
||||
let (_, tail) =
|
||||
parse_char(tail, '<', &mut new_index).ok_or(ParseError::Unknown)?;
|
||||
let (_, tail) =
|
||||
parse_char(tail, '/', &mut new_index).ok_or(ParseError::Unknown)?;
|
||||
let (name, tail) =
|
||||
parse_tag_name(tail, &mut new_index).ok_or(ParseError::InvalidTag)?;
|
||||
let (_, tail) = parse_whitespace(tail, &mut new_index);
|
||||
let (_, tail) =
|
||||
parse_char(tail, '>', &mut new_index).ok_or(ParseError::InvalidTag)?;
|
||||
*index += new_index;
|
||||
Ok((Lexeme::CloseTag { name }, tail))
|
||||
}
|
||||
|
||||
fn parse_doctype<'a>(
|
||||
tail: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
|
||||
let mut new_index = 0;
|
||||
let mut closure = || -> Option<(&str, &str)> {
|
||||
let (_, tail) = parse_str(tail, "<!doctype", &mut new_index)?;
|
||||
let (_, tail) = parse_whitespace_min(tail, 1, &mut new_index)?;
|
||||
let (_, tail) = parse_str(tail, "html", &mut new_index)?;
|
||||
let (_, tail) = parse_whitespace(tail, &mut new_index);
|
||||
parse_char(tail, '>', &mut new_index)
|
||||
};
|
||||
let (_, tail) = closure().ok_or(ParseError::Unknown)?;
|
||||
*index += new_index;
|
||||
Ok((Lexeme::Doctype, tail))
|
||||
}
|
||||
|
||||
fn parse_comment<'a>(
|
||||
tail: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
|
||||
let mut new_index = 0;
|
||||
let (_, tail) =
|
||||
parse_str(tail, "<!--", &mut new_index).ok_or(ParseError::Unknown)?;
|
||||
let (_, tail) =
|
||||
parse_until_str(tail, "-->", &mut new_index).ok_or(ParseError::Unknown)?;
|
||||
*index += new_index;
|
||||
Ok((Lexeme::Comment, tail))
|
||||
}
|
||||
|
||||
fn parse_text<'a>(
|
||||
tail: &'a str,
|
||||
index: &mut usize,
|
||||
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
|
||||
let mut new_index = 0;
|
||||
let (txt, tail) = parse_while(tail, |c| c != '<', &mut new_index);
|
||||
if txt.is_empty() {
|
||||
Err(ParseError::Unknown)
|
||||
} else {
|
||||
*index += new_index;
|
||||
Ok((Lexeme::Text(txt), tail))
|
||||
}
|
||||
}
|
||||
|
||||
fn or_keep_error<'a>(
|
||||
r: Result<(Lexeme<'a>, &'a str), ParseError>,
|
||||
op: impl FnOnce() -> Result<(Lexeme<'a>, &'a str), ParseError>,
|
||||
) -> Result<(Lexeme<'a>, &'a str), ParseError> {
|
||||
match r {
|
||||
Ok(val) => Ok(val),
|
||||
Err(e) => op().map_err(|new_e| match e {
|
||||
ParseError::Unknown => new_e,
|
||||
_ => e,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn index_to_rc(input: &str, index: usize) -> (usize, usize) {
|
||||
let (mut row, mut col) = (1, 1);
|
||||
for c in input[0..index].chars() {
|
||||
if c == '\n' {
|
||||
row += 1;
|
||||
col = 1;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
}
|
||||
(row, col)
|
||||
}
|
||||
|
||||
pub fn parse_html(input: &str) -> trace::Result<Vec<Lexeme>> {
|
||||
let mut tail = input;
|
||||
let mut lexeme_stack = vec![];
|
||||
let mut validation_stack = vec![];
|
||||
let mut index = 0;
|
||||
|
||||
let err = |error: ParseError, index: usize| -> trace::Result<Vec<Lexeme>> {
|
||||
let (row, col) = index_to_rc(input, index);
|
||||
let e: trace::Error = error.into();
|
||||
Err(e).ctx(format!("Starting at line {} character {}", row, col))
|
||||
};
|
||||
|
||||
while !tail.is_empty() {
|
||||
let (_, new_tail) = parse_whitespace(tail, &mut index);
|
||||
if new_tail.is_empty() {
|
||||
break;
|
||||
}
|
||||
let result = or_keep_error(parse_open_tag(new_tail, &mut index), || {
|
||||
parse_close_tag(new_tail, &mut index)
|
||||
});
|
||||
let result = or_keep_error(result, || parse_text(new_tail, &mut index));
|
||||
let result = or_keep_error(result, || parse_comment(new_tail, &mut index));
|
||||
let (lm, new_tail) =
|
||||
match or_keep_error(result, || parse_doctype(new_tail, &mut index)) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return err(e, index);
|
||||
},
|
||||
};
|
||||
// Validate that open and close tags match
|
||||
match lm {
|
||||
Lexeme::OpenTag { name, is_void, .. } => {
|
||||
if !is_void {
|
||||
validation_stack.push(name);
|
||||
}
|
||||
},
|
||||
Lexeme::CloseTag { name } => {
|
||||
if VOID_ELEMENTS.contains(&name) {
|
||||
return err(ParseError::VoidClosingTag(name.into()).into(), index);
|
||||
}
|
||||
if let Some(top) = validation_stack.pop() {
|
||||
if name != top {
|
||||
return err(
|
||||
ParseError::MismatchedClosing {
|
||||
expected: top.into(),
|
||||
found: name.into(),
|
||||
},
|
||||
index,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return err(ParseError::UnmatchedClose(name.into()), index);
|
||||
}
|
||||
},
|
||||
Lexeme::Comment => {
|
||||
tail = new_tail;
|
||||
continue;
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
lexeme_stack.push(lm);
|
||||
|
||||
tail = new_tail;
|
||||
}
|
||||
if let Some(top) = validation_stack.pop() {
|
||||
let e: trace::Error = ParseError::UnmatchedOpen(top.into()).into();
|
||||
return Err(e).ctx("At end of file");
|
||||
}
|
||||
Ok(lexeme_stack)
|
||||
}
|
35
src/trace.rs
|
@ -41,6 +41,7 @@ pub enum ErrorKind {
|
|||
IO,
|
||||
Parsing,
|
||||
Compilation,
|
||||
Memory,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
@ -54,6 +55,7 @@ impl Display for ErrorKind {
|
|||
ErrorKind::Parsing => "PARSING",
|
||||
ErrorKind::Compilation => "COMPILATION",
|
||||
ErrorKind::Unknown => "UNKNOWN",
|
||||
ErrorKind::Memory => "MEMORY",
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -85,39 +87,6 @@ impl From<std::io::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<html_parser::Error> for Error {
|
||||
fn from(value: html_parser::Error) -> Self {
|
||||
match value {
|
||||
html_parser::Error::Parsing(e) => Self {
|
||||
kind: ErrorKind::Parsing,
|
||||
reason: e,
|
||||
backtrace: vec![],
|
||||
},
|
||||
html_parser::Error::Cli(e) => Self {
|
||||
kind: ErrorKind::Unknown,
|
||||
reason: e,
|
||||
backtrace: vec![],
|
||||
},
|
||||
html_parser::Error::IO(e) => Self {
|
||||
kind: ErrorKind::IO,
|
||||
reason: format!("{}", e),
|
||||
backtrace: vec![],
|
||||
},
|
||||
html_parser::Error::Serde(e) => Self {
|
||||
kind: ErrorKind::Unknown,
|
||||
reason: format!("{}", e),
|
||||
backtrace: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for Error {
|
||||
fn from(value: toml::de::Error) -> Self {
|
||||
Error::new(ErrorKind::Parsing, value.message())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WithContext<T, S: Into<String>> {
|
||||
fn ctx(self, s: S) -> Result<T>;
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
body {
|
||||
background-color: linen;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: maroon;
|
||||
margin-left: 40px;
|
||||
}
|
183
templates/blueprints.html
Normal file
90
test.html
|
@ -1,90 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="test_files/main-ad0a5132b4027392.css">
|
||||
|
||||
<link rel="preload" href="https://logan-gatlin.com/web-2abb0afbb41a1a01_bg.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<link rel="modulepreload" href="https://logan-gatlin.com/web-2abb0afbb41a1a01.js">
|
||||
<link
|
||||
href="data:text/css,%3Ais(%5Bid*%3D'google_ads_iframe'%5D%2C%5Bid*%3D'taboola-'%5D%2C.taboolaHeight%2C.taboola-placeholder%2C%23credential_picker_container%2C%23credentials-picker-container%2C%23credential_picker_iframe%2C%5Bid*%3D'google-one-tap-iframe'%5D%2C%23google-one-tap-popup-container%2C.google-one-tap-modal-div%2C%23amp_floatingAdDiv%2C%23ez-content-blocker-container)%20%7Bdisplay%3Anone!important%3Bmin-height%3A0!important%3Bheight%3A0!important%3B%7D"
|
||||
rel="stylesheet" type="text/css">
|
||||
<link
|
||||
href="data:text/css,%3Ais(%5Bid*%3D'google_ads_iframe'%5D%2C%5Bid*%3D'taboola-'%5D%2C.taboolaHeight%2C.taboola-placeholder%2C%23credential_picker_container%2C%23credentials-picker-container%2C%23credential_picker_iframe%2C%5Bid*%3D'google-one-tap-iframe'%5D%2C%23google-one-tap-popup-container%2C.google-one-tap-modal-div%2C%23amp_floatingAdDiv%2C%23ez-content-blocker-container)%20%7Bdisplay%3Anone!important%3Bmin-height%3A0!important%3Bheight%3A0!important%3B%7D"
|
||||
rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<script type="module">import init from '/web-2abb0afbb41a1a01.js'; init('/web-2abb0afbb41a1a01_bg.wasm');</script>
|
||||
<main class="container">
|
||||
<article>
|
||||
<header>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ul>
|
||||
<li><a href="https://logan-gatlin.com/">Home</a></li><!----><!---->
|
||||
<li><a class="contrast">Base Conversion</a></li><!----><!---->
|
||||
</ul>
|
||||
</nav>
|
||||
</header><!---->
|
||||
<div class="grid">
|
||||
<div>
|
||||
<div><input type="text" placeholder="Decimal number" value="0"></div>
|
||||
<table role="grid">
|
||||
<tr>
|
||||
<td role="button" type="button" class="secondary">HEX</td><!---->
|
||||
<td>00<!----></td><!---->
|
||||
</tr>
|
||||
<tr>
|
||||
<td role="button" type="button" class="secondary contrast">DEC</td><!---->
|
||||
<td>0<!----></td><!---->
|
||||
</tr>
|
||||
<tr>
|
||||
<td role="button" type="button" class="secondary">OCT</td><!---->
|
||||
<td>000<!----></td><!---->
|
||||
</tr>
|
||||
<tr>
|
||||
<td role="button" type="button" class="secondary">BIN</td><!---->
|
||||
<td>0000 0000<!----></td><!---->
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td role="button" type="button" class="secondary contrast">BYTE</td><!---->
|
||||
<td role="button" type="button" class="secondary">WORD</td><!---->
|
||||
<td role="button" type="button" class="secondary">DWORD</td><!---->
|
||||
<td role="button" type="button" class="secondary">QWORD</td><!---->
|
||||
</tr>
|
||||
</table><!----><!---->
|
||||
</div>
|
||||
<div>
|
||||
<table class="binary">
|
||||
<tr>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td>
|
||||
<td type="button" role="button" class="secondary">0<!----></td><!---->
|
||||
</tr><!----><!----><!---->
|
||||
</table><!---->
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div><button type="button">Remember</button><!----><button type="button" class="secondary">Clear
|
||||
history</button><!---->
|
||||
<table><!----><!----></table><!---->
|
||||
</div>
|
||||
<footer><a href="https://github.com/Xterminate1818/based" target="_blank">View source code on GitHub</a><!---->
|
||||
</footer>
|
||||
</article><!----><!----><!---->
|
||||
</main><!----><!----><!---->
|
||||
</body>
|
||||
|
||||
</html>
|