poml 0.0.2 → 0.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c09d0d76e15b722fd0ac52fc6dfe843eebe6f85e18f9b4a4444c43d6c8ca651
4
- data.tar.gz: fb88b15378606b36242f7c80b3db61502bc3be8c0403565601250d0e9bce748a
3
+ metadata.gz: b5ba78c3aed937b9021b7d60fa5615fce4e75067ba607226fcb1c98674e720cf
4
+ data.tar.gz: 366238332f24e1f4309c64ff8da6fe52822747727e411d3925e92902b4b0ab2c
5
5
  SHA512:
6
- metadata.gz: 0f61b599f4f5548c38e74fd1413fba6ed3489f8cf8ca4b518d943dcf7205cf8101ae962ef95fc9d9a8eba46ace47f303f0a8d2abcdce643fa1e32be7d6c86abf
7
- data.tar.gz: a28b8954f93dd2c5a54a2353029401f37e78047b9d2e74b721d43ec3eb03ab5939cffedf52eabdbc38d73ae6f9d37dcbc65ddf059750167da5a8bd1ee24208d1
6
+ metadata.gz: f54b42cd18875cb6200eda77ea3f5be6ef21bbb5a8adcc35e3d349c4b7b6c8e8194f9fccc7c618bfe04c44163bcd8363816f04658f8b5ad279958aa6b9a21e64
7
+ data.tar.gz: caab1eb1555492e0bcc7342baa2b7c5a48c1a5606a172dee3d4983b617572940ce04d5cfda2b1d966da073cec0bc35b71c060435a5354126d265b3e43148fc5f
@@ -0,0 +1,62 @@
1
+ <!-- Example demonstrating the new schema syntax in POML -->
2
+ <poml>
3
+ <!-- Example 1: Output schema with new parser attribute -->
4
+ <meta type="responseschema" parser="json">
5
+ {
6
+ "type": "object",
7
+ "properties": {
8
+ "summary": {
9
+ "type": "string",
10
+ "description": "A brief summary of the content"
11
+ },
12
+ "sentiment": {
13
+ "type": "string",
14
+ "enum": ["positive", "negative", "neutral"]
15
+ },
16
+ "confidence": {
17
+ "type": "number",
18
+ "minimum": 0,
19
+ "maximum": 1
20
+ }
21
+ },
22
+ "required": ["summary", "sentiment"]
23
+ }
24
+ </meta>
25
+
26
+ <!-- Example 2: Tool definition with new parser attribute -->
27
+ <meta type="tool" name="analyze_text" description="Analyze text sentiment and content" parser="json">
28
+ {
29
+ "type": "object",
30
+ "properties": {
31
+ "text": {
32
+ "type": "string",
33
+ "description": "The text to analyze"
34
+ },
35
+ "include_keywords": {
36
+ "type": "boolean",
37
+ "description": "Whether to include keyword extraction",
38
+ "default": false
39
+ },
40
+ "language": {
41
+ "type": "string",
42
+ "enum": ["en", "es", "fr", "de"],
43
+ "default": "en"
44
+ }
45
+ },
46
+ "required": ["text"]
47
+ }
48
+ </meta>
49
+
50
+ <role>Expert text analyzer</role>
51
+ <task>
52
+ Analyze the provided text and return insights about its content,
53
+ sentiment, and other relevant characteristics. Use the analyze_text
54
+ tool when additional analysis is needed.
55
+ </task>
56
+
57
+ <p>
58
+ Please analyze this text: "The new POML schema syntax is much clearer
59
+ and more consistent with modern API standards. The migration from 'lang'
60
+ to 'parser' attributes makes the intent more obvious."
61
+ </p>
62
+ </poml>
@@ -0,0 +1,48 @@
1
+ <!-- Example showing backward compatibility support -->
2
+ <poml>
3
+ <!-- This still works: Old lang attribute -->
4
+ <meta type="responseschema" lang="json">
5
+ {
6
+ "type": "object",
7
+ "properties": {
8
+ "old_format": {"type": "string"}
9
+ }
10
+ }
11
+ </meta>
12
+
13
+ <!-- This is the new way: parser attribute -->
14
+ <meta type="responseschema" parser="json">
15
+ {
16
+ "type": "object",
17
+ "properties": {
18
+ "new_format": {"type": "string"}
19
+ }
20
+ }
21
+ </meta>
22
+
23
+ <!-- Expression evaluation with new syntax -->
24
+ <meta type="tool" name="calculator" parser="eval">
25
+ {
26
+ "type": "object",
27
+ "properties": {
28
+ "operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
29
+ "a": {"type": "number"},
30
+ "b": {"type": "number"}
31
+ }
32
+ }
33
+ </meta>
34
+
35
+ <role>Compatibility demonstration assistant</role>
36
+ <task>
37
+ Show that both old and new schema syntax work correctly.
38
+ The Ruby POML implementation supports both for backward compatibility.
39
+ </task>
40
+
41
+ <p>This example demonstrates that our Ruby implementation supports both:</p>
42
+ <list>
43
+ <item>Legacy `lang="json"` and `lang="expr"` attributes</item>
44
+ <item>New `parser="json"` and `parser="eval"` attributes</item>
45
+ </list>
46
+
47
+ <p>When both are present, `parser` takes precedence over `lang`.</p>
48
+ </poml>
@@ -135,7 +135,7 @@ module Poml
135
135
  end
