Apply ruby-style conventions across the codebase
Frozen string literal enforcement moved from per-file comments to
RUBYOPT in .envrc, which is untracked — as are the binstubs, ignored
via /bin.

Assisted-by: GLM-5.3 via pi
change kvqukxkqnytyvlllpwtsqzxunpsvwzxk
commit 989cb6f0b617034956c2cf00991e7c1d999aa130
author Alpha Chen <alpha@kejadlen.dev>
date
parent zwrwvzuq
diff --git a/.gitignore b/.gitignore
index 17f4392..e6a5295 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 /tmp
 /servers
 /carddav.mobileconfig
+/bin
diff --git a/Rakefile b/Rakefile
index 2f0e1e5..5006457 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,12 +1,15 @@
-# frozen_string_literal: true
 
-$LOAD_PATH.unshift(File.expand_path("lib", __dir__))
+require "pathname"
+
+$LOAD_PATH.unshift(Pathname.new(__dir__) / "lib")
 
 require "minitest/test_task"
 
 Minitest::TestTask.create
 
-Dir.glob("tasks/*.rake").sort.each { import it }
+Dir.glob("tasks/*.rake").sort.each do
+  import it
+end
 
 desc "Start development server, reloading on changes"
 task :dev do
@@ -54,7 +57,9 @@ namespace :profile do
     if identifiers.empty?
       puts "No pro-tacts profiles found; remove by hand in System Settings → Profiles if one lingers."
     else
-      identifiers.each { sh "profiles", "remove", "-identifier", it }
+      identifiers.each do
+        sh "profiles", "remove", "-identifier", it
+      end
     end
   end
 end
diff --git a/config.ru b/config.ru
index 7de5d55..20261fe 100644
--- a/config.ru
+++ b/config.ru
@@ -1,6 +1,7 @@
-# frozen_string_literal: true
 
-$LOAD_PATH.unshift File.expand_path("lib", __dir__)
+require "pathname"
+
+$LOAD_PATH.unshift(Pathname.new(__dir__) / "lib")
 require "pro_tacts/web"
 
 run ProTacts::Web.freeze.app
diff --git a/config/puma.rb b/config/puma.rb
index a1dbc39..ec4bd7a 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 # Allow WebDAV methods (PROPFIND, REPORT) in addition to standard HTTP methods
 supported_http_methods %w[GET HEAD OPTIONS PROPFIND REPORT]
diff --git a/lib/pro_tacts.rb b/lib/pro_tacts.rb
index 882af77..31381b8 100644
--- a/lib/pro_tacts.rb
+++ b/lib/pro_tacts.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "pro_tacts/config"
 
diff --git a/lib/pro_tacts/config.rb b/lib/pro_tacts/config.rb
index d8233ab..9781472 100644
--- a/lib/pro_tacts/config.rb
+++ b/lib/pro_tacts/config.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 module ProTacts
   # Single source of truth for configuration read from the environment.
diff --git a/lib/pro_tacts/debug_logger.rb b/lib/pro_tacts/debug_logger.rb
index def352d..b278ada 100644
--- a/lib/pro_tacts/debug_logger.rb
+++ b/lib/pro_tacts/debug_logger.rb
@@ -1,7 +1,7 @@
-# frozen_string_literal: true
 
 require "fileutils"
 require "logger"
+require "pathname"
 require "rack"
 
 module ProTacts
@@ -18,10 +18,10 @@ module ProTacts
     # (Logger syncs its own device), one timestamped line per dump. path
     # "stderr" writes to the process's stderr.
     def self.open_log(path)
-      target = if path == "stderr"
+      target = if path.to_s == "stderr"
                  $stderr
                else
-                 FileUtils.mkdir_p(File.dirname(path))
+                 FileUtils.mkdir_p(Pathname.new(path).dirname)
                  path
                end
 
@@ -48,17 +48,23 @@ module ProTacts
     private
 
     def log_request(env)
-      write(">>", "#{env['REQUEST_METHOD']} #{full_path(env)} #{env['SERVER_PROTOCOL']}")
-      each_header(env) { |name, value| write(">>", "#{name}: #{value}") }
+      write(">>", "#{env.fetch('REQUEST_METHOD')} #{full_path(env)} #{env.fetch('SERVER_PROTOCOL')}")
+      each_header(env) do |name, value|
+        write(">>", "#{name}: #{value}")
+      end
       body = read_request_body(env)
       write(">>", body) unless body.empty?
     end
 
     def log_response(status, headers, body)
       write("<<", "#{status}#{reason(status)}")
