Class: SignalWire::POM::Section

Inherits:
Object
  • Object
show all
Defined in:
lib/signalwire/pom/section.rb

Overview

Represents a section in the Prompt Object Model.

Each section contains a title, optional body text, optional bullet points, and can have any number of nested subsections.

Mirrors Python’s “signalwire.pom.pom.Section“ exactly. See “signalwire-python/signalwire/signalwire/pom/pom.py“ for the source-of-truth specification; rendering output (markdown / XML / JSON / YAML) must match Python byte-for-byte so cross-language POM documents are interoperable.

Attributes:

  • title — the name of the section.

  • body — a paragraph of text associated with the section.

  • bullets — bullet-pointed items (Array<String>).

  • subsections — nested Section objects.

  • numbered — whether this section should be numbered.

  • numbered_bullets — whether bullets should be numbered (rendered to/from the JSON/YAML key numberedBullets for Python parity).

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title = nil, body: '', bullets: nil, numbered: nil, numbered_bullets: false) ⇒ Section

Construct a Section.

All arguments after title are keyword arguments mirroring the Python “Section.__init__“ signature. “numbered_bullets“ is snake_case in Ruby; the camelCase “numberedBullets“ form used by Python’s JSON/YAML serialization is preserved on the wire.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/signalwire/pom/section.rb', line 33

def initialize(title = nil, body: '', bullets: nil, numbered: nil, numbered_bullets: false)
  @title = title

  unless body.is_a?(String)
    raise TypeError,
          "body must be a string, not #{body.class.name}. " \
          'If you meant to pass a list of bullet points, use bullets parameter instead.'
  end
  @body = body

  if !bullets.nil? && !bullets.is_a?(Array)
    raise TypeError, "bullets must be an Array or nil, not #{bullets.class.name}"
  end

  @bullets = bullets || []
  @subsections = []
  @numbered = numbered
  @numbered_bullets = numbered_bullets
end

Instance Attribute Details

#bodyObject

Returns the value of attribute body.



25
26
27
# File 'lib/signalwire/pom/section.rb', line 25

def body
  @body
end

#bulletsObject

Returns the value of attribute bullets.



25
26
27
# File 'lib/signalwire/pom/section.rb', line 25

def bullets
  @bullets
end

#numberedObject

Returns the value of attribute numbered.



25
26
27
# File 'lib/signalwire/pom/section.rb', line 25

def numbered
  @numbered
end

#numbered_bulletsObject

Returns the value of attribute numbered_bullets.



25
26
27
# File 'lib/signalwire/pom/section.rb', line 25

def numbered_bullets
  @numbered_bullets
end

#subsectionsObject

Returns the value of attribute subsections.



25
26
27
# File 'lib/signalwire/pom/section.rb', line 25

def subsections
  @subsections
end

#titleObject

Returns the value of attribute title.



25
26
27
# File 'lib/signalwire/pom/section.rb', line 25

def title
  @title
end

Instance Method Details

#add_body(body) ⇒ Object

Add or replace the body text for this section. Mirrors Python’s “Section.add_body“ (which is documented to “Add or replace”).



55
56
57
58
59
60
61
# File 'lib/signalwire/pom/section.rb', line 55

def add_body(body)
  unless body.is_a?(String)
    raise TypeError, "body must be a string, not #{body.class.name}"
  end

  @body = body
end

#add_bullets(bullets) ⇒ Object

Append bullet points to this section. Does not replace existing bullets — mirrors Python’s “self.bullets.extend(bullets)“.



65
66
67
68
69
70
71
# File 'lib/signalwire/pom/section.rb', line 65

def add_bullets(bullets)
  unless bullets.is_a?(Array)
    raise TypeError, "bullets must be an Array, not #{bullets.class.name}"
  end

  @bullets.concat(bullets)
end

#add_subsection(title, body: '', bullets: nil, numbered: false, numbered_bullets: false) ⇒ Object

Add a subsection to this section, returning the new Section.

Raises ArgumentError when title is nil (Python raises “ValueError(“Subsections must have a title”)“; Ruby idiom is ArgumentError for invalid arguments).

Raises:

  • (ArgumentError)


78
79
80
81
82
83
84
85
# File 'lib/signalwire/pom/section.rb', line 78

