this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rename project files and add escape_attribute function

+63 -74
+2 -2
dev/houdini_dev.gleam
··· 3 3 import gleam/list 4 4 import gleam/string 5 5 import gleamy/bench 6 - import houdini 6 + import houdinis_publicist 7 7 import simplifile 8 8 9 9 pub fn main() { ··· 17 17 bench.Input("just_html (" <> size <> ")", html), 18 18 bench.Input("no_escapes (" <> size <> ")", no_escapes), 19 19 ], 20 - [bench.Function("houdini.escape", houdini.escape)], 20 + [bench.Function("houdinis_publicist.escape", houdinis_publicist.escape)], 21 21 [bench.Duration(5000), bench.Warmup(2000)], 22 22 ) 23 23 |> bench.table([bench.IPS, bench.Min, bench.P(99)])
+3 -3
gleam.toml
··· 1 - name = "houdini" 1 + name = "houdinis_publicist" 2 2 version = "1.2.0" 3 - description = "🪄 Fast HTML escaping" 3 + description = "HTML attribute escaping, that can omit quotes if safe to do so" 4 4 licences = ["Apache-2.0"] 5 - repository = { type = "github", user = "giacomocavalieri", repo = "houdini" } 5 + repository = { type = "github", user = "bentomas", repo = "houdinis_publicist" } 6 6 7 7 [dev-dependencies] 8 8 gleeunit = ">= 1.0.0 and < 2.0.0"
+38 -1
src/houdini.gleam src/houdinis_publicist.gleam
··· 12 12 /// assert escape("wibble & wobble") == "wibble &amp; wobble" 13 13 /// assert escape("wibble > wobble") == "wibble &gt; wobble" 14 14 /// ``` 15 - /// 16 15 @external(javascript, "./houdini.ffi.mjs", "escape") 17 16 pub fn escape(string: String) -> String { 18 17 // This version is highly optimised for the Erlang target, it treats Strings ··· 24 23 // we know that the BitArray we build is definitely a valid string, so we can 25 24 // skip verifying that again as well as dealing with the `Result`. 26 25 unsafe_bit_array_to_string(result) 26 + } 27 + 28 + pub fn escape_attribute(string: String) -> String { 29 + let bits = <<string:utf8>> 30 + let result = do_escape_attribute(bits, bits, 0) 31 + 32 + // we know that the BitArray we build is definitely a valid string, so we can 33 + // skip verifying that again as well as dealing with the `Result`. 34 + unsafe_bit_array_to_string(result) 35 + } 36 + 37 + fn do_escape_attribute(bin: BitArray, original: BitArray, length: Int) { 38 + // case expression that checks if the first bit of the string is in a 39 + // alphanumeric character range of ascii 40 + case bin { 41 + <<c, rest:bits>> if c >= 48 && c <= 57 -> 42 + do_escape_attribute(rest, original, length + 1) 43 + <<c, rest:bits>> if c >= 65 && c <= 90 -> 44 + do_escape_attribute(rest, original, length + 1) 45 + <<c, rest:bits>> if c >= 97 && c <= 122 -> 46 + do_escape_attribute(rest, original, length + 1) 47 + <<"-", rest:bits>> 48 + | <<"_", rest:bits>> 49 + | <<".", rest:bits>> 50 + | <<":", rest:bits>> -> do_escape_attribute(rest, original, length + 1) 51 + 52 + // didn't ever find something that wasn't whitelisted, can go ahead and 53 + // use original string 54 + <<>> -> original 55 + // otherwise do regular houdini escaping 56 + <<_, _:bits>> -> { 57 + let h = do_escape_normal(bin, 0, original, <<>>, length) 58 + <<"\"", h:bits, "\"">> 59 + } 60 + 61 + _ -> 62 + panic as "do_escape_check: non byte aligned string, all strings should be byte aligned" 63 + } 27 64 } 28 65 29 66 // A possible way to escape chars would be to split the string into graphemes,
-68
test/houdini_test.gleam
··· 1 - import gleam/list 2 - import gleam/string 3 - import gleeunit 4 - import houdini 5 - import qcheck.{type Generator} 6 - 7 - pub fn main() -> Nil { 8 - gleeunit.main() 9 - } 10 - 11 - const conversions = [ 12 - #("&", "&amp;"), 13 - #("<", "&lt;"), 14 - #(">", "&gt;"), 15 - #("\"", "&quot;"), 16 - #("'", "&#39;"), 17 - ] 18 - 19 - pub fn simple_conversions_test() { 20 - use #(value, escaped) <- list.each(conversions) 21 - assert houdini.escape(value) == escaped 22 - } 23 - 24 - pub fn strange_unicode_string_test() { 25 - let input = ">a>'ࣉa>aa<a'>><\"aa&aࣉ>aࣉaaaa>ࣉa\"a'&a<<<&\"aaa\"&a>aa\">><'ࣉ\"" 26 - assert houdini.escape(input) == escaped(input) 27 - } 28 - 29 - pub fn regular_string_is_left_unchanged_test() { 30 - let codepoints = 31 - qcheck.from_generators(qcheck.alphanumeric_ascii_codepoint(), [ 32 - qcheck.ascii_whitespace_codepoint(), 33 - ]) 34 - 35 - use regular_string <- given(qcheck.string_from(codepoints)) 36 - assert houdini.escape(regular_string) == regular_string 37 - } 38 - 39 - pub fn string_with_special_characters_is_escaped_test() { 40 - let codepoints = 41 - qcheck.from_generators(escaped_codepoints(), [ 42 - qcheck.alphanumeric_ascii_codepoint(), 43 - ]) 44 - 45 - use string <- given(qcheck.string_from(codepoints)) 46 - assert houdini.escape(string) == escaped(string) 47 - } 48 - 49 - // --- PROPERTY HELPERS -------------------------------------------------------- 50 - 51 - fn escaped_codepoints() -> Generator(UtfCodepoint) { 52 - let assert [first, ..rest] = { 53 - use #(value, _) <- list.map(conversions) 54 - let assert [codepoint] = string.to_utf_codepoints(value) 55 - qcheck.constant(codepoint) 56 - } 57 - qcheck.from_generators(first, rest) 58 - } 59 - 60 - fn given(generator: Generator(a), assertion: fn(a) -> Nil) -> Nil { 61 - let config = qcheck.default_config() |> qcheck.with_test_count(5000) 62 - qcheck.run(config, generator, assertion) 63 - } 64 - 65 - fn escaped(string: String) -> String { 66 - use string, #(value, escaped) <- list.fold(over: conversions, from: string) 67 - string.replace(in: string, each: value, with: escaped) 68 - }
+20
test/houdinis_publicist_test.gleam
··· 1 + import gleam/list 2 + import gleeunit 3 + import houdinis_publicist 4 + 5 + pub fn main() -> Nil { 6 + gleeunit.main() 7 + } 8 + 9 + pub fn simple_conversions_test() { 10 + let tests = [ 11 + #("a", "a"), 12 + #("abcd", "abcd"), 13 + #("ab cd", "\"ab cd\""), 14 + #("a\"", "\"a&quot;\""), 15 + ] 16 + 17 + list.each(tests, fn(tup) { 18 + assert houdinis_publicist.escape_attribute(tup.0) == tup.1 19 + }) 20 + }