poml 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +239 -0
  4. data/TUTORIAL.md +987 -0
  5. data/bin/poml +80 -0
  6. data/examples/101_explain_character.poml +30 -0
  7. data/examples/102_render_xml.poml +40 -0
  8. data/examples/103_word_todos.poml +27 -0
  9. data/examples/104_financial_analysis.poml +33 -0
  10. data/examples/105_write_blog_post.poml +48 -0
  11. data/examples/106_research.poml +36 -0
  12. data/examples/107_read_report_pdf.poml +4 -0
  13. data/examples/201_orders_qa.poml +50 -0
  14. data/examples/202_arc_agi.poml +36 -0
  15. data/examples/301_generate_poml.poml +46 -0
  16. data/examples/README.md +50 -0
  17. data/examples/_generate_expects.py +35 -0
  18. data/examples/assets/101_jerry_mouse.jpg +0 -0
  19. data/examples/assets/101_tom_and_jerry.docx +0 -0
  20. data/examples/assets/101_tom_cat.jpg +0 -0
  21. data/examples/assets/101_tom_introduction.txt +9 -0
  22. data/examples/assets/103_prompt_wizard.docx +0 -0
  23. data/examples/assets/104_chart_normalized_price.png +0 -0
  24. data/examples/assets/104_chart_price.png +0 -0
  25. data/examples/assets/104_mag7.xlsx +0 -0
  26. data/examples/assets/107_usenix_paper.pdf +0 -0
  27. data/examples/assets/201_order_instructions.json +7 -0
  28. data/examples/assets/201_orderlines.csv +2 -0
  29. data/examples/assets/201_orders.csv +3 -0
  30. data/examples/assets/202_arc_agi_data.json +1 -0
  31. data/examples/expects/101_explain_character.txt +117 -0
  32. data/examples/expects/102_render_xml.txt +28 -0
  33. data/examples/expects/103_word_todos.txt +121 -0
  34. data/examples/expects/104_financial_analysis.txt +86 -0
  35. data/examples/expects/105_write_blog_post.txt +41 -0
  36. data/examples/expects/106_research.txt +29 -0
  37. data/examples/expects/107_read_report_pdf.txt +151 -0
  38. data/examples/expects/201_orders_qa.txt +44 -0
  39. data/examples/expects/202_arc_agi.txt +64 -0
  40. data/examples/expects/301_generate_poml.txt +153 -0
  41. data/examples/ruby_expects/101_explain_character.txt +17 -0
  42. data/examples/ruby_expects/102_render_xml.txt +28 -0
  43. data/examples/ruby_expects/103_word_todos.txt +14 -0
  44. data/examples/ruby_expects/104_financial_analysis.txt +0 -0
  45. data/examples/ruby_expects/105_write_blog_post.txt +57 -0
  46. data/examples/ruby_expects/106_research.txt +5 -0
  47. data/examples/ruby_expects/107_read_report_pdf.txt +403 -0
  48. data/examples/ruby_expects/201_orders_qa.txt +41 -0
  49. data/examples/ruby_expects/202_arc_agi.txt +17 -0
  50. data/examples/ruby_expects/301_generate_poml.txt +17 -0
  51. data/lib/poml/components/base.rb +132 -0
  52. data/lib/poml/components/content.rb +156 -0
  53. data/lib/poml/components/data.rb +346 -0
  54. data/lib/poml/components/examples.rb +55 -0
  55. data/lib/poml/components/instructions.rb +93 -0
  56. data/lib/poml/components/layout.rb +50 -0
  57. data/lib/poml/components/lists.rb +82 -0
  58. data/lib/poml/components/styling.rb +36 -0
  59. data/lib/poml/components/text.rb +8 -0
  60. data/lib/poml/components/workflow.rb +63 -0
  61. data/lib/poml/components.rb +47 -0
  62. data/lib/poml/components_new.rb +297 -0
  63. data/lib/poml/components_old.rb +1096 -0
  64. data/lib/poml/context.rb +53 -0
  65. data/lib/poml/parser.rb +153 -0
  66. data/lib/poml/renderer.rb +147 -0
  67. data/lib/poml/template_engine.rb +66 -0
  68. data/lib/poml/version.rb +5 -0
  69. data/lib/poml.rb +53 -0
  70. data/media/logo-16-purple.png +0 -0
  71. data/media/logo-64-white.png +0 -0
  72. metadata +149 -0