def add_subsection(title, body: '', bullets: nil, numbered: false, numbered_bullets: false)
  raise ArgumentError, 'Subsections must have a title' if title.nil?

  sub = Section.new(title, body: body, bullets: bullets || [],
                           numbered: numbered, numbered_bullets: numbered_bullets)
  @subsections << sub
  sub
end

#render_markdown(level: 2, section_number: nil) ⇒ Object

Render this section and all its subsections as Markdown. The output is byte-for-byte identical to Python’s “Section.render_markdown“.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/signalwire/pom/section.rb', line 104

def render_markdown(level: 2, section_number: nil)
  md = []
  section_number = [] if section_number.nil?

  unless @title.nil?
    prefix = ''
    prefix = "#{section_number.join('.')}. " unless section_number.empty?
    md << "#{'#' * level} #{prefix}#{@title}\n"
  end

  md << "#{@body}\n" if @body && !@body.empty?

  @bullets.each_with_index do |bullet, idx|
    if @numbered_bullets
      md << "#{idx + 1}. #{bullet}"
    else
      md << "- #{bullet}"
    end
  end

  md << '' unless @bullets.empty?

  any_subsection_numbered = @subsections.any? { |sub| sub.numbered }

  @subsections.each_with_index do |subsection, idx|
    if !@title.nil? || !section_number.empty?
      new_section_number =
        if any_subsection_numbered && subsection.numbered != false
          section_number + [idx + 1]
        else
          section_number
        end
      next_level = level + 1
    else
      new_section_number = section_number
      next_level = level
    end

    md << subsection.render_markdown(level: next_level, section_number: new_section_number)
  end

  md.join("\n")
end

#render_xml(indent: 0, section_number: nil) ⇒ Object

Render this section and all its subsections as XML. Output is byte-for-byte identical to Python’s “Section.render_xml“.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/signalwire/pom/section.rb', line 150

def render_xml(indent: 0, section_number: nil)
  indent_str = '  ' * indent
  xml = []
  section_number = [] if section_number.nil?

  xml << "#{indent_str}<section>"

  unless @title.nil?
    prefix = ''
    prefix = "#{section_number.join('.')}. " unless section_number.empty?
    xml << "#{indent_str}  <title>#{prefix}#{@title}</title>"
  end

  xml << "#{indent_str}  <body>#{@body}</body>" if @body && !@body.empty?

  if @bullets && !@bullets.empty?
    xml << "#{indent_str}  <bullets>"
    @bullets.each_with_index do |bullet, idx|
      if @numbered_bullets
        xml << "#{indent_str}    <bullet id=\"#{idx + 1}\">#{bullet}</bullet>"
      else
        xml << "#{indent_str}    <bullet>#{bullet}</bullet>"
      end
    end
    xml << "#{indent_str}  </bullets>"
  end

  if @subsections && !@subsections.empty?
    xml << "#{indent_str}  <subsections>"
    any_subsection_numbered = @subsections.any? { |sub| sub.numbered }

    @subsections.each_with_index do |subsection, idx|
      if !@title.nil? || !section_number.empty?
        new_section_number =
          if any_subsection_numbered && subsection.numbered != false
            section_number + [idx + 1]
          else
            section_number
          end
      else
        new_section_number = section_number
      end
      xml << subsection.render_xml(indent: indent + 2, section_number: new_section_number)
    end
    xml << "#{indent_str}  </subsections>"
  end

  xml << "#{indent_str}</section>"
  xml.join("\n")
end

#to_hObject

Convert the section to a Hash representation suitable for JSON or YAML serialization. Keys are emitted in the same order as Python so cross-port string comparisons line up.



90
91
92
93
94
95
96
97
98
99
# File 'lib/signalwire/pom/section.rb', line 90

def to_h
  data = {}
  data['title'] = @title unless @title.nil?
  data['body'] = @body if @body && !@body.empty?
  data['bullets'] = @bullets if @bullets && !@bullets.empty?
  data['subsections'] = @subsections.map(&:to_h) if @subsections && !@subsections.empty?
  data['numbered'] = @numbered if @numbered
  data['numberedBullets'] = @numbered_bullets if @numbered_bullets
  data
end