Skip to content

Commit cc9acef

Browse files
committed
Added Types::Dec.
1 parent ec64f50 commit cc9acef

File tree

4 files changed

+267
-0
lines changed

4 files changed

+267
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ attributes to allow safely and securely executing commands.
2020
* Supports common option types:
2121
* [Str][CommandMapper::Types::Str]: string values
2222
* [Num][CommandMapper::Types::Num]: numeric values
23+
* [Dec][CommandMapper::Types::Dec]: decimal values
2324
* [Hex][CommandMapper::Types::Hex]: hexadecimal values
2425
* [Map][CommandMapper::Types::Map]: maps Ruby values to other String values.
2526
* `Map::YesNo`: maps `true`/`false` to `yes`/`no`.
@@ -50,6 +51,7 @@ attributes to allow safely and securely executing commands.
5051

5152
[CommandMapper::Types::Str]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Str
5253
[CommandMapper::Types::Num]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Num
54+
[CommandMapper::Types::Dec]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Dec
5355
[CommandMapper::Types::Hex]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Hex
5456
[CommandMapper::Types::Map]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Map
5557
[CommandMapper::Types::Enum]: https://rubydoc.info/gems/command_mapper/CommandMapper/Types/Enum

lib/command_mapper/types.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'command_mapper/types/type'
22
require 'command_mapper/types/str'
33
require 'command_mapper/types/num'
4+
require 'command_mapper/types/dec'
45
require 'command_mapper/types/hex'
56
require 'command_mapper/types/map'
67
require 'command_mapper/types/enum'

lib/command_mapper/types/dec.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
require 'command_mapper/types/type'
2+
3+
module CommandMapper
4+
module Types
5+
#
6+
# Represents a decimal value (ex: `1.5`).
7+
#
8+
# @since 0.3.0
9+
#
10+
class Dec < Type
11+
12+
# The optional range of acceptable decimal numbers.
13+
#
14+
# @return [Range<Float,Float>, nil]
15+
#
16+
# @api semipublic
17+
attr_reader :range
18+
19+
#
20+
# Initializes the decimal type.
21+
#
22+
# @param [Range<Float,Float>] range
23+
# Specifies the range of acceptable numbers.
24+
#
25+
def initialize(range: nil)
26+
@range = range
27+
end
28+
29+
#
30+
# Validates a value.
31+
#
32+
# @param [String, Numeric] value
33+
# The given value to validate.
34+
#
35+
# @return [true, (false, String)]
36+
# Returns true if the value is valid, or `false` and a validation error
37+
# message if the value is not compatible.
38+
#
39+
# @api semipublic
40+
#
41+
def validate(value)
42+
case value
43+
when Float
44+
# no-op
45+
when String
46+
unless value =~ /\A\d+(?:\.\d+)?\z/
47+
return [false, "contains non-decimal characters (#{value.inspect})"]
48+
end
49+
else
50+
unless value.respond_to?(:to_f)
51+
return [false, "cannot be converted into a Float (#{value.inspect})"]
52+
end
53+
end
54+
55+
if @range
56+
unless @range.include?(value.to_f)
57+
return [false, "(#{value.inspect}) not within the range of acceptable values (#{range.inspect})"]
58+
end
59+
end
60+
61+
return true
62+
end
63+
64+
#
65+
# Formats a decimal value.
66+
#
67+
# @param [String, Float, #to_f] value
68+
# The given value to format.
69+
#
70+
# @return [String]
71+
# The formatted decimal value.
72+
#
73+
# @api semipublic
74+
#
75+
def format(value)
76+
case value
77+
when Float, String then value.to_s
78+
else value.to_f.to_s
79+
end
80+
end
81+
82+
end
83+
end
84+
end

