latticerb 0.1.0 → 0.3.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 +4 -4
- data/README.md +14 -2
- data/lib/lattice/cli.rb +1 -1
- data/lib/lattice/formatters.rb +76 -9
- data/lib/lattice/parser.rb +27 -19
- data/lib/lattice/schema_text.rb +153 -0
- data/lib/lattice/version.rb +1 -1
- data/lib/lattice.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1cd02dabe1821baf68283ad31fb90aa688bd1642943fa2f5d914cad759389fe
|
|
4
|
+
data.tar.gz: 4c1f1118737aa6fe2a2917db077fd6badf3f03fc169c110bf5c5255a1b8376aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '087a54e0c2896d3a71ffb4eb8737dbf3ad2a0e04ed36682d8fa232cee7b7a131f706cdf5456277c2c6b3eef59446c263dd8ff4277879345160b386c64b06f8d0'
|
|
7
|
+
data.tar.gz: 0fceafc2667a8042a38c7dc292b42d185b7305cf57cbf2a7d21bab7d1915723d0fd5fb33c27a444a349b9bff148357a7313f55542af393a69cf69402551426f3
|
data/README.md
CHANGED
|
@@ -54,7 +54,7 @@ run = Lattice.generate(
|
|
|
54
54
|
|
|
55
55
|
run.scenarios # array of { name => value } hashes
|
|
56
56
|
run.coverage_curve # cumulative coverage per scenario
|
|
57
|
-
run.to_table # human-readable table (also: to_json_output, to_csv, to_summary)
|
|
57
|
+
run.to_table # human-readable table (also: to_json_output, to_yaml_output, to_csv, to_summary)
|
|
58
58
|
|
|
59
59
|
Lattice.scenarios(file: "...") # shortcut straight to the hashes
|
|
60
60
|
Lattice.validate!(schema) # raises Lattice::ValidationError / Lattice::ConstraintError
|
|
@@ -70,6 +70,7 @@ The gem installs a `lattice` executable mirroring the Python CLI:
|
|
|
70
70
|
cat schema.yaml | lattice validate
|
|
71
71
|
cat schema.yaml | lattice generate # JSON by default
|
|
72
72
|
lattice generate schema.yaml --format table
|
|
73
|
+
lattice generate schema.yaml --format yaml # same content as JSON, for hand-edited files
|
|
73
74
|
lattice generate schema.yaml --strength 3 --seed 7
|
|
74
75
|
lattice agent instructions # coding-agent integration guidance
|
|
75
76
|
```
|
|
@@ -98,11 +99,22 @@ constraints:
|
|
|
98
99
|
|
|
99
100
|
Constraint types: `invalid_pair`, `higher_order`, `forward_dep`, `bidirectional`, `conditional`, `forced`. Run `lattice agent instructions` for the full reference.
|
|
100
101
|
|
|
102
|
+
### Values keep their text
|
|
103
|
+
|
|
104
|
+
Every value in a schema (the model name, parameter names and values, and constraint references) is used exactly as written. In YAML, unquoted `1.10`, `0x1F`, `.inf` or `2026-10-26T10:00:00Z` stay as typed rather than being turned into numbers or dates. The exceptions:
|
|
105
|
+
|
|
106
|
+
- Unquoted `yes`, `no`, `on`, `off`, `true` and `false` (lowercase, Capitalized or UPPERCASE) become `true` / `false`, and an empty value, `~` or `null` becomes `null`. Quote them (`"yes"`) to keep the word.
|
|
107
|
+
- JSON integers are written in plain decimal form (`-0` becomes `0`).
|
|
108
|
+
|
|
109
|
+
`strength` must be a whole number, and `weights` decimal numbers such as `0.7` or `1e-3`. Anchors, aliases and `<<` merge keys work; a key written twice, more than one YAML document, or a tag other than YAML's standard ones is an error.
|
|
110
|
+
|
|
111
|
+
Changed in 0.3.0: earlier versions converted unquoted numbers and dates, so `0x1F` became `31`, `1.10` became `1.1`, and some values (`.inf`, timestamps) came out differently in the Ruby and Python implementations.
|
|
112
|
+
|
|
101
113
|
## Parity with the Python implementation
|
|
102
114
|
|
|
103
115
|
The generator seeds an MT19937 RNG that reproduces CPython's `random.Random` bit-for-bit, so both implementations walk the same candidate pools and pick the same rows. `test/python_parity_test.rb` shells out to the Python implementation (checkout via `LATTICE_PYTHON_SRC`, or an installed `lattice-cli`) and asserts byte-equal output across formats, seeds, strengths, and stopping rules; it skips when no Python implementation is available.
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
Schemas are read with Psych here and PyYAML in Python, but both implementations build values from the parsed text with the same rules (see "Values keep their text"), so a schema means the same thing in either. `test/fixtures/scalar_text.*` pins those rules for both.
|
|
106
118
|
|
|
107
119
|
## Development
|
|
108
120
|
|
data/lib/lattice/cli.rb
CHANGED
|
@@ -65,7 +65,7 @@ module Lattice
|
|
|
65
65
|
}
|
|
66
66
|
parser = OptionParser.new do |opts|
|
|
67
67
|
opts.on("--strength STRENGTH", Integer)
|
|
68
|
-
opts.on("--format FORMAT", %w[table json csv summary])
|
|
68
|
+
opts.on("--format FORMAT", %w[table json csv summary yaml])
|
|
69
69
|
opts.on("--seed SEED", Integer)
|
|
70
70
|
opts.on("--pool-size SIZE", Integer) { |value| options[:pool_size] = value }
|
|
71
71
|
opts.on("--max-rows ROWS", Integer) { |value| options[:max_rows] = value }
|
data/lib/lattice/formatters.rb
CHANGED
|
@@ -7,8 +7,9 @@ require_relative "ipog"
|
|
|
7
7
|
require_relative "model"
|
|
8
8
|
|
|
9
9
|
module Lattice
|
|
10
|
-
# Output rendering. Table, JSON, CSV, and
|
|
11
|
-
# to the Python implementation's output for the same model
|
|
10
|
+
# Output rendering. Table, JSON, CSV, summary, and YAML formats are
|
|
11
|
+
# byte-identical to the Python implementation's output for the same model
|
|
12
|
+
# and result.
|
|
12
13
|
module Formatters
|
|
13
14
|
extend self
|
|
14
15
|
|
|
@@ -18,6 +19,7 @@ module Lattice
|
|
|
18
19
|
when "json" then format_json(model, result, seed:)
|
|
19
20
|
when "csv" then format_csv(model, result)
|
|
20
21
|
when "summary" then format_summary(model, result)
|
|
22
|
+
when "yaml" then format_yaml(model, result, seed:)
|
|
21
23
|
else
|
|
22
24
|
raise ArgumentError, "Unknown format `#{output_format}`."
|
|
23
25
|
end
|
|
@@ -77,16 +79,81 @@ module Lattice
|
|
|
77
79
|
}
|
|
78
80
|
end
|
|
79
81
|
}
|
|
80
|
-
|
|
82
|
+
# Python's json.dumps also escapes DEL, which Ruby's ascii_only leaves raw.
|
|
83
|
+
JSON.pretty_generate(payload, ascii_only: true).gsub("\u007f", "\\u007f")
|
|
81
84
|
end
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
# Same content as format_json, written by hand in one fixed style so Ruby
|
|
87
|
+
# and Python emit identical bytes (Psych and PyYAML each quote and wrap
|
|
88
|
+
# differently). Every string is double-quoted, so no value can be read
|
|
89
|
+
# back as a boolean, null, number or date.
|
|
90
|
+
def format_yaml(model, result, seed:)
|
|
91
|
+
last_point = result.coverage_curve.last
|
|
92
|
+
lines = [
|
|
93
|
+
"meta:",
|
|
94
|
+
" model_name: #{yaml_string(model.model_name)}",
|
|
95
|
+
" strength: #{model.strength}",
|
|
96
|
+
" test_count: #{result.rows.length}",
|
|
97
|
+
" exhaustive_count: #{model.exhaustive_count}",
|
|
98
|
+
" reduction: #{yaml_string(reduction_label(model.exhaustive_count, result.rows.length))}",
|
|
99
|
+
" coverage: #{yaml_string(last_point ? format('%.1f%%', last_point.cumulative_pct) : '0.0%')}",
|
|
100
|
+
" seed: #{seed.nil? ? 'null' : seed}"
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
if result.coverage_curve.empty?
|
|
104
|
+
lines << "coverage_curve: []"
|
|
105
|
+
else
|
|
106
|
+
lines << "coverage_curve:"
|
|
107
|
+
result.coverage_curve.each do |point|
|
|
108
|
+
lines << " - scenario: #{point.scenario}"
|
|
109
|
+
lines << " cumulative_pct: #{JSON.generate(point.cumulative_pct)}"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
rows = named_rows(model, result)
|
|
114
|
+
if rows.empty?
|
|
115
|
+
lines << "scenarios: []"
|
|
116
|
+
else
|
|
117
|
+
lines << "scenarios:"
|
|
118
|
+
rows.each_with_index do |row, index|
|
|
119
|
+
lines << " - id: #{index + 1}"
|
|
120
|
+
lines << " values:"
|
|
121
|
+
model.parameter_names.each do |name|
|
|
122
|
+
lines << " #{yaml_string(name)}: #{yaml_string(row[name])}"
|
|
123
|
+
end
|
|
88
124
|
end
|
|
89
|
-
end
|
|
125
|
+
end
|
|
126
|
+
lines.join("\n")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
YAML_ESCAPES = {
|
|
130
|
+
"\"" => "\\\"", "\\" => "\\\\", "\b" => "\\b", "\t" => "\\t", "\n" => "\\n", "\f" => "\\f", "\r" => "\\r"
|
|
131
|
+
}.freeze
|
|
132
|
+
|
|
133
|
+
# Characters escaped inside a double-quoted YAML string: the quote and
|
|
134
|
+
# backslash, C0 controls, DEL, C1 controls (U+0085 would otherwise fold to
|
|
135
|
+
# a space), the Unicode line and paragraph separators, the byte-order mark,
|
|
136
|
+
# and the two noncharacters YAML cannot hold. Everything else is written as
|
|
137
|
+
# UTF-8, so accented letters and emoji stay readable.
|
|
138
|
+
YAML_ESCAPED_CHARS = /["\\\u0000-\u001f\u007f-\u009f\u2028\u2029\ufeff\ufffe\uffff]/
|
|
139
|
+
|
|
140
|
+
def yaml_string(value)
|
|
141
|
+
escaped = value.gsub(YAML_ESCAPED_CHARS) do |char|
|
|
142
|
+
YAML_ESCAPES.fetch(char) { format("\\u%04x", char.ord) }
|
|
143
|
+
end
|
|
144
|
+
"\"#{escaped}\""
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Matches Python's csv.writer: empty fields are written bare, except a row
|
|
148
|
+
# made of a single empty field, which is written as "" so it isn't a blank
|
|
149
|
+
# line.
|
|
150
|
+
def format_csv(model, result)
|
|
151
|
+
records = [model.parameter_names] + named_rows(model, result).map do |row|
|
|
152
|
+
model.parameter_names.map { |name| row[name] }
|
|
153
|
+
end
|
|
154
|
+
records.map do |record|
|
|
155
|
+
record == [""] ? "\"\"\r\n" : CSV.generate_line(record, row_sep: "\r\n", quote_empty: false)
|
|
156
|
+
end.join.strip
|
|
90
157
|
end
|
|
91
158
|
|
|
92
159
|
def headline(model, result)
|
data/lib/lattice/parser.rb
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
|
-
require "yaml"
|
|
5
|
-
|
|
6
3
|
require_relative "model"
|
|
4
|
+
require_relative "schema_text"
|
|
7
5
|
|
|
8
6
|
module Lattice
|
|
9
7
|
class ModelIOError < StandardError; end
|
|
@@ -78,7 +76,7 @@ module Lattice
|
|
|
78
76
|
|
|
79
77
|
errors = []
|
|
80
78
|
loaders.each do |loader|
|
|
81
|
-
return loader == :json ?
|
|
79
|
+
return loader == :json ? SchemaText.load_json(text) : SchemaText.load_yaml(text)
|
|
82
80
|
rescue StandardError => e
|
|
83
81
|
errors << e.message
|
|
84
82
|
end
|
|
@@ -86,10 +84,6 @@ module Lattice
|
|
|
86
84
|
raise ModelIOError, "Unable to parse model from `#{source_name}`: #{errors.join('; ')}"
|
|
87
85
|
end
|
|
88
86
|
|
|
89
|
-
def safe_load_yaml(text)
|
|
90
|
-
YAML.safe_load(text, permitted_classes: [Date, Time], aliases: true)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
87
|
def parse_parameters(raw_parameters, errors)
|
|
94
88
|
parameters = {}
|
|
95
89
|
if raw_parameters.nil?
|
|
@@ -351,8 +345,9 @@ module Lattice
|
|
|
351
345
|
end
|
|
352
346
|
|
|
353
347
|
parsed = []
|
|
354
|
-
raw_weights.each do |
|
|
355
|
-
|
|
348
|
+
raw_weights.each do |raw_weight|
|
|
349
|
+
weight = parse_number(raw_weight)
|
|
350
|
+
if weight.nil?
|
|
356
351
|
errors << "`weights` for parameter `#{parameter_name}` must be numeric."
|
|
357
352
|
return nil
|
|
358
353
|
end
|
|
@@ -360,24 +355,37 @@ module Lattice
|
|
|
360
355
|
errors << "`weights` for parameter `#{parameter_name}` must be greater than zero."
|
|
361
356
|
return nil
|
|
362
357
|
end
|
|
363
|
-
parsed << weight
|
|
358
|
+
parsed << weight
|
|
364
359
|
end
|
|
365
360
|
parsed
|
|
366
361
|
end
|
|
367
362
|
|
|
363
|
+
DECIMAL_TEXT = /\A[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?\z/
|
|
364
|
+
INTEGER_TEXT = /\A[-+]?[0-9]+\z/
|
|
365
|
+
|
|
366
|
+
# A finite number, from a Ruby number or decimal text such as "0.7" or
|
|
367
|
+
# "1e-3" (schema files keep numbers as text). Booleans are not numbers.
|
|
368
|
+
def parse_number(raw)
|
|
369
|
+
number =
|
|
370
|
+
case raw
|
|
371
|
+
when Integer, Float then raw.to_f
|
|
372
|
+
when String then DECIMAL_TEXT.match?(raw) ? Float(raw) : nil
|
|
373
|
+
end
|
|
374
|
+
number&.finite? ? number : nil
|
|
375
|
+
end
|
|
376
|
+
|
|
368
377
|
def parse_strength(raw_strength, parameter_count, errors)
|
|
369
378
|
strength =
|
|
370
379
|
case raw_strength
|
|
371
380
|
when nil then 2
|
|
372
|
-
when
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
Integer(raw_strength)
|
|
376
|
-
rescue TypeError, ArgumentError
|
|
377
|
-
errors << "`strength` must be an integer."
|
|
378
|
-
return 2
|
|
379
|
-
end
|
|
381
|
+
when Integer then raw_strength
|
|
382
|
+
when Float then raw_strength.finite? ? raw_strength.to_i : nil
|
|
383
|
+
when String then INTEGER_TEXT.match?(raw_strength) ? Integer(raw_strength, 10) : nil
|
|
380
384
|
end
|
|
385
|
+
if strength.nil?
|
|
386
|
+
errors << "`strength` must be an integer."
|
|
387
|
+
return 2
|
|
388
|
+
end
|
|
381
389
|
|
|
382
390
|
errors << "`strength` must be at least 1." if strength < 1
|
|
383
391
|
errors << "`strength` cannot exceed the number of parameters." if parameter_count.positive? && strength > parameter_count
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "psych"
|
|
5
|
+
|
|
6
|
+
module Lattice
|
|
7
|
+
# Reads schema text (JSON or YAML) into plain Hashes, Arrays and Strings,
|
|
8
|
+
# keeping every value as the text the user wrote. `1.10` stays "1.10",
|
|
9
|
+
# `0x1F` stays "0x1F" and `2026-10-26T10:00:00Z` stays as typed, instead of
|
|
10
|
+
# being converted to a number or time and printed back differently by Ruby
|
|
11
|
+
# and Python. The only conversions:
|
|
12
|
+
#
|
|
13
|
+
# - unquoted yes/no/on/off/true/false (in the case forms below) become
|
|
14
|
+
# true/false, and an empty value, ~ or null becomes nil;
|
|
15
|
+
# - JSON integers are written in plain decimal form.
|
|
16
|
+
#
|
|
17
|
+
# The Python implementation (lattice.schema_text) follows the same rules, and
|
|
18
|
+
# test/fixtures/scalar_text.* pins the result for both.
|
|
19
|
+
module SchemaText
|
|
20
|
+
extend self
|
|
21
|
+
|
|
22
|
+
class SyntaxError < StandardError; end
|
|
23
|
+
|
|
24
|
+
TRUE_WORDS = %w[yes Yes YES true True TRUE on On ON].freeze
|
|
25
|
+
FALSE_WORDS = %w[no No NO false False FALSE off Off OFF].freeze
|
|
26
|
+
NULL_WORDS = ["", "~", "null", "Null", "NULL"].freeze
|
|
27
|
+
|
|
28
|
+
YAML_TAG_PREFIX = "tag:yaml.org,2002:"
|
|
29
|
+
STR_TAGS = ["!", "#{YAML_TAG_PREFIX}str"].freeze
|
|
30
|
+
SCALAR_TAGS = %w[str int float bool null timestamp binary merge value].map { |name| "#{YAML_TAG_PREFIX}#{name}" }.freeze
|
|
31
|
+
SEQUENCE_TAGS = [nil, "!", "#{YAML_TAG_PREFIX}seq"].freeze
|
|
32
|
+
MAPPING_TAGS = [nil, "!", "#{YAML_TAG_PREFIX}map"].freeze
|
|
33
|
+
|
|
34
|
+
# Hands JSON floats back as their source text.
|
|
35
|
+
class RawNumber
|
|
36
|
+
def self.new(text) = text
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Rejects a key written twice. json 2.x calls []= on a custom object_class;
|
|
40
|
+
# json 3.x checks duplicates itself when allow_duplicate_key is false.
|
|
41
|
+
class UniqueHash < Hash
|
|
42
|
+
def []=(key, value)
|
|
43
|
+
raise JSON::ParserError, "duplicate key `#{key}`" if key?(key)
|
|
44
|
+
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def load_json(text)
|
|
50
|
+
json_strings(
|
|
51
|
+
JSON.parse(text, decimal_class: RawNumber, object_class: UniqueHash, allow_duplicate_key: false)
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_yaml(text)
|
|
56
|
+
documents = Psych.parse_stream(text).children
|
|
57
|
+
raise SyntaxError, "expected a single YAML document, found #{documents.length}" if documents.length > 1
|
|
58
|
+
return nil if documents.empty?
|
|
59
|
+
|
|
60
|
+
yaml_value(documents.first.root, {})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def json_strings(value)
|
|
66
|
+
case value
|
|
67
|
+
when Hash then value.to_h { |key, child| [key, json_strings(child)] }
|
|
68
|
+
when Array then value.map { |child| json_strings(child) }
|
|
69
|
+
when Integer then value.to_s
|
|
70
|
+
else value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# `anchors` maps each anchor to its value once built; an anchor still
|
|
75
|
+
# being built (nil value) means an alias points back into itself.
|
|
76
|
+
def yaml_value(node, anchors)
|
|
77
|
+
return yaml_alias(node, anchors) if node.is_a?(Psych::Nodes::Alias)
|
|
78
|
+
|
|
79
|
+
anchors[node.anchor] = nil if node.anchor
|
|
80
|
+
value =
|
|
81
|
+
case node
|
|
82
|
+
when Psych::Nodes::Scalar then yaml_scalar(node)
|
|
83
|
+
when Psych::Nodes::Sequence
|
|
84
|
+
check_tag(node.tag, SEQUENCE_TAGS)
|
|
85
|
+
node.children.map { |child| yaml_value(child, anchors) }
|
|
86
|
+
when Psych::Nodes::Mapping then yaml_mapping(node, anchors)
|
|
87
|
+
end
|
|
88
|
+
anchors[node.anchor] = [value] if node.anchor
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def yaml_alias(node, anchors)
|
|
93
|
+
built = anchors.fetch(node.anchor) { raise SyntaxError, "unknown alias `*#{node.anchor}`" }
|
|
94
|
+
raise SyntaxError, "recursive alias" if built.nil?
|
|
95
|
+
|
|
96
|
+
built.first
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def yaml_scalar(node)
|
|
100
|
+
check_tag(node.tag, [nil, "!"] + SCALAR_TAGS)
|
|
101
|
+
return node.value unless node.style == Psych::Nodes::Scalar::PLAIN && !STR_TAGS.include?(node.tag)
|
|
102
|
+
return true if TRUE_WORDS.include?(node.value)
|
|
103
|
+
return false if FALSE_WORDS.include?(node.value)
|
|
104
|
+
return nil if NULL_WORDS.include?(node.value)
|
|
105
|
+
|
|
106
|
+
node.value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Keys from `<<` merges come first, earlier sources winning; the
|
|
110
|
+
# mapping's own keys then override them. A key written twice is an error.
|
|
111
|
+
def yaml_mapping(node, anchors)
|
|
112
|
+
check_tag(node.tag, MAPPING_TAGS)
|
|
113
|
+
merged = {}
|
|
114
|
+
own = {}
|
|
115
|
+
seen_merge = false
|
|
116
|
+
node.children.each_slice(2) do |key_node, value_node|
|
|
117
|
+
key = yaml_value(key_node, anchors)
|
|
118
|
+
raise SyntaxError, "mapping keys must be scalars" if key.is_a?(Enumerable)
|
|
119
|
+
|
|
120
|
+
if merge_key?(key_node)
|
|
121
|
+
raise SyntaxError, "duplicate key `<<`" if seen_merge
|
|
122
|
+
|
|
123
|
+
seen_merge = true
|
|
124
|
+
merge_sources(yaml_value(value_node, anchors)).each do |source|
|
|
125
|
+
source.each { |merged_key, merged_value| merged[merged_key] = merged_value unless merged.key?(merged_key) }
|
|
126
|
+
end
|
|
127
|
+
next
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
raise SyntaxError, "duplicate key `#{Lattice.normalize_scalar(key)}`" if own.key?(key)
|
|
131
|
+
|
|
132
|
+
own[key] = yaml_value(value_node, anchors)
|
|
133
|
+
end
|
|
134
|
+
merged.merge(own)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def merge_key?(key_node)
|
|
138
|
+
key_node.is_a?(Psych::Nodes::Scalar) && key_node.style == Psych::Nodes::Scalar::PLAIN && key_node.value == "<<" &&
|
|
139
|
+
[nil, "#{YAML_TAG_PREFIX}merge"].include?(key_node.tag)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def merge_sources(value)
|
|
143
|
+
sources = [value].flatten(1)
|
|
144
|
+
raise SyntaxError, "merge key `<<` needs a mapping or a list of mappings" unless sources.all?(Hash)
|
|
145
|
+
|
|
146
|
+
sources
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def check_tag(tag, allowed)
|
|
150
|
+
raise SyntaxError, "unsupported YAML tag `#{tag}`" unless allowed.include?(tag)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/lattice/version.rb
CHANGED
data/lib/lattice.rb
CHANGED
|
@@ -32,6 +32,7 @@ module Lattice
|
|
|
32
32
|
def to_json_output = Formatters.render_output(model, result, output_format: "json", seed:)
|
|
33
33
|
def to_csv = Formatters.render_output(model, result, output_format: "csv", seed:)
|
|
34
34
|
def to_summary = Formatters.render_output(model, result, output_format: "summary", seed:)
|
|
35
|
+
def to_yaml_output = Formatters.render_output(model, result, output_format: "yaml", seed:)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
class << self
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: latticerb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tyler Klose
|
|
@@ -56,6 +56,7 @@ files:
|
|
|
56
56
|
- lib/lattice/resources/generic/lattice-workflow/SKILL.md
|
|
57
57
|
- lib/lattice/resources/generic/lattice-workflow/references/modeling-rules.md
|
|
58
58
|
- lib/lattice/resources/generic/lattice-workflow/references/worked-examples.md
|
|
59
|
+
- lib/lattice/schema_text.rb
|
|
59
60
|
- lib/lattice/version.rb
|
|
60
61
|
- lib/latticerb.rb
|
|
61
62
|
homepage: https://github.com/tylerklose/lattice-ruby
|