mdphlex 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30fde45581cd7d2edeada97dd0c281cbf4bf6447e6938f5002ad99231c4ac2f0
4
- data.tar.gz: 1168f4d22457f7965160e4248f468d7bce4e0a45eb56a44b6285cb3aebe94d83
3
+ metadata.gz: df56be38ad404b1762ae330d4f59f21de2ab56ed0248688a8134890f61f407e7
4
+ data.tar.gz: 4bde2036075332276a3d3882cdabf931c97be472f67612a72c82a7cb513607be
5
5
  SHA512:
6
- metadata.gz: 8aab63e64b863d0fdba508d8e9d2a570eb7b8bc8d9c11081cf4b8718a9c06365a6069df65024a2c868efd6bc60e1163f4adf20251f220f7c63d0d6d4b4ffd05b
7
- data.tar.gz: 66efd4524781b9fcc52568879f352285db7477f078039c40f1c209c09e13d63c3dc0c24f95c20876839203a68982d79166bf125d54abe4017dc6c9c3c2008e69
6
+ metadata.gz: c8835c134e433334f033dc01dc8614476dd281c2c2be6223cb5dda92dabebdcad101bb1d2bb6f3806a6c792bd69b8a639410739ce417cf6fee90d81930191ee5
7
+ data.tar.gz: db3062396e99e68aa12ffbbe3c950fbf83c1e8549a88b6d6e0b289d4b39791975fd5ff7be99f983eb4a9fd2658bcacdcb6095053e3d5420807e1a49a2aef96cb
data/README.md CHANGED
@@ -1,74 +1,152 @@
1
1
  # MDPhlex
2
2
 
3
- MDPhlex is a Phlex component for rendering Markdown. Write your Markdown in Ruby with Phlex's familiar syntax and render it as a Markdown string.
3
+ > Inversion of Markdown
4
+
5
+ MDPhlex is a Phlex component for rendering Markdown. No... not rendering markdown into HTML, rendering plain Markdown, programmatically.
6
+
7
+ MDPhlex is perfect for dynamically creating context for LLMs, generating an llms.txt file from real content, or doing something silly like passing the output to a markdown renderer to create HTML or generating blog posts that look like they were hand-written markdown.
4
8
 
5
9
  ## Installation
6
10
 
7
11
  Add this line to your application's Gemfile:
8
12
 
9
- ```ruby
13
+ ~~~ruby
10
14
  gem 'mdphlex'
11
- ```
15
+ ~~~
12
16
 
13
- ## Basic Usage
17
+ ## Creating LLM Prompts with MDPhlex
14
18
 
15
- Create an MDPhlex::MD component just like you would create a Phlex component:
19
+ MDPhlex shines when creating structured prompts for LLMs. Here's a simple example using custom tags:
20
+
21
+ ~~~ruby
22
+ class LLMPrompt < MDPhlex::MD
23
+ # Register custom elements for structured prompts
24
+ register_block_element :system
25
+ register_block_element :tools
26
+ register_block_element :tool
27
+
28
+ def initialize(task:, tools: [])
29
+ @task = task
30
+ @tools = tools
31
+ end
16
32
 
