refactor: align Ruby host with RuboCop while retaining double quotes
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user