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 +4 -4
- data/README.md +116 -39
- data/examples/custom_elements.rb +106 -0
- data/examples/custom_elements_output.md +60 -0
- data/examples/html_in_markdown.rb +41 -0
- data/examples/llm_prompt.rb +181 -0
- data/examples/llm_prompt_output.md +75 -0
- data/lib/mdphlex/md.rb +49 -1
- data/lib/mdphlex/version.rb +1 -1
- data/quickdraw/html_escaping.test.rb +145 -0
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df56be38ad404b1762ae330d4f59f21de2ab56ed0248688a8134890f61f407e7
|
4
|
+
data.tar.gz: 4bde2036075332276a3d3882cdabf931c97be472f67612a72c82a7cb513607be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8835c134e433334f033dc01dc8614476dd281c2c2be6223cb5dda92dabebdcad101bb1d2bb6f3806a6c792bd69b8a639410739ce417cf6fee90d81930191ee5
|
7
|
+
data.tar.gz: db3062396e99e68aa12ffbbe3c950fbf83c1e8549a88b6d6e0b289d4b39791975fd5ff7be99f983eb4a9fd2658bcacdcb6095053e3d5420807e1a49a2aef96cb
|
data/README.md
CHANGED
@@ -1,74 +1,152 @@
|
|
1
1
|
# MDPhlex
|
2
2
|
|
3
|
-
|
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
|
-
|
13
|
+
~~~ruby
|
10
14
|
gem 'mdphlex'
|
11
|
-
|
15
|
+
~~~
|
12
16
|
|
13
|
-
##
|
17
|
+
## Creating LLM Prompts with MDPhlex
|
14
18
|
|
15
|
-
|
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
|
-
|
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
|
-
|
62
|
+
puts prompt.call
|
63
|
+
~~~
|
23
64
|
|
24
|
-
|
65
|
+
This outputs clean, structured markdown perfect for LLMs:
|
25
66
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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 "
|
41
|
-
a(href: "https://
|
42
|
-
plain "
|
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
|
-
|
48
|
-
|
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
|
-
|
125
|
+
puts article.call
|
126
|
+
~~~
|
52
127
|
|
53
|
-
|
54
|
-
# Hello, World!
|
128
|
+
Outputs:
|
55
129
|
|
56
|
-
|
130
|
+
~~~markdown
|
131
|
+
# Getting Started with Ruby
|
57
132
|
|
58
|
-
##
|
133
|
+
## Variables
|
59
134
|
|
60
|
-
|
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
|
-
|
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
|
-
|
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
|
-
- **
|
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: © & <div>"
|
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}=\"#{
|
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
|
data/lib/mdphlex/version.rb
CHANGED
@@ -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.
|
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
|