# frozen_string_literal: true require 'bigdecimal' module Plumb Rules.define :eq, 'must be equal to %s' do |result, value| value == result.value end Rules.define :not_eq, 'must not be equal to %s' do |result, value| value != result.value end # :gt for numbers and #size (arrays, strings, hashes) [::String, ::Array, ::Hash].each do |klass| Rules.define :gt, 'must contain more than %s elements', expects: klass do |result, value| value < result.value.size end # :lt for numbers and #size (arrays, strings, hashes) Rules.define :lt, 'must contain fewer than %s elements', expects: klass do |result, value| value > result.value.size end Rules.define :gte, 'must be size greater or equal to %s', expects: klass do |result, value| value <= result.value.size end Rules.define :lte, 'must be size less or equal to %s', expects: klass do |result, value| value >= result.value end end # :gt and :lt for numbers, BigDecimal [::Numeric].each do |klass| Rules.define :gt, 'must be greater than %s', expects: klass do |result, value| value < result.value end Rules.define :lt, 'must be greater than %s', expects: klass do |result, value| value > result.value end Rules.define :gte, 'must be greater or equal to %s', expects: klass do |result, value| value <= result.value end # :lte for numbers and #size (arrays, strings, hashes) Rules.define :lte, 'must be less or equal to %s', expects: klass do |result, value| value >= result.value end end Rules.define :match, 'must match %s', metadata_key: :pattern do |result, value| value === result.value end Rules.define :included_in, 'elements must be included in %s', expects: ::Array, metadata_key: :options do |result, opts| result.value.all? { |v| opts.include?(v) } end Rules.define :included_in, 'must be included in %s', metadata_key: :options do |result, opts| opts.include? result.value end Rules.define :excluded_from, 'elements must not be included in %s', expects: ::Array do |result, value| result.value.all? { |v| !value.include?(v) } end Rules.define :excluded_from, 'must not be included in %s' do |result, value| !value.include?(result.value) end Rules.define :respond_to, 'must respond to %s' do |result, value| Array(value).all? { |m| result.value.respond_to?(m) } end Rules.define :size, 'must be of size %s', expects: :size, metadata_key: :size do |result, value| value === result.value.size end module Types extend TypeRegistry Any = AnyClass.new Undefined = Any.value(Plumb::Undefined) String = Any[::String] Symbol = Any[::Symbol] Numeric = Any[::Numeric] Integer = Any[::Integer] Decimal = Any[BigDecimal] Static = StaticClass.new Value = ValueClass.new Nil = Any[::NilClass] True = Any[::TrueClass] False = Any[::FalseClass] Boolean = (True | False).as_node(:boolean) Array = ArrayClass.new Tuple = TupleClass.new Hash = HashClass.new Interface = InterfaceClass.new # TODO: type-speficic concept of blank, via Rules Blank = ( Undefined \ | Nil \ | String.value(BLANK_STRING) \ | Hash.value(BLANK_HASH) \ | Array.value(BLANK_ARRAY) ) Present = Blank.invalid(errors: 'must be present') Split = String.transform(::String) { |v| v.split(/\s*,\s*/) } module Lax NUMBER_EXPR = /^\d{1,3}(?:,\d{3})*(?:\.\d+)?$/ String = Types::String \ | Any.coerce(BigDecimal) { |v| v.to_s('F') } \ | Any.coerce(::Numeric, &:to_s) Symbol = Types::Symbol | Types::String.transform(::Symbol, &:to_sym) NumberString = Types::String.match(NUMBER_EXPR) CoercibleNumberString = NumberString.transform(::String) { |v| v.tr(',', '') } Numeric = Types::Numeric | CoercibleNumberString.transform(::Numeric, &:to_f) Decimal = Types::Decimal | \ (Types::Numeric.transform(::String, &:to_s) | CoercibleNumberString) \ .transform(::BigDecimal) { |v| BigDecimal(v) } Integer = Numeric.transform(::Integer, &:to_i) end module Forms True = Types::True \ | Types::String >> Any.coerce(/^true$/i) { |_| true } \ | Any.coerce('1') { |_| true } \ | Any.coerce(1) { |_| true } False = Types::False \ | Types::String >> Any.coerce(/^false$/i) { |_| false } \ | Any.coerce('0') { |_| false } \ | Any.coerce(0) { |_| false } Boolean = True | False Nil = Nil | (String[BLANK_STRING] >> nil) end end end