136
136
  end
137
137
  return content unless content.empty?
138
- rescue => e
138
+ rescue
139
139
  # Zip gem not available or error occurred
140
140
  end
141
141
 
@@ -9,13 +9,13 @@ module Poml
9
9
 
10
10
  src = get_attribute('src')
11
11
  records_attr = get_attribute('records')
12
- columns_attr = get_attribute('columns')
12
+ _columns_attr = get_attribute('columns') # Not used but may be needed for future features
13
13
  parser = get_attribute('parser', 'auto')
14
14
  syntax = get_attribute('syntax')
15
- selected_columns = get_attribute('selectedColumns')
16
- selected_records = get_attribute('selectedRecords')
17
- max_records = get_attribute('maxRecords')
18
- max_columns = get_attribute('maxColumns')
15
+ selected_columns = parse_array_attribute('selectedColumns')
16
+ selected_records = parse_array_attribute('selectedRecords')
17
+ max_records = parse_integer_attribute('maxRecords')
18
+ max_columns = parse_integer_attribute('maxColumns')
19
19
 
20
20
  # Load data from source or use provided records
21
21
  data = if src
@@ -85,7 +85,7 @@ module Poml
85
85
  else
86
86
  { records: [], columns: [] }
87
87
  end
88
- rescue => e
88
+ rescue
89
89
  { records: [], columns: [] }
90
90
  end
91
91
 
@@ -136,7 +136,12 @@ module Poml
136
136
  def parse_records_attribute(records_attr)
137
137
  # Handle string records (JSON) or already parsed arrays
138
138
  records = if records_attr.is_a?(String)
139
- JSON.parse(records_attr)
139
+ begin
140
+ JSON.parse(records_attr)
141
+ rescue JSON::ParserError
142
+ # Return empty records on parse error
143
+ return { records: [], columns: [] }
144
+ end
140
145
  else
141
146
  records_attr
142
147
  end
@@ -150,6 +155,38 @@ module Poml
150
155
  { records: records.is_a?(Array) ? records : [records], columns: columns }
151
156
  end
152
157
 
