poml 0.0.1 → 0.0.3

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.
@@ -0,0 +1,148 @@
1
+ module Poml
2
+ # Bold component for emphasizing text
3
+ class BoldComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+ content = @element.children.empty? ? @element.content : render_children
7
+
8
+ if xml_mode?
9
+ render_as_xml('b', content)
10
+ else
11
+ "**#{content}**"
12
+ end
13
+ end
14
+ end
15
+
16
+ # Italic component for emphasizing text
17
+ class ItalicComponent < Component
18
+ def render
19
+ apply_stylesheet
20
+ content = @element.children.empty? ? @element.content : render_children
21
+
22
+ if xml_mode?
23
+ render_as_xml('i', content)
24
+ else
25
+ "*#{content}*"
26
+ end
27
+ end
28
+ end
29
+
30
+ # Underline component for underlining text
31
+ class UnderlineComponent < Component
32
+ def render
33
+ apply_stylesheet
34
+ content = @element.children.empty? ? @element.content : render_children
35
+
36
+ if xml_mode?
37
+ render_as_xml('u', content)
38
+ else
39
+ # Markdown-style underline
40
+ "__#{content}__"
41
+ end
42
+ end
43
+ end
44
+
45
+ # Strikethrough component for crossed-out text
46
+ class StrikethroughComponent < Component
47
+ def render
48
+ apply_stylesheet
49
+ content = @element.children.empty? ? @element.content : render_children
50
+
51
+ if xml_mode?
52
+ render_as_xml('s', content)
53
+ else
54
+ "~~#{content}~~"
55
+ end
56
+ end
57
+ end
58
+
59
+ # Inline span component for generic inline content
60
+ class InlineComponent < Component
61
+ def render
62
+ apply_stylesheet
63
+ content = @element.content.empty? ? render_children : @element.content
64
+
65
+ if xml_mode?
66
+ render_as_xml('span', content)
67
+ else
68
+ content
69
+ end
70
+ end
71
+ end
72
+
73
+ # Header component for headings
74
+ class HeaderComponent < Component
75
+ def render
76
+ apply_stylesheet
77
+
78
+ content = @element.content.empty? ? render_children : @element.content
79
+ level = get_attribute('level', @context.header_level).to_i
80
+ level = [level, 6].min # Cap at h6
81
+ level = [level, 1].max # Minimum h1
82
+
83
+ if xml_mode?
84
+ render_as_xml('h', content, { level: level })
85
+ else
86
+ header_prefix = '#' * level
87
+ "#{header_prefix} #{content}\n\n"
88
+ end
89
+ end
90
+ end
91
+
92
+ # Newline component for explicit line breaks
93
+ class NewlineComponent < Component
94
+ def render
95
+ apply_stylesheet
96
+
97
+ count = get_attribute('newLineCount', 1).to_i
98
+
99
+ if xml_mode?
100
+ render_as_xml('nl', '', { count: count })
101
+ else
102
+ "\n" * count
103
+ end
104
+ end
105
+ end
106
+
107
+ # Code component for code snippets
108
+ class CodeComponent < Component
109
+ def render
110
+ apply_stylesheet
111
+
112
+ content = @element.content.empty? ? render_children : @element.content
113
+ inline = get_attribute('inline', true)
114
+ lang = get_attribute('lang', '')
115
+
116
+ if xml_mode?
117
+ attributes = { inline: inline }
118
+ attributes[:lang] = lang unless lang.empty?
119
+ render_as_xml('code', content, attributes)
120
+ else
121
+ if inline
122
+ "`#{content}`"
123
+ else
124
+ if lang.empty?
125
+ "```\n#{content}\n```\n\n"
126
+ else
127
+ "```#{lang}\n#{content}\n```\n\n"
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ # SubContent component for nested sections
135
+ class SubContentComponent < Component
136
+ def render
137
+ apply_stylesheet
138
+
139
+ content = @context.with_increased_header_level { render_children }
140
+
141
+ if xml_mode?
142
+ render_as_xml('section', content)
143
+ else
144
+ "#{content}"
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,34 @@
1
+ module Poml
2
+ # Audio component for embedding audio files
3
+ class AudioComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ src = get_attribute('src')
8
+ base64 = get_attribute('base64')
9
+ alt = get_attribute('alt', '')
10
+ audio_type = get_attribute('type', '')
11
+ syntax = get_attribute('syntax', 'multimedia')
12
+ position = get_attribute('position', 'here')
13
+
14
+ if xml_mode?
15
+ attributes = {}
16
+ attributes[:src] = src if src
17
+ attributes[:base64] = base64 if base64
18
+ attributes[:alt] = alt unless alt.empty?
19
+ attributes[:type] = audio_type unless audio_type.empty?
20
+ attributes[:position] = position
21
+ render_as_xml('audio', '', attributes)
22
+ else
23
+ if syntax == 'multimedia'
24
+ audio_ref = src || '[embedded audio]'
25
+ result = "[Audio: #{audio_ref}]"
26
+ result += " (#{alt})" unless alt.empty?
27
+ result
28
+ else
29
+ alt.empty? ? '[Audio]' : alt
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,248 @@
1
+ module Poml
2
+ # Meta component for document metadata and configuration
3
+ class MetaComponent < Component
4
+ def render
5
+ apply_stylesheet
6
+
7
+ type = get_attribute('type')
8
+
9
+ if type
10
+ handle_typed_meta(type)
11
+ else
12
+ handle_general_meta
13
+ end
14
+
15
+ # If meta has children and variables are set, render children with variable substitution
16
+ if @element.children.any? && get_attribute('variables')
17
+ child_content = @element.children.map do |child|
18
+ # Render the child element
19
+ rendered_child = Components.render_element(child, @context)
20
+ # Apply template substitution to the rendered content
21
+ @context.template_engine.substitute(rendered_child)
22
+ end.join('')
23
+ return child_content
24
+ end
25
+
26
+ # Meta components don't produce output by default
27
+ ''
28
+ end
29
+
30
+ private
31
+
32
+ def handle_typed_meta(type)
33
+ case type.downcase
34
+ when 'responseschema', 'response_schema'
35
+ handle_response_schema
36
+ when 'tool'
37
+ handle_tool_registration
38
+ when 'runtime'
39
+ handle_runtime_parameters
40
+ else
41
+ # Unknown meta type, ignore
42
+ end
43
+ end
44
+
45
+ def handle_general_meta
46
+ # Handle template variables
47
+ variables_attr = get_attribute('variables')
48
+ if variables_attr
49
+ handle_variables(variables_attr)
50
+ end
51
+
52
+ # Handle general metadata attributes
53
+ %w[title description author keywords].each do |attr|
54
+ value = get_attribute(attr)
55
+ if value
56
+ @context.custom_metadata[attr] = value
57
+ end
58
+ end
59
+
60
+ # Handle version control
61
+ min_version = get_attribute('minVersion')
62
+ max_version = get_attribute('maxVersion')
63
+
64
+ if min_version
65
+ check_version_compatibility(min_version, 'minimum')
66
+ end
67
+
68
+ if max_version
69
+ check_version_compatibility(max_version, 'maximum')
70
+ end
71
+
72
+ # Handle component control
73
+ components = get_attribute('components')
74
+ if components
75
+ apply_component_control(components)
76
+ end
77
+ end
78
+
79
+ def handle_response_schema
80
+ lang = get_attribute('lang', 'auto')
81
+ name = get_attribute('name')
82
+ description = get_attribute('description')
83
+
84
+ content = @element.content.strip
85
+
86
+ # Auto-detect format if lang is auto
87
+ if lang == 'auto'
88
+ lang = content.start_with?('{') ? 'json' : 'expr'
89
+ end
90
+
91
+ schema = case lang.downcase
92
+ when 'json'
93
+ parse_json_schema(content)
94
+ when 'expr'
95
+ evaluate_expression_schema(content)
96
+ else
97
+ nil
98
+ end
99
+
100
+ if schema
101
+ # Store the schema directly for simplicity
102
+ @context.response_schema = schema
103
+ end
104
+ end
105
+
106
+ def handle_tool_registration
107
+ name = get_attribute('name')
108
+ description = get_attribute('description')
109
+ lang = get_attribute('lang', 'auto')
110
+
111
+ return unless name
112
+
113
+ content = @element.content.strip
114
+
115
+ # Auto-detect format if lang is auto
116
+ if lang == 'auto'
117
+ lang = content.start_with?('{') ? 'json' : 'expr'
118
+ end
119
+
120
+ schema = case lang.downcase
121
+ when 'json'
122
+ parse_json_schema(content)
123
+ when 'expr'
124
+ evaluate_expression_schema(content)
125
+ else
126
+ nil
127
+ end
128
+
129
+ if schema
130
+ @context.tools ||= []
131
+ # Store tool with string keys for JSON compatibility
132
+ tool_def = {
133
+ 'name' => name,
134
+ 'description' => description
135
+ }
136
+ # Merge in the parsed schema (should include parameters, etc.)
137
+ tool_def.merge!(schema) if schema.is_a?(Hash)
138
+
139
+ @context.tools << tool_def
140
+ end
141
+ end
142
+
143
+ def handle_runtime_parameters
144
+ # Collect all attributes except 'type' as runtime parameters
145
+ runtime_params = {}
146
+
147
+ @element.attributes.each do |key, value|
148
+ next if key == 'type'
149
+
150
+ # Convert common parameter types
151
+ runtime_params[key] = case key.downcase
152
+ when 'temperature', 'topp', 'frequencypenalty', 'presencepenalty'
153
+ value.to_f
154
+ when 'maxoutputtokens', 'seed'
155
+ value.to_i
156
+ else
157
+ value
158
+ end
159
+ end
160
+
161
+ @context.runtime_parameters ||= {}
162
+ @context.runtime_parameters.merge!(runtime_params)
163
+ end
164
+
165
+ def parse_json_schema(content)
166
+ # Handle template expressions in JSON
167
+ processed_content = @context.template_engine.substitute(content)
168
+
169
+ begin
170
+ JSON.parse(processed_content)
171
+ rescue JSON::ParserError => e
172
+ nil
173
+ end
174
+ end
175
+
176
+ 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
179
+ 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
185
+ end
186
+ end
187
+
188
+ def check_version_compatibility(version, type)
189
+ current_version = Poml::VERSION
190
+
191
+ if type == 'minimum' && compare_versions(current_version, version) < 0
192
+ raise "POML version #{version} or higher required, but current version is #{current_version}"
193
+ elsif type == 'maximum' && compare_versions(current_version, version) > 0
194
+ # Just warn for maximum version
195
+ puts "Warning: POML version #{current_version} may not be compatible with documents requiring version #{version} or lower"
196
+ end
197
+ end
198
+
199
+ def compare_versions(v1, v2)
200
+ # Simple semantic version comparison
201
+ v1_parts = v1.split('.').map(&:to_i)
202
+ v2_parts = v2.split('.').map(&:to_i)
203
+
204
+ [v1_parts.length, v2_parts.length].max.times do |i|
205
+ a = v1_parts[i] || 0
206
+ b = v2_parts[i] || 0
207
+
208
+ return a <=> b if a != b
209
+ end
210
+
211
+ 0
212
+ end
213
+
214
+ def handle_variables(variables_attr)
215
+ # Parse variables JSON and add to context
216
+ begin
217
+ variables = JSON.parse(variables_attr)
218
+ if variables.is_a?(Hash)
219
+ # Merge variables into context
220
+ variables.each do |key, value|
221
+ @context.variables[key] = value
222
+ end
223
+ end
224
+ rescue JSON::ParserError => e
225
+ # Invalid JSON, ignore silently or log error
226
+ puts "Warning: Invalid JSON in meta variables: #{e.message}" if ENV['POML_DEBUG']
227
+ end
228
+ end
229
+
230
+ def apply_component_control(components_attr)
231
+ # Parse component control string like "-table,-image" or "+table"
232
+ components_attr.split(',').each do |component_spec|
233
+ component_spec = component_spec.strip
234
+
235
+ if component_spec.start_with?('+')
236
+ # Re-enable component
237
+ component_name = component_spec[1..-1]
238
+ @context.disabled_components&.delete(component_name)
239
+ elsif component_spec.start_with?('-')
240
+ # Disable component
241
+ component_name = component_spec[1..-1]
242
+ @context.disabled_components ||= Set.new
243
+ @context.disabled_components.add(component_name)
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end