Skip to content

Commit ff0b193

Browse files
committed
fix(isc): fix DSL array keys bug, notes grammar, and codemod whitespace
DSL fix (lib/interscript/dsl/metadata.rb): - STANDARD_ARRAY_KEYS methods now store result in @node (was silently discarded) - This enables url, notes, implementation_notes, original_notes to be compared Grammar fix (grammar/concerns/metadata.rb): - notes_field: move .as(:notes) inside braced() to capture note entries, not the brace characters. Empty notes blocks now produce [] not ["{ }"] Codemod fixes (isc/codemod.rb): - read_heredoc_into_string: preserve blank lines as \n\n (was \n) - read_heredoc_into_string: preserve raw line indentation (was stripping all) This lets normalize_heredoc do proper YAML-style dedent DocumentBuilder fixes (isc/document_builder.rb): - normalize_heredoc: proper YAML dedent (strip common indent, preserve relative) - ARRAY_METADATA_FIELDS: wrap url/notes/etc in Arrays to match DSL convention - Apply normalize_heredoc to notes (was only applied to description) Deep checker (exe/verify_isc_deep): - normalize_meta collapses internal whitespace for semantic comparison - No longer skips url/notes fields (DSL bug is fixed) Result: 247/289 deep equivalent (up from 124), 40 remain (edge cases)
1 parent 9a44542 commit ff0b193

5 files changed

Lines changed: 55 additions & 26 deletions

File tree

exe/verify_isc_deep

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@ class DeepVerifier
2424

2525
details = []
2626

27-
# Compare metadata — skip DSL-internal fields and fields the Ruby DSL
28-
# doesn't store (STANDARD_ARRAY_KEYS like url/notes are parsed but
29-
# silently dropped by the DSL due to a bug in dsl/metadata.rb).
30-
skip_fields = [:nonstandard, :tests, :stages, :aliases, :dependencies,
31-
:url, :notes, :implementation_notes, :original_notes]
27+
# Compare metadata — skip DSL-internal fields only.
28+
# STANDARD_ARRAY_KEYS bug is fixed, so url/notes/etc. are now stored.
29+
skip_fields = [:nonstandard, :tests, :stages, :aliases, :dependencies]
3230
imp_meta = imp_data[:metadata].reject { |k, _| skip_fields.include?(k.to_sym) }
3331
isc_meta = isc_data[:metadata].reject { |k, _| skip_fields.include?(k.to_sym) }
3432
(imp_meta.keys | isc_meta.keys).each do |key|
@@ -119,7 +117,7 @@ class DeepVerifier
119117

120118
def normalize_meta(val)
121119
case val
122-
when String then val.strip
120+
when String then val.gsub(/\s+/, " ").strip
123121
when Array then val.map { |v| normalize_meta(v) }
124122
when Hash then val.transform_values { |v| normalize_meta(v) }
125123
else val

lib/interscript/dsl/metadata.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ def initialize(yaml: false, map_name: "", library: true, &block)
4646

4747
STANDARD_ARRAY_KEYS.each do |sym|
4848
define_method sym do |stuff|
49-
stuff = Array(stuff)
50-
51-
stuff.map do |i|
49+
@node[sym] = Array(stuff).map do |i|
5250
case i
5351
when String
5452
i

lib/interscript/isc/codemod.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,16 @@ def convert_quoted_note_body
458458