-      headers.each { |name, value| write("<<", "#{name}: #{value}") }
+      headers.each do |name, value|
+        write("<<", "#{name}: #{value}")
+      end
       parts = []
-      body.each { |part| parts << part }
+      body.each do |part|
+        parts << part
+      end
       body.close if body.respond_to?(:close)
       write("<<", parts.join) unless parts.join.empty?
       parts
@@ -101,7 +107,9 @@ module ProTacts
     end
 
     def write(prefix, text)
-      text.to_s.each_line(chomp: true) { |line| @logger.debug("#{prefix} #{line}") }
+      text.to_s.lines(chomp: true).each do |line|
+        @logger.debug("#{prefix} #{line}")
+      end
     end
   end
 end
diff --git a/lib/pro_tacts/profile.rb b/lib/pro_tacts/profile.rb
index 0c5664b..08c8410 100644
--- a/lib/pro_tacts/profile.rb
+++ b/lib/pro_tacts/profile.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "nokogiri"
 
diff --git a/lib/pro_tacts/vcard.rb b/lib/pro_tacts/vcard.rb
index 7bdaa37..6636f71 100644
--- a/lib/pro_tacts/vcard.rb
+++ b/lib/pro_tacts/vcard.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "kdl"
 
@@ -23,14 +22,14 @@ module ProTacts
     ALLOWED_PROPERTIES = {
       "phone" => %w[type],
       "email" => %w[type],
-      "address" => %w[type]
+      "address" => %w[type],
     }.freeze
 
     TEXT_ESCAPES = {
       "\\" => "\\\\",
       ";" => "\\;",
       "," => "\\,",
-      "\n" => "\\n"
+      "\n" => "\\n",
     }.freeze
 
     module_function
@@ -100,19 +99,19 @@ module ProTacts
     end
 
     def typed_property_lines(contact, kdl_name, vcard_name)
-      contact.children.select { it.name == kdl_name }.map do |node|
+      contact.children.select { it.name == kdl_name }.map { |node|
         validate_properties(node)
         type = node.properties["type"]&.value
         prefix = type ? "#{vcard_name};TYPE=#{type}" : vcard_name
         "#{prefix}:#{escape(string_argument(node))}"
-      end
+      }
     end
 
     # ADR's seven components in order: pobox, extended address, street,
     # locality, region, postal code, country. The first two have no KDL
     # counterpart and stay empty.
     def address_lines(contact)
-      contact.children.select { it.name == "address" }.map do |node|
+      contact.children.select { it.name == "address" }.map { |node|
         validate_children(node, ADDRESS_PARTS, "address")
         validate_properties(node)
         parts = node.children
@@ -123,7 +122,7 @@ module ProTacts
         type = node.properties["type"]&.value
         prefix = type ? "ADR;TYPE=#{type}" : "ADR"
         "#{prefix}:#{components(components)}"
-      end
+      }
     end
 
     # Text values escape backslash, the component separator, and the
diff --git a/lib/pro_tacts/web.rb b/lib/pro_tacts/web.rb
index 1587cd4..d8b4797 100644
--- a/lib/pro_tacts/web.rb
+++ b/lib/pro_tacts/web.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "pro_tacts"
 require "sentry-ruby"
@@ -141,7 +140,7 @@ module ProTacts
 
             contact_etag = %("a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2")
             collection_ctag = "ctag-2"
-            depth = request.env["HTTP_DEPTH"] || "infinity"
+            depth = request.env.fetch("HTTP_DEPTH", "infinity")
 
             # Check if this is an etag-only request (Depth:1 listing)
             etag_only = body.include?("getetag") && !body.include?("displayname") && !body.include?("resourcetype")
diff --git a/lib/roda/plugins/dav_verbs.rb b/lib/roda/plugins/dav_verbs.rb
index 2a810cd..b9bef93 100644
--- a/lib/roda/plugins/dav_verbs.rb
+++ b/lib/roda/plugins/dav_verbs.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "roda"
 