spec/types/dec_spec.rb

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
require 'spec_helper'
2+
require 'command_mapper/types/dec'
3+
4+
describe CommandMapper::Types::Dec do
5+
describe "#initialize" do
6+
context "when initialized with no keyword arguments" do
7+
it "must set #range to nil" do
8+
expect(subject.range).to be(nil)
9+
end
10+
end
11+
12+
context "when initialized with range: ..." do
13+
let(:range) { 1.0..1.5 }
14+
15+
subject { described_class.new(range: range) }
16+
17+
it "must set #range" do
18+
expect(subject.range).to eq(range)
19+
end
20+
end
21+
end
22+
23+
describe "#validate" do
24+
context "when given an Integer" do
25+
let(:value) { 1 }
26+
27+
it "must return true" do
28+
expect(subject.validate(value)).to be(true)
29+
end
30+
31+
context "when initialized with range: ..." do
32+
let(:range) { 2.0..10.0 }
33+
34+
subject { described_class.new(range: range) }
35+
36+
context "and the value is within the range of values" do
37+
let(:value) { 4.0 }
38+
39+
it "must return true" do
40+
expect(subject.validate(value)).to be(true)
41+
end
42+
end
43+
44+
context "but the value is not within the range of values" do
45+
let(:value) { 0.0 }
46+
47+
it "must return [false, \"(...) not within the range of acceptable values (...)\"]" do
48+
expect(subject.validate(value)).to eq(
49+
[false, "(#{value.inspect}) not within the range of acceptable values (#{range.inspect})"]
50+
)
51+
end
52+
end
53+
end
54+
end
55+
56+
context "when given a String" do
57+
context "and it contains only digits" do
58+
let(:value) { "0123456789" }
59+
60+
it "must return true" do
61+
expect(subject.validate(value)).to be(true)
62+
end
63+
64+
context "when initialized with range: ..." do
65+
let(:range) { 2.0..10.0 }
66+
67+
subject { described_class.new(range: range) }
68+
69+
context "and the value is within the range of values" do
70+
let(:value) { '4.0' }
71+
72+
it "must return true" do
73+
expect(subject.validate(value)).to be(true)
74+
end
75+
end
76+
77+
context "but the value is not within the range of values" do
78+
let(:value) { '0.0' }
79+
80+
it "must return [false, \"(...) not within the range of acceptable values (...)\"]" do
81+
expect(subject.validate(value)).to eq(
82+
[false, "(#{value.inspect}) not within the range of acceptable values (#{range.inspect})"]
83+
)
84+
end
85+
end
86+
end
87+
88+
context "but the String contains a newline" do
89+
let(:value) { "01234.\n56789" }
90+
91+
it "must return [false, \"contains non-decimal characters (...)\"]" do
92+
expect(subject.validate(value)).to eq(
93+
[false, "contains non-decimal characters (#{value.inspect})"]
94+
)
95+
end
96+
end
97+
end
98+
99+
context "but it contains non-digits" do
100+
let(:value) { "12.abc34" }
101+
102+
it "must return [false, \"contains non-decimal characters (...)\"]" do
103+
expect(subject.validate(value)).to eq(
104+
[false, "contains non-decimal characters (#{value.inspect})"]
105+
)
106+
end
107+
end
108+
end
109+
110+
context "when given another type of Object" do
111+
context "and it defines a #to_f method" do
112+
let(:value) { 1 }
113+
114+
it "must return true" do
115+
expect(subject.validate(value)).to be(true)
116+
end
117+
end
118+
119+
context "when initialized with range: ..." do
120+
let(:range) { 2.0..10.0 }
121+
122+
subject { described_class.new(range: range) }
123+
124+
context "and the value is within the range of values" do
125+
let(:value) { 4 }
126+
127+
it "must return true" do
128+
expect(subject.validate(value)).to be(true)
129+
end
130+
end
131+
132+
context "but the value is not within the range of values" do
133+
let(:value) { 0 }
134+
135+
it "must return [false, \"(...) not within the range of acceptable values (...)\"]" do
136+
expect(subject.validate(value)).to eq(
137+
[false, "(#{value.inspect}) not within the range of acceptable values (#{range.inspect})"]
138+
)
139+
end
140+
end
141+
end
142+
143+
context "but it does not define a #to_f method" do
144+
let(:value) { Object.new }
145+
146+
it "must return [false, \"value cannot be converted into a Float\"]" do
147+
expect(subject.validate(value)).to eq(
148+
[false, "cannot be converted into a Float (#{value.inspect})"]
149+
)
150+
end
151+
end
152+
end
153+
end
154+
155+
describe "#format" do
156+
context "when given a String" do
157+
let(:value) { "12345.67890" }
158+
159+
it "must return the same String" do
160+
expect(subject.format(value)).to eq(value)
161+
end
162+
end
163+
164+
context "when given an Intger" do
165+
let(:value) { 12345.67890 }
166+
167+
it "must convert the Integer into a String" do
168+
expect(subject.format(value)).to eq(value.to_s)
169+
end
170+
end
171+
172+
context "when given another type of Object" do
173+
let(:value) { 1 }
174+
175+
it "must call #to_i then #to_s" do
176+
expect(subject.format(value)).to eq(value.to_f.to_s)
177+
end
178+
end
179+
end
180+
end

0 commit comments

Comments
 (0)