From 9b161afddd45ab46df1612941859e8b72fbbcae3 Mon Sep 17 00:00:00 2001 From: Gustavo Ribeiro Date: Tue, 24 Dec 2024 11:32:43 -0300 Subject: [PATCH] Implement argument and option type casting --- lib/dry/cli/option.rb | 30 +++++++++++ lib/dry/cli/parser.rb | 4 +- spec/unit/dry/cli/option_spec.rb | 85 ++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 spec/unit/dry/cli/option_spec.rb diff --git a/lib/dry/cli/option.rb b/lib/dry/cli/option.rb index 710de98..0ba2309 100644 --- a/lib/dry/cli/option.rb +++ b/lib/dry/cli/option.rb @@ -70,6 +70,18 @@ def array? type == :array end + # @since 2.0.0 + # @api private + def integer? + type == :integer + end + + # @since 2.0.0 + # @api private + def float? + type == :float + end + # @since 0.1.0 # @api private def default @@ -121,6 +133,24 @@ def alias_names .map { |name| name.size == 1 ? "-#{name}" : "--#{name}" } .map { |name| boolean? || flag? ? name : "#{name} VALUE" } end + + # @since 2.0.0 + # @api private + # rubocop:disable Metrics/PerceivedComplexity + def type_cast(value) + return value if value.nil? + + if integer? + value.to_i + elsif float? + value.to_f + elsif argument? && (boolean? || flag?) + %w[0 f false off].include?(value.downcase) ? false : true + else + value + end + end + # rubocop:enable Metrics/PerceivedComplexity end # Command line argument diff --git a/lib/dry/cli/parser.rb b/lib/dry/cli/parser.rb index aac127f..1dcb34c 100644 --- a/lib/dry/cli/parser.rb +++ b/lib/dry/cli/parser.rb @@ -20,7 +20,7 @@ def self.call(command, arguments, prog_name) OptionParser.new do |opts| command.options.each do |option| opts.on(*option.parser_options) do |value| - parsed_options[option.name.to_sym] = value + parsed_options[option.name.to_sym] = option.type_cast(value) end end @@ -77,7 +77,7 @@ def self.match_arguments(command_arguments, arguments) result[cmd_arg.name] = arguments[index..] break else - result[cmd_arg.name] = arguments.at(index) + result[cmd_arg.name] = cmd_arg.type_cast(arguments.at(index)) end end diff --git a/spec/unit/dry/cli/option_spec.rb b/spec/unit/dry/cli/option_spec.rb new file mode 100644 index 0000000..dc214a5 --- /dev/null +++ b/spec/unit/dry/cli/option_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +RSpec.describe "Option" do + describe "type casting" do + it "supports :integer" do + o = Dry::CLI::Option.new("test", {type: :integer}) + + expect(o.type_cast("42")).to eq(42) + expect(o.type_cast("4.2")).to eq(4) + end + + it "supports :float" do + o = Dry::CLI::Option.new("test", {type: :float}) + + expect(o.type_cast("4.2")).to eq(4.2) + expect(o.type_cast("42.42")).to eq(42.42) + end + + it "supports :string" do + o = Dry::CLI::Option.new("test", {type: :string}) + + expect(o.type_cast("42")).to eq("42") + expect(o.type_cast("42.42")).to eq("42.42") + expect(o.type_cast("true")).to eq("true") + expect(o.type_cast("this is a string")).to eq("this is a string") + end + + it "returns the received value when no type is defined" do + o = Dry::CLI::Option.new("test") + + expect(o.type_cast("42")).to eq("42") + expect(o.type_cast("42.42")).to eq("42.42") + expect(o.type_cast("true")).to eq("true") + expect(o.type_cast("this is a string")).to eq("this is a string") + end + + it "supports :boolean arguments" do + o = Dry::CLI::Argument.new("test", {type: :boolean}) + + expect(o.type_cast("true")).to eq(true) + expect(o.type_cast("True")).to eq(true) + expect(o.type_cast("TRUE")).to eq(true) + expect(o.type_cast("t")).to eq(true) + expect(o.type_cast("T")).to eq(true) + expect(o.type_cast("1")).to eq(true) + expect(o.type_cast("42")).to eq(true) + expect(o.type_cast("42.42")).to eq(true) + expect(o.type_cast("this is considered true")).to eq(true) + + expect(o.type_cast("false")).to eq(false) + expect(o.type_cast("False")).to eq(false) + expect(o.type_cast("FALSE")).to eq(false) + expect(o.type_cast("off")).to eq(false) + expect(o.type_cast("Off")).to eq(false) + expect(o.type_cast("OFF")).to eq(false) + expect(o.type_cast("f")).to eq(false) + expect(o.type_cast("F")).to eq(false) + expect(o.type_cast("0")).to eq(false) + end + + it "considers :flag arguments as :boolean" do + o = Dry::CLI::Argument.new("test", {type: :flag}) + + expect(o.type_cast("true")).to eq(true) + expect(o.type_cast("True")).to eq(true) + expect(o.type_cast("TRUE")).to eq(true) + expect(o.type_cast("t")).to eq(true) + expect(o.type_cast("T")).to eq(true) + expect(o.type_cast("1")).to eq(true) + expect(o.type_cast("42")).to eq(true) + expect(o.type_cast("42.42")).to eq(true) + expect(o.type_cast("this is considered true")).to eq(true) + + expect(o.type_cast("false")).to eq(false) + expect(o.type_cast("False")).to eq(false) + expect(o.type_cast("FALSE")).to eq(false) + expect(o.type_cast("off")).to eq(false) + expect(o.type_cast("Off")).to eq(false) + expect(o.type_cast("OFF")).to eq(false) + expect(o.type_cast("f")).to eq(false) + expect(o.type_cast("F")).to eq(false) + expect(o.type_cast("0")).to eq(false) + end + end +end