158
+ def parse_array_attribute(attr_name)
159
+ attr_value = get_attribute(attr_name)
160
+ return nil unless attr_value
161
+
162
+ if attr_value.is_a?(String)
163
+ begin
164
+ parsed = JSON.parse(attr_value)
165
+ parsed.is_a?(Array) ? parsed : nil
166
+ rescue JSON::ParserError
167
+ # Try to handle as slice notation
168
+ attr_value.include?(':') ? attr_value : nil
169
+ end
170
+ elsif attr_value.is_a?(Array)
171
+ attr_value
172
+ else
173
+ nil
174
+ end
175
+ end
176
+
177
+ def parse_integer_attribute(attr_name)
178
+ attr_value = get_attribute(attr_name)
179
+ return nil unless attr_value
180
+
181
+ if attr_value.is_a?(String)
182
+ attr_value.to_i
183
+ elsif attr_value.is_a?(Integer)
184
+ attr_value
185
+ else
186
+ nil
187
+ end
188
+ end
189
+
153
190
  def parse_html_table_children
154
191
  records = []
155
192
  columns = []
@@ -219,13 +256,15 @@ module Poml
219
256
  end
220
257
  end
221
258
 
222
- # Apply max records
223
- if max_records && records.length > max_records
224
- # Show top half and bottom half with ellipsis
225
- top_rows = (max_records / 2.0).ceil
226
- bottom_rows = max_records - top_rows
227
- ellipsis_record = columns.each_with_object({}) { |col, record| record[col[:field]] = '...' }
228
- records = records[0...top_rows] + [ellipsis_record] + records[-bottom_rows..-1]
259
+ # Apply max records - simple truncation with ellipsis row
260
+ if max_records && max_records > 0 && records.length > max_records
261
+ truncated_records = records[0...max_records]
262
+ # Add ellipsis row if we truncated
263
+ if records.length > max_records && columns
264
+ ellipsis_record = columns.each_with_object({}) { |col, record| record[col[:field]] = '...' }
265
+ truncated_records << ellipsis_record
266
+ end
267
+ records = truncated_records
229
268
  end
230
269
 
231
270
  # Apply max columns
@@ -440,7 +479,7 @@ module Poml
440
479
  base64 = get_attribute('base64')
441
480
  extract_text = get_attribute('extractText', false)
442
481
  selector = get_attribute('selector', 'body')
443
- syntax = get_attribute('syntax', 'text')
482
+ _syntax = get_attribute('syntax', 'text') # May be used for future formatting options
444
483
 
445
484
  content = if url
446
485
  fetch_webpage_content(url, selector, extract_text)
@@ -33,30 +33,14 @@ module Poml
33
33
  "- "
34
34
  end
35
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
36
+ # Render all content (text + formatting) together
37
+ content = if child.children.any?
38
+ Components.render_element(child, @context).strip
56
39
  else
57
- # Simple text-only item
58
- items << "#{bullet}#{text_content}"
40
+ child.content.strip
59
41
  end
42
+
43
+ items << "#{bullet}#{content}"
60
44
  end
61
45
  end
62
46
 
@@ -70,12 +54,26 @@ module Poml
70
54
  class ItemComponent < Component
71
55
  def render
72
56
  apply_stylesheet
73
- content = @element.content.empty? ? render_children : @element.content.strip
74
57
 
75
58
  if xml_mode?
59
+ content = @element.content.empty? ? render_children : @element.content.strip
76
60
  "<item>#{content}</item>\n"
77
61
  else
78
- content
62
+ # For raw mode, handle mixed content properly
63
+ if @element.children.any?
64
+ # Render text and child elements together
65
+ result = ""
66
+ @element.children.each do |child|
67
+ if child.text?
68
+ result += child.content
69
+ else
70
+ result += Components.render_element(child, @context)
71
+ end
72
+ end
73
+ result.strip
74
+ else
75
+ @element.content.strip
76
+ end
79
77
  end
80
78
  end
81
79
  end
@@ -77,18 +77,22 @@ module Poml
77
77
  end
78
78
 
79
79
  def handle_response_schema
80
- lang = get_attribute('lang', 'auto')
81
- name = get_attribute('name')
82
- description = get_attribute('description')
80
+ # Support both old 'lang' and new 'parser' attributes for compatibility
81
+ parser_attr = get_attribute('parser') || get_attribute('lang', 'auto')
82
+ _name = get_attribute('name') # May be used for schema naming in future
83
+ _description = get_attribute('description') # May be used for schema documentation in future
83
84
 
