Module: FlowChat::ChoiceTitles
- Defined in:
- lib/flow_chat/choice_titles.rb
Overview
Decides, once per choice set rather than once per choice, whether every title in that set needs a 1-based position prefix ("1. ", "2. ", and so on), then returns the on-screen title FlowChat will render for each choice.
The trigger is ambiguity, not truncation specifically: a screen is ambiguous when, having computed each choice's title at the rung's cap, either any title had to be truncated, or two choices land on the same title. Both mean the titles as displayed cannot identify a choice on their own:
- Truncation can make two different labels ("Transfer to savings account" / "Transfer to salary account") land on the same displayed text ("Transfer to sa...").
- Two choices can share a label outright with no truncation involved at all (two accounts both nicknamed "Savings", a menu with two literal "Accept" options) - the keys behind them differ, but the titles a user would type back are identical.
Numbering is decided for the whole set, never per choice: prefixing only the affected title would produce "Yes" / "2. Transfer to savi...", a stray number with no "1." next to it to make sense of.
The renderer and every choice mapper share this one decision, for the same reason FlowChat::TextTruncator is shared: two independent reimplementations could disagree about which rung is ambiguous, and a disagreement here means a title on screen that nothing resolves.
Constant Summary collapse
- IDENTITY =
->(string) { string }
Class Method Summary collapse
-
.ambiguity_reason(choices, cap, fold: IDENTITY, measure: :characters) ⇒ String?
A description of why the set is ambiguous, for logging, or nil when it is not.
-
.ambiguous?(choices, cap, fold: IDENTITY, measure: :characters) ⇒ Boolean
Whether this choice set is ambiguous at this cap.
-
.build(choices, cap, fold: IDENTITY, measure: :characters) ⇒ Array<[String, String, String, Boolean]>
One [key, original_label, displayed_title, label_was_truncated] tuple per choice, in the same order as
choices.
Class Method Details
.ambiguity_reason(choices, cap, fold: IDENTITY, measure: :characters) ⇒ String?
Returns a description of why the set is ambiguous, for logging, or nil when it is not.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/flow_chat/choice_titles.rb', line 80 def self.ambiguity_reason(choices, cap, fold: IDENTITY, measure: :characters) labels = choices.map { |_, label| label.to_s } titles = labels.map { |label| FlowChat::TextTruncator.truncate(label, cap, measure: measure) } truncated_labels = labels.zip(titles).select { |label, title| title != label }.map(&:first) duplicate_titles = titles.map { |title| fold.call(title) }.tally.select { |_, count| count > 1 }.keys return nil if truncated_labels.empty? && duplicate_titles.empty? parts = [] parts << "truncated: #{truncated_labels.inspect}" unless truncated_labels.empty? parts << "duplicate titles: #{duplicate_titles.inspect}" unless duplicate_titles.empty? parts.join(", ") end |
.ambiguous?(choices, cap, fold: IDENTITY, measure: :characters) ⇒ Boolean
Returns whether this choice set is ambiguous at this cap.
74 75 76 |
# File 'lib/flow_chat/choice_titles.rb', line 74 def self.ambiguous?(choices, cap, fold: IDENTITY, measure: :characters) !ambiguity_reason(choices, cap, fold: fold, measure: measure).nil? end |
.build(choices, cap, fold: IDENTITY, measure: :characters) ⇒ Array<[String, String, String, Boolean]>
Returns one
[key, original_label, displayed_title, label_was_truncated] tuple per
choice, in the same order as choices.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/flow_chat/choice_titles.rb', line 47 def self.build(choices, cap, fold: IDENTITY, measure: :characters) reason = ambiguity_reason(choices, cap, fold: fold, measure: measure) prefixed = !reason.nil? if prefixed FlowChat.logger.debug { "#{name}: numbering choices, titles are ambiguous (#{reason})" } end choices.map.with_index(1) do |(key, label), position| label = label.to_s width = (measure == :bytes) ? label.bytesize : label.length if prefixed prefix = "#{position}. " prefix_width = (measure == :bytes) ? prefix.bytesize : prefix.length title = FlowChat::TextTruncator.number(label, position, cap, measure: measure) truncated = width > (cap - prefix_width) else title = FlowChat::TextTruncator.truncate(label, cap, measure: measure) truncated = width > cap end [key.to_s, label, title, truncated] end end |