format: an elided line keeps its parameters' quoting
Format.elide_line rebuilt a long line's header by hand, dropping the
quotes around a parameter value holding `:`, `;` or `,`, so the line
on screen read as different parameters than the card holds. It now
rebuilds through VCard.header_of, the writer's own spelling.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAiyEheRBdzbBjZUDWZUPk
diff --git a/lib/pro_tacts/admin/format.rb b/lib/pro_tacts/admin/format.rb
index bafd033..7ada939 100644
--- a/lib/pro_tacts/admin/format.rb
+++ b/lib/pro_tacts/admin/format.rb
@@ -125,8 +125,9 @@ module ProTacts
# One line of the display card: verbatim when nothing in it runs
# past ELIDE_ABOVE; rebuilt from its parse when something does,
# each long value standing in as its count. The rebuild is the
- # parser's spelling rather than the bytes' — an elided line is a
- # rendering — and the line keeps its own terminator so the
+ # writer's spelling rather than the bytes' (VCard.header_of, which
+ # quotes a parameter value PTEXT cannot hold) — an elided line is
+ # a rendering — and the line keeps its own terminator so the
# card's line-break convention survives.
#: (VCard::Parser::Line line) -> String
def self.elide_line(line)
@@ -137,10 +138,9 @@ module ProTacts
property.value.bytesize > ELIDE_ABOVE
return line.verbatim unless long
- header = property.group ? "#{property.group}.#{property.name}" : property.name
- params = property.parameters.map { |name, value| "#{name}=#{count_octets(value)}" }
+ elided = property.with(parameters: property.parameters.map { |name, value| [name, count_octets(value)] })
terminator = line.verbatim[/\r?\n\z/] || "\r\n"
- "#{[header, *params].join(';')}:#{count_octets(property.value)}#{terminator}"
+ "#{VCard.header_of(elided)}#{count_octets(property.value)}#{terminator}"
end
# A line that would not parse has no structure to rebuild from,
diff --git a/test/pro_tacts/admin/test_format.rb b/test/pro_tacts/admin/test_format.rb
index 5a2abf6..8062baf 100644
--- a/test/pro_tacts/admin/test_format.rb
+++ b/test/pro_tacts/admin/test_format.rb
@@ -197,4 +197,32 @@ class FormatTest < Minitest::Test
assert_equal key, ProTacts::Admin::Format.search_key(key)
end
end
+
+ ## Eliding a line
+
+ # An elided line still reads as the line it stands for: its group,
+ # its name, and each parameter, a long value as its count and every
+ # other as it was — quoted where the grammar must quote it, so a
+ # parameter holding `:` or `,` is not cut apart on the way to the
+ # screen.
+ def test_an_elided_line_reads_as_its_line_with_long_values_counted
+ token = from_regex("[A-Za-z0-9-]{1,8}", fullmatch: true)
+ short = from_regex('[^\x00-\x08\x0a-\x1f\x7f"]{0,12}', fullmatch: true)
+ long = from_regex("[a-z:,;]{121,140}", fullmatch: true)
+ Hegel.test(test_cases: 1_000) do |tc|
+ property = ProTacts::VCard::Parser::Property.new(
+ group: tc.draw(optional(token)),
+ name: tc.draw(token),
+ parameters: tc.draw(arrays(tuples(token, one_of(short, long)), max_size: 3)),
+ value: tc.draw(one_of(short.map { it.delete(":;,\\") }, long)),
+ )
+ line = ProTacts::VCard.new("#{ProTacts::VCard.header_of(property)}#{property.value}\r\n").lines.fetch(0)
+ count = ->(value) { value.bytesize > ProTacts::Admin::Format::ELIDE_ABOVE ? "[#{value.bytesize} octets elided]" : value }
+
+ elided = ProTacts::VCard.new(ProTacts::Admin::Format.elide_line(line)).lines.fetch(0).property
+
+ assert_equal property.with(parameters: property.parameters.map { [it[0], count.(it[1])] }, value: count.(property.value)),
+ elided
+ end
+ end
end