@@ -0,0 +1,82 @@
1
+ module Poml
2
+ # List component
3
+ class ListComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ if xml_mode?
8
+ # In XML mode, lists don't exist - items are rendered directly
9
+ @element.children.map do |child|
10
+ if child.tag_name == :item
11
+ Components.render_element(child, @context)
12
+ end
13
+ end.compact.join('')
14
+ else
15
+ list_style = get_attribute('listStyle', 'dash')
16
+ items = []
17
+ index = 0
18
+
19
+ @element.children.each do |child|
20
+ if child.tag_name == :item
21
+ index += 1
22
+
23
+ bullet = case list_style
24
+ when 'decimal', 'number', 'numbered'
25
+ "#{index}. "
26
+ when 'star'
27
+ "* "
28
+ when 'plus'
29
+ "+ "
30
+ when 'dash', 'bullet', 'unordered'
31
+ "- "
32
+ else
33
+ "- "
34
+ end
35
+
36
+ # Get text content and nested elements separately
37
+ text_content = child.content.strip
38
+ nested_elements = child.children.reject { |c| c.tag_name == :text }
39
+
40
+ if nested_elements.any?
41
+ # Item has both text and nested elements (like nested lists)
42
+ nested_content = nested_elements.map { |nested_child|
43
+ Components.render_element(nested_child, @context)
44
+ }.join('').strip
45
+
46
+ # Format with text content on first line, nested content indented
47
+ indented_nested = nested_content.split("\n").map { |line|
48
+ line.strip.empty? ? "" : " #{line}"
49
+ }.join("\n").strip
50
+
51
+ if text_content.empty?
52
+ items << "#{bullet}#{indented_nested}"
53
+ else
54
+ items << "#{bullet}#{text_content} \n\n#{indented_nested}"
55
+ end
56
+ else
57
+ # Simple text-only item
58
+ items << "#{bullet}#{text_content}"
59
+ end
60
+ end
61
+ end
62
+
63
+ return "\n\n" if items.empty?
64
+ items.join("\n") + "\n\n"
65
+ end
66
+ end
67
+ end
68
+
69
+ # Item component (for list items)
70
+ class ItemComponent < Component
71
+ def render
72
+ apply_stylesheet
73
+ content = @element.content.empty? ? render_children : @element.content.strip
74
+
75
+ if xml_mode?
76
+ "<item>#{content}</item>\n"
77
+ else
78
+ content
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,36 @@
1
+ module Poml
2
+ # Let component (for variable definitions)
3
+ class LetComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ name = get_attribute('name')
8
+ value = @element.content.empty? ? render_children : @element.content
9
+
10
+ # Add to context variables
11
+ @context.variables[name] = value if name
12
+
13
+ # Let components produce no output
14
+ ''
15
+ end
16
+ end
17
+
18
+ # Stylesheet component
19
+ class StylesheetComponent < Component
20
+ def render
21
+ # Parse and apply stylesheet
22
+ begin
23
+ stylesheet_content = @element.content.strip
24
+ if stylesheet_content.start_with?('{') && stylesheet_content.end_with?('}')
25
+ stylesheet = JSON.parse(stylesheet_content)
26
+ @context.stylesheet.merge!(stylesheet) if stylesheet.is_a?(Hash)
27
+ end
28
+ rescue => e
29
+ # Silently fail JSON parsing errors
30
+ end
31
+
32
+ # Stylesheet components don't produce output
33
+ ''
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ module Poml
2
+ # Text component for plain text content
3
+ class TextComponent < Component
4
+ def render
5
+ @element.content
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,63 @@
1
+ module Poml
2
+ # Stepwise Instructions component
3
+ class StepwiseInstructionsComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ content = @element.content.empty? ? render_children : @element.content
8
+
9
+ if xml_mode?
10
+ render_as_xml('stepwise-instructions', content)
11
+ else
12
+ caption = apply_text_transform(get_attribute('caption', 'Stepwise Instructions'))
13
+ caption_style = get_attribute('captionStyle', 'header')
14
+
15
+ case caption_style
16
+ when 'header'
17
+ "# #{caption}\n\n#{content}\n\n"
18
+ when 'bold'
19
+ "**#{caption}:** #{content}\n\n"
20
+ when 'plain'
21
+ "#{caption}: #{content}\n\n"
22
+ when 'hidden'
23
+ "#{content}\n\n"
24
+ else
25
+ "# #{caption}\n\n#{content}\n\n"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ # Human Message component
32
+ class HumanMessageComponent < Component
33
+ def render
34
+ apply_stylesheet
35
+
36
+ content = @element.content.empty? ? render_children : @element.content
37
+ speaker = get_attribute('speaker', 'human')
38
+
39
+ if xml_mode?
40
+ render_as_xml('human-message', content)
41
+ else
42
+ "#{content}\n\n"
43
+ end
44
+ end
45
+ end
46
+
47
+ # Question-Answer component
48
+ class QAComponent < Component
49
+ def render
50
+ apply_stylesheet
51
+
52
+ content = @element.content.empty? ? render_children : @element.content
53
+ question_caption = get_attribute('questionCaption', 'Question')
54
+ answer_caption = get_attribute('answerCaption', 'Answer')
55
+
56
+ if xml_mode?
57
+ render_as_xml('qa', content)
58
+ else
59
+ "**#{question_caption}:** #{content}\n\n**#{answer_caption}:**"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,47 @@
1
+ # Main components file - requires all component modules
2
+ require_relative 'components/base'
3
+ require_relative 'components/text'
4
+ require_relative 'components/instructions'
5
+ require_relative 'components/content'
6
+ require_relative 'components/data'
7
+ require_relative 'components/examples'
8
+ require_relative 'components/lists'
9
+ require_relative 'components/layout'
10
+ require_relative 'components/workflow'
11
+ require_relative 'components/styling'
12
+
13
+ module Poml
14
+ # Update the component mapping after all components are loaded
15
+ Components::COMPONENT_MAPPING.merge!({
16
+ text: TextComponent,
17
+ role: RoleComponent,
18
+ task: TaskComponent,
19
+ hint: HintComponent,
20
+ document: DocumentComponent,
21
+ Document: DocumentComponent, # Capitalized version
22
+ table: TableComponent,
23
+ Table: TableComponent, # Capitalized version
24
+ img: ImageComponent,
25
+ p: ParagraphComponent,
26
+ example: ExampleComponent,
27
+ input: InputComponent,
28
+ output: OutputComponent,
29
+ 'output-format': OutputFormatComponent,
30
+ 'outputformat': OutputFormatComponent,
31
+ list: ListComponent,
32
+ item: ItemComponent,
33
+ cp: CPComponent,
34
+ 'stepwise-instructions': StepwiseInstructionsComponent,
35
+ 'stepwiseinstructions': StepwiseInstructionsComponent,
36
+ StepwiseInstructions: StepwiseInstructionsComponent,
37
+ 'human-message': HumanMessageComponent,
38
+ 'humanmessage': HumanMessageComponent,
39
+ HumanMessage: HumanMessageComponent,
40
+ qa: QAComponent,
41
+ QA: QAComponent,
42
+ let: LetComponent,
43
+ Let: LetComponent,
44
+ stylesheet: StylesheetComponent,
45
+ Stylesheet: StylesheetComponent
46
+ })
47
+ end
@@ -0,0 +1,297 @@
1
+ # Main components file - requires all component modules
2
+ require_relative 'components/base'
3
+ require_relative 'components/text'
4
+ require_relative 'components/instructions'
5
+ require_relative 'components/content'
6
+ require_relative 'components/data'
7
+
8
+ # TODO: Create remaining component files:
9
+ # require_relative 'components/examples' # ExampleComponent, InputComponent, OutputComponent, OutputFormatComponent
10
+ # require_relative 'components/lists' # ListComponent, ItemComponent
11
+ # require_relative 'components/layout' # CPComponent (CaptionedParagraph)
12
+ # require_relative 'components/workflow' # StepwiseInstructionsComponent, HumanMessageComponent, QAComponent
13
+ # require_relative 'components/styling' # StylesheetComponent, LetComponent
14
+
15
+ # Temporary: Keep remaining components inline until they are split out
16
+ module Poml
17
+ # Example component
18
+ class ExampleComponent < Component
19
+ def render
20
+ apply_stylesheet
21
+
22
+ content = @element.content.empty? ? render_children : @element.content
23
+ "#{content}\n\n"
24
+ end
25
+ end
26
+
27
+ # Input component (for examples)
28
+ class InputComponent < Component
29
+ def render
30
+ apply_stylesheet
31
+
32
+ content = @element.content.empty? ? render_children : @element.content
33
+ "#{content}\n\n"
34
+ end
35
+ end
36
+
37
+ # Output component (for examples)
38
+ class OutputComponent < Component
39
+ def render
40
+ apply_stylesheet
41
+
42
+ content = @element.content.empty? ? render_children : @element.content
43
+ "#{content}\n\n"
44
+ end
45
+ end
46
+
47
+ # Output format component
48
+ class OutputFormatComponent < Component
49
+ def render
50
+ apply_stylesheet
51
+
52
+ caption = get_attribute('caption', 'Output Format')
53
+ caption_style = get_attribute('captionStyle', 'header')
54
+ content = @element.content.empty? ? render_children : @element.content
55
+
56
+ case caption_style
57
+ when 'header'
58
+ "# #{caption}\n\n#{content}\n\n"
59
+ when 'bold'
60
+ "**#{caption}:** #{content}\n\n"
61
+ when 'plain'
62
+ "#{caption}: #{content}\n\n"
63
+ when 'hidden'
64
+ "#{content}\n\n"
65
+ else
66
+ "# #{caption}\n\n#{content}\n\n"
67
+ end
68
+ end
69
+ end
70
+
71
+ # List component
72
+ class ListComponent < Component
73
+ def render
74
+ apply_stylesheet
75
+
76
+ if xml_mode?
77
+ # In XML mode, lists don't exist - items are rendered directly
78
+ @element.children.map do |child|
79
+ if child.tag_name == :item
80
+ Components.render_element(child, @context)
81
+ end
82
+ end.compact.join('')
83
+ else
84
+ list_style = get_attribute('listStyle', 'dash')
85
+ items = []
86
+ index = 0
87
+
88
+ @element.children.each do |child|
89
+ if child.tag_name == :item
90
+ index += 1
91
+
92
+ bullet = case list_style
93
+ when 'decimal', 'number', 'numbered'
94
+ "#{index}. "
95
+ when 'star'
96
+ "* "
97
+ when 'plus'
98
+ "+ "
99
+ when 'dash', 'bullet', 'unordered'
100
+ "- "
101
+ else
102
+ "- "
103
+ end
104
+
105
+ # Get text content and nested elements separately
106
+ text_content = child.content.strip
107
+ nested_elements = child.children.reject { |c| c.tag_name == :text }
108
+
109
+ if nested_elements.any?
110
+ # Item has both text and nested elements (like nested lists)
111
+ nested_content = nested_elements.map { |nested_child|
112
+ Components.render_element(nested_child, @context)
113
+ }.join('').strip
114
+
115
+ # Format with text content on first line, nested content indented
116
+ indented_nested = nested_content.split("\n").map { |line|
117
+ line.strip.empty? ? "" : " #{line}"
118
+ }.join("\n").strip
119
+
120
+ if text_content.empty?
121
+ items << "#{bullet}#{indented_nested}"
122
+ else
123
+ items << "#{bullet}#{text_content} \n\n#{indented_nested}"
124
+ end
125
+ else
126
+ # Simple text-only item
127
+ items << "#{bullet}#{text_content}"
128
+ end
129
+ end
130
+ end
131
+
132
+ return "\n\n" if items.empty?
133
+ items.join("\n") + "\n\n"
134
+ end
135
+ end
136
+ end
137
+
138
+ # Item component (for list items)
139
+ class ItemComponent < Component
140
+ def render
141
+ apply_stylesheet
142
+ content = @element.content.empty? ? render_children : @element.content.strip
143
+
144
+ if xml_mode?
145
+ "<item>#{content}</item>\n"
146
+ else
147
+ content
148
+ end
149
+ end
150
+ end
151
+
152
+ # CP component (custom component with caption)
153
+ class CPComponent < Component
154
+ def render
155
+ apply_stylesheet
156
+
157
+ caption = get_attribute('caption', '')
158
+ caption_serialized = get_attribute('captionSerialized', caption)
159
+
160
+ # Render children with increased header level for nested CPs
161
+ content = if @element.content.empty?
162
+ @context.with_increased_header_level { render_children }
163
+ else
164
+ @element.content
165
+ end
166
+
167
+ if xml_mode?
168
+ # Use captionSerialized for XML tag name, fallback to caption
169
+ tag_name = caption_serialized.empty? ? caption : caption_serialized
170
+ return render_as_xml(tag_name, content) unless tag_name.empty?
171
+ # If no caption, just return content
172
+ return "#{content}\n\n"
173
+ else
174
+ caption_style = get_attribute('captionStyle', 'header')
175
+ # Use captionSerialized for the actual header if provided
176
+ display_caption = caption_serialized.empty? ? caption : caption_serialized
177
+
178
+ # Apply stylesheet text transformation
179
+ display_caption = apply_text_transform(display_caption)
180
+
181
+ return content + "\n\n" if display_caption.empty?
182
+
183
+ case caption_style
184
+ when 'header'
185
+ header_prefix = '#' * @context.header_level
186
+ "#{header_prefix} #{display_caption}\n\n#{content}\n\n"
187
+ when 'bold'
188
+ "**#{display_caption}:** #{content}\n\n"
189
+ when 'plain'
190
+ "#{display_caption}: #{content}\n\n"
191
+ when 'hidden'
192
+ "#{content}\n\n"
193
+ else
194
+ header_prefix = '#' * @context.header_level
195
+ "#{header_prefix} #{display_caption}\n\n#{content}\n\n"
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ # Stepwise Instructions component
202
+ class StepwiseInstructionsComponent < Component
203
+ def render
204
+ apply_stylesheet
205
+
206
+ content = @element.content.empty? ? render_children : @element.content
207
+
208
+ if xml_mode?
209
+ render_as_xml('stepwise-instructions', content)
210
+ else
211
+ caption = apply_text_transform(get_attribute('caption', 'Stepwise Instructions'))
212
+ caption_style = get_attribute('captionStyle', 'header')
213
+
214
+ case caption_style
215
+ when 'header'
216
+ "# #{caption}\n\n#{content}\n\n"
217
+ when 'bold'
218
+ "**#{caption}:** #{content}\n\n"
219
+ when 'plain'
220
+ "#{caption}: #{content}\n\n"
221
+ when 'hidden'
222
+ "#{content}\n\n"
223
+ else
224
+ "# #{caption}\n\n#{content}\n\n"
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ # Human Message component
231
+ class HumanMessageComponent < Component
232
+ def render
233
+ apply_stylesheet
234
+
235
+ content = @element.content.empty? ? render_children : @element.content
236
+ speaker = get_attribute('speaker', 'human')
237
+
238
+ if xml_mode?
239
+ render_as_xml('human-message', content)
240
+ else
241
+ "#{content}\n\n"
242
+ end
243
+ end
244
+ end
245
+
246
+ # Question-Answer component
247
+ class QAComponent < Component
248
+ def render
249
+ apply_stylesheet
250
+
251
+ content = @element.content.empty? ? render_children : @element.content
252
+ question_caption = get_attribute('questionCaption', 'Question')
253
+ answer_caption = get_attribute('answerCaption', 'Answer')
254
+
255
+ if xml_mode?
256
+ render_as_xml('qa', content)
257
+ else
258
+ "**#{question_caption}:** #{content}\n\n**#{answer_caption}:**"
259
+ end
260
+ end
261
+ end
262
+
263
+ # Let component (for variable definitions)
264
+ class LetComponent < Component
265
+ def render
266
+ apply_stylesheet
267
+
268
+ name = get_attribute('name')
269
+ value = @element.content.empty? ? render_children : @element.content
270
+
271
+ # Add to context variables
272
+ @context.variables[name] = value if name
273
+
274
+ # Let components produce no output
275
+ ''
276
+ end
277
+ end
278
+
279
+ # Stylesheet component
280
+ class StylesheetComponent < Component
281
+ def render
282
+ # Parse and apply stylesheet
283
+ begin
284
+ stylesheet_content = @element.content.strip
285
+ if stylesheet_content.start_with?('{') && stylesheet_content.end_with?('}')
286
+ stylesheet = JSON.parse(stylesheet_content)
287
+ @context.stylesheet.merge!(stylesheet) if stylesheet.is_a?(Hash)
288
+ end
289
+ rescue => e
290
+ # Silently fail JSON parsing errors
291
+ end
292
+
293
+ # Stylesheet components don't produce output
294
+ ''
295
+ end
296
+ end
297
+ end