probatio_diabolica 0.1.0
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 +7 -0
- data/Gemfile +3 -0
- data/README.md +249 -0
- data/bin/prd +82 -0
- data/lib/pr_d/formatters/formatter.rb +105 -0
- data/lib/pr_d/formatters/html_formatter.rb +320 -0
- data/lib/pr_d/formatters/json_formatter.rb +194 -0
- data/lib/pr_d/formatters/pdf_formatter.rb +236 -0
- data/lib/pr_d/formatters/simple_formatter.rb +160 -0
- data/lib/pr_d/formatters.rb +4 -0
- data/lib/pr_d/helpers/chrome_helper.rb +98 -0
- data/lib/pr_d/helpers/source_code_helper.rb +66 -0
- data/lib/pr_d/matchers/all_matcher.rb +10 -0
- data/lib/pr_d/matchers/be_matcher.rb +10 -0
- data/lib/pr_d/matchers/eq_matcher.rb +10 -0
- data/lib/pr_d/matchers/have_matcher.rb +10 -0
- data/lib/pr_d/matchers/includes_matcher.rb +20 -0
- data/lib/pr_d/matchers/llm_matcher.rb +93 -0
- data/lib/pr_d/matchers/matcher.rb +15 -0
- data/lib/pr_d/matchers.rb +4 -0
- data/lib/pr_d/version.rb +5 -0
- data/lib/pr_d.rb +195 -0
- data/lib/probatio_diabolica.rb +4 -0
- data/probatio_diabolica.gemspec +38 -0
- metadata +137 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c53898870646f0712544d5bc8998715622cffb4981711737757c26d3f8e41ad7
|
|
4
|
+
data.tar.gz: e3a828ea56faac76e54443c1292235a849bbfd0aca1ff9745c94b3f53190650e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 38aa1da254d62abeed89a013e82a1e87b63970c4faedf0fb858d140e3a83a8c61a2fc16fff6f073e0cafd81260f762a7fa0ab294d7a879f20c27c204d9549d50
|
|
7
|
+
data.tar.gz: 5e108178e7da9d4ec7605959f96c6b782754a068bcec05faedac09bf93c709fc559cf1c4fb6e0f71aaa6110f396ec2a2f549092ab1f17581efd7d8a92a5dd8d9
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Probatio Diabolica
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="diable.png" alt="Logo Probatio Diabolica" width="260" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
A Ruby DSL-based testing framework with classic matchers and LLM-powered matchers (text and image).
|
|
8
|
+
|
|
9
|
+
This project is experimental and not production-ready.
|
|
10
|
+
|
|
11
|
+
## What this project does
|
|
12
|
+
|
|
13
|
+
`probatio_diabolica` runs `*_spec.rb` files through a custom runtime (`PrD::Runtime`) with an RSpec-like syntax:
|
|
14
|
+
|
|
15
|
+
- `describe`, `context`, `it`, `pending`, `let`, `subject`
|
|
16
|
+
- `expect(...).to(...)` and `expect(...).not_to(...)`
|
|
17
|
+
- standard matchers (`eq`, `be`, `includes`, `have`, `all`)
|
|
18
|
+
- LLM matcher `satisfy(...)` to validate natural-language conditions
|
|
19
|
+
|
|
20
|
+
Tests are evaluated with `instance_eval` (not through RSpec).
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### From the gem
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem 'probatio_diabolica'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
In Ruby code, you can load it with:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "probatio_diabolica"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### From this repository (local development)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bundle install
|
|
46
|
+
bundle exec prd examples/basics_spec.rb
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Build and install as a gem
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# build the package
|
|
53
|
+
gem build probatio_diabolica.gemspec
|
|
54
|
+
|
|
55
|
+
# install locally from the built gem
|
|
56
|
+
gem install ./probatio_diabolica-*.gem
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
After installation, you can run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
prd examples/basics_spec.rb
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If `prd` is not found, add your gem bin directory to `PATH`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
export PATH="$(ruby -e 'print Gem.user_dir')/bin:$PATH"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Configuration LLM
|
|
72
|
+
|
|
73
|
+
The runtime automatically loads `prd_helper.rb` if present (or a file passed with `-c`).
|
|
74
|
+
|
|
75
|
+
Minimal example:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# prd_helper.rb
|
|
79
|
+
RubyLLM.configure do |config|
|
|
80
|
+
config.openrouter_api_key = ENV['OPENROUTER_API_KEY']
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Without valid configuration, tests using `satisfy(...)` will fail.
|
|
85
|
+
|
|
86
|
+
## Running tests
|
|
87
|
+
|
|
88
|
+
Command:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
prd <file_or_directory> [options]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
From source checkout (without gem install), use:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bundle exec prd <file_or_directory> [options]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Supported options:
|
|
101
|
+
|
|
102
|
+
- `-o, --out DIR` writes output to `DIR/report.qd` (otherwise stdout)
|
|
103
|
+
- `-c, --config FILE` Ruby config file to require
|
|
104
|
+
- `-t, --type TYPE` formatter type (`simple` by default; supports `simple`, `html`, `json`, `pdf`)
|
|
105
|
+
- `-m, --mode MODE` output verbosity mode (`verbose` by default; supports `verbose`, `synthetic`)
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# single file
|
|
111
|
+
bundle exec prd examples/basics_spec.rb
|
|
112
|
+
|
|
113
|
+
# all *_spec.rb files in a directory
|
|
114
|
+
bundle exec prd examples
|
|
115
|
+
|
|
116
|
+
# HTML report
|
|
117
|
+
bundle exec prd examples/image_spec.rb -t html -o ./tmp
|
|
118
|
+
|
|
119
|
+
# compact synthetic output on console
|
|
120
|
+
bundle exec prd examples/basics_spec.rb --mode synthetic
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Available DSL
|
|
124
|
+
|
|
125
|
+
### Structure
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
describe 'My domain' do
|
|
129
|
+
context 'my context' do
|
|
130
|
+
let(:value) { 5 }
|
|
131
|
+
subject { 'hello' }
|
|
132
|
+
|
|
133
|
+
it 'runs an assertion' do
|
|
134
|
+
expect(value).to eq(5)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
pending 'test to implement later'
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Assertions
|
|
143
|
+
|
|
144
|
+
- `expect(actual).to matcher`
|
|
145
|
+
- `expect(actual).not_to matcher`
|
|
146
|
+
- `expect { |subject| ... }.to matcher`
|
|
147
|
+
- `expect.to matcher` (uses `subject`)
|
|
148
|
+
|
|
149
|
+
### Matchers
|
|
150
|
+
|
|
151
|
+
- `eq(expected)` equality with `==`
|
|
152
|
+
- `be(expected)` object identity (`equal?`)
|
|
153
|
+
- `includes(expected)` inclusion for `String`, `Array`, `File`, `PDF::Reader`
|
|
154
|
+
- `have(expected)` alias inclusion via `include?`
|
|
155
|
+
- `all(proc)` checks all elements against a block
|
|
156
|
+
- `satisfy(natural_language_condition)` LLM-based validation
|
|
157
|
+
|
|
158
|
+
### Browser helpers (Ferrum)
|
|
159
|
+
|
|
160
|
+
`PrD::Runtime` exposes helpers to test content loaded in Chrome:
|
|
161
|
+
|
|
162
|
+
- `screen(at:, width:, height:, warmup_time:)` captures a PNG and returns a `File`
|
|
163
|
+
- `text(at:, css:, warmup_time:)` extracts a CSS node into a `.txt` file and returns a `File`
|
|
164
|
+
- `network(at:, warmup_time:)` returns Ferrum network traffic
|
|
165
|
+
- `network_urls(at:, warmup_time:)` returns traffic URLs
|
|
166
|
+
- `pdf(at:, warmup_time:)` generates a PDF and returns a `PDF::Reader`
|
|
167
|
+
- `html(at:, warmup_time:)` returns HTML (`browser.body`)
|
|
168
|
+
|
|
169
|
+
Prerequisites:
|
|
170
|
+
|
|
171
|
+
- Chrome/Chromium must be installed.
|
|
172
|
+
- The `ferrum` gem is optional and only required for these helpers.
|
|
173
|
+
- Add `gem 'ferrum'` to your Gemfile, or install it with `gem install ferrum`.
|
|
174
|
+
- If it is missing, an explicit `LoadError` is raised on the first browser helper call.
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
it 'checks dynamic content loaded in browser' do
|
|
180
|
+
page_text = text(at: 'https://example.com', css: 'main')
|
|
181
|
+
expect(page_text).to(includes('Example Domain'))
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Source code helper (Prism)
|
|
186
|
+
|
|
187
|
+
`source_code(...)` uses the `prism` gem to parse Ruby source and extract class/method code.
|
|
188
|
+
|
|
189
|
+
Prerequisites:
|
|
190
|
+
|
|
191
|
+
- The `prism` gem is optional and only required for `source_code(...)`.
|
|
192
|
+
- Add `gem 'prism'` to your Gemfile, or install it with `gem install prism`.
|
|
193
|
+
- If it is missing, an explicit `LoadError` is raised on the first `source_code(...)` call.
|
|
194
|
+
|
|
195
|
+
## LLM models
|
|
196
|
+
|
|
197
|
+
You can set a model at `context` or `it` level:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
context 'SQL checks', model: 'qwen/qwen-2.5-72b-instruct:free' do
|
|
201
|
+
it 'accepts a valid query' do
|
|
202
|
+
expect('SELECT * FROM users').to satisfy('This statement is valid SQL.')
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The runtime keeps a model stack (`it` can temporarily override the parent `context` model).
|
|
208
|
+
|
|
209
|
+
## Formatters
|
|
210
|
+
|
|
211
|
+
- `PrD::Formatters::SimpleFormatter` (text console output)
|
|
212
|
+
- `PrD::Formatters::HtmlFormatter` (simple HTML output)
|
|
213
|
+
- `PrD::Formatters::JsonFormatter` exists in code but is not currently exposed by `bin/prd`
|
|
214
|
+
|
|
215
|
+
### Subject rendering policy (best effort)
|
|
216
|
+
|
|
217
|
+
When you define a `subject`, each formatter tries to render it in the most useful way for its medium:
|
|
218
|
+
|
|
219
|
+
- `SimpleFormatter`:
|
|
220
|
+
- renders readable text in terminal
|
|
221
|
+
- for files, prints a textual representation (for example path, file preview for `.txt`)
|
|
222
|
+
- `HtmlFormatter`:
|
|
223
|
+
- renders text values directly
|
|
224
|
+
- for image files (`.png`, `.jpg`, `.jpeg`), embeds the image in the report
|
|
225
|
+
- for PDF subjects (`File` `.pdf` or `PDF::Reader`), embeds the PDF with a `data:application/pdf;base64,...` URI
|
|
226
|
+
- `PdfFormatter`:
|
|
227
|
+
- renders text values as report lines
|
|
228
|
+
- for image files (`.png`, `.jpg`, `.jpeg`), inserts the image directly in the PDF report
|
|
229
|
+
- `JsonFormatter`:
|
|
230
|
+
- keeps a structured representation for machine processing
|
|
231
|
+
- `File` values (images, PDFs, text files, etc.) are embedded as base64 payloads
|
|
232
|
+
- `PDF::Reader` values are also embedded as base64 (`application/pdf`)
|
|
233
|
+
|
|
234
|
+
The goal is to preserve readability and report size while surfacing the richest representation each formatter can reasonably support.
|
|
235
|
+
|
|
236
|
+
## Useful references in this repository
|
|
237
|
+
|
|
238
|
+
- Basic example: `examples/basics_spec.rb`
|
|
239
|
+
- Code analysis example: `examples/code_example_spec.rb`
|
|
240
|
+
- Image example: `examples/image_spec.rb`
|
|
241
|
+
- Browser example: `examples/browser_spec.rb`
|
|
242
|
+
- CLI entrypoint: `bin/prd`
|
|
243
|
+
- DSL runtime: `lib/pr_d.rb`
|
|
244
|
+
|
|
245
|
+
## Current limitations
|
|
246
|
+
|
|
247
|
+
- Work in progress, API may change.
|
|
248
|
+
- Strong dependency on an LLM provider for `satisfy`.
|
|
249
|
+
- `-o` output is always written to a file named `report.qd`.
|
data/bin/prd
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'pr_d'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
require_relative '../lib/pr_d'
|
|
7
|
+
end
|
|
8
|
+
require 'optparse'
|
|
9
|
+
|
|
10
|
+
options = {}
|
|
11
|
+
OptionParser
|
|
12
|
+
.new do |opts|
|
|
13
|
+
opts.banner = 'Usage: prd [options] FILE_OR_DIR'
|
|
14
|
+
opts.on('-o', '--out DIR', 'Specify the output directory') { |dir| options[:out] = dir }
|
|
15
|
+
opts.on('-c', '--config FILE', 'Specify the config file') { |file| options[:config] = file }
|
|
16
|
+
opts.on('-t', '--type TYPE', 'Specify the formatter type (simple, html, json, pdf)') do |type|
|
|
17
|
+
options[:formatter] = type
|
|
18
|
+
end
|
|
19
|
+
opts.on('-m', '--mode MODE', 'Specify output mode (verbose, synthetic)') do |mode|
|
|
20
|
+
options[:mode] = mode
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
.parse!
|
|
24
|
+
|
|
25
|
+
input_path = ARGV.shift
|
|
26
|
+
raise 'No tests found. Please specify a test file or directory (e.g. prd examples/basics_spec.rb).' if input_path.nil? || input_path.empty?
|
|
27
|
+
raise "Unexpected arguments: #{ARGV.join(' ')}" unless ARGV.empty?
|
|
28
|
+
raise "Path not found: #{input_path}" unless File.exist?(input_path)
|
|
29
|
+
|
|
30
|
+
tests =
|
|
31
|
+
if File.directory?(input_path)
|
|
32
|
+
files = Dir[File.join(input_path, '**', '*_spec.rb')].sort
|
|
33
|
+
raise "No spec files found in directory: #{input_path}" if files.empty?
|
|
34
|
+
files.map { |file| File.read(file) }
|
|
35
|
+
elsif File.file?(input_path)
|
|
36
|
+
[File.read(input_path)]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
io =
|
|
40
|
+
if options[:out].nil?
|
|
41
|
+
raise 'PDF formatter requires --out to write a binary report file.' if options[:formatter] == 'pdf'
|
|
42
|
+
STDOUT
|
|
43
|
+
else
|
|
44
|
+
Dir.mkdir(options[:out]) unless Dir.exist?(options[:out])
|
|
45
|
+
report_filename = case options[:formatter]
|
|
46
|
+
when 'html'
|
|
47
|
+
'report.html'
|
|
48
|
+
when 'pdf'
|
|
49
|
+
'report.pdf'
|
|
50
|
+
else
|
|
51
|
+
'report.qd'
|
|
52
|
+
end
|
|
53
|
+
mode = options[:formatter] == 'pdf' ? 'wb' : 'w'
|
|
54
|
+
File.open(File.join(options[:out], report_filename), mode)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
formatter_class = case options[:formatter]
|
|
58
|
+
when nil, 'simple'
|
|
59
|
+
PrD::Formatters::SimpleFormatter
|
|
60
|
+
when 'html'
|
|
61
|
+
PrD::Formatters::HtmlFormatter
|
|
62
|
+
when 'json'
|
|
63
|
+
PrD::Formatters::JsonFormatter
|
|
64
|
+
when 'pdf'
|
|
65
|
+
PrD::Formatters::PdfFormatter
|
|
66
|
+
else
|
|
67
|
+
raise "Unsupported formatter type: #{options[:formatter]}. Supported: simple, html, json, pdf"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
mode = options[:mode] || 'verbose'
|
|
71
|
+
supported_modes = %w[verbose synthetic]
|
|
72
|
+
raise "Unsupported mode: #{mode}. Supported: verbose, synthetic" unless supported_modes.include?(mode)
|
|
73
|
+
|
|
74
|
+
serializers = {
|
|
75
|
+
# Ferrum::Network::Exchange => (->(exchange) { exchange.url }),
|
|
76
|
+
# PDF::Reader => (->(pdf) { pdf.pages.map(&:text).join("\n") })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
formatter = formatter_class.new(io:, serializers:, mode: mode.to_sym)
|
|
80
|
+
runtime = PrD::Runtime.new(formatter:, output_dir: options[:out], config_file: options[:config])
|
|
81
|
+
runtime.run(tests)
|
|
82
|
+
io.close if io != STDOUT
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module PrD
|
|
2
|
+
module Formatters
|
|
3
|
+
class Formatter
|
|
4
|
+
SUPPORTED_MODES = %i[verbose synthetic].freeze
|
|
5
|
+
|
|
6
|
+
def initialize(io: $stdout, serializers: {}, mode: :verbose)
|
|
7
|
+
@io = io
|
|
8
|
+
@serializers = serializers
|
|
9
|
+
@level = 0
|
|
10
|
+
@mode = normalize_mode(mode)
|
|
11
|
+
@current_test_title = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def title(message)
|
|
15
|
+
raise NotImplementedError, "#{self.class} must implement #title"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def context(message)
|
|
19
|
+
raise NotImplementedError, "#{self.class} must implement #context"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def success_result(message)
|
|
23
|
+
raise NotImplementedError, "#{self.class} must implement #success_result"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def failure_result(message)
|
|
27
|
+
raise NotImplementedError, "#{self.class} must implement #failure_result"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def it(description = nil, &block)
|
|
31
|
+
raise NotImplementedError, "#{self.class} must implement #it"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def end_it(description = nil, &block)
|
|
35
|
+
raise NotImplementedError, "#{self.class} must implement #end_it"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def justification(justification)
|
|
39
|
+
raise NotImplementedError, "#{self.class} must implement #justification"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def subject(subject)
|
|
43
|
+
raise NotImplementedError, "#{self.class} must implement #subject"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pending(description = nil)
|
|
47
|
+
raise NotImplementedError, "#{self.class} must implement #pending"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def expect(expectation)
|
|
51
|
+
raise NotImplementedError, "#{self.class} must implement #expect"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to
|
|
55
|
+
raise NotImplementedError, "#{self.class} must implement #to"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def not_to
|
|
59
|
+
raise NotImplementedError, "#{self.class} must implement #not_to"
|
|
60
|
+
end
|
|
61
|
+
def matcher(matcher, sources: nil)
|
|
62
|
+
raise NotImplementedError, "#{self.class} must implement #matcher"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def result(passed_count, failed_count)
|
|
66
|
+
raise NotImplementedError, "#{self.class} must implement #result"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def increment_level
|
|
70
|
+
@level += 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def decrement_level
|
|
74
|
+
@level -= 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def flush
|
|
78
|
+
@io.flush
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def synthetic?
|
|
84
|
+
@mode == :synthetic
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def normalize_mode(mode)
|
|
88
|
+
normalized = mode.to_sym
|
|
89
|
+
return normalized if SUPPORTED_MODES.include?(normalized)
|
|
90
|
+
|
|
91
|
+
raise ArgumentError, "Unsupported formatter mode: #{mode}. Supported: #{SUPPORTED_MODES.join(', ')}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def serialize(value)
|
|
95
|
+
serializer = @serializers[value.class]
|
|
96
|
+
return serializer.call(value) if serializer
|
|
97
|
+
return value.path if value.is_a?(File)
|
|
98
|
+
return value.map { |v| serialize(v) } if value.is_a?(Array)
|
|
99
|
+
return value.transform_values { |v| serialize(v) } if value.is_a?(Hash)
|
|
100
|
+
|
|
101
|
+
value
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|