17
- ```ruby
18
- class HelloWorld < MDPhlex::MD
19
33
  def view_template
20
- h1 "Hello, World!"
34
+ system do
35
+ plain "You are an AI assistant specialized in #{@task}."
36
+ plain "\nUse the available tools to help users effectively."
37
+ end
38
+
39
+ plain "\n"
40
+
41
+ if @tools.any?
42
+ tools do
43
+ @tools.each do |tool_def|
44
+ tool name: tool_def[:name] do
45
+ plain tool_def[:description]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # Dynamic prompt generation
54
+ prompt = LLMPrompt.new(
55
+ task: "Ruby code analysis",
56
+ tools: [
57
+ { name: "analyze_code", description: "Analyze Ruby code for improvements" },
58
+ { name: "explain_concept", description: "Explain Ruby concepts and patterns" }
59
+ ]
60
+ )
21
61
 
22
- p "Welcome to MDPhlex - writing Markdown with Phlex syntax!"
62
+ puts prompt.call
63
+ ~~~
23
64
 
24
- h2 "Features"
65
+ This outputs clean, structured markdown perfect for LLMs:
25
66
 
26
- ul do
27
- li "Write Markdown using Ruby"
28
- li do
29
- plain "Support for "
30
- strong "bold"
31
- plain ", "
32
- em "italic"
33
- plain ", and "
34
- code "inline code"
67
+ ~~~xml
68
+ <system>
69
+ You are an AI assistant specialized in Ruby code analysis.
70
+ Use the available tools to help users effectively.</system>
71
+
72
+ <tools>
73
+ <tool name="analyze_code">
74
+ Analyze Ruby code for improvements</tool>
75
+ <tool name="explain_concept">
76
+ Explain Ruby concepts and patterns</tool>
77
+ </tools>
78
+ ~~~
79
+
80
+ The XML-style tags help LLMs understand different sections of the prompt, while Ruby's dynamic nature lets you generate prompts based on runtime conditions.
81
+
82
+ ## Traditional Markdown Generation
83
+
84
+ MDPhlex also works great for generating regular Markdown content:
85
+
86
+ ~~~ruby
87
+ class ArticleContent < MDPhlex::MD
88
+ def initialize(title:, sections:)
89
+ @title = title
90
+ @sections = sections
91
+ end
92
+
93
+ def view_template
94
+ h1 @title
95
+
96
+ @sections.each do |section|
97
+ h2 section[:heading]
98
+
99
+ p section[:content]
100
+
101
+ if section[:code_example]
102
+ pre(language: "ruby") { plain section[:code_example] }
35
103
  end
36
- li "Full Phlex component composition"
37
104
  end
38
105
 
39
106
  p do
40
- plain "Check out the "
41
- a(href: "https://github.com/martinemde/mdphlex") { "MDPhlex repository" }
42
- plain " for more information."
107
+ plain "Learn more in the "
108
+ a(href: "https://docs.example.com") { "documentation" }
109
+ plain "."
43
110
  end
44
111
  end
45
112
  end
46
113
 
47
- # Render the component
48
- puts HelloWorld.new.call
49
- ```
114
+ article = ArticleContent.new(
115
+ title: "Getting Started with Ruby",
116
+ sections: [
117
+ {
118
+ heading: "Variables",
119
+ content: "Ruby variables are dynamically typed and don't require declaration.",
120
+ code_example: "name = 'Alice'\nage = 30"
121
+ }
122
+ ]
123
+ )
50
124
 
51
- This outputs the following Markdown:
125
+ puts article.call
126
+ ~~~
52
127
 
53
- ```markdown
54
- # Hello, World!
128
+ Outputs:
55
129
 
56
- Welcome to MDPhlex - writing Markdown with Phlex syntax!
130
+ ~~~markdown
131
+ # Getting Started with Ruby
57
132
 
58
- ## Features
133
+ ## Variables
59
134
 
60
- - Write Markdown using Ruby
61
- - Support for **bold**, *italic*, and `inline code`
62
- - Full Phlex component composition
135
+ Ruby variables are dynamically typed and don't require declaration.
63
136
 
64
- Check out the [MDPhlex repository](https://github.com/martinemde/mdphlex) for more information.
137
+ ```ruby
138
+ name = 'Alice'
139
+ age = 30
65
140
  ```
66
141
 
142
+ Learn more in the [documentation](https://docs.example.com).
143
+ ~~~
144
+
67
145
  ## Rendering MDPhlex inside Phlex::HTML
68
146
 
69
147
  MDPhlex components can be seamlessly integrated into your Phlex::HTML views:
70
148
 
71
- ```ruby
149
+ ~~~ruby
72
150
  class ArticlePage < Phlex::HTML
73
151
  def initialize(article)
74
152
  @article = article
@@ -109,14 +187,13 @@ class ArticleContent < MDPhlex::MD
109
187
  end
110
188
  end
111
189
  end
