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 = confidential, bit 0 = deprecated
["", "-", "*", "*-", "!", "!-", "!*", "!*-"].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.value)}" }, Types::OdinInteger => ->(v) { "###{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_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), * (confidential), - (deprecated).
-
.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 ─────────────────────────────────────────────────────────────────────
152 153 154 155 156 157 158 159 |
# File 'lib/odin/utils/format_utils.rb', line 152 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
140 141 142 143 144 145 146 |
# File 'lib/odin/utils/format_utils.rb', line 140 def self.format_canonical_currency(value) dp = [value.decimal_places, 2].max formatted = format("%.#{dp}f", value.value.to_f) result = +"#$#{formatted}" result << ":#{value.currency_code.upcase}" if value.currency_code result end |
.format_canonical_number(value) ⇒ Object
Format number in canonical form: strip trailing zeros. Uses String() representation then removes unnecessary zeros.
108 109 110 111 112 113 114 115 116 |
# File 'lib/odin/utils/format_utils.rb', line 108 def self.format_canonical_number(value) s = value.to_s # Ruby's Float#to_s already produces clean output like "3.14", "42.0" # But we need to handle the case where it produces "3.14" correctly 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
175 176 177 178 179 180 181 182 183 |
# File 'lib/odin/utils/format_utils.rb', line 175 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), * (confidential), - (deprecated)
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.confidential idx |= 1 if modifiers.deprecated 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
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/odin/utils/format_utils.rb', line 123 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 ─────────────────────────────────────────────────────────────────────
165 166 167 168 169 170 171 172 173 |
# File 'lib/odin/utils/format_utils.rb', line 165 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 |