diff --git a/tasks/libhegel.rake b/tasks/libhegel.rake
index b3d549e..526e203 100644
--- a/tasks/libhegel.rake
+++ b/tasks/libhegel.rake
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 # Stages the native libhegel engine that the hegeltest gem drives — the
 # gem is pre-release and bundles no binary yet. Once it ships a real gem
@@ -13,6 +12,7 @@
 # whichever platform's asset landed there resolves.
 
 require "digest"
+require "pathname"
 require "rbconfig"
 require "fileutils"
 require "tmpdir"
@@ -22,7 +22,7 @@ LIBHEGEL_VERSION = Hegel::LIBHEGEL_VERSION
 LIBHEGEL_ASSETS = {
   "arm64-darwin" => "libhegel-darwin-arm64.dylib",
   "aarch64-linux" => "libhegel-linux-arm64.so",
-  "x86_64-linux" => "libhegel-linux-amd64.so"
+  "x86_64-linux" => "libhegel-linux-amd64.so",
 }.freeze
 
 # RbConfig's host_os carries version detail ("darwin25", "linux-gnu") that
@@ -36,29 +36,30 @@ rescue KeyError
   raise "no published libhegel for #{RbConfig::CONFIG.fetch('host_cpu')}-#{RbConfig::CONFIG.fetch('host_os')}; build one and point HEGEL_LIBHEGEL_PATH at it"
 end
 
-LIBHEGEL_DIR = File.expand_path("tmp/libhegel/#{LIBHEGEL_VERSION}", __dir__ + "/..")
-LIBHEGEL_PATH = File.join(LIBHEGEL_DIR, libhegel_asset_name)
+LIBHEGEL_DIR = Pathname.new(__dir__).parent / "tmp" / "libhegel" / LIBHEGEL_VERSION
+LIBHEGEL_PATH = LIBHEGEL_DIR / libhegel_asset_name
 
 file LIBHEGEL_PATH do |task|
-  asset = File.basename(task.name)
-  Dir.mktmpdir do |staging|
+  asset = Pathname.new(task.name).basename.to_s
+  Dir.mktmpdir do |dir|
+    staging = Pathname.new(dir)
     sh "gh", "release", "download", "v#{LIBHEGEL_VERSION}",
       "--repo", "hegeldev/hegel-rust",
       "--pattern", asset, "--pattern", "#{asset}.sha256",
-      "--dir", staging, verbose: false
+      "--dir", dir, verbose: false
 
-    expected = File.read(File.join(staging, "#{asset}.sha256")).split.first
-    actual = Digest::SHA256.hexdigest(File.binread(File.join(staging, asset)))
+    expected = File.read(staging / "#{asset}.sha256").split.first
+    actual = Digest::SHA256.hexdigest(File.binread(staging / asset))
     raise "SHA-256 mismatch for #{asset}: expected #{expected}, got #{actual}" unless actual == expected
 
     FileUtils.mkdir_p(LIBHEGEL_DIR)
-    FileUtils.mv(File.join(staging, asset), task.name)
+    FileUtils.mv(staging / asset, task.name)
   end
 end
 
 # Keep a bare `rake test` self-contained: the engine resolves through
 # HEGEL_LIBHEGEL_PATH when direnv has loaded .envrc, and through the
 # staged copy otherwise.
-ENV["HEGEL_LIBHEGEL_PATH"] = LIBHEGEL_DIR if ENV["HEGEL_LIBHEGEL_PATH"].nil? || ENV["HEGEL_LIBHEGEL_PATH"].empty?
+ENV["HEGEL_LIBHEGEL_PATH"] = LIBHEGEL_DIR.to_s if ENV.fetch("HEGEL_LIBHEGEL_PATH", "").empty?
 
 task test: LIBHEGEL_PATH
diff --git a/test/pro_tacts/exchange_fixtures.rb b/test/pro_tacts/exchange_fixtures.rb
index 16277d4..72a5a2d 100644
--- a/test/pro_tacts/exchange_fixtures.rb
+++ b/test/pro_tacts/exchange_fixtures.rb
@@ -1,12 +1,12 @@
-# frozen_string_literal: true
 
+require "pathname"
 require "rack/test"
 
 # Loads and replays the recorded macOS Contacts exchange in
 # test/fixtures/macos-exchange. See that directory's README for provenance
 # and the request/response file format.
 module ExchangeFixtures
