Skip to content

Commit

Permalink
Make SimpleElementVals behave more consistently across types (AN, DT,…
Browse files Browse the repository at this point in the history
… ID, Nn, R, TM)

- Invalid#map and Invalid#copy always return Invalid
- Invaild#== is now #eql?, so it will not == nil any longer
- Empty#map and Empty#copy always call the type's .value constructor;
  previously sometimes it only constructed another Empty
- Empty#== is true for "" on AN and ID, and nil on DT, Nn, R, TM
- Empty#value is now implemented and returns "" for AN and ID, and nil
  for DT, Nn, R, and TM
- Empty#map now yields #value, which is either nil or ""
- All non-Invalid values implement #coerce
- Each type now has a case in .value for when the argument is already
  the correct type (similar to a copy constructor), this is used in
  #coerce
- Each type also has a case in .value for the data in #value of the
  corresponding type, so eg. DateVal.value(date_val.value) == date_val
- DateVal::Improper#value is now [year, month, day], and now #map yields
  this single argument (a three-element array) instead of three arguments
- TimeVal::Valid#value is now [hour, minute, second], and now #map
  yields this single argument instead of three arguments
- Greatly reduce duplication of specs for each element type by using
  shared_examples for invalid, empty, and non-empty cases
  • Loading branch information
kputnam committed Feb 15, 2019
1 parent 1235c80 commit 7648764
Show file tree
Hide file tree
Showing 15 changed files with 848 additions and 1,865 deletions.
114 changes: 40 additions & 74 deletions lib/stupidedi/versions/common/element_types/an.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,6 @@ def too_short?
false
end

#
# Objects passed to StringVal.value that don't respond to #to_s are
# modeled by this class. Note most everything in Ruby responds to
# that method, including things that really shouldn't be considered
# StringVals (like Array or Class), so other validation should be
# performed on StringVal::NonEmpty values.
#
class Invalid < StringVal
# @return [Object]
attr_reader :value
Expand All @@ -167,12 +160,13 @@ def empty?
false
end

# @return [StringVal]
# @return [Invalid]
def map
StringVal.value(yield(nil), usage, position)
self
end

# @return [String]
# :nocov:
def inspect
id = definition.bind do |d|
"[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
Expand All @@ -188,6 +182,7 @@ def inspect

ansi.element("AN.invalid#{id}") + "(#{ansi.invalid(@value.inspect)})"
end
# :nocov:

# @return [String]
def to_s
Expand All @@ -201,7 +196,7 @@ def to_x12(truncate = true)

# @return [Boolean]
def ==(other)
eql?(other) or other.nil?
eql?(other)
end

# @return [Invalid]
Expand All @@ -210,43 +205,43 @@ def copy(changes = {})
end
end

#
# Empty string value. Shouldn't be directly instantiated -- instead,
# use the {StringVal.empty} constructor.
#
class Empty < StringVal
class Valid < StringVal
include Comparable

# (string any* -> any)
def_delegators :value, :to_d, :to_s, :to_f, :to_c, :to_r, :to_sym,
:to_str, :hex, :oct, :ord, :sum, :length, :count, :index, :rindex,
:lines, :bytes, :chars, :each, :upto, :split, :scan, :unpack, :=~,
:match, :partition, :rpatition, :encoding, :valid_enocding?, :at,
:empty?, :blank?

# (string any* -> StringVal)
extend Operators::Wrappers
wrappers :%, :+, :*, :slice, :take, :drop, :[], :capitalize,
:center, :ljust, :rjust, :chomp, :delete, :tr, :tr_s,
:sub, :gsub, :encode, :force_encoding, :squeeze

# (string -> StringVal)
extend Operators::Unary
unary_operators :chr, :chop, :upcase, :downcase, :strip,
:lstrip, :rstrip, :dump, :succ, :next, :reverse, :swapcase

