barefoot_js 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BarefootJS
4
+ # Request-scoped SSR view of the query string behind the reactive
5
+ # `searchParams()` environment signal (router v0.5, #1922). The framework
6
+ # integration builds one per request from the request URL and threads it
7
+ # into the template scope as `v[:searchParams]` (the camelCase JS name the
8
+ # adapters keep, like every other signal/prop var); the compiled ERB
9
+ # template reads it via `v[:searchParams].get('key')`.
10
+ #
11
+ # This runtime is template-engine- and framework-agnostic (Ruby stdlib
12
+ # only), matching the rest of BarefootJS, so it can ship standalone.
13
+ #
14
+ # Semantics mirror the browser's URLSearchParams#get exactly under the
15
+ # adapters' `?? -> ||=`-style lowering: get() returns the first value for a
16
+ # key, or `nil` when the key is absent. A present-but-empty value
17
+ # (`?sort=`) keeps the empty string -- the same distinction JS `??` draws
18
+ # between `null` and `''`.
19
+ class SearchParams
20
+ # Parse a raw query string into the reader. A leading '?' is tolerated,
21
+ # '+' decodes to a space, and %XX escapes are decoded -- mirroring
22
+ # URLSearchParams's application/x-www-form-urlencoded parsing. A
23
+ # malformed pair never raises; it simply contributes nothing, matching
24
+ # the browser's lenient parsing.
25
+ def initialize(query = '')
26
+ query ||= ''
27
+ query = query.sub(/\A\?/, '')
28
+ @values = Hash.new { |h, k| h[k] = [] }
29
+ query.split(/[&;]/).each do |pair|
30
+ next if pair.empty?
31
+
32
+ key, val = pair.split('=', 2)
33
+ key = decode(key)
34
+ val = val.nil? ? '' : decode(val)
35
+ @values[key] << val
36
+ end
37
+ end
38
+
39
+ # First value for `key`, or `nil` when the key is absent (see the class
40
+ # docstring for why `nil` -- not '' -- is the right "missing" sentinel).
41
+ # A present-but-empty value returns ''.
42
+ def get(key)
43
+ vals = @values[key]
44
+ return nil if vals.nil? || vals.empty?
45
+
46
+ vals.first
47
+ end
48
+
49
+ private
50
+
51
+ def decode(s)
52
+ s = (s || '').dup
53
+ s.tr!('+', ' ')
54
+ # %XX -> raw octet, then interpret the octet stream as UTF-8 (what
55
+ # URLSearchParams does). A byte run that isn't valid UTF-8 is
56
+ # replaced rather than raising (lenient parsing, mirrors the Perl
57
+ # port's leave-as-is policy as closely as Ruby's String#encode allows).
58
+ bytes = s.gsub(/%([0-9A-Fa-f]{2})/) { [Regexp.last_match(1)].pack('H2') }
59
+ bytes.force_encoding('UTF-8')
60
+ bytes.valid_encoding? ? bytes : bytes.force_encoding('ASCII-8BIT').encode('UTF-8', invalid: :replace, undef: :replace)
61
+ end
62
+ end
63
+ end