token-resolver 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +30 -1
- data/README.md +39 -8
- data/lib/token/resolver/config.rb +12 -3
- data/lib/token/resolver/grammar.rb +4 -2
- data/lib/token/resolver/resolve.rb +28 -3
- data/lib/token/resolver/version.rb +1 -1
- data.tar.gz.sig +4 -2
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66d9dd5b6e50ded563ba876174ef20a039d7c074193136becdb5c49117b34039
|
|
4
|
+
data.tar.gz: 24245d7bd943145c2c7096931d3d2b5c514b226538e2c1f0f35dc408c72a9cb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 178cc7d07c311b8c8c66b022c71a6ef0e6fa6e903b00abe2c815d217ff03129ff546b66ea02f956e85c71149a56c02340e4f0fdff7c6acb02fe44e6d736736cb
|
|
7
|
+
data.tar.gz: 9acb5dd248d648b4db87d34fa39f34401e365904b899736a0ef97f21dca47beb8446b02673e9ce7dd7bdca5045a909127b2765e18bc99a5a8ffd8d079cfd917e
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,33 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.0.1] - 2026-02-22
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.0.1][1.0.1t]
|
|
36
|
+
- COVERAGE: 98.13% -- 263/268 lines in 10 files
|
|
37
|
+
- BRANCH COVERAGE: 91.18% -- 62/68 branches in 10 files
|
|
38
|
+
- 96.77% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `Config#segment_pattern` option — a parslet character class constraining which characters
|
|
43
|
+
are valid inside token segments (default: `"[A-Za-z0-9_]"`). This prevents false positive
|
|
44
|
+
token matches against Ruby block parameters (`{ |x| expr }`), shell variable expansion
|
|
45
|
+
(`${VAR:+val}`), and other syntax that structurally resembles tokens but contains spaces
|
|
46
|
+
or punctuation in the "segments".
|
|
47
|
+
- `Resolve#resolve` now validates replacement keys against the config's `segment_pattern` and
|
|
48
|
+
raises `ArgumentError` if a key contains characters that the grammar would never parse.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **False positive token matches** — the grammar previously used `any` (match any character)
|
|
53
|
+
for segment content, which allowed spaces, operators, and punctuation inside token segments.
|
|
54
|
+
This caused Ruby block syntax like `{ |fp| File.exist?(fp) }` and shell expansion like
|
|
55
|
+
`${CLASSPATH:+:$CLASSPATH}` to be incorrectly parsed as tokens. With multi-separator configs
|
|
56
|
+
(`["|", ":"]`), the second `|` was reconstructed as `:` during `on_missing: :keep`
|
|
57
|
+
roundtripping, silently corrupting source files. The grammar now uses
|
|
58
|
+
`match(segment_pattern)` instead of `any`, limiting segments to word characters by default.
|
|
59
|
+
|
|
33
60
|
## [1.0.0] - 2026-02-21
|
|
34
61
|
|
|
35
62
|
- TAG: [v1.0.0][1.0.0t]
|
|
@@ -43,6 +70,8 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
43
70
|
|
|
44
71
|
### Security
|
|
45
72
|
|
|
46
|
-
[Unreleased]: https://github.com/kettle-rb/token-resolver/compare/v1.0.
|
|
73
|
+
[Unreleased]: https://github.com/kettle-rb/token-resolver/compare/v1.0.1...HEAD
|
|
74
|
+
[1.0.1]: https://github.com/kettle-rb/token-resolver/compare/v1.0.0...v1.0.1
|
|
75
|
+
[1.0.1t]: https://github.com/kettle-rb/token-resolver/releases/tag/v1.0.1
|
|
47
76
|
[1.0.0]: https://github.com/kettle-rb/ast-merge/compare/e0e299cad6e6914d512845c71df6b7ac8009e5ac...v1.0.0
|
|
48
77
|
[1.0.0t]: https://github.com/kettle-rb/ast-merge/tags/v1.0.0
|
data/README.md
CHANGED
|
@@ -157,13 +157,36 @@ NOTE: Be prepared to track down certs for signed gems and add them the same way
|
|
|
157
157
|
|
|
158
158
|
### Token Config Options
|
|
159
159
|
|
|
160
|
-
| Option
|
|
161
|
-
|
|
162
|
-
| `pre`
|
|
163
|
-
| `post`
|
|
164
|
-
| `separators`
|
|
165
|
-
| `min_segments`
|
|
166
|
-
| `max_segments`
|
|
160
|
+
| Option | Default | Description |
|
|
161
|
+
|-------------------|---------------------|----------------------------------------------------|
|
|
162
|
+
| `pre` | `"{"` | Opening delimiter |
|
|
163
|
+
| `post` | `"}"` | Closing delimiter |
|
|
164
|
+
| `separators` | `["|"]` (pipe) | Segment separators (sequential; last repeats) |
|
|
165
|
+
| `min_segments` | `2` | Minimum segments for a valid token |
|
|
166
|
+
| `max_segments` | `nil` | Maximum segments (`nil` = unlimited) |
|
|
167
|
+
| `segment_pattern` | `"[A-Za-z0-9_]"` | Parslet character class for valid segment content |
|
|
168
|
+
|
|
169
|
+
### Segment Character Constraints
|
|
170
|
+
|
|
171
|
+
Token segments (the parts between delimiters and separators) only match characters that
|
|
172
|
+
conform to the `segment_pattern`. By default, this is word characters: uppercase and
|
|
173
|
+
lowercase letters, digits, and underscores.
|
|
174
|
+
|
|
175
|
+
This prevents false positives with syntax that structurally resembles tokens but isn't:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# These are NOT parsed as tokens (spaces, punctuation disqualify them):
|
|
179
|
+
"items.map { |x| x.to_s }" # Ruby block parameters
|
|
180
|
+
"${CLASSPATH:+:$CLASSPATH}" # Shell variable expansion
|
|
181
|
+
"cert_chain.select! { |fp| File.exist? }" # Ruby block with expressions
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
If you need different characters in your token segments, provide a custom pattern:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
# Allow hyphens in segments: {NS|my-key}
|
|
188
|
+
config = Token::Resolver::Config.new(segment_pattern: "[A-Za-z0-9_-]")
|
|
189
|
+
```
|
|
167
190
|
|
|
168
191
|
## 🔧 Basic Usage
|
|
169
192
|
|
|
@@ -263,6 +286,14 @@ infinite loops and ensures predictable behavior when replacement values contain
|
|
|
263
286
|
If the input doesn't contain the `pre` delimiter at all, the parser fast-paths and returns
|
|
264
287
|
a single Text node without invoking parslet.
|
|
265
288
|
|
|
289
|
+
### False Positive Prevention
|
|
290
|
+
|
|
291
|
+
The grammar constrains segment content to the configured `segment_pattern` (default: word
|
|
292
|
+
characters). This ensures that syntax using the same delimiter characters — such as Ruby
|
|
293
|
+
block parameters (`{ |x| expr }`) or shell variable expansion (`${VAR:+val}`) — is never
|
|
294
|
+
mistakenly parsed as a token. Replacement keys that contain characters outside the
|
|
295
|
+
`segment_pattern` are rejected with an `ArgumentError` at resolve time.
|
|
296
|
+
|
|
266
297
|
|
|
267
298
|
## 🦷 FLOSS Funding
|
|
268
299
|
|
|
@@ -623,7 +654,7 @@ Thanks for RTFM. ☺️
|
|
|
623
654
|
[📌gitmoji]: https://gitmoji.dev
|
|
624
655
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
625
656
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
626
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.
|
|
657
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.268-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
627
658
|
[🔐security]: SECURITY.md
|
|
628
659
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
629
660
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -44,6 +44,11 @@ module Token
|
|
|
44
44
|
# @return [Integer, nil] Maximum number of segments (nil = unlimited)
|
|
45
45
|
attr_reader :max_segments
|
|
46
46
|
|
|
47
|
+
# @return [String] Parslet-compatible character class for segment content.
|
|
48
|
+
# Only characters matching this pattern are valid inside a token segment.
|
|
49
|
+
# Default: `"[A-Za-z0-9_]"` (word characters — no spaces, no operators).
|
|
50
|
+
attr_reader :segment_pattern
|
|
51
|
+
|
|
47
52
|
# Create a new Config.
|
|
48
53
|
#
|
|
49
54
|
# @param pre [String] Opening delimiter (default: "{")
|
|
@@ -51,9 +56,11 @@ module Token
|
|
|
51
56
|
# @param separators [Array<String>] Segment separators (default: ["|"])
|
|
52
57
|
# @param min_segments [Integer] Minimum segment count (default: 2)
|
|
53
58
|
# @param max_segments [Integer, nil] Maximum segment count (default: nil)
|
|
59
|
+
# @param segment_pattern [String] Parslet match() character class for valid segment
|
|
60
|
+
# characters (default: "[A-Za-z0-9_]")
|
|
54
61
|
#
|
|
55
62
|
# @raise [ArgumentError] If any delimiter is empty or constraints are invalid
|
|
56
|
-
def initialize(pre: "{", post: "}", separators: ["|"], min_segments: 2, max_segments: nil)
|
|
63
|
+
def initialize(pre: "{", post: "}", separators: ["|"], min_segments: 2, max_segments: nil, segment_pattern: "[A-Za-z0-9_]")
|
|
57
64
|
validate!(pre, post, separators, min_segments, max_segments)
|
|
58
65
|
|
|
59
66
|
@pre = pre.dup.freeze
|
|
@@ -61,6 +68,7 @@ module Token
|
|
|
61
68
|
@separators = separators.map { |s| s.dup.freeze }.freeze
|
|
62
69
|
@min_segments = min_segments
|
|
63
70
|
@max_segments = max_segments
|
|
71
|
+
@segment_pattern = segment_pattern.dup.freeze
|
|
64
72
|
|
|
65
73
|
freeze
|
|
66
74
|
end
|
|
@@ -85,7 +93,8 @@ module Token
|
|
|
85
93
|
post == other.post &&
|
|
86
94
|
separators == other.separators &&
|
|
87
95
|
min_segments == other.min_segments &&
|
|
88
|
-
max_segments == other.max_segments
|
|
96
|
+
max_segments == other.max_segments &&
|
|
97
|
+
segment_pattern == other.segment_pattern
|
|
89
98
|
end
|
|
90
99
|
|
|
91
100
|
alias_method :==, :eql?
|
|
@@ -94,7 +103,7 @@ module Token
|
|
|
94
103
|
#
|
|
95
104
|
# @return [Integer]
|
|
96
105
|
def hash
|
|
97
|
-
[pre, post, separators, min_segments, max_segments].hash
|
|
106
|
+
[pre, post, separators, min_segments, max_segments, segment_pattern].hash
|
|
98
107
|
end
|
|
99
108
|
|
|
100
109
|
# Get the separator for a given boundary index.
|
|
@@ -53,6 +53,7 @@ module Token
|
|
|
53
53
|
separators = config.separators
|
|
54
54
|
min_segs = config.min_segments
|
|
55
55
|
max_segs = config.max_segments
|
|
56
|
+
seg_pattern = config.segment_pattern
|
|
56
57
|
|
|
57
58
|
Class.new(Parslet::Parser) do
|
|
58
59
|
# A segment is one or more characters that are not a separator or post delimiter.
|
|
@@ -62,10 +63,11 @@ module Token
|
|
|
62
63
|
# Build the set of strings that terminate a segment
|
|
63
64
|
terminators = ([post_str] + separators).uniq
|
|
64
65
|
|
|
65
|
-
# segment: one or more chars that
|
|
66
|
+
# segment: one or more chars that match the segment_pattern and
|
|
67
|
+
# aren't any terminator string.
|
|
66
68
|
rule(:segment) {
|
|
67
69
|
terminator_absent = terminators.map { |t| str(t).absent? }.reduce(:>>)
|
|
68
|
-
(terminator_absent >>
|
|
70
|
+
(terminator_absent >> match(seg_pattern)).repeat(1)
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
# token: pre + segment + (sep + segment).repeat + post
|
|
@@ -50,16 +50,19 @@ module Token
|
|
|
50
50
|
# @return [String] Resolved text
|
|
51
51
|
#
|
|
52
52
|
# @raise [UnresolvedTokenError] If on_missing is :raise and a token has no replacement
|
|
53
|
+
# @raise [ArgumentError] If a replacement key contains characters outside the config's segment_pattern
|
|
53
54
|
def resolve(document_or_nodes, replacements)
|
|
54
|
-
nodes = case document_or_nodes
|
|
55
|
+
nodes, config = case document_or_nodes
|
|
55
56
|
when Document
|
|
56
|
-
document_or_nodes.nodes
|
|
57
|
+
[document_or_nodes.nodes, document_or_nodes.config]
|
|
57
58
|
when Array
|
|
58
|
-
document_or_nodes
|
|
59
|
+
[document_or_nodes, nil]
|
|
59
60
|
else
|
|
60
61
|
raise ArgumentError, "Expected Document or Array of nodes, got #{document_or_nodes.class}"
|
|
61
62
|
end
|
|
62
63
|
|
|
64
|
+
validate_replacement_keys!(replacements, config) if config && !replacements.empty?
|
|
65
|
+
|
|
63
66
|
result = +""
|
|
64
67
|
nodes.each do |node|
|
|
65
68
|
if node.token?
|
|
@@ -88,6 +91,28 @@ module Token
|
|
|
88
91
|
# emit nothing
|
|
89
92
|
end
|
|
90
93
|
end
|
|
94
|
+
|
|
95
|
+
# Validate that all replacement keys only contain characters allowed by the config.
|
|
96
|
+
# Each key is composed of segments (matching segment_pattern) joined by separators.
|
|
97
|
+
#
|
|
98
|
+
# @param replacements [Hash{String => String}]
|
|
99
|
+
# @param config [Config]
|
|
100
|
+
# @raise [ArgumentError] If any key contains invalid characters
|
|
101
|
+
def validate_replacement_keys!(replacements, config)
|
|
102
|
+
# Build a regex that matches a valid key: segment (sep segment)*
|
|
103
|
+
seg = config.segment_pattern
|
|
104
|
+
seps = config.separators.map { |s| Regexp.escape(s) }.join("|")
|
|
105
|
+
valid_key_re = /\A#{seg}+((?:#{seps})#{seg}+)*\z/
|
|
106
|
+
|
|
107
|
+
replacements.each_key do |key|
|
|
108
|
+
unless valid_key_re.match?(key)
|
|
109
|
+
raise ArgumentError,
|
|
110
|
+
"Invalid replacement key: #{key.inspect}. " \
|
|
111
|
+
"Key segments must match #{config.segment_pattern.inspect} " \
|
|
112
|
+
"and be separated by one of #{config.separators.inspect}."
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
91
116
|
end
|
|
92
117
|
end
|
|
93
118
|
end
|
data.tar.gz.sig
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
iد�쾰�'Ctۨfi��(�t!��I�S���?��o�
|
|
2
|
+
�}l���1�%��p=>U
|
|
3
|
+
�#F��h<cg0G��,* A�4)8��h�h`]��X�ۛ��M�<���-M�!���Z�txKFl�CJ�ˇ�d^ � �6�;+�ԝ�Q3�m�{���X
|
|
4
|
+
���ѫ1ى�p����[z{v�r&2�� N�,���9�k��Q攛�Տݰr졽�^��"uQI ���+r����F��Vm��Uxw&`� ��Rނ��������[]Z��x����j���b
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: token-resolver
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -274,10 +274,10 @@ licenses:
|
|
|
274
274
|
- MIT
|
|
275
275
|
metadata:
|
|
276
276
|
homepage_uri: https://token-resolver.galtzo.com/
|
|
277
|
-
source_code_uri: https://github.com/kettle-rb/token-resolver/tree/v1.0.
|
|
278
|
-
changelog_uri: https://github.com/kettle-rb/token-resolver/blob/v1.0.
|
|
277
|
+
source_code_uri: https://github.com/kettle-rb/token-resolver/tree/v1.0.1
|
|
278
|
+
changelog_uri: https://github.com/kettle-rb/token-resolver/blob/v1.0.1/CHANGELOG.md
|
|
279
279
|
bug_tracker_uri: https://github.com/kettle-rb/token-resolver/issues
|
|
280
|
-
documentation_uri: https://www.rubydoc.info/gems/token-resolver/1.0.
|
|
280
|
+
documentation_uri: https://www.rubydoc.info/gems/token-resolver/1.0.1
|
|
281
281
|
funding_uri: https://github.com/sponsors/pboling
|
|
282
282
|
wiki_uri: https://github.com/kettle-rb/token-resolver/wiki
|
|
283
283
|
news_uri: https://www.railsbling.com/tags/token-resolver
|
metadata.gz.sig
CHANGED
|
Binary file
|