459459
def read_heredoc_into_string(indent)
460460
# Read lines that are indented deeper than `indent` (or blank). Concatenate.
461+
# Preserve raw indentation — the DocumentBuilder's normalize_heredoc
462+
# handles YAML-style dedent to match the DSL's output.
461463
until @scanner.eos?
462464
if @scanner.check(/\n(?:[ \t]*\n)*([ \t]{0,#{indent.length}}\S)/)
463465
return
464466
elsif @scanner.scan(/\n[ \t]*\n/)
465-
# Blank line inside heredoc — preserve as \n
466-
@out << "\\n"
467-
elsif @scanner.scan(/\n[ \t]+([^\n]*)/)
468-
# Indented line — strip indent, join with \n. Escape quotes.
467+
# Blank line inside heredoc — preserve as \n\n
468+
@out << "\\n\\n"
469+
elsif @scanner.scan(/\n([ \t]+[^\n]*)/)
470+
# Indented line — preserve raw content (indent + text)
469471
@out << "\\n" + @scanner[1].to_s.gsub('"', '\\"')
470472
elsif @scanner.scan(/\n/)
471473
@out << "\\n"

lib/interscript/isc/document_builder.rb

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ module Isc
1313
class DocumentBuilder
1414
SCHEMA_VERSION = 1
1515

16+
# Metadata fields that the Ruby DSL stores as Arrays (STANDARD_ARRAY_KEYS).
17+
# ISC stores them as generic fields, so we wrap in an Array to match.
18+
ARRAY_METADATA_FIELDS = %i[notes implementation_notes original_notes url].freeze
19+
1620
def self.build(tree, filename: nil)
1721
new(tree, filename: filename).build
1822
end
@@ -74,7 +78,29 @@ def unescape_braces(text)
7478
end
7579

7680
def normalize_heredoc(text)
77-
text.lines.map { |l| l.strip }.join("\n").strip + "\n"
81+
lines = text.lines.map(&:chomp)
82+
content_lines = lines.reject { |l| l.strip.empty? }
83+
return "" if content_lines.empty?
84+
return content_lines.first.strip if content_lines.size == 1
85+
86+
# YAML-style dedent: strip the minimum indent across all non-blank
87+
# lines. The first line's indent may have been partially consumed
88+
# by the grammar, so we compute min_indent from lines 2+ and treat
89+
# the first line as having at least that much indent.
90+
min_indent = content_lines
91+
.drop(1)
92+
.map { |l| l[/\A[ \t]*/].length }
93+
.min || 0
94+
95+
lines.map do |l|
96+
if l.strip.empty?
97+
""
98+
elsif l[/\A[ \t]*/].length >= min_indent
99+
l[min_indent..]
100+
else
101+
l.strip
102+
end
103+
end.join("\n").strip
78104
end
79105

80106
# Apply Transform to an identifier fragment.
@@ -101,32 +127,37 @@ def extract_metadata(arr)
101127
h[:notes] ||= []
102128
Array(field[:notes]).each do |n|
103129
note_val = n.is_a?(Hash) ? n[:note] : n
104-
h[:notes] << unquote(note_val)
130+
h[:notes] << normalize_heredoc(unquote(note_val).to_s)
105131
end
106132
when field.key?(:note)
107133
h[:notes] ||= []
108-
h[:notes] << unquote(field[:note])
134+
h[:notes] << normalize_heredoc(unquote(field[:note]).to_s)
109135
when field.key?(:provenance)
110136
h[:provenance] ||= []
111137
h[:provenance] << unquote(field[:provenance])
112138
when field.key?(:relations)
113139
h[:relations] = extract_relations(field[:relations])
114140
when field.key?(:description)
115-
h[:description] = normalize_heredoc(unescape_braces(field[:description].to_s))
141+
h[:description] = normalize_heredoc(unescape_braces(field[:description].to_s)) + "\n"
116142
when field.key?(:field_name)
117143
# Generic field: identifier + raw value
118144
name = ident(field[:field_name]).to_sym
119145
if field.key?(:field_block)
120-
h[name] = normalize_heredoc(unescape_braces(field[:field_block].to_s))
146+
val = normalize_heredoc(unescape_braces(field[:field_block].to_s))
121147
else
122148
raw = field[:field_value]
123-
val_str = case raw
124-
when Hash
125-
raw.key?(:string) ? unquote(raw) : (raw[:raw]&.to_s || "").strip
126-
when nil then ""
127-
else raw.to_s.strip
128-
end
129-
h[name] = val_str
149+
val = case raw
150+
when Hash
151+
raw.key?(:string) ? unquote(raw) : (raw[:raw]&.to_s || "").strip
152+
when nil then ""
153+
else raw.to_s.strip
154+
end
155+
end
156+
# DSL stores these as Arrays — match that convention.
157+
if ARRAY_METADATA_FIELDS.include?(name)
158+
h[name] = val.to_s.empty? ? [] : [val]
159+
else
160+
h[name] = val
130161
end
131162
else
132163
# Specific named field (authority, name, system_status, etc.)

lib/interscript/isc/grammar/concerns/metadata.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ module Metadata
9292

9393
rule(:notes_field) do
9494
str("notes") >> whitespace? >>
95-
braced(note_line.repeat(0)).as(:notes)
95+
braced(note_line.repeat(0).as(:notes))
9696
end
9797

9898
rule(:note_line) do

0 commit comments

Comments
 (0)