-  DIRECTORY = File.expand_path("../fixtures/macos-exchange", __dir__)
+  DIRECTORY = Pathname.new(__dir__).parent / "fixtures" / "macos-exchange"
 
   Step = Struct.new(:name, :method, :path, :headers, :body, keyword_init: true)
   Response = Struct.new(:status, :headers, :body, keyword_init: true)
@@ -18,7 +18,7 @@ module ExchangeFixtures
   class << self
     def steps
       Dir.children(DIRECTORY)
-        .select { |child| File.directory?(File.join(DIRECTORY, child)) }
+        .select { (DIRECTORY / it).directory? }
         .sort
         .map { |name| Step.new(name:, **parse_request(name)) }
     end
@@ -37,10 +37,10 @@ module ExchangeFixtures
     # Rack env for the recorded headers: Content-Type and Depth are the only
     # ones the app reads, but replaying all of them keeps the fidelity.
     def env_for(step)
-      step.headers.to_h do |name, value|
+      step.headers.to_h { |name, value|
         key = name == "Content-Type" ? "CONTENT_TYPE" : "HTTP_#{name.tr('-', '_').upcase}"
         [key, value]
-      end
+      }
     end
 
     # Replays every recorded request against the app and rewrites the
@@ -57,7 +57,7 @@ module ExchangeFixtures
     private
 
     def read(name, file)
-      File.read(File.join(DIRECTORY, name, file))
+      File.read(DIRECTORY / name / file)
     end
 
     def split_message(raw)
@@ -76,7 +76,7 @@ module ExchangeFixtures
       content = ([response.status.to_s] + headers).join("\n") + "\n\n"
       body = response.body.chomp
       content = "#{content}#{body}\n" unless body.empty?
-      File.write(File.join(DIRECTORY, step.name, "response"), content)
+      File.write(DIRECTORY / step.name / "response", content)
     end
   end
 end
diff --git a/test/pro_tacts/test_config.rb b/test/pro_tacts/test_config.rb
index 9268eea..d32215e 100644
--- a/test/pro_tacts/test_config.rb
+++ b/test/pro_tacts/test_config.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "minitest/autorun"
 
diff --git a/test/pro_tacts/test_debug_logger.rb b/test/pro_tacts/test_debug_logger.rb
index 8ecd32f..fd76286 100644
--- a/test/pro_tacts/test_debug_logger.rb
+++ b/test/pro_tacts/test_debug_logger.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "minitest/autorun"
 require "logger"
@@ -25,8 +24,8 @@ class DebugLoggerTest < Minitest::Test
       "PATH_INFO" => "/dav/addressbook/",
       "QUERY_STRING" => "",
       "SERVER_PROTOCOL" => "HTTP/1.1",
-      "rack.input" => StringIO.new(body)
-    }.merge(headers.transform_keys { |k| "HTTP_#{k.to_s.upcase.tr('-', '_')}" })
+      "rack.input" => StringIO.new(body),
+    }.merge(headers.transform_keys { "HTTP_#{it.to_s.upcase.tr('-', '_')}" })
   end
 
   def test_dumps_request_line_with_method_path_and_protocol
@@ -53,7 +52,7 @@ class DebugLoggerTest < Minitest::Test
     read = nil
     app = ProTacts::DebugLogger.new(
       ->(e) { read = e["rack.input"].read; [200, {}, [""]] },
-      logger: Logger.new(StringIO.new)
+      logger: Logger.new(StringIO.new),
     )
 
     app.call(env(body: "<x/>"))
@@ -86,9 +85,10 @@ class DebugLoggerTest < Minitest::Test
 
   class OpenLogTest < Minitest::Test
     def test_appends_timestamped_lines_to_a_file_it_creates
+      require "pathname"
       require "tmpdir"
       Dir.mktmpdir do |dir|
-        path = File.join(dir, "nested", "debug.log")
+        path = Pathname.new(dir) / "nested" / "debug.log"
         logger = ProTacts::DebugLogger.open_log(path)
         logger.debug(">> PROPFIND / HTTP/1.1")
 
