vcard: build hashes and unfolds with plain enumerables
to_h over each_with_object for the component hashes;
slice_when over an accumulating loop for unfolding.
Assisted-by: GLM-5.3 via pi
diff --git a/lib/pro_tacts/vcard.rb b/lib/pro_tacts/vcard.rb
index f903679..c205abe 100644
--- a/lib/pro_tacts/vcard.rb
+++ b/lib/pro_tacts/vcard.rb
@@ -39,10 +39,11 @@ module ProTacts
*typed_property_lines(contact, "email", "EMAIL"),
*address_lines(contact),
"UID:#{escape(uid)}",
- "END:VCARD"
+ "END:VCARD",
+ "", # trailing newline
]
- "#{lines.map { |line| fold(line) }.join("\r\n")}\r\n"
+ lines.map { fold(it) }.join("\r\n")
end
# `name "John Smith"` derives N:Smith;John;;; (last token family, the
@@ -50,11 +51,9 @@ module ProTacts
# when any of them is present, N is built from exactly those, and
# every missing component renders empty.
def structured_name(name)
- overrides = name.children.each.with_object({}) do |child, acc|
- next unless NAME_COMPONENTS.include?(child.name)
-
- acc[child.name] = string_argument(child)
- end
+ overrides = name.children
+ .select { |child| NAME_COMPONENTS.include?(child.name) }
+ .to_h { |child| [child.name, string_argument(child)] }
return components(NAME_COMPONENTS.map { |component| overrides.fetch(component, "") }) unless overrides.empty?
@@ -78,11 +77,9 @@ module ProTacts
# counterpart and stay empty.
def address_lines(contact)
contact.children.select { |node| node.name == "address" }.map do |node|
- parts = node.children.each.with_object({}) do |child, acc|
- next unless ADDRESS_PARTS.include?(child.name)
-
- acc[child.name] = string_argument(child)
- end
+ parts = node.children
+ .select { |child| ADDRESS_PARTS.include?(child.name) }
+ .to_h { |child| [child.name, string_argument(child)] }
components = ["", "", *ADDRESS_PARTS.map { |part| parts.fetch(part, "") }]
type = node.properties["type"]&.value
diff --git a/test/pro_tacts/test_vcard.rb b/test/pro_tacts/test_vcard.rb
index dab986b..de8d354 100644
--- a/test/pro_tacts/test_vcard.rb
+++ b/test/pro_tacts/test_vcard.rb
@@ -111,9 +111,7 @@ class VCardTest < Minitest::Test
assert_operator physical.length, :>, 1, "expected folding"
physical.each { |line| assert_operator line.bytesize, :<=, 75 }
- logical = physical.each.with_object([]) do |line, acc|
- line.start_with?(" ") ? acc.last << line[1..] : acc << line.dup
- end
+ logical = unfold(physical)
assert_equal "FN:#{"x" * 30}#{("é" * 60)}", logical.find { |l| l.start_with?("FN:") }
end
@@ -217,12 +215,16 @@ class VCardTest < Minitest::Test
"\"#{escaped}\""
end
- # Joins physical lines back into logical ones by removing the folding
- # break: CRLF followed by a single space.
+ # Joins physical lines back into logical ones: a line starting with a
+ # single space continues the previous one. slice_when starts a new
+ # group wherever the next line is not a continuation.
def logical_lines(vcard)
- vcard.split("\r\n").each.with_object([]) do |line, acc|
- line.start_with?(" ") ? acc.last << line[1..] : acc << line.dup
- end
+ unfold(vcard.split("\r\n"))
+ end
+
+ def unfold(lines)
+ lines.slice_when { |_line, next_line| !next_line.start_with?(" ") }
+ .map { |group| group.first + group.drop(1).map { |line| line[1..] }.join }
end
# Reverses RFC 2426 section 2.4.2 escaping.