prompt_manager 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/COMMITS.md +196 -0
  4. data/README.md +485 -203
  5. data/docs/.keep +0 -0
  6. data/docs/advanced/custom-keywords.md +421 -0
  7. data/docs/advanced/dynamic-directives.md +535 -0
  8. data/docs/advanced/performance.md +612 -0
  9. data/docs/advanced/search-integration.md +635 -0
  10. data/docs/api/configuration.md +355 -0
  11. data/docs/api/directive-processor.md +431 -0
  12. data/docs/api/prompt-class.md +354 -0
  13. data/docs/api/storage-adapters.md +462 -0
  14. data/docs/assets/favicon.ico +1 -0
  15. data/docs/assets/logo.svg +24 -0
  16. data/docs/core-features/comments.md +48 -0
  17. data/docs/core-features/directive-processing.md +38 -0
  18. data/docs/core-features/erb-integration.md +68 -0
  19. data/docs/core-features/error-handling.md +197 -0
  20. data/docs/core-features/parameter-history.md +76 -0
  21. data/docs/core-features/parameterized-prompts.md +500 -0
  22. data/docs/core-features/shell-integration.md +79 -0
  23. data/docs/development/architecture.md +544 -0
  24. data/docs/development/contributing.md +425 -0
  25. data/docs/development/roadmap.md +234 -0
  26. data/docs/development/testing.md +822 -0
  27. data/docs/examples/advanced.md +523 -0
  28. data/docs/examples/basic.md +688 -0
  29. data/docs/examples/real-world.md +776 -0
  30. data/docs/examples.md +337 -0
  31. data/docs/getting-started/basic-concepts.md +318 -0
  32. data/docs/getting-started/installation.md +97 -0
  33. data/docs/getting-started/quick-start.md +256 -0
  34. data/docs/index.md +230 -0
  35. data/docs/migration/v0.9.0.md +459 -0
  36. data/docs/migration/v1.0.0.md +591 -0
  37. data/docs/storage/activerecord-adapter.md +348 -0
  38. data/docs/storage/custom-adapters.md +176 -0
  39. data/docs/storage/filesystem-adapter.md +236 -0
  40. data/docs/storage/overview.md +427 -0
  41. data/examples/advanced_integrations.rb +52 -0
  42. data/examples/prompts_dir/advanced_demo.txt +79 -0
  43. data/examples/prompts_dir/directive_example.json +1 -0
  44. data/examples/prompts_dir/directive_example.txt +8 -0
  45. data/examples/prompts_dir/todo.json +1 -1
  46. data/improvement_plan.md +996 -0
  47. data/lib/prompt_manager/storage/file_system_adapter.rb +8 -2
  48. data/lib/prompt_manager/version.rb +1 -1
  49. data/mkdocs.yml +146 -0
  50. data/prompt_manager_logo.png +0 -0
  51. metadata +46 -3
  52. data/LICENSE.txt +0 -21