84
85
  content = @element.content.strip
85
86
 
86
- # Auto-detect format if lang is auto
87
- if lang == 'auto'
88
- lang = content.start_with?('{') ? 'json' : 'expr'
87
+ # Auto-detect format if parser_attr is auto
88
+ if parser_attr == 'auto'
89
+ parser_attr = content.start_with?('{') ? 'json' : 'expr'
89
90
  end
90
91
 
91
- schema = case lang.downcase
92
+ # Handle new 'eval' parser type as alias for 'expr'
93
+ parser_attr = 'expr' if parser_attr == 'eval'
94
+
95
+ schema = case parser_attr.downcase
92
96
  when 'json'
93
97
  parse_json_schema(content)
94
98
  when 'expr'
@@ -106,18 +110,22 @@ module Poml
106
110
  def handle_tool_registration
107
111
  name = get_attribute('name')
108
112
  description = get_attribute('description')
109
- lang = get_attribute('lang', 'auto')
113
+ # Support both old 'lang' and new 'parser' attributes for compatibility
114
+ parser_attr = get_attribute('parser') || get_attribute('lang', 'auto')
110
115
 
111
116
  return unless name
112
117
 
113
118
  content = @element.content.strip
114
119
 
115
- # Auto-detect format if lang is auto
116
- if lang == 'auto'
117
- lang = content.start_with?('{') ? 'json' : 'expr'
120
+ # Auto-detect format if parser_attr is auto
121
+ if parser_attr == 'auto'
122
+ parser_attr = content.start_with?('{') ? 'json' : 'expr'
118
123
  end
119
124
 
120
- schema = case lang.downcase
125
+ # Handle new 'eval' parser type as alias for 'expr'
126
+ parser_attr = 'expr' if parser_attr == 'eval'
127
+
128
+ schema = case parser_attr.downcase
121
129
  when 'json'
122
130
  parse_json_schema(content)
123
131
  when 'expr'
@@ -168,20 +176,24 @@ module Poml
168
176
 
169
177
  begin
170
178
  JSON.parse(processed_content)
171
- rescue JSON::ParserError => e
179
+ rescue JSON::ParserError
172
180
  nil
173
181
  end
174
182
  end
175
183
 
176
184
  def evaluate_expression_schema(content)
177
- # This is a simplified version - in a full implementation,
178
- # you'd want to evaluate JavaScript expressions with Zod support
185
+ # Handle template expressions in the content
186
+ processed_content = @context.template_engine.substitute(content)
187
+
179
188
  begin
180
- # For now, just return the content as-is for expression schemas
181
- # In a complete implementation, you'd evaluate this as JavaScript
182
- { type: 'expression', content: content }
183
- rescue => e
184
- nil
189
+ # For expression schemas, try to parse as JSON first
190
+ # In the original POML, 'expr' mode evaluates JavaScript expressions
191
+ # but for simplicity in Ruby, we'll try JSON parsing
192
+ JSON.parse(processed_content)
193
+ rescue JSON::ParserError
194
+ # If JSON parsing fails, return a simple schema object
195
+ # This is a fallback for complex expressions that can't be easily evaluated
196
+ { 'type' => 'string', 'description' => 'Expression schema result' }
185
197
  end
186
198
  end
187
199
 
@@ -25,7 +25,7 @@ module Poml
25
25
  stylesheet = JSON.parse(stylesheet_content)
26
26
  @context.stylesheet.merge!(stylesheet) if stylesheet.is_a?(Hash)
27
27
  end
28
- rescue => e
28
+ rescue
29
29
  # Silently fail JSON parsing errors
30
30
  end
31
31
 
@@ -161,7 +161,7 @@ module Poml
161
161
  def render_conversation_xml(messages)
162
162
  result = ['<conversation>']
163
163
  messages.each do |msg|
