duckling 0.3.0 → 0.4.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
- data/CHANGELOG.md +91 -0
- data/README.md +42 -2
- data/Rakefile +18 -16
- data/docs/tz-database-axis.md +454 -0
- data/ext/duckling/Cargo.toml +7 -0
- data/lib/duckling/tzinfo_capabilities.rb +59 -0
- data/lib/duckling/version.rb +1 -1
- data/lib/duckling.rb +33 -17
- metadata +4 -19
- data/.claude/settings.json +0 -46
- data/AGENTS.md +0 -178
- data/CLAUDE.md +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '059bb11a0d905a85d7577db06b62d8971c2896bda2433d0bbeb8fd0a2393c9b2'
|
|
4
|
+
data.tar.gz: 9101b8b87ada67851e4468270d73131be44dca36ea3c1f72a99bc403f5ef46e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe7ad8e2c972e6070f2a2bea717e30f7d66d021b0ad3bf618466acb29b3d8bbc51efec151424f2ce3d8b08f0c0edbf5a2b78b667b7ffc8f2f81ff380f08ecf96
|
|
7
|
+
data.tar.gz: 83fa337d4259faa6063c4322fac789c9b98e57bd7f2c8c3dc7e7142515179d2db69bb8700755c11c29078904af9bc7c6a99d01c1ffec2e6610dff5b6b4c2460a
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,97 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.1] - 2026-08-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- The packaged gem no longer includes agent- and development-tooling files.
|
|
14
|
+
0.3.0 and 0.4.0 shipped `.claude/settings.json`, `AGENTS.md`, and
|
|
15
|
+
`CLAUDE.md`: the gemspec built its file list from `git ls-files` with a
|
|
16
|
+
reject-list of paths to exclude, so every newly tracked dotfile or tool
|
|
17
|
+
directory was packaged by default. The gemspec now allow-lists what ships
|
|
18
|
+
(`lib/`, `ext/`, `docs/` other than `docs/benchmarks/`, and a named set of
|
|
19
|
+
root files), so anything new stays out of the gem unless it is added
|
|
20
|
+
deliberately. Regression coverage checks the gemspec's file list in the
|
|
21
|
+
main suite and the built artifacts in `test/gem/packaged_gem_test.rb`.
|
|
22
|
+
None of the previously shipped files contained secrets — they are
|
|
23
|
+
development configuration and documentation, all public in the repository
|
|
24
|
+
— so the already-published 0.3.0/0.4.0 gems are unaffected in behavior and
|
|
25
|
+
have been left in place.
|
|
26
|
+
|
|
27
|
+
## [0.4.0] - 2026-08-10
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **On a stock Debian/Ubuntu host, roughly a hundred IANA zone identifiers
|
|
32
|
+
stop resolving.** `reference_zone: "US/Eastern"` — and every other
|
|
33
|
+
backward-compatibility name, such as `"US/Pacific"`, `"Europe/Kiev"`, or
|
|
34
|
+
`"Japan"` — now raises `ArgumentError` there. Those names live in the
|
|
35
|
+
`tzdata-legacy` system package, which is not installed by default. Two ways
|
|
36
|
+
to get them back, either of which restores the previous behavior exactly:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
gem "tzinfo-data" # in your Gemfile
|
|
40
|
+
```
|
|
41
|
+
```bash
|
|
42
|
+
apt install tzdata-legacy # on the host
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Canonical identifiers (`"America/New_York"`, `"Europe/Kyiv"`) are
|
|
46
|
+
unaffected. A host with no zoneinfo files at all — a scratch or distroless
|
|
47
|
+
container — needs the gem for `reference_zone:` to work at all.
|
|
48
|
+
|
|
49
|
+
The error message names the tz database that answered, how many identifiers
|
|
50
|
+
it has, and both remedies, so this is distinguishable from a typo. The
|
|
51
|
+
datasource and the count describe whichever database answered on your host,
|
|
52
|
+
so both differ from the example below:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
invalid reference_zone: "US/Eastern" (resolved against system zoneinfo at
|
|
56
|
+
/usr/share/zoneinfo, which provides 497 identifiers; this database has no
|
|
57
|
+
backward-compat names (US/Eastern and ~100 others), so if that is what this
|
|
58
|
+
is, it needs either the tzinfo-data gem or the tzdata-legacy system package)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The remedy is worded as a condition rather than a claim about the name you
|
|
62
|
+
passed: whether a given identifier is one of the ~100 in IANA's `backward`
|
|
63
|
+
file isn't knowable without shipping that list, and asserting it would tell
|
|
64
|
+
every typo on such a host that `tzdata-legacy` will supply it.
|
|
65
|
+
|
|
66
|
+
A second, quieter difference comes with the same change: some distributions
|
|
67
|
+
compile tzdata in *rearguard* format, which strips negative DST, and on such
|
|
68
|
+
a host `Europe/Dublin` is modelled as an ordinary positive-DST zone rather
|
|
69
|
+
than a negative-DST one. Which distributions is not guessable — Ubuntu 24.04
|
|
70
|
+
is rearguard, Debian trixie is vanguard — so if you depend on tzinfo's
|
|
71
|
+
`dst?` flag, read it from the host rather than assuming. Resolved offsets
|
|
72
|
+
are the same either way, so no `Duckling.parse` result changes because of
|
|
73
|
+
it.
|
|
74
|
+
|
|
75
|
+
- `tzinfo-data` is no longer a runtime dependency. `tzinfo` already prefers
|
|
76
|
+
that gem when it is installed and falls back to the host's zoneinfo files
|
|
77
|
+
otherwise, so depending on it forced bundled tz data on every consumer to
|
|
78
|
+
serve the ones who want it. This is the change that produces the identifier
|
|
79
|
+
behavior above. Consumers who add `gem "tzinfo-data"` themselves get exactly
|
|
80
|
+
the previous behavior with no code change, and can still pick up a
|
|
81
|
+
tz-database revision by bumping that one gem. Dropping it means the bundled
|
|
82
|
+
tz data is no longer loaded at boot, so the first zone lookup does less
|
|
83
|
+
work; steady-state parsing is unaffected either way, since `reference_zone:`
|
|
84
|
+
resolution goes through the same tzinfo call once a database is loaded.
|
|
85
|
+
|
|
86
|
+
### Added
|
|
87
|
+
|
|
88
|
+
- `Duckling::TZDataUnavailable`, raised when `reference_zone:` is given on a
|
|
89
|
+
host with no tz database at all — no zoneinfo files and no `tzinfo-data`
|
|
90
|
+
gem, as in a scratch or distroless container. Newly reachable because of the
|
|
91
|
+
dependency change above; previously a database always existed. It names both
|
|
92
|
+
fixes, where the underlying tzinfo error mentioned neither this gem nor
|
|
93
|
+
`reference_zone:`. Deliberately not an `ArgumentError`: it reports the
|
|
94
|
+
deployment's state, not a bad argument, so code rescuing `ArgumentError`
|
|
95
|
+
around caller-supplied zone names does not swallow it. Every other keyword
|
|
96
|
+
works without a tz database.
|
|
97
|
+
|
|
98
|
+
## [0.3.0] - 2026-08-04
|
|
99
|
+
|
|
9
100
|
### Changed
|
|
10
101
|
|
|
11
102
|
- **Breaking:** `reference_time:` now requires a Ruby `Time` object (or
|
data/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Duckling.parse("tomorrow", locale: "en")
|
|
|
33
33
|
`Array` of entity `Hash`es (empty if nothing matched):
|
|
34
34
|
|
|
35
35
|
```ruby
|
|
36
|
-
Duckling.parse(text, locale: "en", dims: ["time"], reference_time: nil, with_latent: false)
|
|
36
|
+
Duckling.parse(text, locale: "en", dims: ["time"], reference_time: nil, with_latent: false, reference_zone: nil)
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### Keyword arguments
|
|
@@ -55,9 +55,49 @@ Duckling.parse(text, locale: "en", dims: ["time"], reference_time: nil, with_lat
|
|
|
55
55
|
raises `TypeError`; wrap it in `Time.at(seconds)` first.
|
|
56
56
|
- `with_latent:` (Boolean, default `false`) — include ambiguous/latent
|
|
57
57
|
matches (e.g. a bare "morning") in the results.
|
|
58
|
+
- `reference_zone:` (String, default `nil`) — an IANA zone name, e.g.
|
|
59
|
+
`"America/New_York"`. Resolves each wall-clock result's UTC offset against
|
|
60
|
+
that zone on the result's *own* date, so a result before a DST transition
|
|
61
|
+
and one after it get different offsets instead of sharing
|
|
62
|
+
`reference_time:`'s single fixed one. An unknown identifier raises
|
|
63
|
+
`ArgumentError`; so does a `reference_time:` whose `utc_offset` disagrees
|
|
64
|
+
with the zone at that instant. It does not anchor the parse — see "Time
|
|
65
|
+
zone data" below.
|
|
58
66
|
|
|
59
67
|
There is no `Duckling::Error` class — invalid `locale:`/`dims:` values raise
|
|
60
|
-
plain `ArgumentError
|
|
68
|
+
plain `ArgumentError`, as do an unknown `reference_zone:` and a
|
|
69
|
+
`reference_time:` whose offset disagrees with it. The one named error a caller
|
|
70
|
+
is likely to meet is `Duckling::TZDataUnavailable`, for a host with no tz
|
|
71
|
+
database at all — see "Time zone data" below.
|
|
72
|
+
|
|
73
|
+
### Time zone data
|
|
74
|
+
|
|
75
|
+
`reference_zone:` resolves against [tzinfo][], which uses the `tzinfo-data`
|
|
76
|
+
gem when it is installed and the host's own zoneinfo files otherwise. This gem
|
|
77
|
+
does not depend on `tzinfo-data`, so by default you get the host's database.
|
|
78
|
+
|
|
79
|
+
That is usually what you want, and it is the faster of the two to start up.
|
|
80
|
+
Two cases where it is not:
|
|
81
|
+
|
|
82
|
+
- **Backward-compatibility names.** Debian and Ubuntu ship names like
|
|
83
|
+
`"US/Eastern"` in a separate `tzdata-legacy` package that is not installed
|
|
84
|
+
by default, so roughly a hundred valid IANA identifiers raise
|
|
85
|
+
`ArgumentError` on a stock host. Either `gem "tzinfo-data"` or
|
|
86
|
+
`apt install tzdata-legacy` restores them. The error message names whichever
|
|
87
|
+
database answered and how many identifiers it has, so you can tell this
|
|
88
|
+
apart from a typo.
|
|
89
|
+
- **No zoneinfo files at all**, as in a scratch or distroless container.
|
|
90
|
+
`reference_zone:` raises `Duckling::TZDataUnavailable` there, naming both
|
|
91
|
+
fixes. Add `gem "tzinfo-data"` to bundle the data with your app, where you
|
|
92
|
+
can also patch its vintage by bumping one gem, or install the system
|
|
93
|
+
`tzdata` package. Every other keyword works without a tz database.
|
|
94
|
+
|
|
95
|
+
`reference_zone:` reinterprets result offsets; it does not anchor the parse.
|
|
96
|
+
Given without `reference_time:`, a relative expression like `"tomorrow"` still
|
|
97
|
+
anchors on the machine-local clock rather than on "now" in that zone — pass a
|
|
98
|
+
`reference_time:` in the zone to anchor as well.
|
|
99
|
+
|
|
100
|
+
[tzinfo]: https://github.com/tzinfo/tzinfo
|
|
61
101
|
|
|
62
102
|
### Return value
|
|
63
103
|
|
data/Rakefile
CHANGED
|
@@ -60,7 +60,7 @@ end
|
|
|
60
60
|
# extconf.rb runs in its own subprocess. ENV changes made there do not
|
|
61
61
|
# reach the parent process, and the parent process runs `make`.
|
|
62
62
|
#
|
|
63
|
-
# So this fix changes ENV here, in the Rakefile
|
|
63
|
+
# So this fix changes ENV here, in the Rakefile.
|
|
64
64
|
# The fix adds a prerequisite task to the local build's Makefile task.
|
|
65
65
|
# This prerequisite task sets the correct host target. It runs before
|
|
66
66
|
# the Makefile task, and before `make` runs later.
|
|
@@ -71,7 +71,7 @@ if (ruby_target = ENV["RUBY_TARGET"]) && ruby_target != RUBY_PLATFORM
|
|
|
71
71
|
# away. Clearing RUST_TARGET alone lets rb_sys fall back to the target
|
|
72
72
|
# baked into the container's $CARGO_HOME/config.toml, which is the
|
|
73
73
|
# cross-compile target again. So a host triple that cannot be read is a
|
|
74
|
-
# hard error
|
|
74
|
+
# hard error.
|
|
75
75
|
#
|
|
76
76
|
# Both variables belong to the whole rake process, and the cross build
|
|
77
77
|
# reads them too. This is safe only because rake generates the cross
|
|
@@ -121,8 +121,8 @@ end
|
|
|
121
121
|
#
|
|
122
122
|
# So build from a throwaway plain clone instead, whose .git is a real
|
|
123
123
|
# directory under the mount. Two consequences: the gem carries *committed*
|
|
124
|
-
# state
|
|
125
|
-
# directory
|
|
124
|
+
# state (uncommitted changes are excluded), and the clone gets its own Cargo
|
|
125
|
+
# target directory separate from this checkout's.
|
|
126
126
|
CLONE_DIR = "tmp/native_gem_clone"
|
|
127
127
|
|
|
128
128
|
def build_native_gem(platform)
|
|
@@ -163,10 +163,10 @@ end
|
|
|
163
163
|
|
|
164
164
|
task :benchmark_env do
|
|
165
165
|
# Force a realistic release-profile build regardless of .env.local's
|
|
166
|
-
# RB_SYS_CARGO_PROFILE=dev (local dev checkouts only
|
|
167
|
-
#
|
|
168
|
-
# rake process, so it
|
|
169
|
-
#
|
|
166
|
+
# RB_SYS_CARGO_PROFILE=dev (local dev checkouts only; CI never has it).
|
|
167
|
+
# Must reenable :compile in case it already ran earlier in this same
|
|
168
|
+
# rake process, so it recompiles under the forced profile. A stale
|
|
169
|
+
# dev-profile build would be reused otherwise.
|
|
170
170
|
ENV.delete("RB_SYS_CARGO_PROFILE")
|
|
171
171
|
Rake::Task[:compile].reenable
|
|
172
172
|
end
|
|
@@ -184,8 +184,8 @@ namespace :benchmark do
|
|
|
184
184
|
|
|
185
185
|
desc "Run :record on a fresh branch off origin/main, then commit/push and open+auto-merge a PR via gh"
|
|
186
186
|
task record_pr: ["release:guard_clean"] do
|
|
187
|
-
# Explicit bash
|
|
188
|
-
# runners
|
|
187
|
+
# Explicit bash: Rake's default `sh -c` is dash on Debian/Ubuntu
|
|
188
|
+
# runners, and dash's `set` doesn't support the `-o pipefail` flag below.
|
|
189
189
|
sh("bash", "-c", <<~SH)
|
|
190
190
|
set -euo pipefail
|
|
191
191
|
original_ref="$(git symbolic-ref -q --short HEAD || git rev-parse HEAD)"
|
|
@@ -214,12 +214,14 @@ namespace :benchmark do
|
|
|
214
214
|
end
|
|
215
215
|
|
|
216
216
|
# The default suite exercises the extension compiled in this checkout.
|
|
217
|
-
# test
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
217
|
+
# Three test subtrees run outside it:
|
|
218
|
+
# - test/gem/ exercises a *built* or *installed* gem instead — it needs one
|
|
219
|
+
# handed to it, and the installed suite must not see this checkout's lib/.
|
|
220
|
+
# - test/capabilities/ is loaded by test_helper itself, gated on the tz probes.
|
|
221
|
+
# - test/environments/ holds contracts invoked directly by their CI step.
|
|
222
|
+
# See docs/tz-database-axis.md.
|
|
221
223
|
Minitest::TestTask.create do |t|
|
|
222
|
-
t.test_globs = FileList["test/**/*_test.rb"].exclude("test/gem/**/*")
|
|
224
|
+
t.test_globs = FileList["test/**/*_test.rb"].exclude("test/gem/**/*", "test/capabilities/**/*", "test/environments/**/*")
|
|
223
225
|
end
|
|
224
226
|
|
|
225
227
|
# Minitest::TestTask has no built-in way to declare a task dependency, and
|
|
@@ -227,7 +229,7 @@ end
|
|
|
227
229
|
# `bundle exec rake` itself — `bundle exec rake test` run directly has no
|
|
228
230
|
# guarantee `compile` ran first, which would surface as a confusing
|
|
229
231
|
# LoadError/stale-behavior failure unrelated to the code under test.
|
|
230
|
-
task test:
|
|
232
|
+
task test: %i[compile]
|
|
231
233
|
|
|
232
234
|
require "standard/rake"
|
|
233
235
|
|