tomlrb 2.0.4 → 3.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +103 -0
- data/README.md +85 -0
- data/lib/tomlrb/local_date.rb +24 -13
- data/lib/tomlrb/local_date_time.rb +33 -20
- data/lib/tomlrb/local_time.rb +36 -16
- data/lib/tomlrb/parser.rb +789 -14
- data/lib/tomlrb/version.rb +2 -2
- data/lib/tomlrb.rb +18 -32
- data/sig/tomlrb.rbs +75 -0
- metadata +16 -26
- data/lib/tomlrb/generated_parser.rb +0 -808
- data/lib/tomlrb/handler.rb +0 -279
- data/lib/tomlrb/parser.y +0 -230
- data/lib/tomlrb/scanner.rb +0 -92
- data/lib/tomlrb/string_utils.rb +0 -40
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e6628b6e0662be0698c44c3dab186115a3424bfd9fe94dde4cb7c6e308d079ce
|
|
4
|
+
data.tar.gz: 81f84bf3dd98784ce68b7b6b7945461e381edb7234e84c86778f7d9eef2d8fab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bcefe0976b4840098240fd353784c886d58c1e1711a3130a8946751e85a670e170dceecd38ddb2557f06dd2d21ad1e96927abe0f52d1b17c855db6b1576ea29
|
|
7
|
+
data.tar.gz: bcdfd0a6f4638667e06d4228681b9cc764f849a16fb111125e3423910a00576f52e50c6c8749e0250b530d28ca4983e8e1eb0c9c2b513bf99da70123f6b0eaab
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
### 3.0.0 - 2026-10-15
|
|
2
|
+
|
|
3
|
+
Breaking changes:
|
|
4
|
+
|
|
5
|
+
* Require Ruby 3.3 or later
|
|
6
|
+
* Read documents as TOML 1.1 by default. Every valid TOML 1.0 document parses unchanged; 1.1 additionally accepts `\e` and `\xHH` escapes, times without seconds, and inline tables that span lines, contain comments or end with a comma. `version: "1.0"` keeps rejecting those
|
|
7
|
+
* Fractional seconds are exact `Rational`s instead of `Float`s: `Time`, `LocalDateTime` and `LocalTime` values carry `56.123`, not `56.122999…`
|
|
8
|
+
* Remove `Tomlrb::Scanner`, `Tomlrb::Handler`, `Tomlrb::StringUtils`, `Tomlrb::GeneratedParser`, `Tomlrb::Keys` and `Tomlrb::Key`; `Tomlrb::Parser.new` now takes the document string. Key redefinitions raise `Tomlrb::ParseError` as before
|
|
9
|
+
* `require "tomlrb"` no longer loads `time` and `stringio`
|
|
10
|
+
|
|
11
|
+
Features:
|
|
12
|
+
|
|
13
|
+
* TOML 1.1 support, selected with `version: "1.1"` (default) or `"1.0"`
|
|
14
|
+
|
|
15
|
+
Security:
|
|
16
|
+
|
|
17
|
+
* Nesting depth of arrays and inline tables is limited only by memory: they are parsed with an explicit stack
|
|
18
|
+
|
|
19
|
+
Performance:
|
|
20
|
+
|
|
21
|
+
* Replace the Racc grammar with a hand-written single-pass parser; Racc is no longer used. Parsing executes 2.2-2.5x fewer instructions than perfect_toml 0.9.1 and 12-14x fewer than 2.0.4 (2.6-2.9x and 14-16x with YJIT)
|
|
22
|
+
* Multi-line strings are scanned by searching for the closing quotes, a backslash or a control character: a 1 MB `"""` or `'''` string parses 25x faster, peak memory no longer grows by about 48 MB per MB of string, and quote-dense content (`ab"cd""ef`) no longer costs a step per quote
|
|
23
|
+
|
|
24
|
+
Conformance (every TOML 1.0 and 1.1 case of toml-test 2.2.0 passes):
|
|
25
|
+
|
|
26
|
+
* An escaped backslash right before a closing `"""` (`"""x\\"""`) ends the string
|
|
27
|
+
* Reject a carriage return not followed by a line feed in multi-line strings, and trim a CRLF (not just LF) right after an opening `"""` or `'''`
|
|
28
|
+
* Parse bare keys that start with `true`, `false`, `inf` or `nan` (`trueval`, `inf-x`) or look like dates and numbers (`1979-05-27a`, `0x1f`, `[[1e5]]`), and reject keys containing `:`
|
|
29
|
+
* Reject empty values in inline tables (`{a = }`), inline tables that are not assigned to a key, and dotted keys that extend an inline table
|
|
30
|
+
* Reject spaced array-of-tables brackets (`[ [a]]`, `[[a] ]`) on indented and CRLF lines too, and stop rejecting multi-line arrays whose line starts with `[ [`
|
|
31
|
+
* Separate date and time only with `T`, `t` or a space, accept an offset only after a time, and reject negative years and days that do not exist (`1979-06-31T00:00:00-00:00`)
|
|
32
|
+
* Reject floats with leading zeros (`01e5`) and accept underscores in exponents (`1e1_0`)
|
|
33
|
+
|
|
34
|
+
Other:
|
|
35
|
+
|
|
36
|
+
* Parse errors report the line and column (in characters)
|
|
37
|
+
* `benchmarks/matrix.rb` measures time, allocations and peak memory over a set of generated workloads; `benchmarks/bench.rb` accepts several files
|
|
38
|
+
* `LocalDate`, `LocalDateTime` and `LocalTime` validate their fields arithmetically and build their `Time` on first use
|
|
39
|
+
* Ship RBS type signatures in `sig/`
|
|
40
|
+
|
|
41
|
+
### 2.0.5 - 2026-10-15
|
|
42
|
+
|
|
43
|
+
* Fix exponential backtracking (ReDoS) when scanning unterminated basic and literal strings ([GHSA-gq8g-3x53-v8fh](https://github.com/fbernier/tomlrb/security/advisories/GHSA-gq8g-3x53-v8fh))
|
|
44
|
+
* Fix `SystemStackError` and quadratic scan time on long runs of spaces/tabs ([GHSA-mc56-vpwf-3m26](https://github.com/fbernier/tomlrb/security/advisories/GHSA-mc56-vpwf-3m26))
|
|
45
|
+
* Literal strings no longer treat `\'` as an escaped quote, per the TOML spec
|
|
46
|
+
* Add SECURITY.md: report vulnerabilities through GitHub private vulnerability reporting
|
|
47
|
+
* Declare racc as a runtime dependency: Ruby 3.3 made it a bundled gem, so under Bundler `require "tomlrb"` failed unless the app's Gemfile pulled racc in
|
|
48
|
+
* Stop shipping the unused `parser.y` grammar source in the gem
|
|
49
|
+
|
|
50
|
+
Both vulnerabilities were reported by [@tsigouris007](https://github.com/tsigouris007), advised by [@kpatsakis](https://github.com/kpatsakis).
|
|
51
|
+
|
|
52
|
+
### 2.0.4 - 2025-12-18
|
|
53
|
+
|
|
54
|
+
* Update Racc to v1.8.1
|
|
55
|
+
* Code cleanup
|
|
56
|
+
* Fix conformance to TOML v1.0.0
|
|
57
|
+
|
|
58
|
+
### 2.0.3 - 2022-05-28
|
|
59
|
+
|
|
60
|
+
* Enable symbolize_keys: true for inline tables
|
|
61
|
+
|
|
62
|
+
### 2.0.2 - 2022-05-25
|
|
63
|
+
|
|
64
|
+
* Conform to TOML v1.0.0
|
|
65
|
+
|
|
66
|
+
### 2.0.1 - 2020-12-20
|
|
67
|
+
|
|
68
|
+
* Fix error with table nested under array-of-tables
|
|
69
|
+
* Some performance improvements
|
|
70
|
+
|
|
71
|
+
### 2.0.0 - 2020-11-26
|
|
72
|
+
|
|
73
|
+
* Conform to TOML v1.0.0-rc3
|
|
74
|
+
|
|
75
|
+
#### Breanking Changes ####
|
|
76
|
+
|
|
77
|
+
TOML v0.5.0 introduced new value types: Local Date-Time, Local Date and Local Time which represent time without time zone information. Tomlrb also introduced corresponding classes starting from v2.0.0. By this change, some table values such as `2020-11-24T20:32:18`, `2020-11-24` or `20:32:18` are not treated as strings but as new the classes `Tomlrb::LocalDateTime`, `Tomlrb::LocalDate` and `Tomlrb::LocalTime` respectively. You can get the string values by calling `#to_s` method on any of those classes. Additionally, You can also get `Time` objects with the `#to_time` method. See [API documentation](https://www.rubydoc.info/gems/tomlrb) for the methods' details.
|
|
78
|
+
|
|
79
|
+
### 1.3.0 - 2020-03-19
|
|
80
|
+
|
|
81
|
+
* Fix error with falsy table values
|
|
82
|
+
|
|
83
|
+
### 1.2.9 - 2019-11-22
|
|
84
|
+
|
|
85
|
+
* Fixes and cleanups for ruby 2.7
|
|
86
|
+
|
|
87
|
+
### 1.2.8 - 2018-12-18
|
|
88
|
+
|
|
89
|
+
* Reduce gem size by excluding tests (tas50)
|
|
90
|
+
* Make integer and float parsing closer to the spec (sgarciac)
|
|
91
|
+
|
|
92
|
+
### 1.2.7 - 2018-07-12
|
|
93
|
+
|
|
94
|
+
* Datetime should be UTC when no offset or timezone are specified
|
|
95
|
+
|
|
96
|
+
### 1.2.6 - 2017-10-23
|
|
97
|
+
|
|
98
|
+
* Fix issue where an unclosed table could make the parsing loop infinitely.
|
|
99
|
+
* Proper string escaping
|
|
100
|
+
|
|
101
|
+
### 1.1.3 - 2015-11-24
|
|
102
|
+
|
|
103
|
+
* Bare integers can be used as keys as per the spec
|
data/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Tomlrb
|
|
2
|
+
|
|
3
|
+
[](https://github.com/fbernier/tomlrb/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/tomlrb)
|
|
5
|
+
|
|
6
|
+
A fast, pure-Ruby [TOML](https://toml.io) parser with no runtime dependencies, for [TOML 1.1](https://toml.io/en/v1.1.0) and [1.0](https://toml.io/en/v1.0.0). It passes every case of the official [toml-test](https://github.com/toml-lang/toml-test) suite for both versions.
|
|
7
|
+
|
|
8
|
+
Requires Ruby 3.3 or later. Runs on CRuby (with or without YJIT), JRuby and TruffleRuby.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
bundle add tomlrb
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
require "tomlrb"
|
|
20
|
+
|
|
21
|
+
Tomlrb.parse(%(title = "TOML"\n[owner]\nname = "Tom"))
|
|
22
|
+
# => {"title" => "TOML", "owner" => {"name" => "Tom"}}
|
|
23
|
+
|
|
24
|
+
Tomlrb.load_file("config.toml", symbolize_keys: true)
|
|
25
|
+
# => {title: "TOML", owner: {name: "Tom"}}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`Tomlrb.parse` also accepts an IO. Invalid documents raise `Tomlrb::ParseError` with the line and column; impossible dates and times raise `ArgumentError`.
|
|
29
|
+
|
|
30
|
+
Documents are read as TOML 1.1, which accepts every valid TOML 1.0 document unchanged. Pass `version: "1.0"` to also reject what 1.1 added (`\e` and `\xHH` escapes, times without seconds, inline tables spanning lines or ending with a comma), for example when a file must stay readable by a 1.0-only parser:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
Tomlrb.parse("t = 07:32") # => {"t" => #<Tomlrb::LocalTime: 07:32:00>}
|
|
34
|
+
Tomlrb.parse("t = 07:32", version: "1.0") # raises Tomlrb::ParseError
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
TOML values map to Ruby as follows:
|
|
38
|
+
|
|
39
|
+
| TOML | Ruby |
|
|
40
|
+
|---|---|
|
|
41
|
+
| string | `String` |
|
|
42
|
+
| integer | `Integer` |
|
|
43
|
+
| float | `Float` (`inf`, `nan` included) |
|
|
44
|
+
| boolean | `true`, `false` |
|
|
45
|
+
| offset date-time | `Time` |
|
|
46
|
+
| local date-time | `Tomlrb::LocalDateTime` |
|
|
47
|
+
| local date | `Tomlrb::LocalDate` |
|
|
48
|
+
| local time | `Tomlrb::LocalTime` |
|
|
49
|
+
| array | `Array` |
|
|
50
|
+
| table, inline table | `Hash` |
|
|
51
|
+
|
|
52
|
+
Fractional seconds are exact (`Rational`), and the local types convert with `#to_time`.
|
|
53
|
+
|
|
54
|
+
Type signatures for the public API are in [`sig/`](sig/tomlrb.rbs).
|
|
55
|
+
|
|
56
|
+
## Benchmark
|
|
57
|
+
|
|
58
|
+
`ruby benchmarks/bench.rb [file...]` compares against [toml-rb](https://github.com/emancu/toml-rb) and [perfect_toml](https://github.com/mame/perfect_toml) when they are installed. Each iteration parses every given file (`benchmarks/example.toml` by default, a 4.6 KB TOML 1.0 document using every value type and table form, which all three parsers accept).
|
|
59
|
+
|
|
60
|
+
`ruby [--yjit] benchmarks/matrix.rb` runs one parser over generated workloads, one per input shape (plain, escaped and 1 MB strings, plain and quote-dense multi-line strings, signed and unsigned numbers, floats, inline tables, date-times, many small documents), and reports time, Ruby allocations, peak RSS growth and a digest of the results per workload. `--lib DIR` loads tomlrb from another checkout, so equal digests show that two versions parse alike; `--parser perfect_toml` measures perfect_toml instead. Inputs keep the known encoding code range a string from `File.read` has; `--unknown-coderange` also times the UTF-8 validation of a string whose code range Ruby has not computed.
|
|
61
|
+
|
|
62
|
+
CPU instructions per parse (fewer is better), measured with `perf stat` on CRuby 3.3 without YJIT. With YJIT tomlrb's lead grows: 2.6-2.9x fewer instructions than perfect_toml and 14-16x fewer than 2.0.4. The crates.io corpus is the first 300 `Cargo.toml` files of a local Cargo registry (`~/.cargo/registry/src/*/*/Cargo.toml`, sorted by path) that all three parsers accept.
|
|
63
|
+
|
|
64
|
+
| Input | tomlrb | perfect_toml 0.9.1 | tomlrb 2.0.4 |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| `benchmarks/example.toml` (4.6 KB) | 3.0 M | 6.6 M | 34.1 M |
|
|
67
|
+
| 300 crates.io `Cargo.toml` files (622 KB) | 243 M | 559 M | 3,100 M |
|
|
68
|
+
| rust-lang/rust `Cargo.lock` at `56df459` (158 KB) | 48 M | 121 M | 687 M |
|
|
69
|
+
|
|
70
|
+
`ruby benchmarks/profile.rb [file]` records a [Vernier](https://github.com/jhawthorn/vernier) profile.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
bin/setup # fetch the toml-test submodule and install gems
|
|
76
|
+
bundle exec rake # tests, Standard (lint), RBS validation
|
|
77
|
+
bundle exec rake toml_test # the official toml-test runner (needs Go)
|
|
78
|
+
bin/console # IRB with tomlrb loaded
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Releases are published from GitHub Actions with [trusted publishing](https://guides.rubygems.org/trusted-publishing/): bump `lib/tomlrb/version.rb`, add the CHANGELOG entry, then run the `release` workflow.
|
|
82
|
+
|
|
83
|
+
## Thanks
|
|
84
|
+
|
|
85
|
+
Thanks to [@jpbougie](https://github.com/jpbougie) for the crash course on the Chomsky hierarchy and general tips.
|
data/lib/tomlrb/local_date.rb
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
require 'forwardable'
|
|
1
|
+
# frozen_string_literal: true
|
|
4
2
|
|
|
5
3
|
module Tomlrb
|
|
4
|
+
# A TOML local date (no time, no offset), e.g. `1979-05-27`.
|
|
6
5
|
class LocalDate
|
|
7
|
-
|
|
6
|
+
DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31].freeze
|
|
7
|
+
|
|
8
|
+
# @return [Boolean] whether year, month and day (Integers) name a real calendar day
|
|
9
|
+
def self.valid_date?(year, month, day)
|
|
10
|
+
return false unless month.between?(1, 12) && day >= 1
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
day <= DAYS_IN_MONTH[month] ||
|
|
13
|
+
(month == 2 && day == 29 && year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
|
|
14
|
+
end
|
|
10
15
|
|
|
16
|
+
attr_reader :year, :month, :day
|
|
17
|
+
|
|
18
|
+
# The Time behind #to_time is built on first use: parsing creates many of these
|
|
19
|
+
# and building a Time costs more than validating the fields.
|
|
11
20
|
def initialize(year, month, day)
|
|
12
|
-
@
|
|
13
|
-
|
|
21
|
+
@year = year.to_i
|
|
22
|
+
@month = month.to_i
|
|
23
|
+
@day = day.to_i
|
|
24
|
+
raise ArgumentError, "Invalid Local Date: #{self}" unless LocalDate.valid_date?(@year, @month, @day)
|
|
14
25
|
end
|
|
15
26
|
|
|
16
27
|
# @param offset see {LocalDateTime#to_time}
|
|
17
28
|
# @return [Time] 00:00:00 of the date
|
|
18
|
-
def to_time(offset =
|
|
19
|
-
return @time if offset ==
|
|
20
|
-
|
|
29
|
+
def to_time(offset = "-00:00")
|
|
30
|
+
return @time ||= Time.utc(@year, @month, @day, 0, 0, 0) if offset == "-00:00"
|
|
31
|
+
|
|
32
|
+
Time.new(@year, @month, @day, 0, 0, 0, offset)
|
|
21
33
|
end
|
|
22
34
|
|
|
23
35
|
def to_s
|
|
24
|
-
|
|
36
|
+
format("%04d-%02d-%02d", @year, @month, @day)
|
|
25
37
|
end
|
|
26
38
|
|
|
27
39
|
def ==(other)
|
|
28
|
-
other.is_a?(self.class) &&
|
|
29
|
-
to_time == other.to_time
|
|
40
|
+
other.is_a?(self.class) && @year == other.year && @month == other.month && @day == other.day
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
def inspect
|
|
@@ -1,44 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "forwardable"
|
|
4
|
+
require_relative "local_date"
|
|
5
|
+
require_relative "local_time"
|
|
4
6
|
|
|
5
7
|
module Tomlrb
|
|
8
|
+
# A TOML local date-time (no offset), e.g. `1979-05-27T07:32:00.999999`.
|
|
6
9
|
class LocalDateTime
|
|
7
10
|
extend Forwardable
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
attr_reader :year, :month, :day, :hour, :min
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
@time = Time.utc(year, month, day, hour, min, sec)
|
|
13
|
-
raise ArgumentError, "Invalid Local Date-Time: #{year}-#{month}-#{day}T#{hour}:#{min}:#{sec}" unless min.to_i == @time.min && hour.to_i == @time.hour && day.to_i == @time.day && month.to_i == @time.month && year.to_i == @time.year
|
|
14
|
+
def_delegators :to_time, :sec, :usec, :nsec
|
|
14
15
|
|
|
16
|
+
# Fields are validated here; the Time behind them is built on first use.
|
|
17
|
+
#
|
|
18
|
+
# @param sec [Integer, Rational, Float] seconds, fraction included
|
|
19
|
+
def initialize(year, month, day, hour, min, sec) # rubocop:disable Metrics/ParameterLists
|
|
20
|
+
@year = year.to_i
|
|
21
|
+
@month = month.to_i
|
|
22
|
+
@day = day.to_i
|
|
23
|
+
@hour = hour.to_i
|
|
24
|
+
@min = min.to_i
|
|
15
25
|
@sec = sec
|
|
26
|
+
raise ArgumentError, "Invalid Local Date-Time: #{self}" unless
|
|
27
|
+
LocalDate.valid_date?(@year, @month, @day) && @hour < 24 && @min < 60 && sec < 60
|
|
16
28
|
end
|
|
17
29
|
|
|
18
|
-
# @param offset [String,
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return @time if offset == '-00:00'
|
|
26
|
-
Time.new(year, month, day, hour, min, @sec, offset)
|
|
30
|
+
# @param offset [String, Integer, nil] UTC offset as accepted by Time.new
|
|
31
|
+
# ("+09:00", "UTC", "Z", seconds east of UTC, or nil for the local zone)
|
|
32
|
+
# @return [Time] this date-time at the given offset (UTC by default)
|
|
33
|
+
def to_time(offset = "-00:00")
|
|
34
|
+
return @time ||= Time.utc(@year, @month, @day, @hour, @min, @sec) if offset == "-00:00"
|
|
35
|
+
|
|
36
|
+
Time.new(@year, @month, @day, @hour, @min, @sec, offset)
|
|
27
37
|
end
|
|
28
38
|
|
|
29
39
|
def to_s
|
|
30
|
-
|
|
31
|
-
frac_str = frac.zero? ? '' : frac.to_s[1..-1]
|
|
32
|
-
@time.strftime('%FT%T') << frac_str
|
|
40
|
+
format("%04d-%02d-%02dT", @year, @month, @day) << LocalTime.format(@hour, @min, @sec)
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
def ==(other)
|
|
36
|
-
other.is_a?(self.class) &&
|
|
37
|
-
|
|
44
|
+
other.is_a?(self.class) && @year == other.year && @month == other.month && @day == other.day &&
|
|
45
|
+
@hour == other.hour && @min == other.min && @sec == other.seconds
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
def inspect
|
|
41
49
|
"#<#{self.class}: #{self}>"
|
|
42
50
|
end
|
|
51
|
+
|
|
52
|
+
protected
|
|
53
|
+
|
|
54
|
+
# Seconds with their fraction; #sec is the whole part only, like Time#sec.
|
|
55
|
+
def seconds = @sec
|
|
43
56
|
end
|
|
44
57
|
end
|
data/lib/tomlrb/local_time.rb
CHANGED
|
@@ -1,41 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "forwardable"
|
|
4
4
|
|
|
5
5
|
module Tomlrb
|
|
6
|
+
# A TOML local time (no date, no offset), e.g. `07:32:00.999999`.
|
|
6
7
|
class LocalTime
|
|
7
8
|
extend Forwardable
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
attr_reader :hour, :min
|
|
10
11
|
|
|
12
|
+
def_delegators :time, :sec, :usec, :nsec
|
|
13
|
+
|
|
14
|
+
# @return [String] `hh:mm:ss`, plus `.` and the fraction digits if `sec` has any
|
|
15
|
+
def self.format(hour, min, sec)
|
|
16
|
+
whole = sec.floor
|
|
17
|
+
fraction = sec - whole
|
|
18
|
+
text = Kernel.format("%02d:%02d:%02d", hour, min, whole)
|
|
19
|
+
fraction.zero? ? text : text << Kernel.format("%.9f", fraction)[1..].sub(/0+\z/, "")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Fields are validated here; the Time behind them is built on first use.
|
|
23
|
+
#
|
|
24
|
+
# @param sec [Integer, Rational, Float] seconds, fraction included
|
|
11
25
|
def initialize(hour, min, sec)
|
|
12
|
-
@
|
|
13
|
-
|
|
26
|
+
@hour = hour.to_i
|
|
27
|
+
@min = min.to_i
|
|
14
28
|
@sec = sec
|
|
29
|
+
raise ArgumentError, "Invalid Local Time: #{self}" unless @hour < 24 && @min < 60 && sec < 60
|
|
15
30
|
end
|
|
16
31
|
|
|
17
|
-
# @param year [Integer]
|
|
18
|
-
# @param month [Integer]
|
|
19
|
-
# @param day [Integer]
|
|
20
32
|
# @param offset see {LocalDateTime#to_time}
|
|
21
|
-
# @return [Time]
|
|
22
|
-
def to_time(year, month, day, offset =
|
|
23
|
-
Time.new(year, month, day, hour, min, @sec, offset)
|
|
33
|
+
# @return [Time] this time on the given day
|
|
34
|
+
def to_time(year, month, day, offset = "-00:00")
|
|
35
|
+
Time.new(year, month, day, @hour, @min, @sec, offset)
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
def to_s
|
|
27
|
-
|
|
28
|
-
frac_str = frac.zero? ? '' : frac.to_s[1..-1]
|
|
29
|
-
@time.strftime('%T') << frac_str
|
|
39
|
+
LocalTime.format(@hour, @min, @sec)
|
|
30
40
|
end
|
|
31
41
|
|
|
32
42
|
def ==(other)
|
|
33
|
-
other.is_a?(self.class) &&
|
|
34
|
-
@time == other.to_time(0, 1, 1)
|
|
43
|
+
other.is_a?(self.class) && @hour == other.hour && @min == other.min && @sec == other.seconds
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
def inspect
|
|
38
47
|
"#<#{self.class}: #{self}>"
|
|
39
48
|
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
# Seconds with their fraction; #sec is the whole part only, like Time#sec.
|
|
53
|
+
def seconds = @sec
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def time
|
|
58
|
+
@time ||= Time.utc(0, 1, 1, @hour, @min, @sec)
|
|
59
|
+
end
|
|
40
60
|
end
|
|
41
61
|
end
|