Module: FlowChat::TextTruncator
- Defined in:
- lib/flow_chat/text_truncator.rb
Overview
Shortens text to fit a display cap.
Every renderer that fits a label into a platform's title limit needs this, and so does every choice mapper that has to know exactly what the user is looking at: a mapper that reimplemented the rule separately could drift from what the renderer actually renders, silently reopening the bug this class exists to prevent.
Constant Summary collapse
- ELLIPSIS =
"..."
Class Method Summary collapse
-
.number(text, position, cap, measure: :characters) ⇒ Object
Prefixes text with its 1-based position on the rung ("1. ", "10. ", "100. ") and truncates the label to make room, so the combined string never exceeds cap.
-
.truncate(text, length, measure: :characters) ⇒ Object
length is clamped to zero or more before anything else: String#[] with a negative second argument returns nil rather than raising, so a negative length (reachable from #number below, whose prefix can eat more than the whole cap at a small enough cap) would otherwise turn
nil + "..."into a NoMethodError deep inside a render.
Class Method Details
.number(text, position, cap, measure: :characters) ⇒ Object
Prefixes text with its 1-based position on the rung ("1. ", "10. ", "100. ") and truncates the label to make room, so the combined string never exceeds cap. The prefix's length depends on position, so it has to be computed per choice rather than once for the whole rung: choice 10 loses one more character to its label than choice 1 does.
Every renderer and choice mapper that numbers a choice's on-screen title shares this, for the same reason .truncate is shared: a mapper that reimplemented the prefix-and-truncate rule separately could drift from what the renderer actually renders, silently reopening the bug this class exists to prevent.
49 50 51 52 |
# File 'lib/flow_chat/text_truncator.rb', line 49 def self.number(text, position, cap, measure: :characters) prefix = "#{position}. " prefix + truncate(text.to_s, cap - size_of(prefix, measure), measure: measure) end |
.truncate(text, length, measure: :characters) ⇒ Object
length is clamped to zero or more before anything else: String#[] with
a negative second argument returns nil rather than raising, so a
negative length (reachable from #number below, whose prefix can eat
more than the whole cap at a small enough cap) would otherwise turn
nil + "..." into a NoMethodError deep inside a render.
Below 3 there is no room left for the ellipsis itself - it alone is 3 characters - so a cap that small hard-truncates with no ellipsis rather than returning something longer than the cap it was supposed to respect. measure picks the unit the cap is expressed in. :bytes exists because Meta and Telegram both size these fields in bytes rather than characters - FlowChat::Instagram::Client#measure already draws the same distinction for message bodies, and for the same reason: a character count lets multibyte text through to be rejected by the platform.
27 28 29 30 31 32 33 34 35 36 |
# File 'lib/flow_chat/text_truncator.rb', line 27 def self.truncate(text, length, measure: :characters) text = text.to_s length = 0 if length.negative? return text if size_of(text, measure) <= length ellipsis = size_of(ELLIPSIS, measure) return cut(text, length, measure) if length < ellipsis cut(text, length - ellipsis, measure) + ELLIPSIS end |