aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe <me@christine.website>2022-11-22 14:55:31 -0500
committerXe <me@christine.website>2022-11-22 14:55:31 -0500
commit4cb18058ba49416e8413f65e966dff002fcea4f7 (patch)
tree0fc801caeaee09598bbca1529831fb8411c1a9c9
parentc1fd35aa707fe8dfbd6ab1351e5e3f0564ad0b11 (diff)
downloadx-4cb18058ba49416e8413f65e966dff002fcea4f7.tar.xz
x-4cb18058ba49416e8413f65e966dff002fcea4f7.zip
add mastosan package to turn Mastodon HTML to Slackdown
Signed-off-by: Xe <me@christine.website>
-rw-r--r--flake.lock24
-rw-r--r--flake.nix22
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--web/mastosan/.cargo/config2
-rw-r--r--web/mastosan/.gitignore1
-rw-r--r--web/mastosan/Cargo.lock496
-rw-r--r--web/mastosan/Cargo.toml15
-rw-r--r--web/mastosan/README.md24
-rw-r--r--web/mastosan/mastosan.go64
-rw-r--r--web/mastosan/mastosan_test.go32
-rw-r--r--web/mastosan/src/main.rs48
-rw-r--r--web/mastosan/testdata/mastosan.wasmbin0 -> 460001 bytes
13 files changed, 729 insertions, 2 deletions
diff --git a/flake.lock b/flake.lock
index 318c22a..587d3ab 100644
--- a/flake.lock
+++ b/flake.lock
@@ -94,9 +94,33 @@
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs",
"portable-svc": "portable-svc",
+ "rust-overlay": "rust-overlay",
"utils": "utils"
}
},
+ "rust-overlay": {
+ "inputs": {
+ "flake-utils": [
+ "utils"
+ ],
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1669084742,
+ "narHash": "sha256-aLYwYVnrmEE1LVqd17v99CuqVmAZQrlgi2DVTAs4wFg=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "9652ef34c7439eca9f86cee11e94dbef5c9adb09",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
+ }
+ },
"utils": {
"locked": {
"lastModified": 1659877975,
diff --git a/flake.nix b/flake.nix
index adb570e..33133a0 100644
--- a/flake.nix
+++ b/flake.nix
@@ -7,6 +7,12 @@
portable-svc.url = "git+https://tulpa.dev/cadey/portable-svc.git?ref=main";
ckiee.url = "github:ckiee/nixpkgs?ref=gpt2simple-py-init";
+ rust-overlay = {
+ url = "github:oxalica/rust-overlay";
+ inputs.nixpkgs.follows = "nixpkgs";
+ inputs.flake-utils.follows = "utils";
+ };
+
gomod2nix = {
url = "github:tweag/gomod2nix";
inputs.nixpkgs.follows = "nixpkgs";
@@ -14,7 +20,7 @@
};
};
- outputs = { self, nixpkgs, utils, gomod2nix, portable-svc, ckiee }@attrs:
+ outputs = { self, nixpkgs, utils, gomod2nix, portable-svc, ckiee, rust-overlay }@attrs:
utils.lib.eachSystem [
"x86_64-linux"
"aarch64-linux"
@@ -31,7 +37,8 @@
})
gomod2nix.overlays.default
portable-svc.overlay
- (final: prev: self.packages.${system})
+ rust-overlay.overlays.default
+ #(final: prev: self.packages.${system})
];
};
ckieepkgs = import ckiee { inherit system; };
@@ -182,6 +189,17 @@
pkg-config
libaom
libavif
+
+ cargo
+ cargo-watch
+ rustfmt
+ rust-analyzer
+ wasmtime
+ binaryen
+ (rust-bin.stable.latest.default.override {
+ extensions = [ "rust-src" ];
+ targets = [ "wasm32-wasi" ];
+ })
];
};
});
diff --git a/go.mod b/go.mod
index 060ea7c..bee3941 100644
--- a/go.mod
+++ b/go.mod
@@ -122,6 +122,7 @@ require (
github.com/tcnksm/go-httpstat v0.2.0 // indirect
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect
+ github.com/tetratelabs/wazero v1.0.0-pre.3 // indirect
github.com/tjfoc/gmsm v1.0.1 // indirect
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect
diff --git a/go.sum b/go.sum
index 66f7044..69f9165 100644
--- a/go.sum
+++ b/go.sum
@@ -395,6 +395,8 @@ github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7S
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM=
github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
+github.com/tetratelabs/wazero v1.0.0-pre.3 h1:Z5fbogMUGcERzaQb9mQU8+yJSy0bVvv2ce3dfR4wcZg=
+github.com/tetratelabs/wazero v1.0.0-pre.3/go.mod h1:M8UDNECGm/HVjOfq0EOe4QfCY9Les1eq54IChMLETbc=
github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU=
github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY=
diff --git a/web/mastosan/.cargo/config b/web/mastosan/.cargo/config
new file mode 100644
index 0000000..6b77899
--- /dev/null
+++ b/web/mastosan/.cargo/config
@@ -0,0 +1,2 @@
+[build]
+target = "wasm32-wasi"
diff --git a/web/mastosan/.gitignore b/web/mastosan/.gitignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/web/mastosan/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/web/mastosan/Cargo.lock b/web/mastosan/Cargo.lock
new file mode 100644
index 0000000..a3449fc
--- /dev/null
+++ b/web/mastosan/Cargo.lock
@@ -0,0 +1,496 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom 0.2.8",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cssparser"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa",
+ "matches",
+ "phf",
+ "proc-macro2",
+ "quote",
+ "smallvec",
+ "syn",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "lol_html"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ff2adf9c54f4de7d66a9177ea7d27d5b8108503bb03d5b719869b8f4bc2ab2"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cssparser",
+ "encoding_rs",
+ "hashbrown",
+ "lazy_static",
+ "lazycell",
+ "memchr",
+ "safemem",
+ "selectors",
+ "thiserror",
+]
+
+[[package]]
+name = "mastosan"
+version = "0.1.0"
+dependencies = [
+ "lol_html",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "nodrop"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
+
+[[package]]
+name = "once_cell"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared",
+ "rand",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+
+[[package]]
+name = "selectors"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe"
+dependencies = [
+ "bitflags",
+ "cssparser",
+ "derive_more",
+ "fxhash",
+ "log",
+ "matches",
+ "phf",
+ "phf_codegen",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+ "thin-slice",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
+
+[[package]]
+name = "servo_arc"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432"
+dependencies = [
+ "nodrop",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "syn"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thin-slice"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
+
+[[package]]
+name = "thiserror"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
diff --git a/web/mastosan/Cargo.toml b/web/mastosan/Cargo.toml
new file mode 100644
index 0000000..3aba4e8
--- /dev/null
+++ b/web/mastosan/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "mastosan"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+lol_html = "0.3"
+
+[profile.release]
+lto = true
+opt-level = "z"
+panic = "abort"
+
diff --git a/web/mastosan/README.md b/web/mastosan/README.md
new file mode 100644
index 0000000..f3c5a00
--- /dev/null
+++ b/web/mastosan/README.md
@@ -0,0 +1,24 @@
+# mastosan
+
+Package mastosan takes Mastodon flavored HTML and emits Slack-flavored
+markdown.
+
+It works by using an embedded Rust program compiled to WebAssembly. At
+the time of writing this adds an extra 0.2 seconds to compile and run the
+WebAssembly module, but this can probably be fixed with aggressive sync.Once
+caching should this prove a problem in the real world.
+
+Building this Rust program outside of Nix flakes is UNSUPPORTED.
+
+To build the Rust module from inside the Nix flake:
+
+ cargo install wasm-snip
+ cargo build --release
+ wasm-opt -Oz -o ./testdata/mastosan-pre-snip.wasm
+ wasm-snip --skip-producers-section --snip-rust-panicking-code -i ./testdata/mastosan-pre-snip.wasm ./testdata/mastosan.wasm
+ rm ./testdata/mastosan-pre-snip.wasm
+
+This adds about two megabytes to the resulting binary, including the AOT
+WebAssembly runtime wazero: https://wazero.io/
+
+
diff --git a/web/mastosan/mastosan.go b/web/mastosan/mastosan.go
new file mode 100644
index 0000000..87bee1d
--- /dev/null
+++ b/web/mastosan/mastosan.go
@@ -0,0 +1,64 @@
+// Package mastosan takes Mastodon flavored HTML and emits Slack-flavored
+// markdown.
+//
+// It works by using an embedded Rust program compiled to WebAssembly. At
+// the time of writing this adds an extra 0.2 seconds to compile and run the
+// WebAssembly module, but this can probably be fixed with aggressive sync.Once
+// caching should this prove a problem in the real world.
+//
+// Building this Rust program outside of Nix flakes is UNSUPPORTED.
+//
+// To build the Rust module from inside the Nix flake:
+//
+// cargo install wasm-snip
+// cargo build --release
+// wasm-opt -Oz -o ./testdata/mastosan-pre-snip.wasm
+// wasm-snip --skip-producers-section --snip-rust-panicking-code -i ./testdata/mastosan-pre-snip.wasm ./testdata/mastosan.wasm
+// rm ./testdata/mastosan-pre-snip.wasm
+//
+// This adds about two megabytes to the resulting binary, including the AOT
+// WebAssembly runtime wazero: https://wazero.io/
+package mastosan
+
+import (
+ "bytes"
+ "context"
+ _ "embed"
+ "log"
+ "strings"
+
+ "github.com/tetratelabs/wazero"
+ "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
+)
+
+//go:embed testdata/mastosan.wasm
+var mastosanWasm []byte
+
+// HTML2Slackdown converts a string full of HTML text to slack-flavored markdown.
+//
+// Internally this works by taking that HTML and piping it to a small Rust program
+// using lol_html to parse the HTML and rejigger it into slack-flavored markdown.
+// This has an added latency of about 0.2 seconds per invocation, but this is as
+// fast as I can make it for now.
+func HTML2Slackdown(ctx context.Context, text string) (string, error) {
+ r := wazero.NewRuntime(ctx)
+ defer r.Close(ctx)
+
+ fout := &bytes.Buffer{}
+ fin := bytes.NewBufferString(text)
+
+ config := wazero.NewModuleConfig().WithStdout(fout).WithStdin(fin).WithArgs("mastosan")
+
+ wasi_snapshot_preview1.MustInstantiate(ctx, r)
+
+ code, err := r.CompileModule(ctx, mastosanWasm)
+ if err != nil {
+ log.Panicln(err)
+ }
+
+ if _, err = r.InstantiateModule(ctx, code, config); err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(fout.String()), nil
+}
diff --git a/web/mastosan/mastosan_test.go b/web/mastosan/mastosan_test.go
new file mode 100644
index 0000000..efdec88
--- /dev/null
+++ b/web/mastosan/mastosan_test.go
@@ -0,0 +1,32 @@
+package mastosan
+
+import (
+ "context"
+ "testing"
+ "time"
+)
+
+func TestHTML2Slackdown(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ inp, out string
+ }{
+ {"basic mention", `<p>test mention <span class="h-card"><a href="https://vt.social/@xe" class="u-url mention">@<span>xe</span></a></span> so I can see what HTML mastodon makes</p>`, "test mention <https://vt.social/@xe|@xe> so I can see what HTML mastodon makes"},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+ result, err := HTML2Slackdown(ctx, tt.inp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if tt.out != result {
+ t.Logf("inp: %s", tt.inp)
+ t.Logf("out: %q", tt.out)
+ t.Logf("got: %q", result)
+ t.Fatal("output did not match what was expected")
+ }
+ })
+ }
+}
diff --git a/web/mastosan/src/main.rs b/web/mastosan/src/main.rs
new file mode 100644
index 0000000..7535129
--- /dev/null
+++ b/web/mastosan/src/main.rs
@@ -0,0 +1,48 @@
+use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings};
+use std::io::{self, prelude::*, stdin, stdout};
+
+fn main() -> io::Result<()> {
+ let mut output = Vec::new();
+ let mut rewriter = HtmlRewriter::new(
+ Settings {
+ element_content_handlers: vec![
+ element!("span", |el| {
+ el.remove_and_keep_content();
+ Ok(())
+ }),
+ element!("p", |el| {
+ el.append("\n\n", ContentType::Html);
+ el.remove_and_keep_content();
+ Ok(())
+ }),
+ element!("br", |el| {
+ el.append("\n\n", ContentType::Html);
+ el.remove_and_keep_content();
+ Ok(())
+ }),
+ element!("a[href]", |el| {
+ let href = el.get_attribute("href").unwrap();
+ el.prepend(&format!("<{href}|"), ContentType::Html);
+ el.append(">", ContentType::Html);
+ el.remove_and_keep_content();
+
+ Ok(())
+ }),
+ ],
+ ..Settings::default()
+ },
+ |c: &[u8]| output.extend_from_slice(c),
+ );
+
+ let mut input = Vec::new();
+ let mut fin = stdin().lock();
+ fin.read_to_end(&mut input)?;
+
+ rewriter.write(&input).unwrap();
+ rewriter.end().unwrap();
+
+ let mut fout = stdout().lock();
+ fout.write(&output)?;
+
+ Ok(())
+}
diff --git a/web/mastosan/testdata/mastosan.wasm b/web/mastosan/testdata/mastosan.wasm
new file mode 100644
index 0000000..59f88d0
--- /dev/null
+++ b/web/mastosan/testdata/mastosan.wasm
Binary files differ