# (string string -> any)
extend Operators::Relational
relational_operators :==, :<=>, :start_with?, :end_with?,
:include?, :casecmp, :coerce => :to_s
relational_operators :<=>, :start_with?, :end_with?,
:include?, :casecmp, :coerce => :to_str

def ==(other)
other = StringVal.value(other, usage, position)
other.valid? and other.value == value
end

# @return [Empty]
# @return [StringVal]
def copy(changes = {})
self
StringVal.value \
changes.fetch(:value, value),
changes.fetch(:usage, usage),
changes.fetch(:position, position)
end

def value
""
def coerce(other)
return StringVal.value(other, usage, position), self
end

def valid?
Expand All @@ -255,10 +250,18 @@ def valid?

# @return [StringVal]
def map
StringVal.value(yield(nil), usage, position)
StringVal.value(yield(value), usage, position)
end
end

class Empty < Valid
# @return [String]
def value
""
end

# @return [String]
# :nocov:
def inspect
id = definition.bind do |d|
"[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
Expand All @@ -274,59 +277,27 @@ def inspect

ansi.element("AN.empty#{id}")
end
# :nocov:

# @return [String]
def to_x12(truncate = true)
""
end
end

#
# Non-empty string value. Shouldn't be directly instantiated --
# instead, use the {StringVal.value} constructor.
#
class NonEmpty < StringVal
include Comparable
def to_date(format)
nil
end
end

class NonEmpty < Valid
# @return [String]
attr_reader :value

# (string any* -> any)
def_delegators :@value, :to_d, :to_s, :to_f, :to_c, :to_r, :to_sym,
:to_str, :hex, :oct, :ord, :sum, :length, :count, :index, :rindex,
:lines, :bytes, :chars, :each, :upto, :split, :scan, :unpack, :=~,
:match, :partition, :rpatition, :encoding, :valid_enocding?, :at,
:empty?, :blank?

# (string any* -> StringVal)
extend Operators::Wrappers
wrappers :%, :+, :*, :slice, :take, :drop, :[], :capitalize,
:center, :ljust, :rjust, :chomp, :delete, :tr, :tr_s,
:sub, :gsub, :encode, :force_encoding, :squeeze

# (string -> StringVal)
extend Operators::Unary
unary_operators :chr, :chop, :upcase, :downcase, :strip,
:lstrip, :rstrip, :dump, :succ, :next, :reverse, :swapcase

# (string string -> any)
extend Operators::Relational
relational_operators :==, :<=>, :start_with?, :end_with?,
:include?, :casecmp, :coerce => :to_s

def initialize(string, usage, position)
@value = string
super(usage, position)
end

# @return [StringVal]
def copy(changes = {})
StringVal.value \
changes.fetch(:value, @value),
changes.fetch(:usage, usage),
changes.fetch(:position, position)
end

def too_long?
@value.lstrip.length > definition.max_length
end
Expand All @@ -335,18 +306,14 @@ def too_short?
@value.lstrip.length < definition.min_length
end

# @return [StringVal]
def map
StringVal.value(yield(@value), usage, position)
end

# @return [String]
def to_x12(truncate = true)
x12 = @value.ljust(definition.min_length, " ")
truncate ? x12.take(definition.max_length) : x12
end

# @return [String]
# :nocov:
def inspect
id = definition.bind do |d|
"[#{"% 5s" % d.id}: #{d.name}]".bind do |s|
Expand All @@ -362,10 +329,7 @@ def inspect

ansi.element("AN.value#{id}") + "(#{@value})"
end

def valid?
true
end
# :nocov:

# (see AN.stpftime)
def to_date(format)
Expand All @@ -385,7 +349,9 @@ def empty(usage, position)

# @return [StringVal]
def value(object, usage, position)
if object.blank?
if object.is_a?(StringVal)
object#.copy(:usage => usage, :position => position)
elsif object.blank?
self::Empty.new(usage, position)
elsif object.kind_of?(Date) or object.kind_of?(Time)
self::Invalid.new(object, usage, position)
Expand Down
Loading

0 comments on commit 7648764

Please sign in to comment.