duckling 0.4.7-aarch64-linux
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 +7 -0
- data/Brewfile +3 -0
- data/CHANGELOG.md +218 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/NOTICES +52 -0
- data/README.md +253 -0
- data/Rakefile +243 -0
- data/docs/2026-07-01-roadmap.md +106 -0
- data/docs/tz-database-axis.md +454 -0
- data/lib/duckling/3.2/duckling.so +0 -0
- data/lib/duckling/3.3/duckling.so +0 -0
- data/lib/duckling/3.4/duckling.so +0 -0
- data/lib/duckling/4.0/duckling.so +0 -0
- data/lib/duckling/tzinfo_capabilities.rb +59 -0
- data/lib/duckling/version.rb +5 -0
- data/lib/duckling.rb +284 -0
- metadata +121 -0
data/lib/duckling.rb
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tzinfo"
|
|
4
|
+
|
|
5
|
+
require_relative "duckling/version"
|
|
6
|
+
require_relative "duckling/tzinfo_capabilities"
|
|
7
|
+
|
|
8
|
+
# A precompiled gem carries one binary per Ruby ABI, each in its own
|
|
9
|
+
# lib/duckling/<major.minor>/ directory. Load the one for the running Ruby.
|
|
10
|
+
#
|
|
11
|
+
# rb-sys reads Ruby's internal object layout through the headers it compiles
|
|
12
|
+
# against, so a binary understands only the Ruby that built it. Load the wrong
|
|
13
|
+
# one and it misreads objects: magnus rejects a genuine Time as not a Time.
|
|
14
|
+
#
|
|
15
|
+
# A source-gem build, and a plain `rake compile` in a checkout, write one
|
|
16
|
+
# binary straight to lib/duckling/ instead. That build always matches the
|
|
17
|
+
# running Ruby, so it needs no version directory.
|
|
18
|
+
begin
|
|
19
|
+
require_relative "duckling/#{RUBY_VERSION[/\d+\.\d+/]}/duckling"
|
|
20
|
+
rescue LoadError => abi_load_error
|
|
21
|
+
begin
|
|
22
|
+
require_relative "duckling/duckling"
|
|
23
|
+
rescue LoadError
|
|
24
|
+
# The ABI directory is missing, and so is the plain path. Raise the first
|
|
25
|
+
# error.
|
|
26
|
+
#
|
|
27
|
+
# A binary that exists but refuses to load fails here too, and its error
|
|
28
|
+
# says why. An Alpine install does this: RubyGems matches an unversioned
|
|
29
|
+
# -linux gem against a musl runtime, so the binary is present and asks for
|
|
30
|
+
# glibc. Reporting a missing file there would hide the real reason.
|
|
31
|
+
raise abi_load_error
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Duckling
|
|
36
|
+
# Raised when the serialized :time :value shape drifts from the typed walk in
|
|
37
|
+
# ext/duckling/src/lib.rs's patch_time_value/patch_time_point — a wrapper bug.
|
|
38
|
+
# Deliberately a RuntimeError subclass (not ArgumentError) so a caller
|
|
39
|
+
# rescuing ArgumentError around bad locale:/reference_zone: input can't
|
|
40
|
+
# swallow it, and a *named* one (not bare RuntimeError) so it's greppable and
|
|
41
|
+
# can't be satisfied by the unrelated native-panic RuntimeError. Mirrors the
|
|
42
|
+
# internal_error() class in lib.rs.
|
|
43
|
+
class ShapeError < RuntimeError; end
|
|
44
|
+
|
|
45
|
+
# Raised when `reference_zone:` is given on a host with no tz database at
|
|
46
|
+
# all (no zoneinfo files, no tzinfo-data gem). Deliberately outside
|
|
47
|
+
# ArgumentError: it reports the deployment, so input-validation rescues do
|
|
48
|
+
# not swallow it. See docs/tz-database-axis.md.
|
|
49
|
+
class TZDataUnavailable < RuntimeError; end
|
|
50
|
+
|
|
51
|
+
# Native.parse already releases the GVL around the native call, but a bare
|
|
52
|
+
# GVL release alone does not hand control back to an Async::Reactor —
|
|
53
|
+
# Ruby 3.4's Fiber::Scheduler#blocking_operation_wait auto-offload path
|
|
54
|
+
# requires a flag rb_thread_call_without_gvl never sets. Spawning a real
|
|
55
|
+
# background Thread lets the calling Fiber yield to the reactor via
|
|
56
|
+
# Thread#value's block/unblock scheduler hooks instead, which have been
|
|
57
|
+
# present since Ruby 3.0. See
|
|
58
|
+
# https://github.com/cpb/duckling/wiki/research-fiber-scheduler-mechanism-spike
|
|
59
|
+
# for the empirical result driving this.
|
|
60
|
+
#
|
|
61
|
+
# Only worth paying for when a Fiber scheduler is actually installed on the
|
|
62
|
+
# calling thread: a plain thread pool (Puma/Sidekiq-style, no reactor to
|
|
63
|
+
# yield to) already gets its concurrency from Native.parse's own GVL
|
|
64
|
+
# release, so the extra Thread.new there is a pure spawn+join tax. Calling
|
|
65
|
+
# Native.parse directly (no thread) is also the benchmark suite's baseline
|
|
66
|
+
# for measuring the dispatch overhead itself.
|
|
67
|
+
#
|
|
68
|
+
# report_on_exception is disabled from the very first line inside the
|
|
69
|
+
# spawned thread (not set on the Thread object afterward, which would race
|
|
70
|
+
# a fast-failing call) so a rescued error doesn't also print a
|
|
71
|
+
# thread-termination backtrace to stderr — Thread#value still re-raises it
|
|
72
|
+
# to the caller as ordinary control flow.
|
|
73
|
+
#
|
|
74
|
+
# reference_time: is coerced here because the native extension cannot:
|
|
75
|
+
# Native.parse's Magnus binding only accepts a strict kind_of?(Time) (issue
|
|
76
|
+
# #45), which rejects ActiveSupport::TimeWithZone and stdlib DateTime even
|
|
77
|
+
# though both carry the same to_i/utc_offset a real Time does — #to_time
|
|
78
|
+
# normalizes any of those (and anything else that offers the same
|
|
79
|
+
# conversion) to a real Time before it crosses into Rust.
|
|
80
|
+
# reference_zone: never crosses into Native.parse — the wrapped Rust crate
|
|
81
|
+
# has no IANA-zone concept at all, only the single FixedOffset it derives
|
|
82
|
+
# from reference_time:. Per-date-correct offsets therefore have to come from
|
|
83
|
+
# a real tz database on the Ruby side, so reference_zone: is applied as a
|
|
84
|
+
# two-part step around the native call: validate the zone (and reference_time:'s
|
|
85
|
+
# agreement with it) before, then reinterpret Naive results after.
|
|
86
|
+
#
|
|
87
|
+
# A fixed offset and a zone that disagree at the reference instant have no
|
|
88
|
+
# principled resolution — silently preferring either would resolve results
|
|
89
|
+
# against an offset the caller never asked for — so that combination raises.
|
|
90
|
+
#
|
|
91
|
+
# reference_zone: only reinterprets result offsets after the fact; it does
|
|
92
|
+
# NOT anchor the parse. Given without reference_time:, relative expressions
|
|
93
|
+
# ("tomorrow") still anchor on the machine-local clock. They do not anchor
|
|
94
|
+
# on "now" in that zone, so on a US host reference_zone: "Asia/Tokyo" can land on the wrong
|
|
95
|
+
# calendar day. Pass a reference_time: in the zone to anchor as well.
|
|
96
|
+
def self.parse(text, locale: "en", dims: ["time"], reference_time: nil, with_latent: false, reference_zone: nil)
|
|
97
|
+
reference_time = reference_time.to_time if reference_time && !reference_time.is_a?(Time) && reference_time.respond_to?(:to_time)
|
|
98
|
+
|
|
99
|
+
if reference_zone
|
|
100
|
+
zone = timezone_for(reference_zone)
|
|
101
|
+
verify_reference_time_offset!(reference_time, zone, reference_zone) if reference_time
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
kwargs = {locale: locale, dims: dims, with_latent: with_latent}
|
|
105
|
+
kwargs[:reference_time] = reference_time if reference_time
|
|
106
|
+
|
|
107
|
+
entities = if Fiber.scheduler
|
|
108
|
+
Thread.new do
|
|
109
|
+
Thread.current.report_on_exception = false
|
|
110
|
+
Native.parse(text, **kwargs)
|
|
111
|
+
end.value
|
|
112
|
+
else
|
|
113
|
+
Native.parse(text, **kwargs)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
zone ? reinterpret_entities!(entities, zone) : entities
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Reinterprets every TimePoint::Naive (wall-clock) leaf of each :time entity
|
|
120
|
+
# against `reference_zone`, using the real IANA offset for that leaf's own
|
|
121
|
+
# date. reference_time: carries a single fixed offset, which cannot be right
|
|
122
|
+
# for every leaf.
|
|
123
|
+
#
|
|
124
|
+
# TimePoint::Instant leaves are left strictly alone: the wrapped crate
|
|
125
|
+
# already collapsed their relative arithmetic against one FixedOffset before
|
|
126
|
+
# this gem ever saw the result, so there is no wall-clock left to reinterpret.
|
|
127
|
+
# That arithmetic's DST imprecision is known and out of scope (issue #83).
|
|
128
|
+
#
|
|
129
|
+
# Walks the externally-tagged shape ext/duckling/src/lib.rs's patch_time_value
|
|
130
|
+
# produces, and raises on any tag it doesn't recognize: a shape drift on the
|
|
131
|
+
# Rust side must fail loudly here. The outcome to prevent is quiet results
|
|
132
|
+
# resolved against the wrong offset.
|
|
133
|
+
def self.apply_reference_zone(entities, reference_zone)
|
|
134
|
+
return entities unless reference_zone
|
|
135
|
+
|
|
136
|
+
reinterpret_entities!(entities, timezone_for(reference_zone))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Zone-object core of apply_reference_zone. parse calls this directly with
|
|
140
|
+
# the TZInfo::Timezone it already resolved for offset validation, so the
|
|
141
|
+
# zone is looked up once per call. Validation and reinterpretation share the
|
|
142
|
+
# lookup.
|
|
143
|
+
def self.reinterpret_entities!(entities, zone)
|
|
144
|
+
entities.each do |entity|
|
|
145
|
+
next unless entity[:dim] == :time
|
|
146
|
+
reinterpret_time_value!(entity[:value][:Time], zone)
|
|
147
|
+
end
|
|
148
|
+
entities
|
|
149
|
+
end
|
|
150
|
+
private_class_method :reinterpret_entities!
|
|
151
|
+
|
|
152
|
+
# Walks the Single/Interval + Naive/Instant tagged shape. A primary value
|
|
153
|
+
# (single[:value], or an interval's from/to) and every `values` recurrence
|
|
154
|
+
# entry are resolved identically — see local_time_in_zone.
|
|
155
|
+
def self.reinterpret_time_value!(value, zone)
|
|
156
|
+
if (single = value && value[:Single])
|
|
157
|
+
reinterpret_time_point!(single[:value], zone)
|
|
158
|
+
single[:values]&.each { |point| reinterpret_time_point!(point, zone) }
|
|
159
|
+
elsif (interval = value && value[:Interval])
|
|
160
|
+
reinterpret_interval_endpoints!(interval, zone)
|
|
161
|
+
interval[:values]&.each { |endpoints| reinterpret_interval_endpoints!(endpoints, zone) }
|
|
162
|
+
else
|
|
163
|
+
raise ShapeError, "unrecognized :time value shape, expected a :Single or :Interval tag: #{value.inspect}"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
private_class_method :reinterpret_time_value!
|
|
167
|
+
|
|
168
|
+
# An Interval's from/to are Option<TimePoint> on the Rust side, and serde
|
|
169
|
+
# emits Option::None as a present key holding nil — hence the nil tolerance
|
|
170
|
+
# in reinterpret_time_point!. A missing-key check here would reject that shape.
|
|
171
|
+
def self.reinterpret_interval_endpoints!(endpoints, zone)
|
|
172
|
+
reinterpret_time_point!(endpoints[:from], zone)
|
|
173
|
+
reinterpret_time_point!(endpoints[:to], zone)
|
|
174
|
+
end
|
|
175
|
+
private_class_method :reinterpret_interval_endpoints!
|
|
176
|
+
|
|
177
|
+
def self.reinterpret_time_point!(point, zone)
|
|
178
|
+
return if point.nil?
|
|
179
|
+
|
|
180
|
+
if (naive = point[:Naive])
|
|
181
|
+
naive[:value] = local_time_in_zone(zone, naive[:value])
|
|
182
|
+
elsif !point.key?(:Instant)
|
|
183
|
+
raise ShapeError, "unrecognized TimePoint shape, expected a :Naive or :Instant tag: #{point.inspect}"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
private_class_method :reinterpret_time_point!
|
|
187
|
+
|
|
188
|
+
# The Rust side already resolved this Naive wall-clock against reference_time:'s
|
|
189
|
+
# fixed offset, so the Time's own calendar fields still read as that intended
|
|
190
|
+
# wall-clock — re-anchoring those same fields in `zone` is what picks up the
|
|
191
|
+
# per-date DST offset.
|
|
192
|
+
#
|
|
193
|
+
# A wall-clock that a spring-forward gap skipped, or that a fall-back overlap
|
|
194
|
+
# made ambiguous, has no single correct offset — but there's no benefit to
|
|
195
|
+
# raising over it either, whether the value is a primary one the caller
|
|
196
|
+
# literally named or a generated recurrence entry: both get the same
|
|
197
|
+
# deterministic resolution — a gap shifts the wall clock forward by the
|
|
198
|
+
# transition's delta (02:30 on a US spring-forward day becomes 03:30 EDT),
|
|
199
|
+
# an overlap takes the first (pre-transition) occurrence.
|
|
200
|
+
#
|
|
201
|
+
# The first occurrence is selected via the block form (periods_for_local
|
|
202
|
+
# yields periods in chronological order), NOT tzinfo's dst flag: dst=true
|
|
203
|
+
# only means "first" where the pre-transition period observes DST, and
|
|
204
|
+
# negative-DST zones invert that — Europe/Dublin models winter GMT as its
|
|
205
|
+
# dst?==true period, so dst=true there would pick the second occurrence, an
|
|
206
|
+
# hour off as an instant. (ActiveSupport::TimeZone#local has exactly that
|
|
207
|
+
# Dublin behavior, via period_for_local's dst=true default — a deliberate
|
|
208
|
+
# departure here, like the Lord Howe one on gap_delta.) The explicit nil dst
|
|
209
|
+
# argument keeps a global Timezone.default_dst setting from pre-filtering
|
|
210
|
+
# the periods before the block sees them.
|
|
211
|
+
#
|
|
212
|
+
# Preserving the gap's original wall clock and merely stamping the
|
|
213
|
+
# post-transition offset on it would produce a Time whose offset the zone
|
|
214
|
+
# does not observe at that instant — 02:30 -04:00 is 06:30Z, and at 06:30Z
|
|
215
|
+
# New York is still on EST — so it would read back as 01:30 EST, an hour
|
|
216
|
+
# before the occurrence it stands for and on the wrong side of the
|
|
217
|
+
# transition. Shifting forward keeps the instant and its rendered local
|
|
218
|
+
# time in agreement.
|
|
219
|
+
def self.local_time_in_zone(zone, time)
|
|
220
|
+
first_occurrence = lambda do |t|
|
|
221
|
+
zone.local_time(t.year, t.month, t.day, t.hour, t.min, t.sec, t.subsec, nil) do |periods|
|
|
222
|
+
periods.first
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
first_occurrence.call(time)
|
|
226
|
+
rescue TZInfo::PeriodNotFound
|
|
227
|
+
first_occurrence.call(time + gap_delta(zone, time))
|
|
228
|
+
end
|
|
229
|
+
private_class_method :local_time_in_zone
|
|
230
|
+
|
|
231
|
+
# How wide the spring-forward gap `time` fell into is — i.e. how far forward
|
|
232
|
+
# a skipped wall clock must move to land on a real one. DST transitions are
|
|
233
|
+
# months apart, so the single offset-increasing transition within a day of
|
|
234
|
+
# `time` is necessarily the one whose gap it landed in.
|
|
235
|
+
#
|
|
236
|
+
# The ±1-day scan window is centered on the skipped wall clock itself read
|
|
237
|
+
# as UTC. The UTC midnight of its date would be the wrong anchor. The transition's UTC instant
|
|
238
|
+
# is the wall clock minus a zone offset, and offsets never reach a day, so
|
|
239
|
+
# this window always contains it; a midnight-anchored window does not. A
|
|
240
|
+
# gap late in the local day in a negative-offset zone (America/Nuuk springs
|
|
241
|
+
# forward at 23:00 local) has its transition instant past the *next* UTC
|
|
242
|
+
# midnight, outside a midnight-anchored window — `find` returned nil and
|
|
243
|
+
# this method crashed.
|
|
244
|
+
#
|
|
245
|
+
# Read from the transition. Do not assume 3600. ActiveSupport's
|
|
246
|
+
# TimeWithZone#get_period_and_ensure_valid_local_time instead hardcodes
|
|
247
|
+
# `@time += 1.hour` and retries: for Australia/Lord_Howe's 30-minute gap
|
|
248
|
+
# that lands half an hour past the gap's end (02:15 → 03:15; the correct
|
|
249
|
+
# answer is 02:45). Every one-hour gap — i.e. every zone in current use but Lord Howe
|
|
250
|
+
# — resolves identically either way.
|
|
251
|
+
def self.gap_delta(zone, time)
|
|
252
|
+
wall_clock = Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec)
|
|
253
|
+
transition = zone.transitions_up_to(wall_clock + 86400, wall_clock - 86400)
|
|
254
|
+
.find { |t| t.offset.observed_utc_offset > t.previous_offset.observed_utc_offset }
|
|
255
|
+
transition.offset.observed_utc_offset - transition.previous_offset.observed_utc_offset
|
|
256
|
+
end
|
|
257
|
+
private_class_method :gap_delta
|
|
258
|
+
|
|
259
|
+
def self.timezone_for(reference_zone)
|
|
260
|
+
TZInfo::Timezone.get(reference_zone)
|
|
261
|
+
rescue TZInfo::InvalidTimezoneIdentifier
|
|
262
|
+
raise ArgumentError,
|
|
263
|
+
"invalid reference_zone: #{reference_zone.inspect} " \
|
|
264
|
+
"(#{TZInfoCapabilities.unknown_identifier_diagnosis})"
|
|
265
|
+
rescue TZInfo::DataSourceNotFound => error
|
|
266
|
+
# tzinfo raises this before any identifier lookup, so restate it in
|
|
267
|
+
# terms of the keyword the caller passed, naming both fixes.
|
|
268
|
+
raise TZDataUnavailable,
|
|
269
|
+
"cannot resolve reference_zone: #{reference_zone.inspect} — this host has no tz database. " \
|
|
270
|
+
"Add `gem \"tzinfo-data\"` to bundle the IANA data with your app, or install the system " \
|
|
271
|
+
"tzdata package to provide zoneinfo files. (#{error.message.lines.first.to_s.strip})"
|
|
272
|
+
end
|
|
273
|
+
private_class_method :timezone_for
|
|
274
|
+
|
|
275
|
+
def self.verify_reference_time_offset!(reference_time, zone, reference_zone)
|
|
276
|
+
zone_offset = zone.period_for(reference_time).observed_utc_offset
|
|
277
|
+
return if reference_time.utc_offset == zone_offset
|
|
278
|
+
|
|
279
|
+
raise ArgumentError,
|
|
280
|
+
"reference_time's utc_offset (#{reference_time.utc_offset}) does not match " \
|
|
281
|
+
"reference_zone #{reference_zone.inspect}'s utc_offset (#{zone_offset}) at that instant"
|
|
282
|
+
end
|
|
283
|
+
private_class_method :verify_reference_time_offset!
|
|
284
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: duckling
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.7
|
|
5
|
+
platform: aarch64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Caleb Buxton
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: tzinfo
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake-compiler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.3.1
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.3.1
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: benchmark-ips
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: async
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.41'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.41'
|
|
69
|
+
description: Duckling NER without an HTTP service for Ruby
|
|
70
|
+
email:
|
|
71
|
+
- me@cpb.ca
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- Brewfile
|
|
77
|
+
- CHANGELOG.md
|
|
78
|
+
- CODE_OF_CONDUCT.md
|
|
79
|
+
- LICENSE.txt
|
|
80
|
+
- NOTICES
|
|
81
|
+
- README.md
|
|
82
|
+
- Rakefile
|
|
83
|
+
- docs/2026-07-01-roadmap.md
|
|
84
|
+
- docs/tz-database-axis.md
|
|
85
|
+
- lib/duckling.rb
|
|
86
|
+
- lib/duckling/3.2/duckling.so
|
|
87
|
+
- lib/duckling/3.3/duckling.so
|
|
88
|
+
- lib/duckling/3.4/duckling.so
|
|
89
|
+
- lib/duckling/4.0/duckling.so
|
|
90
|
+
- lib/duckling/tzinfo_capabilities.rb
|
|
91
|
+
- lib/duckling/version.rb
|
|
92
|
+
homepage: https://github.com/cpb/duckling
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
metadata:
|
|
96
|
+
homepage_uri: https://github.com/cpb/duckling
|
|
97
|
+
source_code_uri: https://github.com/cpb/duckling
|
|
98
|
+
rubygems_mfa_required: 'true'
|
|
99
|
+
post_install_message:
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 3.2.0
|
|
108
|
+
- - "<"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 4.1.dev
|
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0'
|
|
116
|
+
requirements: []
|
|
117
|
+
rubygems_version: 3.5.23
|
|
118
|
+
signing_key:
|
|
119
|
+
specification_version: 4
|
|
120
|
+
summary: Ruby FFI adapter to a Rust Duckling
|
|
121
|
+
test_files: []
|