112
- ```
190
+ ~~~
113
191
 
114
192
  ## Why MDPhlex?
115
193
 
116
194
  - **Component-based**: Build reusable Markdown components
117
- - **Type-safe**: Get Ruby's type checking and IDE support
195
+ - **Dynamic Markdown**: Generate markdown from dynamic data
118
196
  - **Composable**: Mix Phlex::HTML and MDPhlex components freely
119
- - **Familiar**: Uses the same syntax as Phlex
120
197
 
121
198
  ## License
122
199
 
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/mdphlex"
5
+ require "json"
6
+
7
+ # Example: Creating custom block elements for specialized markdown output
8
+ class DocumentationTemplate < MDPhlex::MD
9
+ # Register custom elements for API documentation
10
+ register_block_element :api_endpoint
11
+ register_block_element :request
12
+ register_block_element :response
13
+ register_block_element :warning
14
+ register_block_element :note
15
+ register_block_element :deprecated
16
+
17
+ def initialize(endpoint_name:, method:, path:)
18
+ @endpoint_name = endpoint_name
19
+ @method = method
20
+ @path = path
21
+ end
22
+
23
+ def view_template
24
+ h1 "API Documentation: #{@endpoint_name}"
25
+
26
+ api_endpoint method: @method, path: @path do
27
+ h2 "Overview"
28
+ p "This endpoint handles user authentication and returns a JWT token."
29
+
30
+ warning do
31
+ p "This endpoint rate limits requests to 10 per minute per IP address."
32
+ end
33
+
34
+ h2 "Request"
35
+
36
+ request do
37
+ h3 "Headers"
38
+ pre do
39
+ <<~HEADERS
40
+ Content-Type: application/json
41
+ Accept: application/json
42
+ HEADERS
43
+ end
44
+
45
+ h3 "Body"
46
+ pre language: "json" do
47
+ JSON.pretty_generate({ email: "user@example.com", password: "secure_password123" })
48
+ end
49
+ end
50
+
51
+ h2 "Response"
52
+
53
+ response status: "200" do
54
+ h3 "Success Response"
55
+ pre language: "json" do
56
+ plain JSON.pretty_generate({
57
+ token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
58
+ user: {
59
+ id: 123,
60
+ email: "user@example.com",
61
+ name: "John Doe",
62
+ },
63
+ })
64
+ end
65
+ end
66
+
67
+ response status: "401" do
68
+ h3 "Authentication Failed"
69
+ pre language: "json" do
70
+ JSON.pretty_generate({
71
+ error: "Invalid credentials",
72
+ })
73
+ end
74
+ end
75
+
76
+ note do
77
+ p "The JWT token expires after 24 hours. Use the refresh endpoint to get a new token."
78
+ end
79
+
80
+ deprecated version: "2.0" do
81
+ p "The 'username' field in the request body is deprecated. Use 'email' instead."
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # Example usage
88
+ if __FILE__ == $0
89
+ doc = DocumentationTemplate.new(
90
+ endpoint_name: "User Login",
91
+ method: "POST",
92
+ path: "/api/v1/auth/login"
93
+ )
94
+
95
+ output = doc.call
96
+ puts output
97
+
98
+ # Save to file
99
+ File.write(
100
+ File.join(File.dirname(__FILE__), "custom_elements_output.md"),
101
+ output
102
+ )
103
+
104
+ puts "\n---"
105
+ puts "Output saved to examples/custom_elements_output.md"
106
+ end
@@ -0,0 +1,60 @@
1
+ # API Documentation: User Login
2
+ <api-endpoint method="POST" path="/api/v1/auth/login">
3
+ ## Overview
4
+ This endpoint handles user authentication and returns a JWT token.
5
+
6
+ <warning>
7
+ This endpoint rate limits requests to 10 per minute per IP address.
8
+
9
+ </warning>
10
+ ## Request
11
+ <request>
12
+ ### Headers
13
+ ```
14
+ Content-Type: application/json
15
+ Accept: application/json
16
+
17
+ ```
18
+
19
+ ### Body
20
+ ```json
21
+ {
22
+ "email": "user@example.com",
23
+ "password": "secure_password123"
24
+ }
25
+ ```
26
+
27
+ </request>
28
+ ## Response
29
+ <response status="200">
30
+ ### Success Response
31
+ ```json
32
+ {
33
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
34
+ "user": {
35
+ "id": 123,
36
+ "email": "user@example.com",
37
+ "name": "John Doe"
38
+ }
39
+ }
40
+ ```
41
+
42
+ </response>
43
+ <response status="401">
44
+ ### Authentication Failed
45
+ ```json
46
+ {
47
+ "error": "Invalid credentials"
48
+ }
49
+ ```
50
+
51
+ </response>
52
+ <note>
53
+ The JWT token expires after 24 hours. Use the refresh endpoint to get a new token.
54
+
55
+ </note>
56
+ <deprecated version="2.0">
57
+ The 'username' field in the request body is deprecated. Use 'email' instead.
58
+
59
+ </deprecated>
60
+ </api-endpoint>
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "mdphlex"
6
+
7
+ class HTMLInMarkdownExample < MDPhlex::MD
8
+ def view_template
9
+ h1 { "HTML in Markdown" }
10
+
11
+ p { "Markdown allows inline HTML like <strong>this bold text</strong> and <em>this italic text</em>." }
12
+
13
+ p do
14
+ plain "You can use HTML entities directly: &copy; &amp; &lt;div&gt;"
15
+ end
16
+
17
+ h2 { "Complex HTML" }
18
+
19
+ p do
20
+ plain <<~HTML
21
+ You can even include complex HTML structures:
22
+ <div class="alert" style="border: 1px solid red;">
23
+ <h3>Warning!</h3>
24
+ <p>This is an alert box created with HTML.</p>
25
+ </div>
26
+ HTML
27
+ end
28
+
29
+ h2 { "Mixing Markdown and HTML" }
30
+
31
+ p do
32
+ plain "You can mix "
33
+ strong { "Markdown formatting" }
34
+ plain " with <code>HTML tags</code> seamlessly."
35
+ end
36
+ end
37
+ end
38
+
39
+ if __FILE__ == $0
40
+ puts HTMLInMarkdownExample.new.call
41
+ end
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/mdphlex"
5
+
6
+ # Example: Creating structured LLM prompts with custom XML-style tags
7
+ class LLMPrompt < MDPhlex::MD
8
+ # Register custom block elements for LLM prompt structure
9
+ register_block_element :system
10
+ register_block_element :tools
11
+ register_block_element :tool
12
+ register_element :description
13
+ register_block_element :parameters
14
+ register_block_element :param
15
+ register_block_element :examples
16
+ register_block_element :example
17
+ register_block_element :user
18
+ register_block_element :assistant
19
+ register_block_element :context
20
+ register_block_element :constraints
21
+
22
+ def initialize(task:, tools: [], examples: [], context: nil, constraints: [])
23
+ @task = task
24
+ @tools = tools
25
+ @examples = examples
26
+ @context = context
27
+ @constraints = constraints
28
+ end
29
+
30
+ def view_template
31
+ # System instructions
32
+ system do
33
+ plain "You are an AI assistant specialized in #{@task}."
34
+ plain "\n"
35
+ plain "You have access to tools and should use them when appropriate to help the user."
36
+ plain "\n"
37
+ plain "Always be helpful, accurate, and follow the constraints provided."
38
+ end
39
+
40
+ plain "\n"
41
+
42
+ # Context section
43
+ if @context
44
+ context do
45
+ plain @context
46
+ end
47
+ plain "\n"
48
+ end
49
+
50
+ # Constraints
51
+ if @constraints.any?
52
+ constraints do
53
+ @constraints.each do |constraint|
54
+ plain "- #{constraint}\n"
55
+ end
56
+ end
57
+ plain "\n"
58
+ end
59
+
60
+ # Available tools
61
+ if @tools.any?
62
+ tools do
63
+ @tools.each do |tool_def|
64
+ tool name: tool_def[:name], category: tool_def[:category] do
65
+ description { plain tool_def[:description] }
66
+
67
+ if tool_def[:parameters]&.any?
68
+ parameters do
69
+ tool_def[:parameters].each do |param|
70
+ param name: param[:name], type: param[:type], required: param[:required].to_s do
71
+ plain param[:description]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ plain "\n"
80
+ end
81
+
82
+ # Examples
83
+ if @examples.any?
84
+ examples do
85
+ @examples.each_with_index do |ex, i|
86
+ example id: (i + 1).to_s do
87
+ user { plain ex[:user] }
88
+ assistant { plain ex[:assistant] }
89
+ end
90
+ end
91
+ end
92
+ plain "\n"
93
+ end
94
+
95
+ # Instructions footer
96
+ h2 "Instructions"
97
+
98
+ p "When responding to user queries, follow the system guidelines above and use the available tools as needed."
99
+ end
100
+ end
101
+
102
+ # Example usage
103
+ if __FILE__ == $0
104
+ prompt = LLMPrompt.new(
105
+ task: "code review and refactoring",
106
+ context: "The user is working on a Ruby on Rails application and needs help improving code quality.",
107
+ constraints: [
108
+ "Focus on Ruby idioms and best practices",
109
+ "Consider performance implications",
110
+ "Suggest tests when appropriate",
111
+ "Keep explanations clear and concise",
112
+ ],
113
+ tools: [
114
+ {
115
+ name: "analyze_code",
116
+ category: "analysis",
117
+ description: "Analyzes code for potential improvements, security issues, and best practices",
118
+ parameters: [
119
+ {
120
+ name: "code",
121
+ type: "string",
122
+ required: true,
123
+ description: "The code snippet to analyze",
124
+ },
125
+ {
126
+ name: "language",
127
+ type: "string",
128
+ required: false,
129
+ description: "Programming language (defaults to auto-detect)",
130
+ },
131
+ {
132
+ name: "focus_areas",
133
+ type: "array",
134
+ required: false,
135
+ description: "Specific areas to focus on: performance, security, readability, testing",
136
+ },
137
+ ],
138
+ },
139
+ {
140
+ name: "suggest_refactoring",
141
+ category: "refactoring",
142
+ description: "Provides specific refactoring suggestions with before/after examples",
143
+ parameters: [
144
+ {
145
+ name: "code",
146
+ type: "string",
147
+ required: true,
148
+ description: "The code to refactor",
149
+ },
150
+ {
151
+ name: "refactoring_type",
152
+ type: "string",
153
+ required: false,
154
+ description: "Type of refactoring: extract_method, simplify_conditionals, remove_duplication",
155
+ },
156
+ ],
157
+ },
158
+ ],
159
+ examples: [
160
+ {
161
+ user: "Review this Ruby method for improvements:\n```ruby\ndef process_items(items)\n results = []\n items.each do |item|\n if item.valid?\n results << item.process\n end\n end\n results\nend\n```",
162
+ assistant: "I'll analyze your Ruby method and suggest improvements.\n\nThe method can be refactored to be more idiomatic and concise using Ruby's enumerable methods:\n\n```ruby\ndef process_items(items)\n items.select(&:valid?).map(&:process)\nend\n```\n\nKey improvements:\n1. **Use `select` and `map`**: More idiomatic than manual array building\n2. **Symbol-to-proc syntax**: `&:valid?` is cleaner than blocks\n3. **Method chaining**: Reads naturally left-to-right\n4. **No temporary variable**: Eliminates the `results` array\n\nThis refactored version is more functional, easier to test, and clearly expresses the intent.",
163
+ },
164
+ ]
165
+ )
166
+
167
+ # Generate the output once and store it
168
+ output = prompt.call
169
+
170
+ # Print to console
171
+ puts output
172
+
173
+ # Also save the output to a file for reference
174
+ File.write(
175
+ File.join(File.dirname(__FILE__), "llm_prompt_output.md"),
176
+ output
177
+ )
178
+
179
+ puts "\n---"
180
+ puts "Output saved to examples/llm_prompt_output.md"
181
+ end
@@ -0,0 +1,75 @@
1
+ <system>
2
+ You are an AI assistant specialized in code review and refactoring.
3
+ You have access to tools and should use them when appropriate to help the user.
4
+ Always be helpful, accurate, and follow the constraints provided.</system>
5
+
6
+ <context>
7
+ The user is working on a Ruby on Rails application and needs help improving code quality.</context>
8
+
9
+ <constraints>
10
+ - Focus on Ruby idioms and best practices
11
+ - Consider performance implications
12
+ - Suggest tests when appropriate
13
+ - Keep explanations clear and concise
14
+ </constraints>
15
+
16
+ <tools>
17
+ <tool name="analyze_code" category="analysis">
18
+ <description>Analyzes code for potential improvements, security issues, and best practices</description><parameters>
19
+ <param name="code" type="string" required="true">
20
+ The code snippet to analyze</param>
21
+ <param name="language" type="string" required="false">
22
+ Programming language (defaults to auto-detect)</param>
23
+ <param name="focus_areas" type="array" required="false">
24
+ Specific areas to focus on: performance, security, readability, testing</param>
25
+ </parameters>
26
+ </tool>
27
+ <tool name="suggest_refactoring" category="refactoring">
28
+ <description>Provides specific refactoring suggestions with before/after examples</description><parameters>
29
+ <param name="code" type="string" required="true">
30
+ The code to refactor</param>
31
+ <param name="refactoring_type" type="string" required="false">
32
+ Type of refactoring: extract_method, simplify_conditionals, remove_duplication</param>
33
+ </parameters>
34
+ </tool>
35
+ </tools>
36
+
37
+ <examples>
38
+ <example id="1">
39
+ <user>
40
+ Review this Ruby method for improvements:
41
+ ```ruby
42
+ def process_items(items)
43
+ results = []
44
+ items.each do |item|
45
+ if item.valid?
46
+ results << item.process
47
+ end
48
+ end
49
+ results
50
+ end
51
+ ```</user>
52
+ <assistant>
53
+ I'll analyze your Ruby method and suggest improvements.
54
+
55
+ The method can be refactored to be more idiomatic and concise using Ruby's enumerable methods:
56
+
57
+ ```ruby
58
+ def process_items(items)
59
+ items.select(&:valid?).map(&:process)
60
+ end
61
+ ```
62
+
63
+ Key improvements:
64
+ 1. **Use `select` and `map`**: More idiomatic than manual array building
65
+ 2. **Symbol-to-proc syntax**: `&:valid?` is cleaner than blocks
66
+ 3. **Method chaining**: Reads naturally left-to-right
67
+ 4. **No temporary variable**: Eliminates the `results` array
68
+
69
+ This refactored version is more functional, easier to test, and clearly expresses the intent.</assistant>
70
+ </example>
71
+ </examples>
72
+
73
+ ## Instructions
74
+ When responding to user queries, follow the system guidelines above and use the available tools as needed.
75
+
data/lib/mdphlex/md.rb CHANGED
@@ -22,7 +22,7 @@ module MDPhlex
22
22
 
23
23
  if attributes.length > 0
24
24
  attributes.each do |key, value|
25
- buffer << " #{key}=\"#{Phlex::Escape.html_escape(value.to_s)}\""
25
+ buffer << " #{key}=\"#{value}\""
26
26
  end
27
27
  end
28
28
 
@@ -299,5 +299,53 @@ module MDPhlex
299
299
  buffer << marker
300
300
  nil
301
301
  end
302
+
303
+ # Override __text__ to avoid HTML escaping since markdown allows raw HTML
304
+ private def __text__(content)
305
+ state = @_state
306
+ return true unless state.should_render?
307
+
308
+ case content
309
+ when String
310
+ state.buffer << content
311
+ when Symbol
312
+ state.buffer << content.name
313
+ when nil
314
+ nil
315
+ else
316
+ if (formatted_object = format_object(content))
317
+ state.buffer << formatted_object
318
+ else
319
+ return false
320
+ end
321
+ end
322
+
323
+ true
324
+ end
325
+
326
+ # Override __implicit_output__ to avoid HTML escaping
327
+ private def __implicit_output__(content)
328
+ state = @_state
329
+ return true unless state.should_render?
330
+
331
+ case content
332
+ when Phlex::SGML::SafeObject
333
+ state.buffer << content.to_s
334
+ when String
335
+ state.buffer << content
336
+ when Symbol
337
+ state.buffer << content.name
338
+ when nil
339
+ nil
340
+ else
341
+ if (formatted_object = format_object(content))
342
+ state.buffer << formatted_object
343
+ else
344
+ return false
345
+ end
346
+ end
347
+
348
+ true
349
+ end
302
350
  end
303
351
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MDPhlex
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ test "markdown does not escape HTML entities" do
4
+ component = Class.new(MDPhlex::MD) do
5
+ def view_template
6
+ p { "HTML tags: <strong>bold</strong> & <em>italic</em>" }
7
+ end
8
+ end
9
+
10
+ output = component.new.call
11
+ assert_equal output, "HTML tags: <strong>bold</strong> & <em>italic</em>\n\n"
12
+ end
13
+
14
+ test "markdown preserves raw HTML in plain text" do
15
+ component = Class.new(MDPhlex::MD) do
16
+ def view_template
17
+ p do
18
+ plain "Raw HTML: <div>content</div> & entities"
19
+ end
20
+ end
21
+ end
22
+
23
+ output = component.new.call
24
+ assert_equal output, "Raw HTML: <div>content</div> & entities\n\n"
25
+ end
26
+
27
+ test "markdown preserves HTML in inline elements" do
28
+ component = Class.new(MDPhlex::MD) do
29
+ def view_template
30
+ p do
31
+ strong { "<b>nested</b> & more" }
32
+ plain " "
33
+ em { "<i>italic</i>" }
34
+ end
35
+ end
36
+ end
37
+
38
+ output = component.new.call
39
+ assert_equal output, "**<b>nested</b> & more** *<i>italic</i>*\n\n"
40
+ end
41
+
42
+ test "custom elements do not escape attribute values" do
43
+ component = Class.new(MDPhlex::MD) do
44
+ register_block_element :custom_div, tag: "div"
45
+
46
+ def view_template
47
+ custom_div(class: "test & class", "data-value": "x > 5") do
48
+ p { "Content" }
49
+ end
50
+ end
51
+ end
52
+
53
+ output = component.new.call
54
+ assert_includes output, '<div class="test & class" data-value="x > 5">'
55
+ end
56
+
57
+ test "code blocks preserve HTML content" do
58
+ component = Class.new(MDPhlex::MD) do
59
+ def view_template
60
+ pre(language: "html") do
61
+ plain "<div class=\"example\">\n <p>HTML code</p>\n</div>"
62
+ end
63
+ end
64
+ end
65
+
66
+ output = component.new.call
67
+ expected = <<~MD
68
+ ```html
69
+ <div class="example">
70
+ <p>HTML code</p>
71
+ </div>
72
+ ```
73
+
74
+ MD
75
+ assert_equal output, expected
76
+ end
77
+
78
+ test "code blocks with JSON.pretty_generate preserve quotes and special characters" do
79
+ require "json"
80
+
81
+ component = Class.new(MDPhlex::MD) do
82
+ def view_template
83
+ pre language: "json" do
84
+ JSON.pretty_generate({ email: "user@example.com", password: "secure_password123" })
85
+ end
86
+ end
87
+ end
88
+
89
+ output = component.new.call
90
+ expected = <<~MD
91
+ ```json
92
+ {
93
+ "email": "user@example.com",
94
+ "password": "secure_password123"
95
+ }
96
+ ```
97
+
98
+ MD
99
+ assert_equal output, expected
100
+ end
101
+
102
+ test "code blocks preserve complex JSON with special characters" do
103
+ require "json"
104
+
105
+ component = Class.new(MDPhlex::MD) do
106
+ def view_template
107
+ pre language: "json" do
108
+ JSON.pretty_generate({
109
+ users: [
110
+ { name: "John & Jane", role: "admin" },
111
+ { name: "Bob <Smith>", role: "user" },
112
+ ],
113
+ config: {
114
+ api_key: "key-with-special-chars-!@#$%^&*()",
115
+ html_template: "<div class=\"test\">Content</div>",
116
+ },
117
+ })
118
+ end
119
+ end
120
+ end
121
+
122
+ output = component.new.call
123
+ expected = <<~MD
124
+ ```json
125
+ {
126
+ "users": [
127
+ {
128
+ "name": "John & Jane",
129
+ "role": "admin"
130
+ },
131
+ {
132
+ "name": "Bob <Smith>",
133
+ "role": "user"
134
+ }
135
+ ],
136
+ "config": {
137
+ "api_key": "key-with-special-chars-!@#$%^&*()",
138
+ "html_template": "<div class=\\"test\\">Content</div>"
139
+ }
140
+ }
141
+ ```
142
+
143
+ MD
144
+ assert_equal output, expected
145
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mdphlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Emde
@@ -49,9 +49,15 @@ files:
49
49
  - README.md
50
50
  - Rakefile
51
51
  - config/quickdraw.rb
52
+ - examples/custom_elements.rb
53
+ - examples/custom_elements_output.md
54
+ - examples/html_in_markdown.rb
55
+ - examples/llm_prompt.rb
56
+ - examples/llm_prompt_output.md
52
57
  - lib/mdphlex.rb
53
58
  - lib/mdphlex/md.rb
54
59
  - lib/mdphlex/version.rb
60
+ - quickdraw/html_escaping.test.rb
55
61
  - quickdraw/interoperability.test.rb
56
62
  - quickdraw/mdphlex.test.rb
57
63
  homepage: https://github.com/martinemde/mdphlex