164
- speaker = msg['speaker'] || 'human'
164
+ speaker = msg['speaker'] || msg['role'] || 'human'
165
165
  content = msg['content'] || ''
166
166
  result << " <msg speaker=\"#{speaker}\">#{escape_xml(content)}</msg>"
167
167
  end
@@ -172,7 +172,7 @@ module Poml
172
172
  def render_conversation_markdown(messages)
173
173
  result = []
174
174
  messages.each do |msg|
175
- speaker = msg['speaker'] || 'human'
175
+ speaker = msg['speaker'] || msg['role'] || 'human'
176
176
  content = msg['content'] || ''
177
177
 
178
178
  case speaker.downcase
@@ -244,12 +244,20 @@ module Poml
244
244
  if File.directory?(full_path)
245
245
  # Check if directory should be included
246
246
  if !filter || entry.match?(Regexp.new(filter))
247
- sub_items = build_tree_structure(full_path, filter, max_depth, show_content, current_depth + 1)
248
- if sub_items && !sub_items.empty?
247
+ if current_depth + 1 < max_depth
248
+ # If we can go deeper, recurse and include subdirectories
249
+ sub_items = build_tree_structure(full_path, filter, max_depth, show_content, current_depth + 1)
249
250
  items << {
250
251
  name: "#{entry}/",
251
252
  type: 'directory',
252
- children: sub_items
253
+ children: sub_items || []
254
+ }
255
+ else
256
+ # At max depth, just show the directory without recursing
257
+ items << {
258
+ name: "#{entry}/",
259
+ type: 'directory',
260
+ children: []
253
261
  }
254
262
  end
255
263
  end
@@ -505,4 +513,68 @@ module Poml
505
513
  text.to_s.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&quot;')
506
514
  end
507
515
  end
516
+
517
+ # File component for reading and including file contents
518
+ class FileComponent < Component
519
+ def render
520
+ apply_stylesheet
521
+
522
+ src = get_attribute('src')
523
+
524
+ # Handle missing src attribute
525
+ unless src
526
+ return handle_error("no src specified")
527
+ end
528
+
529
+ # Resolve file path
530
+ file_path = resolve_file_path(src)
531
+
532
+ # Check if file exists
533
+ unless File.exist?(file_path)
534
+ return handle_error("file not found: #{src}")
535
+ end
536
+
537
+ # Read file content
538
+ begin
539
+ content = File.read(file_path, encoding: 'utf-8')
540
+
541
+ if xml_mode?
542
+ render_as_xml('file', content, { src: src })
543
+ else
544
+ content
545
+ end
546
+ rescue => e
547
+ handle_error("error reading file: #{e.message}")
548
+ end
549
+ end
550
+
551
+ private
552
+
553
+ def resolve_file_path(src)
554
+ # Handle absolute paths
555
+ return src if src.start_with?('/')
556
+
557
+ # Handle relative paths - try relative to source file first
558
+ if @context.source_path
559
+ base_path = File.dirname(@context.source_path)
560
+ candidate_path = File.join(base_path, src)
561
+ return candidate_path if File.exist?(candidate_path)
562
+ end
563
+
564
+ # Try relative to current working directory
565
+ candidate_path = File.join(Dir.pwd, src)
566
+ return candidate_path if File.exist?(candidate_path)
567
+
568
+ # Return the original path for final existence check
569
+ src
570
+ end
571
+
572
+ def handle_error(message)
573
+ if xml_mode?
574
+ render_as_xml('file-error', message)
575
+ else
576
+ "[File: #{message}]"
577
+ end
578
+ end
579
+ end
508
580
  end
@@ -28,22 +28,6 @@ module Poml
28
28
  end
29
29
  end
30
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
31
  # Question-Answer component
48
32
  class QAComponent < Component
49
33
  def render
@@ -114,6 +114,8 @@ module Poml
114
114
  Folder: FolderComponent,
115
115
  tree: TreeComponent,
116
116
  Tree: TreeComponent,
117
+ file: FileComponent,
118
+ File: FileComponent,
117
119
 
