Module: Ruby::Rego::Builtins::Times::GoLayout
- Defined in:
- lib/ruby/rego/builtins/times/go_layout.rb,
lib/ruby/rego/builtins/times/go_layout/parser.rb,
lib/ruby/rego/builtins/times/go_layout/scanner.rb,
lib/ruby/rego/builtins/times/go_layout/formatter.rb,
lib/ruby/rego/builtins/times/go_layout/parser/zones.rb,
lib/ruby/rego/builtins/times/go_layout/parser/fields.rb,
lib/ruby/rego/builtins/times/go_layout/parser/compose.rb,
lib/ruby/rego/builtins/times/go_layout/parser/consumers.rb
Overview
The Go reference-time emitter (a port of appendFormat + appendNano from src/time/format.go). Lives apart from the tokenizer so the go_layout file stays under RubyCritic’s complexity budget; reopens the same GoLayout module so bare references to MONTHS/WEEKDAYS and the public surface resolve unchanged.
Defined Under Namespace
Classes: Parser
Constant Summary collapse
- NAMED =
OPA’s
acceptedTimeFormats— named layouts mapped to their Go reference-time string. { "ANSIC" => "Mon Jan _2 15:04:05 2006", "UnixDate" => "Mon Jan _2 15:04:05 MST 2006", "RubyDate" => "Mon Jan 02 15:04:05 -0700 2006", "RFC822" => "02 Jan 06 15:04 MST", "RFC822Z" => "02 Jan 06 15:04 -0700", "RFC850" => "Monday, 02-Jan-06 15:04:05 MST", "RFC1123" => "Mon, 02 Jan 2006 15:04:05 MST", "RFC1123Z" => "Mon, 02 Jan 2006 15:04:05 -0700", "RFC3339" => "2006-01-02T15:04:05Z07:00", "RFC3339Nano" => "2006-01-02T15:04:05.999999999Z07:00" }.freeze
- DEFAULT =
The default layout when none is given (Go’s time.RFC3339Nano).
NAMED.fetch("RFC3339Nano")
- MONTHS =
%w[January February March April May June July August September October November December].freeze
- WEEKDAYS =
%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].freeze
- ZONE_SHAPES =
Per-token digit geometry for the numeric/ISO zone offsets: total source length, the index of the hour/minute/second digit pairs, and the indices that must hold a colon.
{ num_short_tz: { len: 3, hh: 1 }, iso_short_tz: { len: 3, hh: 1, z: true }, num_tz: { len: 5, hh: 1, mm: 3 }, iso_tz: { len: 5, hh: 1, mm: 3, z: true }, num_colon_tz: { len: 6, hh: 1, mm: 4, colons: [3] }, iso_colon_tz: { len: 6, hh: 1, mm: 4, colons: [3], z: true }, num_seconds_tz: { len: 7, hh: 1, mm: 3, ss: 5 }, iso_seconds_tz: { len: 7, hh: 1, mm: 3, ss: 5, z: true }, num_colon_seconds_tz: { len: 9, hh: 1, mm: 4, ss: 7, colons: [3, 6] }, iso_colon_seconds_tz: { len: 9, hh: 1, mm: 4, ss: 7, colons: [3, 6], z: true } }.freeze
- ZERO_TOKENS =
{ "1" => :zero_month, "2" => :zero_day, "3" => :zero_twelve_hour, "4" => :zero_minute, "5" => :zero_second, "6" => :year }.freeze
- NUM_TZ =
Numeric (always-signed) zone tokens, longest first.
[["-070000", :num_seconds_tz], ["-07:00:00", :num_colon_seconds_tz], ["-0700", :num_tz], ["-07:00", :num_colon_tz], ["-07", :num_short_tz]].freeze
- ISO_TZ =
ISO-8601 zone tokens (a “Z” prefix prints Z for UTC), longest first.
[["Z070000", :iso_seconds_tz], ["Z07:00:00", :iso_colon_seconds_tz], ["Z0700", :iso_tz], ["Z07:00", :iso_colon_tz], ["Z07", :iso_short_tz]].freeze
- SCANNERS =
{ "J" => method(:scan_letter_j), "M" => method(:scan_letter_m), "0" => method(:scan_zero), "1" => method(:scan_one), "2" => method(:scan_two), "_" => method(:scan_underscore), "3" => ->(_l, _i) { [:twelve_hour, 1] }, "4" => ->(_l, _i) { [:minute, 1] }, "5" => ->(_l, _i) { [:second, 1] }, "P" => method(:scan_pm_upper), "p" => method(:scan_pm_lower), "-" => method(:scan_dash), "Z" => method(:scan_zed), "." => method(:scan_fraction), "," => method(:scan_fraction) }.freeze
- ISO_ZONE =
ISO-8601 zone tokens print “Z” for a zero (UTC) offset; numeric tokens never do.
%i[iso_tz iso_colon_tz iso_seconds_tz iso_short_tz iso_colon_seconds_tz].freeze
- COLON_ZONE =
%i[iso_colon_tz num_colon_tz iso_colon_seconds_tz num_colon_seconds_tz].freeze
- SHORT_ZONE =
%i[num_short_tz iso_short_tz].freeze
- SECONDS_ZONE =
%i[iso_seconds_tz num_seconds_tz num_colon_seconds_tz iso_colon_seconds_tz].freeze
- COLON_SECONDS_ZONE =
%i[num_colon_seconds_tz iso_colon_seconds_tz].freeze
- ZONE_OFFSET =
(ISO_ZONE + %i[num_tz num_colon_tz num_seconds_tz num_short_tz num_colon_seconds_tz]).freeze
Class Method Summary collapse
-
.format(time, layout) ⇒ String
-
.next_chunk(layout) ⇒ Object
A port of Go’s nextStdChunk: the next [literal prefix, token, suffix].
-
.parse(layout, value) ⇒ Object
Parse
valueagainstlayout, returning epoch nanoseconds, or nil when the value does not match the layout, a field is out of range, input is left over, or the instant falls outside the representable range. -
.resolve(layout) ⇒ Object
The Go layout string for a (possibly named) layout, or the literal layout if not named.
-
.scan_dash(layout, index) ⇒ Object
-
.scan_fraction(layout, index) ⇒ Object
“.000”/”.999”/”,000”/”,999” — a separator then a run of all-0 or all-9 digits.
-
.scan_letter_j(layout, index) ⇒ Object
Each scanner returns [token, source_length] for a match at
index(or a third literal_prefix_length element when a leading char is literal, as in scan_underscore’s_2006), else [nil, 0]. -
.scan_letter_m(layout, index) ⇒ Object
:reek:TooManyStatements.
-
.scan_one(layout, index) ⇒ Object
-
.scan_pm_lower(layout, index) ⇒ Object
-
.scan_pm_upper(layout, index) ⇒ Object
-
.scan_two(layout, index) ⇒ Object
-
.scan_underscore(layout, index) ⇒ Object
:reek:TooManyStatements.
-
.scan_zed(layout, index) ⇒ Object
-
.scan_zero(layout, index) ⇒ Object
Class Method Details
.format(time, layout) ⇒ String
45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/ruby/rego/builtins/times/go_layout.rb', line 45 def self.format(time, layout) out = +"" remaining = resolve(layout) until remaining.empty? prefix, token, remaining = next_chunk(remaining) out << prefix break if token.nil? out << emit(time, token) end out end |
.next_chunk(layout) ⇒ Object
A port of Go’s nextStdChunk: the next [literal prefix, token, suffix]. token is a Symbol
for a fixed field, a [:frac, :zero|:nine, digits, separator] tuple for fractional seconds,
or nil when no more layout tokens remain (prefix is then the trailing literal).
:reek:TooManyStatements
17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 17 def self.next_chunk(layout) index = 0 while index < layout.length token, length, literal = token_at(layout, index) if token prefix = layout[0, index + (literal || 0)].to_s return [prefix, token, layout[(index + length)..].to_s] end index += 1 end [layout, nil, ""] end |
.parse(layout, value) ⇒ Object
Parse value against layout, returning epoch nanoseconds, or nil when the value
does not match the layout, a field is out of range, input is left over, or the
instant falls outside the representable range. A named layout (NAMED) resolves to
its Go string; unlike format, an empty layout is NOT defaulted (it stays the empty
layout, which matches only the empty value), matching OPA’s time.Parse.
26 27 28 |
# File 'lib/ruby/rego/builtins/times/go_layout/parser.rb', line 26 def self.parse(layout, value) Parser.new(NAMED.fetch(layout, layout), value).run end |
.resolve(layout) ⇒ Object
The Go layout string for a (possibly named) layout, or the literal layout if not named.
36 37 38 39 40 |
# File 'lib/ruby/rego/builtins/times/go_layout.rb', line 36 def self.resolve(layout) return DEFAULT if layout.empty? NAMED.fetch(layout, layout) end |
.scan_dash(layout, index) ⇒ Object
125 126 127 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 125 def self.scan_dash(layout, index) scan_table(layout, index, NUM_TZ) end |
.scan_fraction(layout, index) ⇒ Object
“.000”/”.999”/”,000”/”,999” — a separator then a run of all-0 or all-9 digits. :reek:TooManyStatements
143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 143 def self.scan_fraction(layout, index) separator = layout[index] digit = layout[index + 1] return [nil, 0] unless %w[0 9].include?(digit) stop = index + 1 stop += 1 while layout[stop] == digit return [nil, 0] if digit_at?(layout, stop) # the run must end here kind = digit == "0" ? :zero : :nine [[:frac, kind, stop - (index + 1), separator], stop - index] end |
.scan_letter_j(layout, index) ⇒ Object
Each scanner returns [token, source_length] for a match at index (or a third
literal_prefix_length element when a leading char is literal, as in scan_underscore’s
_2006), else [nil, 0]. The fixed-field scanners mirror nextStdChunk’s per-leading-byte cases.
:reek:TooManyStatements
56 57 58 59 60 61 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 56 def self.scan_letter_j(layout, index) return [:long_month, 7] if layout[index, 7] == "January" return [:month, 3] if layout[index, 3] == "Jan" && !starts_lower?(layout[(index + 3)..]) [nil, 0] end |
.scan_letter_m(layout, index) ⇒ Object
:reek:TooManyStatements
64 65 66 67 68 69 70 71 72 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 64 def self.scan_letter_m(layout, index) if layout[index, 3] == "Mon" return [:long_weekday, 6] if layout[index, 6] == "Monday" return [:weekday, 3] unless starts_lower?(layout[(index + 3)..]) end return [:tz, 3] if layout[index, 3] == "MST" [nil, 0] end |
.scan_one(layout, index) ⇒ Object
85 86 87 88 89 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 85 def self.scan_one(layout, index) return [:hour, 2] if layout[index + 1] == "5" [:num_month, 1] end |
.scan_pm_lower(layout, index) ⇒ Object
114 115 116 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 114 def self.scan_pm_lower(layout, index) layout[index + 1] == "m" ? [:pm_lower, 2] : [nil, 0] end |
.scan_pm_upper(layout, index) ⇒ Object
110 111 112 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 110 def self.scan_pm_upper(layout, index) layout[index + 1] == "M" ? [:pm_upper, 2] : [nil, 0] end |
.scan_two(layout, index) ⇒ Object
91 92 93 94 95 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 91 def self.scan_two(layout, index) return [:long_year, 4] if layout[index, 4] == "2006" [:day, 1] end |
.scan_underscore(layout, index) ⇒ Object
:reek:TooManyStatements
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 98 def self.scan_underscore(layout, index) if layout[index + 1] == "2" # "_2006" is a literal underscore followed by the long year (the "_" stays literal). return [:long_year, 5, 1] if layout[index, 5] == "_2006" return [:under_day, 2] end return [:under_year_day, 3] if layout[index, 3] == "__2" [nil, 0] end |
.scan_zed(layout, index) ⇒ Object
129 130 131 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 129 def self.scan_zed(layout, index) scan_table(layout, index, ISO_TZ) end |
.scan_zero(layout, index) ⇒ Object
77 78 79 80 81 82 83 |
# File 'lib/ruby/rego/builtins/times/go_layout/scanner.rb', line 77 def self.scan_zero(layout, index) following = layout[index + 1] return [ZERO_TOKENS.fetch(following), 2] if following && ZERO_TOKENS.key?(following) return [:zero_year_day, 3] if layout[index, 3] == "002" [nil, 0] end |