diff --git a/test/pro_tacts/test_macos_exchange.rb b/test/pro_tacts/test_macos_exchange.rb
index 9d0ae3b..594e97c 100644
--- a/test/pro_tacts/test_macos_exchange.rb
+++ b/test/pro_tacts/test_macos_exchange.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require_relative "../test_helper"
 require "rack/test"
diff --git a/test/pro_tacts/test_profile.rb b/test/pro_tacts/test_profile.rb
index 49ed2c1..3eb5bb5 100644
--- a/test/pro_tacts/test_profile.rb
+++ b/test/pro_tacts/test_profile.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require_relative "../test_helper"
 
@@ -62,7 +61,7 @@ class ProfileTest < Minitest::Test
 
     assert_equal [
       "#{ProTacts::Profile::IDENTIFIER_PREFIX}-20260818ab12",
-      "#{ProTacts::Profile::IDENTIFIER_PREFIX}-20260818cd34"
+      "#{ProTacts::Profile::IDENTIFIER_PREFIX}-20260818cd34",
     ], ProTacts::Profile.installed_identifiers(list_output)
   end
 
diff --git a/test/pro_tacts/test_vcard.rb b/test/pro_tacts/test_vcard.rb
index d1ca07a..756c239 100644
--- a/test/pro_tacts/test_vcard.rb
+++ b/test/pro_tacts/test_vcard.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require_relative "../test_helper"
 
@@ -127,7 +126,9 @@ class VCardTest < Minitest::Test
 
     physical = vcard.split("\r\n")
     assert_operator physical.length, :>, 1, "expected folding"
-    physical.each { assert_operator it.bytesize, :<=, 75 }
+    physical.each do
+      assert_operator it.bytesize, :<=, 75
+    end
 
     logical = unfold(physical)
     assert_equal "FN:#{"x" * 30}#{("é" * 60)}", logical.find { it.start_with?("FN:") }
@@ -304,7 +305,7 @@ class VCardTest < Minitest::Test
   # control characters that KDL forbids raw inside a string become \u
   # escapes.
   def kdl_string(text)
-    escaped = normalize(text).chars.map do |char|
+    escaped = normalize(text).chars.map { |char|
       case char
       when "\\" then "\\\\"
       when '"' then '\\"'
@@ -312,7 +313,7 @@ class VCardTest < Minitest::Test
       when /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/ then format('\u{%02x}', char.ord)
       else char
       end
-    end.join
+    }.join
     "\"#{escaped}\""
   end
 
@@ -467,16 +468,16 @@ class VCardTest < Minitest::Test
         given: tc.draw(optional(text(max_size: 20))),
         additional: tc.draw(optional(text(max_size: 20))),
         prefix: tc.draw(optional(text(max_size: 20))),
-        suffix: tc.draw(optional(text(max_size: 20)))
+        suffix: tc.draw(optional(text(max_size: 20))),
       }
       phones = tc.draw(arrays(tuples(text(max_size: 30), optional(type)), max_size: 5))
       emails = tc.draw(arrays(tuples(text(max_size: 30), optional(type)), max_size: 5))
       addresses = tc.draw(arrays(
         tuples(
           text(max_size: 20), text(max_size: 20), text(max_size: 20),
-          text(max_size: 20), text(max_size: 20), optional(type)
+          text(max_size: 20), text(max_size: 20), optional(type),
         ),
-        max_size: 3
+        max_size: 3,
       ))
       uid = tc.draw(uuids)
       # The renderer rejects a components-only name whose parts are all
diff --git a/test/pro_tacts/test_web.rb b/test/pro_tacts/test_web.rb
index a381e98..f61aabf 100644
--- a/test/pro_tacts/test_web.rb
+++ b/test/pro_tacts/test_web.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require_relative "../test_helper"
 require "rack/test"
diff --git a/test/roda/plugins/test_dav_verbs.rb b/test/roda/plugins/test_dav_verbs.rb
index 7b8ac0c..31ef275 100644
--- a/test/roda/plugins/test_dav_verbs.rb
+++ b/test/roda/plugins/test_dav_verbs.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 require "minitest/autorun"
 require "rack/test"
diff --git a/test/test_helper.rb b/test/test_helper.rb
index bc8d5e0..81d6508 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,4 +1,3 @@
-# frozen_string_literal: true
 
 # Marks the process as running tests before anything requires the app, so
 # web.rb skips Sentry.init and no SENTRY_DSN is needed.