118
120
  # Styling components
119
121
  let: LetComponent,
data/lib/poml/parser.rb CHANGED
@@ -163,7 +163,7 @@ module Poml
163
163
  stylesheet = JSON.parse(stylesheet_text)
164
164
  @context.stylesheet.merge!(stylesheet) if stylesheet.is_a?(Hash)
165
165
  end
166
- rescue => e
166
+ rescue
167
167
  # Silently fail JSON parsing errors
168
168
  end
169
169
  end
@@ -235,7 +235,8 @@ module Poml
235
235
 
236
236
  def preprocess_void_elements(content)
237
237
  # List of HTML void elements that should be self-closing in XML
238
- void_elements = %w[br hr img input area base col embed link meta param source track wbr]
238
+ # Note: 'meta' is removed from this list because POML meta components can have content
239
+ void_elements = %w[br hr img input area base col embed link param source track wbr]
239
240
 
240
241
  # Convert <element> to <element/> for void elements, but only if not already self-closing
241
242
  void_elements.each do |element|
data/lib/poml/renderer.rb CHANGED
@@ -134,7 +134,7 @@ module Poml
134
134
  # Check for system-oriented components
135
135
  has_role = tag_names.include?(:role)
136
136
  has_task = tag_names.include?(:task)
137
- has_hint = tag_names.include?(:hint)
137
+ _has_hint = tag_names.include?(:hint) # Prefix with underscore to indicate intentionally unused
138
138
 
139
139
  # Check for human-oriented components or content that suggests user interaction
140
140
  has_document = tag_names.include?(:document) || tag_names.include?('Document')
data/lib/poml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Poml
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.5"
5
5
  end
data/lib/poml.rb CHANGED
@@ -17,69 +17,15 @@ module Poml
17
17
  # - stylesheet: Optional stylesheet as string or hash
18
18
  # - chat: Boolean indicating chat format (default: true)
19
19
  # - output_file: Path to save output (optional)
20
+ # Process POML markup and return the rendered result
21
+ #
22
+ # Parameters:
23
+ # - markup: POML markup string or file path
20
24
  # - format: 'raw', 'dict', 'openai_chat', 'langchain', 'pydantic' (default: 'dict')
21
- # - extra_args: Array of extra CLI args (ignored in pure Ruby implementation)
22
- def self.process(markup:, context: nil, stylesheet: nil, chat: true, output_file: nil, format: 'dict', extra_args: [])
23
- # Create POML context
24
- poml_context = Context.new(
25
- variables: context || {},
26
- stylesheet: stylesheet,
27
- chat: chat
28
- )
29
-
30
- # Read markup content and set source path
31
- content = if File.exist?(markup.to_s)
32
- poml_context.source_path = File.expand_path(markup.to_s)
33
- File.read(markup)
34
- else
35
- markup.to_s
36
- end
37
-
38
- # Parse POML content
39
- parser = Parser.new(poml_context)
40
- parsed_elements = parser.parse(content)
41
-
42
- # Render to the desired format
43
- renderer = Renderer.new(poml_context)
44
- result = renderer.render(parsed_elements, format)
45
-
46
- # Save to file if specified
47
- if output_file
48
- File.write(output_file, result)
49
- end
50
-
51
- result
52
- end
53
-
54
- def self.parse(content, context: nil)
55
- context ||= Context.new
56
- parser = Parser.new(context)
57
- parser.parse(content)
58
- end
59
-
60
- def self.render(content, format: 'text', context: nil, **options)
61
- context ||= Context.new(**options)
62
- elements = parse(content, context: context)
63
- renderer = Renderer.new(context)
64
- renderer.render(elements, format)
65
- end
66
-
67
- # Convenience method for quick text rendering
68
- def self.to_text(content, **options)
69
- render(content, format: 'raw', **options)
70
- end
71
-
72
- # Convenience method for chat format
73
- def self.to_chat(content, **options)
74
- render(content, format: 'openai_chat', **options)
75
- end
76
-
77
- # Convenience method for dict format
78
- def self.to_dict(content, **options)
79
- render(content, format: 'dict', **options)
80
- end
81
-
82
- # Legacy method for backward compatibility
25
+ # - context/variables: Hash of template variables
26
+ # - stylesheet: CSS/styling rules
27
+ # - chat: Enable chat mode (default: true)
28
+ # - output_file: File path to write output to
83
29
  def self.process(markup:, format: 'dict', **options)
