Module: Odin::Utils::FormatUtils
- Defined in:
- lib/odin/utils/format_utils.rb
Constant Summary collapse
- ESCAPE_MAP =
Escape special characters in an ODIN string. Handles: \, ", n, r, t and control chars as uXXXX.
{ "\\" => "\\\\", "\"" => "\\\"", "\n" => "\\n", "\r" => "\\r", "\t" => "\\t" }.freeze
- RE_ESCAPE =
/[\\"\n\r\t\x00-\x1f]/.freeze
- MODIFIER_PREFIXES =
Pre-computed modifier prefix lookup table (indexed by bit flags) Bit 2 = required, bit 1 = deprecated, bit 0 = confidential
["", "*", "-", "-*", "!", "!*", "!-", "!-*"].freeze
- STRINGIFY_FORMATTERS =
─────────────────────────────────────────────────────────────────────Value Formatting (Stringify — preserves raw values) ─────────────────────────────────────────────────────────────────────
{ Types::OdinNull => ->(v) { "~" }, Types::OdinBoolean => ->(v) { v.value ? "?true" : "?false" }, Types::OdinString => ->(v) { format_quoted_string(v.value) }, Types::OdinNumber => ->(v) { v.raw ? "##{v.raw}" : "##{v.value}" }, Types::OdinInteger => ->(v) { v.raw ? "###{v.raw}" : "###{v.value}" }, Types::OdinCurrency => ->(v) { format_stringify_currency(v) }, Types::OdinPercent => ->(v) { v.raw ? "#%#{v.raw}" : "#%#{v.value}" }, Types::OdinDate => ->(v) { v.raw }, Types::OdinTimestamp => ->(v) { v.raw }, Types::OdinTime => ->(v) { v.value }, Types::OdinDuration => ->(v) { v.value }, Types::OdinReference => ->(v) { "@#{v.path}" }, Types::OdinBinary => ->(v) { format_binary(v) }, Types::OdinVerbExpression => ->(v) { format_verb(v) }, Types::OdinArray => ->(_v) { "[]" }, Types::OdinObject => ->(_v) { "{}" }, }.freeze
- CANONICAL_FORMATTERS =
─────────────────────────────────────────────────────────────────────Value Formatting (Canonical — deterministic, no raw) ─────────────────────────────────────────────────────────────────────
{ Types::OdinNull => ->(v) { "~" }, Types::OdinBoolean => ->(v) { v.value ? "true" : "false" }, Types::OdinString => ->(v) { format_quoted_string(v.value) }, Types::OdinNumber => ->(v) { "##{format_canonical_number(v.raw || v.value)}" }, Types::OdinInteger => ->(v) { "###{v.raw || v.value}" }, Types::OdinCurrency => ->(v) { format_canonical_currency(v) }, Types::OdinPercent => ->(v) { v.raw ? "#%#{v.raw}" : "#%#{v.value}" }, Types::OdinDate => ->(v) { v.raw }, Types::OdinTimestamp => ->(v) { v.raw }, Types::OdinTime => ->(v) { v.value }, Types::OdinDuration => ->(v) { v.value }, Types::OdinReference => ->(v) { "@#{v.path}" }, Types::OdinBinary => ->(v) { format_binary(v) }, Types::OdinVerbExpression => ->(v) { format_canonical_verb(v) }, Types::OdinArray => ->(_v) { "[]" }, Types::OdinObject => ->(_v) { "{}" }, }.freeze
Class Method Summary collapse
- .escape_string(value) ⇒ Object
-
.format_binary(value) ⇒ Object
───────────────────────────────────────────────────────────────────── Binary Formatting ─────────────────────────────────────────────────────────────────────.
-
.format_canonical_currency(value) ⇒ Object
Canonical currency: always min 2 decimal places, code uppercase.
-
.format_canonical_currency_number(value) ⇒ Object
Build the numeric portion of a canonical currency, at least 2 decimal places.
-
.format_canonical_number(value) ⇒ Object
Format number in canonical form: strip trailing zeros.
- .format_canonical_value(value) ⇒ Object
- .format_canonical_verb(value) ⇒ Object
-
.format_modifier_prefix(modifiers) ⇒ Object
Format modifier prefix in canonical order: ! (required), - (deprecated), * (confidential).
-
.format_quoted_string(value) ⇒ Object
Format a string value as quoted ODIN string.
-
.format_stringify_currency(value) ⇒ Object
Stringify currency: use raw if available, else format with stored decimal places.
- .format_value(value) ⇒ Object
-
.format_verb(value) ⇒ Object
───────────────────────────────────────────────────────────────────── Verb Formatting ─────────────────────────────────────────────────────────────────────.
Class Method Details
.escape_string(value) ⇒ Object
17 18 19 20 21 |
# File 'lib/odin/utils/format_utils.rb', line 17 def self.escape_string(value) value.gsub(RE_ESCAPE) do |ch| ESCAPE_MAP[ch] || format("\\u%04X", ch.ord) end end |
.format_binary(value) ⇒ Object
─────────────────────────────────────────────────────────────────────Binary Formatting ─────────────────────────────────────────────────────────────────────
168 169 170 171 172 173 174 175 |
# File 'lib/odin/utils/format_utils.rb', line 168 def self.format_binary(value) # data is stored as base64 string already if value.algorithm "^#{value.algorithm}:#{value.data}" else "^#{value.data}" end end |
.format_canonical_currency(value) ⇒ Object
Canonical currency: always min 2 decimal places, code uppercase. Prefers raw to preserve precision and integer parts beyond Float range.
139 140 141 142 143 |
# File 'lib/odin/utils/format_utils.rb', line 139 def self.format_canonical_currency(value) result = +"#$#{format_canonical_currency_number(value)}" result << ":#{value.currency_code.upcase}" if value.currency_code result end |
.format_canonical_currency_number(value) ⇒ Object
Build the numeric portion of a canonical currency, at least 2 decimal places.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/odin/utils/format_utils.rb', line 146 def self.format_canonical_currency_number(value) if value.raw raw = value.raw # Defensive: drop any code suffix that leaked into raw raw = raw.split(":", 2)[0] if raw.include?(":") negative = raw.start_with?("-") unsigned = negative ? raw[1..] : raw dot = unsigned.index(".") int_part = dot ? unsigned[0...dot] : unsigned frac_part = dot ? unsigned[(dot + 1)..] : "" frac_part = frac_part.ljust(2, "0") if frac_part.length < 2 "#{negative ? '-' : ''}#{int_part}.#{frac_part}" else dp = [value.decimal_places, 2].max format("%.#{dp}f", value.value.to_f) end end |
.format_canonical_number(value) ⇒ Object
Format number in canonical form: strip trailing zeros. Prefers the raw string (when passed) to preserve precision beyond Float range.
108 109 110 111 112 113 114 |
# File 'lib/odin/utils/format_utils.rb', line 108 def self.format_canonical_number(value) s = value.is_a?(String) ? value : value.to_s if s.include?(".") && !s.include?("e") && !s.include?("E") s = s.sub(/\.?0+\z/, "") end s end |
.format_canonical_value(value) ⇒ Object
97 98 99 100 |
# File 'lib/odin/utils/format_utils.rb', line 97 def self.format_canonical_value(value) formatter = CANONICAL_FORMATTERS[value.class] formatter ? formatter.call(value) : value.to_s end |
.format_canonical_verb(value) ⇒ Object
191 192 193 194 195 196 197 198 199 |
# File 'lib/odin/utils/format_utils.rb', line 191 def self.format_canonical_verb(value) prefix = value.is_custom ? "%&" : "%" result = +"#{prefix}#{value.verb}" value.args.each do |arg| result << " " result << format_canonical_value(arg) end result end |
.format_modifier_prefix(modifiers) ⇒ Object
Format modifier prefix in canonical order: ! (required), - (deprecated), * (confidential)
37 38 39 40 41 42 43 44 |
# File 'lib/odin/utils/format_utils.rb', line 37 def self.format_modifier_prefix(modifiers) return "" unless modifiers idx = 0 idx |= 4 if modifiers.required idx |= 2 if modifiers.deprecated idx |= 1 if modifiers.confidential MODIFIER_PREFIXES[idx] end |
.format_quoted_string(value) ⇒ Object
Format a string value as quoted ODIN string.
24 25 26 |
# File 'lib/odin/utils/format_utils.rb', line 24 def self.format_quoted_string(value) "\"#{escape_string(value)}\"" end |
.format_stringify_currency(value) ⇒ Object
Stringify currency: use raw if available, else format with stored decimal places
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/odin/utils/format_utils.rb', line 121 def self.format_stringify_currency(value) if value.raw result = +"#$#{value.raw}" if value.currency_code && !value.raw.include?(":") result << ":#{value.currency_code}" end result else dp = value.decimal_places formatted = format("%.#{dp}f", value.value.to_f) result = +"#$#{formatted}" result << ":#{value.currency_code}" if value.currency_code result end end |
.format_value(value) ⇒ Object
69 70 71 72 |
# File 'lib/odin/utils/format_utils.rb', line 69 def self.format_value(value) formatter = STRINGIFY_FORMATTERS[value.class] formatter ? formatter.call(value) : value.to_s end |
.format_verb(value) ⇒ Object
─────────────────────────────────────────────────────────────────────Verb Formatting ─────────────────────────────────────────────────────────────────────
181 182 183 184 185 186 187 188 189 |
# File 'lib/odin/utils/format_utils.rb', line 181 def self.format_verb(value) prefix = value.is_custom ? "%&" : "%" result = +"#{prefix}#{value.verb}" value.args.each do |arg| result << " " result << format_value(arg) end result end |