Module: WhittakerTech::Midas::Coin::Presenter
- Included in:
- WhittakerTech::Midas::Coin
- Defined in:
- app/models/whittaker_tech/midas/coin/presenter.rb
Overview
Presenter provides a strftime-like token grammar for rendering Coin values.
Design principles
- Declarative — tokens map directly to named handler methods.
- Pure — no mutation, rounding, or currency conversion.
- Direction-safe — all text is wrapped with Unicode bidirectional
isolation markers via
Coin::Bidito prevent display corruption in mixed LTR/RTL contexts.
Token reference
Patterns are strings containing %x tokens (similar to strftime).
Tokens:
%t: Formatted total (symbol + amount), e.g.$29.99%m: Minor units (raw integer), e.g.2999%M: Major units (decimal string), e.g.29.99%c: Currency code, e.g.USD%s: Currency symbol, e.g.$%n: Number only (no symbol), e.g.29.99%u: Custom units label (see opts), e.g.per kg%p: Custom per-exact label (see opts), e.g.0.2997%~: Approximate marker (≈or empty)%%: Literal percent sign
Usage
coin = Coin.value(2999, 'USD')
coin.present('%s%M %c') # => "$29.99 USD"
coin.present('%~%t', approx: true) # => "≈$29.99"
Options
Pass keyword options to present / format to populate optional tokens:
approx:[Boolean] — if true,~expands to≈; otherwise empty string.units:[String] — value for%utoken.per_exact:[String] — value for%ptoken.currency_dir:[Symbol] — override the currency display direction (:ltror:rtl); defaults to the value fromWhittakerTech::Midas.currency_direction_for.
Constant Summary collapse
- TOKEN_MAP =
Registry of recognised tokens and their handler method names.
{ '%' => :token_percent, 't' => :token_total, 'm' => :token_minor, 'M' => :token_major, 'c' => :token_currency_code, 's' => :token_currency_symbol, 'n' => :token_number_only, 'u' => :token_units, 'p' => :token_per_exact, '~' => :token_approx }.freeze
Class Method Summary collapse
-
.build_context(coin, **opts) ⇒ Context
Builds a
Contextstruct from a Coin and caller-supplied options. -
.dispatch(token, ctx) ⇒ String
Dispatches a single token character to its handler.
-
.format(coin, pattern) ⇒ String
Formats a Coin using a pattern string.
-
.scan(pattern, ctx) ⇒ String
Scans a pattern string, expanding
%xtokens into rendered values. - .token_approx(approx:) ⇒ Object private
- .token_currency_code(coin:) ⇒ Object private
- .token_currency_symbol(coin:, currency_dir:) ⇒ Object private
- .token_major(coin:) ⇒ Object private
- .token_minor(coin:) ⇒ Object private
- .token_number_only(coin:) ⇒ Object private
- .token_per_exact(per_exact:) ⇒ Object private
- .token_percent ⇒ Object private
- .token_total(coin:, currency_dir:) ⇒ Object private
- .token_units(units:) ⇒ Object private
Instance Method Summary collapse
-
#present(pattern) ⇒ String
Renders this Coin using a format pattern.
Class Method Details
.build_context(coin, **opts) ⇒ Context
Builds a Context struct from a Coin and caller-supplied options.
119 120 121 122 123 124 125 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 119 def build_context(coin, **opts) Context.new(coin:, currency_dir: opts[:currency_dir] || coin.bidi_currency_dir(coin.currency_code), approx: opts[:approx] || false, units: opts[:units] || nil, per_exact: opts[:per_exact] || nil) end |
.dispatch(token, ctx) ⇒ String
Dispatches a single token character to its handler.
159 160 161 162 163 164 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 159 def dispatch(token, ctx) handler = TOKEN_MAP[token] raise ArgumentError, "Unknown presenter token: %#{token}" unless handler send(handler, **ctx.to_h) end |
.format(coin, pattern) ⇒ String
Formats a Coin using a pattern string.
This is the class-level entry point; instance-level access is via
Coin#present.
103 104 105 106 107 108 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 103 def format(coin, pattern, **) raise ArgumentError, 'pattern required' if pattern.nil? ctx = build_context(coin, **) scan(pattern, ctx) end |
.scan(pattern, ctx) ⇒ String
Scans a pattern string, expanding %x tokens into rendered values.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 133 def scan(pattern, ctx) is_token = false out = +'' # output buffer pattern.to_s.each_char do |char| if is_token out << dispatch(char, ctx) is_token = false elsif char == '%' is_token = true else out << char end end raise ArgumentError, "Unterminated token in pattern: #{pattern}" if is_token out end |
.token_approx(approx:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
225 226 227 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 225 def token_approx(approx:, **) approx ? '≈' : '' end |
.token_currency_code(coin:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
191 192 193 194 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 191 def token_currency_code(coin:, **) # Currency codes are neutral; isolate as LTR for stability coin.bidi_isolate(coin.currency_code, dir: :ltr) end |
.token_currency_symbol(coin:, currency_dir:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
197 198 199 200 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 197 def token_currency_symbol(coin:, currency_dir:, **) symbol = Money::Currency.new(coin.currency_code).symbol coin.bidi_isolate(symbol, dir: currency_dir) end |
.token_major(coin:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
186 187 188 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 186 def token_major(coin:, **) coin.bidi_isolate_number(coin.major.to_s('F')) end |
.token_minor(coin:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
181 182 183 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 181 def token_minor(coin:, **) coin.bidi_isolate_number(coin.currency_minor) end |
.token_number_only(coin:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
203 204 205 206 207 208 209 210 211 212 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 203 def token_number_only(coin:, **) # Best-effort extraction of the numeric portion formatted = coin.amount.format symbol = Money::Currency.new(coin.currency_code).symbol.to_s numberish = symbol.empty? ? formatted : formatted.gsub(symbol, '').strip coin.bidi_isolate_number(numberish) end |
.token_per_exact(per_exact:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
220 221 222 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 220 def token_per_exact(per_exact:, **) per_exact.nil? ? '' : per_exact.to_s end |
.token_percent ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
171 172 173 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 171 def token_percent(**) '%' end |
.token_total(coin:, currency_dir:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
176 177 178 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 176 def token_total(coin:, currency_dir:, **) coin.bidi_isolate(coin.amount.format, dir: currency_dir) end |
.token_units(units:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
215 216 217 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 215 def token_units(units:, **) units.nil? ? '' : units.to_s end |
Instance Method Details
#present(pattern) ⇒ String
Renders this Coin using a format pattern.
71 72 73 |
# File 'app/models/whittaker_tech/midas/coin/presenter.rb', line 71 def present(pattern, **) WhittakerTech::Midas::Coin::Presenter.format(self, pattern, **) end |