84
30
  # Handle file paths
85
31
  content = if File.exist?(markup)
@@ -116,4 +62,32 @@ module Poml
116
62
  result
117
63
  end
118
64
  end
65
+
66
+ def self.parse(content, context: nil)
67
+ context ||= Context.new
68
+ parser = Parser.new(context)
69
+ parser.parse(content)
70
+ end
71
+
72
+ def self.render(content, format: 'text', context: nil, **options)
73
+ context ||= Context.new(**options)
74
+ elements = parse(content, context: context)
75
+ renderer = Renderer.new(context)
76
+ renderer.render(elements, format)
77
+ end
78
+
79
+ # Convenience method for quick text rendering
80
+ def self.to_text(content, **options)
81
+ render(content, format: 'raw', **options)
82
+ end
83
+
84
+ # Convenience method for chat format
85
+ def self.to_chat(content, **options)
86
+ render(content, format: 'openai_chat', **options)
87
+ end
88
+
89
+ # Convenience method for dict format
90
+ def self.to_dict(content, **options)
91
+ render(content, format: 'dict', **options)
92
+ end
119
93
  end
data/readme.md CHANGED
@@ -6,6 +6,8 @@ A Ruby implementation of the POML (Prompt Oriented Markup Language) interpreter.
6
6
 
7
7
  This is a **Ruby port** of the original [POML library](https://github.com/microsoft/poml) developed by Microsoft, which was originally implemented in JavaScript/TypeScript and Python. This Ruby gem is designed to be **fully compatible** with the original POML specification and will **closely follow** the development of the original library to maintain feature parity.
8
8
 
9
+ > **🔄 Recent Update**: The original POML library has introduced **breaking changes** in schema definitions. Schema attributes have been renamed from `lang` to `parser` (e.g., `lang="json"` → `parser="json"`, `lang="expr"` → `parser="eval"`). Our Ruby implementation is being updated to maintain compatibility with these changes.
10
+
9
11
  ## Demo Video
10
12
 
11
13
  [![The 5-minute guide to POML](https://i3.ytimg.com/vi/b9WDcFsKixo/maxresdefault.jpg)](https://youtu.be/b9WDcFsKixo)
@@ -205,6 +207,8 @@ Customize component appearance:
205
207
  - ✅ Stylesheet support
206
208
  - ✅ Command-line interface
207
209
  - ✅ Chat vs non-chat modes
210
+ - 🔄 **Schema definitions** (updating to new `parser` attribute syntax)
211
+ - 🔄 **Tool registration** (updating for enhanced tool use support)
208
212
 
209
213
  ## Document Support
210
214
 
@@ -239,7 +243,7 @@ Build and install locally:
239
243
 
240
244
  ```bash
241
245
  gem build poml.gemspec
242
- gem install poml-0.0.1.gem
246
+ gem install poml-0.0.3.gem
243
247
  ```
244
248
 
245
249
  ## License
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ghennadii Mirosnicenco
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-18 00:00:00.000000000 Z
10
+ date: 2025-08-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rexml
@@ -62,6 +62,8 @@ files:
62
62
  - examples/201_orders_qa.poml
63
63
  - examples/202_arc_agi.poml
64
64
  - examples/301_generate_poml.poml
65
+ - examples/301_new_schema_syntax.poml
66
+ - examples/302_schema_compatibility.poml
65
67
  - examples/README.md
66
68
  - examples/assets/101_jerry_mouse.jpg
67
69
  - examples/assets/101_tom_and_jerry.docx