refactor: align Ruby host with RuboCop while retaining double quotes

This commit is contained in:
2026-10-09 20:41:12 +02:00
parent bbb61c2fdd
commit 2c3c73e5b9
10 changed files with 904 additions and 529 deletions
+186
View File
@@ -0,0 +1,186 @@
# frozen_string_literal: true
# Shared value validation and frozen editor-owned configuration objects.
module Vamp
MAX_FRAME = 4 * 1024 * 1024
# Validates a boundary value and returns a UTF-8 copy.
# @param value [Object]
# @param label [String]
# @return [String]
def self.string!(value, label)
raise TypeError, "#{label} must be a String" unless value.is_a?(String)
copy = value.dup.force_encoding(Encoding::UTF_8)
raise ArgumentError, "#{label} must be valid UTF-8" unless copy.valid_encoding?
copy
end
# Recursively validates JSON values and normalizes hash keys to strings.
# @param value [Object]
# @return [Hash{String => Object}, Array<Object>, String, Integer, Float, Boolean, nil]
def self.json_value!(value)
case value
when Hash
json_hash(value)
when Array
value.map { |item| json_value!(item) }
else
json_scalar(value)
end
end
# Freezes the supplied value and every nested hash/array member in place.
# @param value [Object]
# @return [Object] the same value
def self.deep_freeze(value)
case value
when Hash
value.each do |key, item|
deep_freeze(key)
deep_freeze(item)
end
when Array
value.each { |item| deep_freeze(item) }
end
value.freeze
end
# @!override Vamp::Action#name
# @return [String] the catalog's dotted action name
Action = Struct.new(:name)
# @!override Vamp::Mode#name
# @return [String] the lowercase binding mode
Mode = Struct.new(:name)
# Action methods and nested modules come exclusively from the Rust catalog.
# @type [Module<Vamp::Core>]
Core = Module.new
# @param names [Array<String>] dotted core action names
# @return [Array<Module>] frozen action namespaces
def self.install_actions(names)
raise TypeError, "core action catalog must be an Array" unless names.is_a?(Array)
namespaces = [Core]
names.each { |name| install_action(name, namespaces) }
namespaces.each(&:freeze)
end
# Frozen binding mode values, distinct from a buffer's editing state.
module Modes
# @!method self.normal
# @return [Vamp::Mode]
# @!method self.insert
# @return [Vamp::Mode]
# @!method self.command
# @return [Vamp::Mode]
# @!method self.search
# @return [Vamp::Mode]
# @!method self.visual
# @return [Vamp::Mode]
# @!method self.selection
# @return [Vamp::Mode]
# @!method self.tree
# @return [Vamp::Mode]
%w[normal insert command search visual selection tree].each do |name|
mode = Mode.new(name.freeze).freeze
define_singleton_method(name) { mode }
end
end
Action.private_class_method :new
Mode.private_class_method :new
# @param value [Hash{String, Symbol => Object}]
# @return [Hash{String => Object}]
def self.json_hash(value)
value.to_h do |key, item|
raise TypeError, "option keys must be Strings or Symbols" unless key.is_a?(String) || key.is_a?(Symbol)
[string!(key.to_s, "option key"), json_value!(item)]
end
end
# @param value [Object]
# @return [String, Integer, Float, Boolean, nil]
def self.json_scalar(value)
case value
when String
string!(value, "option value")
when Integer, TrueClass, FalseClass, NilClass, Float
json_literal(value)
else
raise TypeError, "options must contain only JSON values"
end
end
# @param value [Integer, Float, Boolean, nil]
# @return [Integer, Float, Boolean, nil]
def self.json_literal(value)
raise ArgumentError, "option numbers must be finite" if value.is_a?(Float) && !value.finite?
value
end
# @param name [String]
# @param namespaces [Array<Module>]
# @return [Symbol] the installed action method name
def self.install_action(name, namespaces)
name = action_name(name)
parts = name.split(".").drop(1)
namespace = parts[0...-1].reduce(Core) { |parent, part| action_namespace(parent, part, namespaces, name) }
leaf = parts.last
available_action!(namespace, leaf, name)
action = Action.send(:new, name.freeze).freeze
namespace.define_singleton_method(leaf) { action }
end
# @param name [Object]
# @return [String]
def self.action_name(name)
name = string!(name, "core action name")
raise ArgumentError, "invalid core action name: #{name}" unless name.match?(/\Acore(?:\.[a-z_][a-z0-9_]*)+\z/)
name
end
# @param parent [Module]
# @param part [String]
# @param namespaces [Array<Module>]
# @param name [String]
# @return [Module]
def self.action_namespace(parent, part, namespaces, name)
return new_action_namespace(parent, part, namespaces) unless parent.singleton_methods(false).include?(part.to_sym)
child = parent.public_send(part)
raise ArgumentError, "conflicting core action namespace: #{name}" unless child.is_a?(Module)
child
end
# @param parent [Module]
# @param part [String]
# @param namespaces [Array<Module>]
# @return [Module]
def self.new_action_namespace(parent, part, namespaces)
child = Module.new
parent.define_singleton_method(part) { child }
namespaces << child
child
end
# @param namespace [Module]
# @param leaf [String]
# @param name [String]
# @return [nil]
def self.available_action!(namespace, leaf, name)
return unless namespace.singleton_methods(false).include?(leaf.to_sym)
raise ArgumentError, "conflicting core action name: #{name}"
end
private_class_method :json_hash, :json_scalar, :json_literal, :install_action, :action_name,
:action_namespace, :new_action_namespace, :available_action!
end