lib/tty/prompt/converters.rb in tty-prompt-0.21.0 vs lib/tty/prompt/converters.rb in tty-prompt-0.22.0

- old
+ new

@@ -1,75 +1,182 @@ # frozen_string_literal: true -require 'pathname' -require 'necromancer' +require_relative "const" +require_relative "converter_dsl" -require_relative 'converter_dsl' - module TTY class Prompt module Converters extend ConverterDSL - # Delegate Necromancer errors - # - # @api private - def self.on_error - if block_given? - yield - else - raise ArgumentError, 'You need to provide a block argument.' + TRUE_VALUES = /^(t(rue)?|y(es)?|on|1)$/i.freeze + FALSE_VALUES = /^(f(alse)?|n(o)?|off|0)$/i.freeze + + SINGLE_DIGIT_MATCHER = /^(?<digit>\-?\d+(\.\d+)?)$/.freeze + DIGIT_MATCHER = /^(?<open>-?\d+(\.\d+)?) + \s*(?<sep>(\.\s*){2,3}|-|,)\s* + (?<close>-?\d+(\.\d+)?)$ + /x.freeze + LETTER_MATCHER = /^(?<open>\w) + \s*(?<sep>(\.\s*){2,3}|-|,)\s* + (?<close>\w)$ + /x.freeze + + converter(:boolean, :bool) do |input| + case input.to_s + when TRUE_VALUES then true + when FALSE_VALUES then false + else Const::Undefined end - rescue Necromancer::ConversionTypeError => e - raise ConversionError, e.message end - converter(:bool) do |input| - on_error { Necromancer.convert(input).to(:boolean, strict: true) } - end - - converter(:string) do |input| + converter(:string, :str) do |input| String(input).chomp end - converter(:symbol) do |input| + converter(:symbol, :sym) do |input| input.to_sym end + converter(:char) do |input| + String(input).chars.to_a[0] + end + converter(:date) do |input| - on_error { Necromancer.convert(input).to(:date, strict: true) } + begin + require "date" unless defined?(::Date) + ::Date.parse(input) + rescue ArgumentError + Const::Undefined + end end converter(:datetime) do |input| - on_error { Necromancer.convert(input).to(:datetime, strict: true) } + begin + require "date" unless defined?(::Date) + ::DateTime.parse(input.to_s) + rescue ArgumentError + Const::Undefined + end end - converter(:int) do |input| - on_error { Necromancer.convert(input).to(:integer, strict: true) } + converter(:time) do |input| + begin + require "time" + ::Time.parse(input.to_s) + rescue ArgumentError + Const::Undefined + end end + converter(:integer, :int) do |input| + begin + Integer(input) + rescue ArgumentError + Const::Undefined + end + end + converter(:float) do |input| - on_error { Necromancer.convert(input).to(:float, strict: true) } + begin + Float(input) + rescue TypeError, ArgumentError + Const::Undefined + end end + # Convert string number to integer or float + # + # @return [Integer,Float,Const::Undefined] + # + # @api private + def cast_to_num(num) + ([convert(:int, num), convert(:float, num)] - [Const::Undefined]).first || + Const::Undefined + end + module_function :cast_to_num + converter(:range) do |input| - on_error { Necromancer.convert(input).to(:range, strict: true) } + if input.is_a?(::Range) + input + elsif match = input.to_s.match(SINGLE_DIGIT_MATCHER) + digit = cast_to_num(match[:digit]) + ::Range.new(digit, digit) + elsif match = input.to_s.match(DIGIT_MATCHER) + open = cast_to_num(match[:open]) + close = cast_to_num(match[:close]) + ::Range.new(open, close, match[:sep].gsub(/\s*/, "") == "...") + elsif match = input.to_s.match(LETTER_MATCHER) + ::Range.new(match[:open], match[:close], + match[:sep].gsub(/\s*/, "") == "...") + else Const::Undefined + end end converter(:regexp) do |input| Regexp.new(input) end - converter(:file) do |input| - ::File.open(::File.join(Dir.pwd, input)) + converter(:filepath, :file) do |input| + ::File.expand_path(input) end - converter(:path) do |input| - Pathname.new(::File.join(Dir.pwd, input)) + converter(:pathname, :path) do |input| + require "pathname" unless defined?(::Pathname) + ::Pathname.new(input) end - converter(:char) do |input| - String(input).chars.to_a[0] + converter(:uri) do |input| + require "uri" unless defined?(::URI) + ::URI.parse(input) + end + + converter(:list, :array) do |val| + (val.respond_to?(:to_a) ? val : val.split(/(?<!\\),/)) + .map { |v| v.strip.gsub(/\\,/, ",") } + .reject(&:empty?) + end + + converter(:hash, :map) do |val| + values = val.respond_to?(:to_a) ? val : val.split(/[& ]/) + values.each_with_object({}) do |pair, pairs| + key, value = pair.split(/[=:]/, 2) + if (current = pairs[key.to_sym]) + pairs[key.to_sym] = Array(current) << value + else + pairs[key.to_sym] = value + end + pairs + end + end + + converter_registry.keys.each do |type| + next if type =~ /list|array|map|hash/ + + [:"#{type}_list", :"#{type}_array", :"#{type}s"].each do |new_type| + converter(new_type) do |val| + converter_registry[:array].(val).map do |obj| + converter_registry[type].(obj) + end + end + end + + [:"#{type}_map", :"#{type}_hash"].each do |new_type| + converter(new_type) do |val| + converter_registry[:hash].(val).each_with_object({}) do |(k, v), h| + h[k] = converter_registry[type].(v) + end + end + end + + [:"string_#{type}_map", :"str_#{type}_map", + :"string_#{type}_hash", :"str_#{type}_hash"].each do |new_type| + converter(new_type) do |val| + converter_registry[:hash].(val).each_with_object({}) do |(k, v), h| + h[converter_registry[:string].(k)] = converter_registry[type].(v) + end + end + end end end # Converters end # Prompt end # TTY