data/docs/.keep ADDED
File without changes
@@ -0,0 +1,421 @@
1
+ # Custom Keywords
2
+
3
+ PromptManager allows you to define custom keywords and parameter patterns beyond the standard `[PARAMETER_NAME]` syntax.
4
+
5
+ ## Overview
6
+
7
+ Custom keywords enable you to create domain-specific parameter patterns, validation rules, and transformation logic for your prompts.
8
+
9
+ ## Defining Custom Keywords
10
+
11
+ ### Basic Custom Keywords
12
+
13
+ ```ruby
14
+ PromptManager.configure do |config|
15
+ config.custom_keywords = {
16
+ 'EMAIL' => {
17
+ pattern: /\{EMAIL:([^}]+)\}/,
18
+ validator: ->(value) { value.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i) },
19
+ transformer: ->(value) { value.downcase }
20
+ },
21
+
22
+ 'PHONE' => {
23
+ pattern: /\{PHONE:([^}]+)\}/,
24
+ validator: ->(value) { value.match?(/\A\+?[\d\-\(\)\s]+\z/) },
25
+ transformer: ->(value) { value.gsub(/[^\d+]/, '') }
26
+ },
27
+
28
+ 'CURRENCY' => {
29
+ pattern: /\{CURRENCY:([^}]+):([A-Z]{3})\}/,
30
+ transformer: ->(amount, currency) {
31
+ formatted = sprintf('%.2f', amount.to_f)
32
+ case currency
33
+ when 'USD' then "$#{formatted}"
34
+ when 'EUR' then "€#{formatted}"
35
+ else "#{formatted} #{currency}"
36
+ end
37
+ }
38
+ }
39
+ }
40
+ end
41
+ ```
42
+
43
+ ### Usage in Prompts
44
+
45
+ ```text
46
+ # email_template.txt
47
+ Dear Customer,
48
+
49
+ Your account {EMAIL:customer_email} has been updated.
50
+ Please contact us at {PHONE:support_phone} if you have questions.
51
+ Your order total is {CURRENCY:order_amount:USD}.
52
+
53
+ Best regards,
54
+ Support Team
55
+ ```
56
+
57
+ ```ruby
58
+ prompt = PromptManager::Prompt.new(id: 'email_template')
59
+ result = prompt.render(
60
+ customer_email: 'JOHN.DOE@EXAMPLE.COM',
61
+ support_phone: '1-800-555-0123',
62
+ order_amount: 123.45
63
+ )
64
+
65
+ # Result:
66
+ # Dear Customer,
67
+ # Your account john.doe@example.com has been updated.
68
+ # Please contact us at +18005550123 if you have questions.
69
+ # Your order total is $123.45.
70
+ ```
71
+
72
+ ## Advanced Custom Keywords
73
+
74
+ ### Conditional Keywords
75
+
76
+ ```ruby
77
+ config.custom_keywords['IF_PREMIUM'] = {
78
+ pattern: /\{IF_PREMIUM:([^}]+)\}/,
79
+ processor: ->(content, context) {
80
+ user_tier = context.dig(:parameters, :user_tier)
81
+ user_tier == 'premium' ? content : ''
82
+ }
83
+ }
84
+ ```
85
+
86
+ ```text
87
+ # Usage in prompt:
88
+ {IF_PREMIUM:🌟 Thank you for being a Premium member!}
89
+ ```
90
+
91
+ ### Loop Keywords
92
+
93
+ ```ruby
94
+ config.custom_keywords['FOREACH'] = {
95
+ pattern: /\{FOREACH:([^:]+):([^}]+)\}/,
96
+ processor: ->(array_name, template, context) {
97
+ array_data = context.dig(:parameters, array_name.to_sym) || []
98
+
99
+ array_data.map.with_index do |item, index|
100
+ item_template = template.gsub(/\{ITEM\.(\w+)\}/) { item[Regexp.last_match(1).to_sym] }
101
+ item_template.gsub(/\{INDEX\}/, index.to_s)
102
+ end.join("\n")
103
+ }
104
+ }
105
+ ```
106
+
107
+ ```text
108
+ # Usage in prompt:
109
+ Your order items:
110
+ {FOREACH:order_items:- {ITEM.name}: ${ITEM.price}}
111
+ ```
112
+
113
+ ### Date/Time Keywords
114
+
115
+ ```ruby
116
+ config.custom_keywords['DATE'] = {
117
+ pattern: /\{DATE:([^:}]+)(?::([^}]+))?\}/,
118
+ processor: ->(format, offset, context) {
119
+ base_date = Time.current
120
+
121
+ if offset
122
+ case offset
123
+ when /\+(\d+)d/ then base_date += Regexp.last_match(1).to_i.days
124
+ when /-(\d+)d/ then base_date -= Regexp.last_match(1).to_i.days
125
+ when /\+(\d+)w/ then base_date += Regexp.last_match(1).to_i.weeks
126
+ end
127
+ end
128
+
129
+ base_date.strftime(format)
130
+ }
131
+ }
132
+ ```
133
+
134
+ ```text
135
+ # Usage in prompt:
136
+ Today: {DATE:%B %d, %Y}
137
+ Next week: {DATE:%B %d, %Y:+7d}
138
+ Last month: {DATE:%B %Y:-1m}
139
+ ```
140
+
141
+ ## Validation and Error Handling
142
+
143
+ ### Parameter Validation
144
+
145
+ ```ruby
146
+ config.custom_keywords['VALIDATED_EMAIL'] = {
147
+ pattern: /\{EMAIL:([^}]+)\}/,
148
+ validator: ->(email) {
149
+ return false unless email.is_a?(String)
150
+ return false unless email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
151
+
152
+ # Additional validation
153
+ domain = email.split('@').last
154
+ !['tempmail.com', 'throwaway.email'].include?(domain)
155
+ },
156
+ error_message: 'Please provide a valid email address from an allowed domain'
157
+ }
158
+ ```
159
+
160
+ ### Custom Error Handling
161
+
162
+ ```ruby
163
+ class CustomKeywordProcessor
164
+ def self.process_keyword(keyword, args, context)
165
+ case keyword
166
+ when 'SECURE_DATA'
167
+ return '[REDACTED]' if context[:redact_sensitive_data]
168
+ args.first
169
+
170
+ when 'API_CALL'
171
+ begin
172
+ api_result = make_api_call(args.first)
173
+ api_result['data']
174
+ rescue => e
175
+ Rails.logger.error "API call failed: #{e.message}"
176
+ '[API_ERROR]'
177
+ end
178
+
179
+ else
180
+ raise PromptManager::UnknownKeywordError.new("Unknown keyword: #{keyword}")
181
+ end
182
+ end
183
+ end
184
+ ```
185
+
186
+ ## Dynamic Keywords
187
+
188
+ ### Runtime Registration
189
+
190
+ ```ruby
191
+ class DynamicKeywordManager
192
+ def self.register_for_user(user)
193
+ PromptManager.configure do |config|
194
+ config.custom_keywords ||= {}
195
+
196
+ # User-specific keywords
197
+ config.custom_keywords["USER_#{user.id}_NAME"] = {
198
+ pattern: /\{USER_NAME\}/,
199
+ processor: ->(*args, context) { user.full_name }
200
+ }
201
+
202
+ # Role-based keywords
203
+ if user.admin?
204
+ config.custom_keywords['ADMIN_PANEL'] = {
205
+ pattern: /\{ADMIN_PANEL:([^}]+)\}/,
206
+ processor: ->(content, context) { content }
207
+ }
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ # Usage
214
+ DynamicKeywordManager.register_for_user(current_user)
215
+ ```
216
+
217
+ ### Database-Driven Keywords
218
+
219
+ ```ruby
220
+ class DatabaseKeywordLoader
221
+ def self.load_keywords
222
+ CustomKeyword.active.each do |keyword_record|
223
+ PromptManager.configure do |config|
224
+ config.custom_keywords[keyword_record.name] = {
225
+ pattern: Regexp.new(keyword_record.pattern),
226
+ processor: eval(keyword_record.processor_code),
227
+ description: keyword_record.description
228
+ }
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ # Load keywords on application startup
235
+ DatabaseKeywordLoader.load_keywords
236
+ ```
237
+
238
+ ## Integration with ERB
239
+
240
+ ### ERB-Enhanced Keywords
241
+
242
+ ```ruby
243
+ config.custom_keywords['ERB_EVAL'] = {
244
+ pattern: /\{ERB:([^}]+)\}/,
245
+ processor: ->(erb_code, context) {
246
+ template = ERB.new(erb_code)
247
+ template.result(binding)
248
+ }
249
+ }
250
+ ```
251
+
252
+ ```text
253
+ # Usage in prompt:
254
+ Current time: {ERB:<%= Time.current.strftime('%H:%M') %>}
255
+ Random number: {ERB:<%= rand(100) %>}
256
+ ```
257
+
258
+ ### Template Inheritance
259
+
260
+ ```ruby
261
+ config.custom_keywords['PARENT'] = {
262
+ pattern: /\{PARENT:([^}]+)\}/,
263
+ processor: ->(parent_template, context) {
264
+ parent_prompt = PromptManager::Prompt.new(id: parent_template)
265
+ parent_prompt.render(context[:parameters])
266
+ }
267
+ }
268
+ ```
269
+
270
+ ## Performance Optimization
271
+
272
+ ### Keyword Caching
273
+
274
+ ```ruby
275
+ class CachedKeywordProcessor
276
+ @cache = {}
277
+
278
+ def self.process_with_cache(keyword, args, context, cache_ttl: 300)
279
+ cache_key = "#{keyword}:#{args.join(':')}:#{context.hash}"
280
+
281
+ cached_result = @cache[cache_key]
282
+ if cached_result && (Time.current - cached_result[:timestamp]) < cache_ttl
283
+ return cached_result[:value]
284
+ end
285
+
286
+ result = process_keyword(keyword, args, context)
287
+ @cache[cache_key] = {
288
+ value: result,
289
+ timestamp: Time.current
290
+ }
291
+
292
+ result
293
+ end
294
+ end
295
+ ```
296
+
297
+ ### Lazy Evaluation
298
+
299
+ ```ruby
300
+ config.custom_keywords['LAZY_LOAD'] = {
301
+ pattern: /\{LAZY:([^}]+)\}/,
302
+ processor: ->(data_source, context) {
303
+ # Only load data when actually needed
304
+ -> { expensive_data_load(data_source) }
305
+ }
306
+ }
307
+ ```
308
+
309
+ ## Testing Custom Keywords
310
+
311
+ ### RSpec Examples
312
+
313
+ ```ruby
314
+ describe 'Custom Keywords' do
315
+ before do
316
+ PromptManager.configure do |config|
317
+ config.custom_keywords = {
318
+ 'TEST_UPPER' => {
319
+ pattern: /\{UPPER:([^}]+)\}/,
320
+ transformer: ->(value) { value.upcase }
321
+ }
322
+ }
323
+ end
324
+ end
325
+
326
+ it 'processes custom keyword' do
327
+ prompt = PromptManager::Prompt.new(id: 'test')
328
+ allow(prompt.storage).to receive(:read).and_return('Hello {UPPER:world}')
329
+
330
+ result = prompt.render
331
+ expect(result).to eq 'Hello WORLD'
332
+ end
333
+
334
+ it 'validates custom keyword input' do
335
+ PromptManager.configure do |config|
336
+ config.custom_keywords['VALIDATED'] = {
337
+ pattern: /\{VALIDATED:([^}]+)\}/,
338
+ validator: ->(value) { value.length > 3 },
339
+ error_message: 'Value must be longer than 3 characters'
340
+ }
341
+ end
342
+
343
+ prompt = PromptManager::Prompt.new(id: 'test')
344
+ allow(prompt.storage).to receive(:read).and_return('Hello {VALIDATED:ab}')
345
+
346
+ expect {
347
+ prompt.render
348
+ }.to raise_error(PromptManager::ValidationError, /Value must be longer than 3 characters/)
349
+ end
350
+ end
351
+ ```
352
+
353
+ ## Real-World Examples
354
+
355
+ ### E-commerce Keywords
356
+
357
+ ```ruby
358
+ PromptManager.configure do |config|
359
+ config.custom_keywords.merge!({
360
+ 'PRICE' => {
361
+ pattern: /\{PRICE:([^:}]+)(?::([A-Z]{3}))?\}/,
362
+ processor: ->(amount, currency, context) {
363
+ currency ||= 'USD'
364
+ user_country = context.dig(:parameters, :user_country)
365
+
366
+ # Adjust currency based on user location
367
+ case user_country
368
+ when 'GB' then currency = 'GBP'
369
+ when 'DE', 'FR', 'IT' then currency = 'EUR'
370
+ end
371
+
372
+ CurrencyFormatter.format(amount.to_f, currency)
373
+ }
374
+ },
375
+
376
+ 'INVENTORY_STATUS' => {
377
+ pattern: /\{STOCK:([^}]+)\}/,
378
+ processor: ->(product_id, context) {
379
+ stock_level = InventoryService.check_stock(product_id)
380
+
381
+ case stock_level
382
+ when 0 then '❌ Out of Stock'
383
+ when 1..5 then '⚠️ Low Stock'
384
+ else '✅ In Stock'
385
+ end
386
+ }
387
+ }
388
+ })
389
+ end
390
+ ```
391
+
392
+ ### Localization Keywords
393
+
394
+ ```ruby
395
+ config.custom_keywords['TRANSLATE'] = {
396
+ pattern: /\{T:([^:}]+)(?::([a-z]{2}))?\}/,
397
+ processor: ->(key, locale, context) {
398
+ locale ||= context.dig(:parameters, :locale) || 'en'
399
+ I18n.with_locale(locale) { I18n.t(key) }
400
+ }
401
+ }
402
+
403
+ config.custom_keywords['PLURALIZE'] = {
404
+ pattern: /\{PLURAL:([^:]+):([^:]+):([^}]+)\}/,
405
+ processor: ->(count, singular, plural, context) {
406
+ count_val = context.dig(:parameters, count.to_sym) || 0
407
+ count_val.to_i == 1 ? singular : plural
408
+ }
409
+ }
410
+ ```
411
+
412
+ ## Best Practices
413
+
414
+ 1. **Descriptive Names**: Use clear, descriptive names for custom keywords
415
+ 2. **Validation**: Always validate input parameters
416
+ 3. **Error Handling**: Provide meaningful error messages
417
+ 4. **Documentation**: Document keyword syntax and behavior
418
+ 5. **Performance**: Cache expensive operations
419
+ 6. **Security**: Sanitize user input in keyword processors
420
+ 7. **Testing**: Write comprehensive tests for custom keywords
421
+ 8. **Consistency**: Follow consistent naming conventions across keywords