dry-logic-1.0.5/0000755000175000017500000000000013617270721013340 5ustar utkarshutkarshdry-logic-1.0.5/CODE_OF_CONDUCT.md0000644000175000017500000000266113617270721016144 0ustar utkarshutkarsh# Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.4.0, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct) dry-logic-1.0.5/CONTRIBUTING.md0000644000175000017500000000312413617270721015571 0ustar utkarshutkarsh# Issue Guidelines ## Reporting bugs If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated. ## Reporting feature requests Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed. ## Reporting questions, support requests, ideas, concerns etc. **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead. # Pull Request Guidelines A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc. Other requirements: 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue. 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style. 3) Add API documentation if it's a new feature 4) Update API documentation if it changes an existing feature 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides # Asking for help If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org) or join [our chat](https://dry-rb.zulipchat.com). dry-logic-1.0.5/.rubocop.yml0000644000175000017500000000241413617270721015613 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project AllCops: TargetRubyVersion: 2.4 Style/EachWithObject: Enabled: false Style/StringLiterals: Enabled: true EnforcedStyle: single_quotes Style/Alias: Enabled: false Style/LambdaCall: Enabled: false Style/StabbyLambdaParentheses: Enabled: false Style/FormatString: Enabled: false Style/Documentation: Enabled: false Layout/SpaceInLambdaLiteral: Enabled: false Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented Metrics/LineLength: Max: 100 Metrics/MethodLength: Max: 22 Metrics/ClassLength: Max: 150 Metrics/AbcSize: Max: 20 Metrics/BlockLength: Enabled: false Metrics/CyclomaticComplexity: Enabled: true Max: 10 Lint/BooleanSymbol: Enabled: false Style/AccessModifierDeclarations: Enabled: false Style/BlockDelimiters: Enabled: false Layout/IndentFirstArrayElement: EnforcedStyle: consistent Style/ClassAndModuleChildren: Exclude: - "spec/**/*_spec.rb" Lint/HandleExceptions: Exclude: - "spec/spec_helper.rb" Naming/FileName: Exclude: - "lib/dry-*.rb" Style/SymbolArray: Exclude: - "spec/**/*_spec.rb" Style/ConditionalAssignment: Enabled: false Naming/MethodName: Enabled: false Style/AsciiComments: Enabled: false dry-logic-1.0.5/Rakefile0000644000175000017500000000044013617270721015003 0ustar utkarshutkarsh#!/usr/bin/env rake # frozen_string_literal: true require 'bundler/gem_tasks' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib')) require 'rspec/core' require 'rspec/core/rake_task' task default: :spec desc 'Run all specs in spec directory' RSpec::Core::RakeTask.new(:spec) dry-logic-1.0.5/CHANGELOG.md0000644000175000017500000001507213617270721015156 0ustar utkarshutkarsh# v1.0.5 2019-11-07 - Make `format?` tolerant to `nil` values. It already worked like that before, but starting Ruby 2.7 it would produce warnings. Now it won't. Don't rely on this behavior, it's only added to make tests pass in dry-schema. Use explicit type checks instead (@flash-gordon) [Compare v1.0.4...v1.0.5](https://github.com/dry-rb/dry-logic/compare/v1.0.4...v1.0.5) # v1.0.4 2019-11-06 - Fix keyword warnings (@flash-gordon) [Compare v1.0.3...v1.0.4](https://github.com/dry-rb/dry-logic/compare/v1.0.3...v1.0.4) # v1.0.3 2019-08-01 ### Added - `bytesize?` predicate (@bmalinconico) - `min_bytesize?` predicate (@bmalinconico) - `max_bytesize? predicate (@bmalinconico) ### Changed - Min ruby version was set to `>= 2.4.0` (@flash-gordon) [Compare v1.0.2...v1.0.3](https://github.com/dry-rb/dry-logic/compare/v1.0.2...v1.0.3) # v1.0.2 2019-06-14 Re-pushed 1.0.1 after dry-schema 1.2.0 release. [Compare v1.0.1...v1.0.2](https://github.com/dry-rb/dry-logic/compare/v1.0.1...v1.0.2) # v1.0.1 2019-06-04 (yanked) ### Added - `uuid_v4?` predicate (radar) - `respond_to?` predicate (waiting-for-dev) [Compare v1.0.0...v1.0.1](https://github.com/dry-rb/dry-logic/compare/v1.0.0...v1.0.1) This release was removed from rubygems because it broke dry-schema. # v1.0.0 2019-04-23 - Version bump to `1.0.0` (flash-gordon) [Compare v0.6.1...v1.0.0](https://github.com/dry-rb/dry-logic/compare/v0.6.1...v1.0.0) # v0.6.1 2019-04-18 - Fix a regression in dry-validation 0.x for argument-less predicates (flash-gordon) [Compare v0.6.0...v0.6.1](https://github.com/dry-rb/dry-logic/compare/v0.6.0...v0.6.1) # v0.6.0 2019-04-04 ### Added - Generating hints can be disabled by building `Operations::And` with `hints: false` option set (solnic) ### Changed - `Rule` construction has been optimized so that currying and application is multiple-times faster (flash-gordon) [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-logic/compare/v0.5.0...v0.6.0) # v0.5.0 2019-01-29 ### Added - `:nil?` predicate (`none?` is now an alias) (solnic) ### Fixed - `Operation::Key#ast` will now return a correct AST with non-Undefined inputs (solnic) [Compare v0.4.2...v0.5.0](https://github.com/dry-rb/dry-logic/compare/v0.4.2...v0.5.0) # v0.4.2 2017-09-15 ### Added - New `:case?` predicate matches a value against the given object with `#===` (flash-gordon) - New `:is?` predicate checks objects identity (using `#equal?`) (flash-gordon) ### Fixed - A bug with using custom predicates within a standalone module in `dry-validation` (flash-gordon) [Compare v0.4.1...v0.4.2](https://github.com/dry-rb/dry-logic/compare/v0.4.1...v0.4.2) # v0.4.1 2017-01-23 ### Changed - Predicates simply reuse other predicate methods instead of referring to them via `#[]` (georgemillo) ### Fixed - Warnings on MRI 2.4.0 are gone (jtippett) [Compare v0.4.0...v0.4.1](https://github.com/dry-rb/dry-logic/compare/v0.4.0...v0.4.1) # v0.4.0 2016-09-21 This is a partial rewrite focused on internal clean up and major performance improvements. This is also the beginning of the work to make this library first-class rather than "just" a rule backend for dry-validation and dry-types. ### Added - `Rule#[]` which applies a rule and always returns `true` or `false` (solnic) - `Rule#bind` which returns a rule with its predicate bound to a given object (solnic) - `Rule#eval_args` which evaluates unbound-methods-args in the context of a given object (solnic) - `Logic.Rule` builder function (solnic) - Nice `#inspect` on rules and operation objects (solnic) ### Changed - [BRAEKING] New result API (solnic) - [BREAKING] `Predicate` is now `Rule::Predicate` (solnic) - [BREAKING] `Rule::Conjunction` is now `Operation::And` (solnic) - [BREAKING] `Rule::Disjunction` is now `Operation::Or` (solnic) - [BREAKING] `Rule::ExlusiveDisjunction` is now `Operation::Xor` (solnic) - [BREAKING] `Rule::Implication` is now `Operation::Implication` (solnic) - [BREAKING] `Rule::Set` is now `Operation::Set` (solnic) - [BREAKING] `Rule::Each` is now `Operation::Each` (solnic) - [BREAKING] `Rule.new` accepts a predicate function as its first arg now (solnic) - [BREAKING] `Rule#name` is now `Rule#id` (solnic) - `Rule#parameters` is public now (solnic) [Compare v0.3.0...v0.4.0](https://github.com/dry-rb/dry-logic/compare/v0.3.0...v0.4.0) # v0.3.0 2016-07-01 ### Added - `:type?` predicate imported from dry-types (solnic) - `Rule#curry` interface (solnic) ### Changed - Predicates AST now includes information about args (names & possible values) (fran-worley + solnic) - Predicates raise errors when they are called with invalid arity (fran-worley + solnic) - Rules no longer evaluate input twice when building result objects (solnic) [Compare v0.2.3...v0.3.0](https://github.com/dry-rb/dry-logic/compare/v0.2.3...v0.3.0) # v0.2.3 2016-05-11 ### Added - `not_eql?`, `includes?`, `excludes?` predicates (fran-worley) ### Changed - Renamed `inclusion?` to `included_in?` and deprecated `inclusion?` (fran-worley) - Renamed `exclusion?` to `excluded_from?` and deprecated `exclusion?` (fran-worley) [Compare v0.2.2...v0.2.3](https://github.com/dry-rb/dry-logic/compare/v0.2.2...v0.2.3) # v0.2.2 2016-03-30 ### Added - `number?`, `odd?`, `even?` predicates (fran-worley) [Compare v0.2.1...v0.2.2](https://github.com/dry-rb/dry-logic/compare/v0.2.1...v0.2.2) # v0.2.1 2016-03-20 ### Fixed - Result AST for `Rule::Each` correctly maps elements with eql inputs (solnic) # v0.2.0 2016-03-11 ### Changed - Entire AST has been redefined (solnic) [Compare v0.1.4...v0.2.0](https://github.com/dry-rb/dry-logic/compare/v0.1.4...v0.2.0) # v0.1.4 2016-01-27 ### Added - Support for hash-names in `Check` and `Result` which can properly resolve input from nested results (solnic) [Compare v0.1.3...v0.1.4](https://github.com/dry-rb/dry-logic/compare/v0.1.3...v0.1.4) # v0.1.3 2016-01-27 ### Added - Support for resolving input from `Rule::Result` (solnic) ### Changed - `Check` and `Result` carry original input(s) (solnic) [Compare v0.1.2...v0.1.3](https://github.com/dry-rb/dry-logic/compare/v0.1.2...v0.1.3) # v0.1.2 2016-01-19 ### Fixed - `xor` returns wrapped results when used against another result-rule (solnic) [Compare v0.1.1...v0.1.2](https://github.com/dry-rb/dry-logic/compare/v0.1.1...v0.1.2) # v0.1.1 2016-01-18 ### Added - `Rule::Attr` which can be applied to a data object with attr readers (SunnyMagadan) - `Rule::Result` which can be applied to a result object (solnic) - `true?` and `false?` predicates (solnic) [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-logic/compare/v0.1.0...v0.1.1) # v0.1.0 2016-01-11 Code extracted from dry-validation 0.4.1 dry-logic-1.0.5/.codeclimate.yml0000644000175000017500000000025013617270721016407 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project version: "2" exclude_patterns: - "benchmarks/" - "examples/" - "spec/" plugins: rubocop: enabled: true dry-logic-1.0.5/.gitignore0000644000175000017500000000011113617270721015321 0ustar utkarshutkarsh.DS_Store coverage /.bundle vendor/bundle tmp/ pkg/ .idea/ Gemfile.lock dry-logic-1.0.5/Gemfile0000644000175000017500000000052213617270721014632 0ustar utkarshutkarsh# frozen_string_literal: true source 'https://rubygems.org' gemspec group :test do gem 'simplecov', require: false, platform: :mri end group :tools do gem 'pry-byebug', platform: :mri gem 'benchmark-ips', platform: :mri gem 'hotch', platform: :mri gem 'ossy', git: 'https://github.com/solnic/ossy.git', branch: 'master' end dry-logic-1.0.5/README.md0000644000175000017500000000245713617270721014627 0ustar utkarshutkarsh[gem]: https://rubygems.org/gems/dry-logic [ci]: https://github.com/dry-rb/dry-logic/actions?query=workflow%3Aci [codeclimate]: https://codeclimate.com/github/dry-rb/dry-logic [chat]: https://dry-rb.zulipchat.com [inchpages]: http://inch-ci.org/github/dry-rb/dry-logic # dry-logic [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat] [![Gem Version](https://badge.fury.io/rb/dry-logic.svg)][gem] [![Build Status](https://github.com/dry-rb/dry-logic/workflows/ci/badge.svg)][ci] [![Code Climate](https://codeclimate.com/github/dry-rb/dry-logic/badges/gpa.svg)][codeclimate] [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-logic/badges/coverage.svg)][codeclimate] [![Inline docs](http://inch-ci.org/github/dry-rb/dry-logic.svg?branch=master)][inchpages] Predicate logic and rule composition used by: - [dry-types](https://github.com/dry-rb/dry-types) for constrained types - [dry-schema](https://github.com/dry-rb/dry-schema) and [dry-validation](https://github.com/dry-rb/dry-validation) for composing validation rules - your project...? ## Links - [Documentation](http://dry-rb.org/gems/dry-logic) ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-logic. ## License See `LICENSE` file. dry-logic-1.0.5/examples/0000755000175000017500000000000013617270721015156 5ustar utkarshutkarshdry-logic-1.0.5/examples/basic.rb0000644000175000017500000000063113617270721016564 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic user_present = Rule::Predicate.build(Predicates[:key?]).curry(:user) has_min_age = Operations::Key.new(Rule::Predicate.build(Predicates[:gt?]).curry(18), name: [:user, :age]) user_rule = user_present & has_min_age puts user_rule.(user: { age: 19 }).success? puts user_rule.(user: { age: 18 }).success? dry-logic-1.0.5/.rspec0000644000175000017500000000005613617270721014456 0ustar utkarshutkarsh--color --require spec_helper --order random dry-logic-1.0.5/docsite/0000755000175000017500000000000013617270721014772 5ustar utkarshutkarshdry-logic-1.0.5/docsite/source/0000755000175000017500000000000013617270721016272 5ustar utkarshutkarshdry-logic-1.0.5/docsite/source/predicates.html.md0000644000175000017500000000424313617270721021705 0ustar utkarshutkarsh--- title: Predicates layout: gem-single name: dry-logic --- Dry-logic comes with a lot predicates to compose multiple rules: ``` ruby require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic ``` Now you can access all built-in predicates: ``` ruby Predicates[:key?] # => # ``` In the end predicates return true or false. ```ruby Predicates[:key?].(:name, {name: 'John'}) # => true ``` * Built-in: - `type?` - `none?` - `key?` - `attr?` - `empty?` - `filled?` - `bool?` - `date?` - `date_time?` - `time?` - `number?` - `int?` - `float?` - `decimal?` - `str?` - `hash?` - `array?` - `odd?` - `even?` - `lt?` - `gt?` - `lteq?` - `gteq?` - `size?` - `min_size?` - `max_size?` - `bytesize?` - `min_bytesize?` - `max_bytesize?` - `inclusion?` - `exclusion?` - `included_in?` - `excluded_from?` - `includes?` - `excludes?` - `eql?` - `not_eql?` - `is?` - `case?` - `true?` - `false?` - `format?` - `respond_to?` - `predicate` - `uuid_v4?` With predicates you can build more composable and complex operations: For example, let's say we want to check that a given input is a hash and has a specify key. ``` ruby require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic is_hash = Rule::Predicate.new(Predicates[:type?]).curry(Hash) # => # options={:args=>[:hash]}> name_key = Rule::Predicate.new(Predicates[:key?]).curry(:name) # => # options={:args=>[:name]}> hash_with_key = is_hash & name_key # => # options={:args=>[:hash]}>, # options={:args=>[:name]}>] options={}> hash_with_key.(name: 'John').success? # => true hash_with_key.(not_valid: 'John').success? # => false hash_with_key.([1,2]).success? # => false ``` dry-logic-1.0.5/docsite/source/index.html.md0000644000175000017500000000244513617270721020673 0ustar utkarshutkarsh--- title: Introduction description: Predicate logic with composable rules layout: gem-single type: gem name: dry-logic sections: - predicates - operations --- Predicate logic and rule composition used by: * [dry-types](https://github.com/dry-rb/dry-types) for constrained types * [dry-validation](https://github.com/dry-rb/dry-validation) for composing validation rules * your project...? ## Synopsis ``` ruby require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic # Rule::Predicate will only apply its predicate to its input, that’s all # require input to have the key :user user_present = Rule::Predicate.new(Predicates[:key?]).curry(:user) # curry allows us to prepare predicates with args, without the input # require value to be greater than 18 min_18 = Rule::Predicate.new(Predicates[:gt?]).curry(18) # use the min_18 predicate on the the value of user[:age] has_min_age = Operations::Key.new(min_18, name: [:user, :age]) user_rule = user_present & has_min_age user_rule.(user: { age: 19 }).success? # => true user_rule.(user: { age: 18 }).success? # => false user_rule.(user: { age: 'seventeen' }) # => ArgumentError: comparison of String with 18 failed user_rule.(user: { }) # => NoMethodError: undefined method `>' for nil:NilClass user_rule.({}).success? # => false ``` dry-logic-1.0.5/docsite/source/operations.html.md0000644000175000017500000000276313617270721021752 0ustar utkarshutkarsh--- title: Operations layout: gem-single name: dry-logic --- Dry-logic uses operations to interact with the input passed to the different rules. ``` ruby require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic user_present = Rule::Predicate.new(Predicates[:key?]).curry(:user) min_18 = Rule::Predicate.new(Predicates[:gt?]).curry(18) # Here Operations::Key and Rule::Predicate are use to compose and logic based on the value of a given key e.g [:user, :age] has_min_age = Operations::Key.new(min_18, name: [:user, :age]) # => # options={:args=>[18]}>] options={:name=>[:user, :age], :evaluator=>#, :path=>[:user, :age]}> # Thanks to the composable structure of the library we can use multiple Rules and Operations to create custom logic user_rule = user_present & has_min_age user_rule.(user: { age: 19 }).success? # => true ``` * Built-in: - `and` - `or` - `key` - `attr` - `binary` - `check` - `each` - `implication` - `negation` - `set` - `xor` Another example, lets create the `all?` method from the `Enumerable` module. ``` ruby require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic def all?(value) Operations::Each.new(Rule::Predicate.new(Predicates[:gt?]).curry(value)) end all_6 = all?(6) all_6.([6,7,8,9]).success? # => true all_6.([1,2,3,4]).success? # => false ``` dry-logic-1.0.5/LICENSE0000644000175000017500000000207313617270721014347 0ustar utkarshutkarshThe MIT License (MIT) Copyright (c) 2015-2019 dry-rb team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dry-logic-1.0.5/spec/0000755000175000017500000000000013617270721014272 5ustar utkarshutkarshdry-logic-1.0.5/spec/unit/0000755000175000017500000000000013617270721015251 5ustar utkarshutkarshdry-logic-1.0.5/spec/unit/rule_compiler_spec.rb0000644000175000017500000000604313617270721021454 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/rule_compiler' RSpec.describe Dry::Logic::RuleCompiler, '#call' do subject(:compiler) { RuleCompiler.new(predicates) } let(:predicates) { { key?: predicate, attr?: predicate, filled?: predicate, gt?: predicate, one: predicate } } let(:predicate) { double(:predicate, name: :test?, arity: 2).as_null_object } let(:rule) { Rule::Predicate.build(predicate) } let(:key_op) { Operations::Key.new(rule, name: :email) } let(:attr_op) { Operations::Attr.new(rule, name: :email) } let(:check_op) { Operations::Check.new(rule, keys: [:email]) } let(:not_key_op) { Operations::Negation.new(key_op) } let(:and_op) { key_op.curry(:email) & rule } let(:or_op) { key_op.curry(:email) | rule } let(:xor_op) { key_op.curry(:email) ^ rule } let(:set_op) { Operations::Set.new(rule) } let(:each_op) { Operations::Each.new(rule) } it 'compiles key rules' do ast = [[:key, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([key_op]) end it 'compiles attr rules' do ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([attr_op]) end it 'compiles check rules' do ast = [[:check, [[:email], [:predicate, [:filled?, [[:input, Undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([check_op]) end it 'compiles attr rules' do ast = [[:attr, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]] rules = compiler.(ast) expect(rules).to eql([attr_op]) end it 'compiles negated rules' do ast = [[:not, [:key, [:email, [:predicate, [:filled?, [[:input, Undefined]]]]]]]] rules = compiler.(ast) expect(rules).to eql([not_key_op]) end it 'compiles and rules' do ast = [ [ :and, [ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, Undefined]]]]]], [:predicate, [:filled?, [[:input, Undefined]]]] ] ] ] rules = compiler.(ast) expect(rules).to eql([and_op]) end it 'compiles or rules' do ast = [ [ :or, [ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, Undefined]]]]]], [:predicate, [:filled?, [[:input, Undefined]]]] ] ] ] rules = compiler.(ast) expect(rules).to eql([or_op]) end it 'compiles exclusive or rules' do ast = [ [ :xor, [ [:key, [:email, [:predicate, [:key?, [[:name, :email], [:input, Undefined]]]]]], [:predicate, [:filled?, [[:input, Undefined]]]] ] ] ] rules = compiler.(ast) expect(rules).to eql([xor_op]) end it 'compiles set rules' do ast = [[:set, [[:predicate, [:filled?, [[:input, nil]]]]]]] rules = compiler.(ast) expect(rules).to eql([set_op]) end it 'compiles each rules' do ast = [[:each, [:predicate, [:filled?, [[:input, nil]]]]]] rules = compiler.(ast) expect(rules).to eql([each_op]) end end dry-logic-1.0.5/spec/unit/predicates_spec.rb0000644000175000017500000000073613617270721020741 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Predicates do it 'can be included in another module' do mod = Module.new { include Predicates } expect(mod[:key?]).to be_a(Method) end describe '.predicate' do it 'defines a predicate method' do mod = Module.new { include Predicates predicate(:test?) do |foo| true end } expect(mod.test?('arg')).to be(true) end end end dry-logic-1.0.5/spec/unit/rule_spec.rb0000644000175000017500000001324413617270721017563 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Dry::Logic::Rule do subject(:rule) { Rule.build(predicate, **options) } let(:predicate) { -> { true } } let(:options) { {} } let(:schema) do Class.new do define_method(:class, Kernel.instance_method(:class)) def method_missing(m, *) if m.to_s.end_with?('?') self.class.new else super end end def to_proc -> value { value } end def arity 1 end def parameters [[:req, :value]] end end.new end it_behaves_like Dry::Logic::Rule describe '.new' do it 'accepts an :id' do expect(Rule.build(predicate, id: :check_num).id).to be(:check_num) end end describe 'with a function returning truthy value' do it 'is successful for valid input' do expect(Rule.build(-> val { val }).('true')).to be_success end it 'is not successful for invalid input' do expect(Rule.build(-> val { val }).(nil)).to be_failure end end describe '#ast' do it 'returns predicate node with :id' do expect(Rule.build(-> value { true }).with(id: :email?).ast('oops')).to eql( [:predicate, [:email?, [[:value, 'oops']]]] ) end it 'returns predicate node with undefined args' do expect(Rule.build(-> value { true }).with(id: :email?).ast).to eql( [:predicate, [:email?, [[:value, Undefined]]]] ) end end describe '#type' do it 'returns rule type' do expect(rule.type).to be(:rule) end end describe '#bind' do let(:bound) { rule.with(id: :bound).bind(object) } context 'with an unbound method' do let(:predicate) { klass.instance_method(:test?) } let(:klass) { Class.new { def test?; true; end } } let(:object) { klass.new } it 'returns a new rule with its predicate bound to a specific object' do expect(bound.()).to be_success end it 'carries id' do expect(bound.id).to be(:bound) end end context 'with an arbitrary block' do let(:predicate) { -> value { value == expected } } let(:object) { Class.new { def expected; 'test'; end }.new } it 'returns a new with its predicate executed in the context of the provided object' do expect(bound.('test')).to be_success expect(bound.('oops')).to be_failure end it 'carries id' do expect(bound.id).to be(:bound) end it 'stores arity' do expect(bound.options[:arity]).to be(rule.arity) end it 'stores parameters' do expect(bound.options[:parameters]).to eql(rule.parameters) end end context 'with a schema instance' do let(:object) { schema } let(:predicate) { schema } it 'returns a new with its predicate executed in the context of the provided object' do expect(bound.(true)).to be_success expect(bound.(false)).to be_failure end end end describe '#eval_args' do context 'with an unbound method' do let(:options) { { args: [1, klass.instance_method(:num), :foo], arity: 3 } } let(:klass) { Class.new { def num; 7; end } } let(:object) { klass.new } it 'evaluates args in the context of the provided object' do expect(rule.eval_args(object).args).to eql([1, 7, :foo]) end end context 'with a schema instance' do let(:options) { { args: [1, schema, :foo], arity: 3 } } let(:object) { Object.new } it 'returns a new with its predicate executed in the context of the provided object' do expect(rule.eval_args(object).args).to eql([1, schema, :foo]) end end end describe 'arity specialization' do describe '0-arity rule' do let(:options) { { args: [1], arity: 1 } } let(:predicate) { :odd?.to_proc } it 'generates interface with the right arity' do expect(rule.method(:call).arity).to be_zero expect(rule.method(:[]).arity).to be_zero expect(rule[]).to be(true) expect(rule.()).to be_success end end describe '1-arity rule' do let(:options) { { args: [1], arity: 2 } } let(:predicate) { -> a, b { a + b } } it 'generates interface with the right arity' do expect(rule.method(:call).arity).to be(1) expect(rule.method(:[]).arity).to be(1) expect(rule[10]).to be(11) expect(rule.(1)).to be_success end end describe 'currying' do let(:options) { { args: [], arity: 2 } } let(:predicate) { -> a, b { a + b } } let(:rule) { super().curry(1) } it 'generates correct arity on currying' do expect(rule.method(:call).arity).to be(1) expect(rule.method(:[]).arity).to be(1) expect(rule[10]).to be(11) expect(rule.(1)).to be_success end end describe 'arbitrary arity' do let(:arity) { rand(1..20) } let(:curried) { rand(arity) } let(:options) { { args: [1] * curried, arity: arity } } let(:predicate) { double(:predicate) } it 'generates correct arity' do expect(rule.method(:call).arity).to be(arity - curried) expect(rule.method(:[]).arity).to be(arity - curried) end end describe '-1 arity' do let(:options) { { args: [], arity: -1 } } it 'accepts variable number of arguments' do expect(rule.method(:call).arity).to be(-1) expect(rule.method(:[]).arity).to be(-1) end end describe 'constants' do let(:options) { { args: [], arity: 0 } } it 'accepts variable number of arguments' do expect(rule.method(:call).arity).to be(-1) expect(rule.method(:[]).arity).to be(-1) end end end end dry-logic-1.0.5/spec/unit/predicates/0000755000175000017500000000000013617270721017374 5ustar utkarshutkarshdry-logic-1.0.5/spec/unit/predicates/time_spec.rb0000644000175000017500000000113613617270721021672 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#time?' do let(:predicate_name) { :time? } context 'when value is a time' do let(:arguments_list) do [[Time.now]] end it_behaves_like 'a passing predicate' end context 'with value is not an integer' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/none_spec.rb0000644000175000017500000000111613617270721021671 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#nil?' do let(:predicate_name) { :nil? } context 'when value is nil' do let(:arguments_list) { [[nil]] } it_behaves_like 'a passing predicate' end context 'when value is not nil' do let(:arguments_list) do [ [''], [true], [false], [0], [:symbol], [[]], [{}], [String] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/excludes_spec.rb0000644000175000017500000000236313617270721022553 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#excludes?' do let(:predicate_name) { :excludes? } context 'with input excludes value' do let(:arguments_list) do [ ['Jack', ['Jill', 'John']], [0, 1..2], [3, 1..2], ['foo', 'Hello World'], [:foo, { bar: 0 }], [true, [nil, false]] ] end it_behaves_like 'a passing predicate' end context 'with input of invalid type' do let(:arguments_list) do [ [2, 1], [1, nil], ["foo", 1], [1, "foo"], [1..2, "foo"], ["foo", 1..2], [:key, "foo"] ] end it_behaves_like 'a passing predicate' end context 'when input includes value' do let(:arguments_list) do [ ['Jill', ['Jill', 'John']], ['John', ['Jill', 'John']], [1, 1..2], [2, 1..2], ['Hello', 'Hello World'], ['World', 'Hello World'], [:bar, { bar: 0 }], [nil, [nil, false]], [false, [nil, false]] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/format_spec.rb0000644000175000017500000000115213617270721022222 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates, '#format?' do let(:predicate_name) { :format? } context 'when value matches provided regexp' do let(:arguments_list) do [[/^F/, 'Foo']] end it_behaves_like 'a passing predicate' end context 'when value does not match provided regexp' do let(:arguments_list) do [[/^F/, 'Bar']] end it_behaves_like 'a failing predicate' end context 'when input is nil' do let(:arguments_list) do [[/^F/, nil]] end it_behaves_like 'a failing predicate' end end dry-logic-1.0.5/spec/unit/predicates/gt_spec.rb0000644000175000017500000000141113617270721021342 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#gt?' do let(:predicate_name) { :gt? } context 'when value is greater than n' do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like 'a passing predicate' end context 'when value is equal to n' do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like 'a failing predicate' end context 'with value is less than n' do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/decimal_spec.rb0000644000175000017500000000117013617270721022330 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#decimal?' do let(:predicate_name) { :decimal? } context 'when value is a decimal' do let(:arguments_list) do [[1.2.to_d]] end it_behaves_like 'a passing predicate' end context 'with value is not an integer' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String], [1], [1.0] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/bool_spec.rb0000644000175000017500000000123213617270721021664 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#bool?' do let(:predicate_name) { :bool? } context 'when value is a boolean' do let(:arguments_list) do [[true], [false]] end it_behaves_like 'a passing predicate' end context 'when value is not a bool' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String], [1], [0], ['true'], ['false'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/lt_spec.rb0000644000175000017500000000141213617270721021350 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#lt?' do let(:predicate_name) { :lt? } context 'when value is less than n' do let(:arguments_list) do [ [13, 12], [13.37, 13.36], ] end it_behaves_like 'a passing predicate' end context 'when value is equal to n' do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like 'a failing predicate' end context 'with value is greater than n' do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/uuid_v4_spec.rb0000644000175000017500000000131413617270721022311 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#uuid_v4?' do let(:predicate_name) { :uuid_v4? } context 'when value is a valid V4 UUID' do let(:arguments_list) do [['f2d26c57-e07c-4416-a749-57e937930e04']] end it_behaves_like 'a passing predicate' end context 'with value is not a valid V4 UUID' do let(:arguments_list) do [ ['f2d26c57-e07c-3416-a749-57e937930e04'], # wrong version number (3, not 4) ['20633928-6a07-11e9-a923-1681be663d3e'], # UUID V1 ['not-a-uuid-at-all'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/float_spec.rb0000644000175000017500000000113413617270721022037 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#float?' do let(:predicate_name) { :float? } context 'when value is a float' do let(:arguments_list) do [[1.0]] end it_behaves_like 'a passing predicate' end context 'with value is not an integer' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/gteq_spec.rb0000644000175000017500000000141513617270721021674 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#gteq?' do let(:predicate_name) { :gteq? } context 'when value is greater than n' do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like 'a passing predicate' end context 'when value is equal to n' do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like 'a passing predicate' end context 'with value is less than n' do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/max_size_spec.rb0000644000175000017500000000202413617270721022550 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#max_size?' do let(:predicate_name) { :max_size? } context 'when value size is less than n' do let(:arguments_list) do [ [3, [1, 2]], [5, 'Jill'], [3, { 1 => 'st', 2 => 'nd' }], [9, 1], [6, 1..5] ] end it_behaves_like 'a passing predicate' end context 'when value size is equal to n' do let(:arguments_list) do [ [2, [1, 2]], [4, 'Jill'], [2, { 1 => 'st', 2 => 'nd' }], [8, 1], [5, 1..5] ] end it_behaves_like 'a passing predicate' end context 'with value size is greater than n' do let(:arguments_list) do [ [1, [1, 2]], [3, 'Jill'], [1, { 1 => 'st', 2 => 'nd' }], [7, 1], [4, 1..5] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/excluded_from_spec.rb0000644000175000017500000000145113617270721023554 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#excluded_from?' do let(:predicate_name) { :excluded_from? } context 'when value is not present in list' do let(:arguments_list) do [ [['Jill', 'John'], 'Jack'], [1..2, 0], [1..2, 3], [[nil, false], true] ] end it_behaves_like 'a passing predicate' end context 'with value is present in list' do let(:arguments_list) do [ [['Jill', 'John'], 'Jill'], [['Jill', 'John'], 'John'], [1..2, 1], [1..2, 2], [[nil, false], nil], [[nil, false], false] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/key_spec.rb0000644000175000017500000000116513617270721021526 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#key?' do let(:predicate_name) { :key? } context 'when key is present in value' do let(:arguments_list) do [ [:name, { name: 'John' }], [:age, { age: 18 }] ] end it_behaves_like 'a passing predicate' end context 'with key is not present in value' do let(:arguments_list) do [ [:name, { age: 18 }], [:age, { name: 'Jill' }] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/empty_spec.rb0000644000175000017500000000133113617270721022067 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#empty?' do let(:predicate_name) { :empty? } context 'when value is empty' do let(:arguments_list) do [ [''], [[]], [{}], [nil] ] end it_behaves_like 'a passing predicate' end context 'with value is not empty' do let(:arguments_list) do [ ['Jill'], [[1, 2, 3]], [{ name: 'John' }], [true], [false], ['1'], ['0'], [:symbol], [String] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/hash_spec.rb0000644000175000017500000000132113617270721021653 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#hash?' do let(:predicate_name) { :hash? } context 'when value is a hash' do let(:arguments_list) do [ [{}], [foo: :bar], [Hash.new] ] end it_behaves_like 'a passing predicate' end context 'when value is not a hash' do let(:arguments_list) do [ [''], [[]], [nil], [:symbol], [String], [1], [1.0], [true], [Array.new], [Hash] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/false_spec.rb0000644000175000017500000000124113617270721022023 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#false?' do let(:predicate_name) { :false? } context 'when value is false' do let(:arguments_list) do [[false]] end it_behaves_like 'a passing predicate' end context 'when value is not false' do let(:arguments_list) do [ [true], [''], [[]], [{}], [nil], [:symbol], [String], [1], [0], ['true'], ['false'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/included_in_spec.rb0000644000175000017500000000144513617270721023214 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#included_in?' do let(:predicate_name) { :included_in? } context 'when value is present in list' do let(:arguments_list) do [ [['Jill', 'John'], 'Jill'], [['Jill', 'John'], 'John'], [1..2, 1], [1..2, 2], [[nil, false], nil], [[nil, false], false] ] end it_behaves_like 'a passing predicate' end context 'with value is not present in list' do let(:arguments_list) do [ [['Jill', 'John'], 'Jack'], [1..2, 0], [1..2, 3], [[nil, false], true] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/type_spec.rb0000644000175000017500000000145413617270721021720 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#type?' do let(:predicate_name) { :type? } context 'when value has a correct type' do let(:arguments_list) do [[TrueClass, true]] end it_behaves_like 'a passing predicate' end context 'with value is not true' do let(:arguments_list) do [ [TrueClass, false], [TrueClass, ''], [TrueClass, []], [TrueClass, {}], [TrueClass, nil], [TrueClass, :symbol], [TrueClass, String], [TrueClass, 1], [TrueClass, 0], [TrueClass, 'true'], [TrueClass, 'false'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/even_spec.rb0000644000175000017500000000110513617270721021665 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#even?' do let(:predicate_name) { :even? } context 'when value is an odd int' do let(:arguments_list) do [ [13], [1], [1111] ] end it_behaves_like 'a failing predicate' end context 'with value is an even int' do let(:arguments_list) do [ [0], [2], [2222] ] end it_behaves_like 'a passing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/includes_spec.rb0000644000175000017500000000103613617270721022541 0ustar utkarshutkarshrequire 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates, '#is?' do let(:predicate_name) { :is? } let(:one) { Object.new } let(:two) { Object.new } context 'when value is equal to the arg' do let(:arguments_list) do [[one, one], [:one, :one]] end it_behaves_like 'a passing predicate' end context 'with value is not equal to the arg' do let(:arguments_list) do # Strings are not equal. Yet [[one, two], ['one', 'one']] end it_behaves_like 'a failing predicate' end end dry-logic-1.0.5/spec/unit/predicates/case_spec.rb0000644000175000017500000000131013617270721021641 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#case?' do let(:predicate_name) { :case? } context 'when the value matches the pattern' do let(:arguments_list) do [ [11, 11], [:odd?.to_proc, 11], [/\Af/, 'foo'], [Integer, 11] ] end it_behaves_like 'a passing predicate' end context "when the value doesn't match the pattern" do let(:arguments_list) do [ [13, 14], [:odd?.to_proc, 12], [/\Af/, 'bar'], [String, 11] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/not_eql_spec.rb0000644000175000017500000000073013617270721022374 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates, '#not_eql?' do let(:predicate_name) { :not_eql? } context 'when value is equal to the arg' do let(:arguments_list) do [['Foo', 'Foo']] end it_behaves_like 'a failing predicate' end context 'with value is not equal to the arg' do let(:arguments_list) do [['Bar', 'Foo']] end it_behaves_like 'a passing predicate' end end dry-logic-1.0.5/spec/unit/predicates/min_bytesize_spec.rb0000644000175000017500000000134713617270721023441 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#min_bytesize?' do let(:predicate_name) { :min_bytesize? } context 'when value size is greater than n' do let(:arguments_list) do [ [3, 'こa'] ] end it_behaves_like 'a passing predicate' end context 'when value size is equal to n' do let(:arguments_list) do [ [5, 'こab'] ] end it_behaves_like 'a passing predicate' end context 'with value size is less than n' do let(:arguments_list) do [ [5, 'こ'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/attr_spec.rb0000644000175000017500000000127713617270721021714 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#attr?' do let(:predicate_name) { :attr? } context 'when value responds to the attr name' do let(:arguments_list) do [ [:name, Struct.new(:name).new('John')], [:age, Struct.new(:age).new(18)] ] end it_behaves_like 'a passing predicate' end context 'with value does not respond to the attr name' do let(:arguments_list) do [ [:name, Struct.new(:age).new(18)], [:age, Struct.new(:name).new('Jill')] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/true_spec.rb0000644000175000017500000000123513617270721021713 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#true?' do let(:predicate_name) { :true? } context 'when value is true' do let(:arguments_list) do [[true]] end it_behaves_like 'a passing predicate' end context 'with value is not true' do let(:arguments_list) do [ [false], [''], [[]], [{}], [nil], [:symbol], [String], [1], [0], ['true'], ['false'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/array_spec.rb0000644000175000017500000000147413617270721022057 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#array?' do let(:predicate_name) { :array? } context 'when value is an array' do let(:arguments_list) do [ [ [] ], [ ['other', 'array'] ], [ [123, 'really', :blah] ], [ Array.new ], [ [nil] ], [ [false] ], [ [true] ] ] end it_behaves_like 'a passing predicate' end context 'when value is not an array' do let(:arguments_list) do [ [''], [{}], [nil], [:symbol], [String], [1], [1.0], [true], [Hash.new] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/lteq_spec.rb0000644000175000017500000000141513617270721021701 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#lteq?' do let(:predicate_name) { :lteq? } context 'when value is less than n' do let(:arguments_list) do [ [13, 12], [13.37, 13.36] ] end it_behaves_like 'a passing predicate' end context 'when value is equal to n' do let(:arguments_list) do [ [13, 13], [13.37, 13.37] ] end it_behaves_like 'a passing predicate' end context 'with value is greater than n' do let(:arguments_list) do [ [13, 14], [13.37, 13.38] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/str_spec.rb0000644000175000017500000000113713617270721021545 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#str?' do let(:predicate_name) { :str? } context 'when value is a string' do let(:arguments_list) do [ [''], ['John'] ] end it_behaves_like 'a passing predicate' end context 'with value is not a string' do let(:arguments_list) do [ [[]], [{}], [nil], [:symbol], [String] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/max_bytesize_spec.rb0000644000175000017500000000135213617270721023437 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#max_bytesize?' do let(:predicate_name) { :max_bytesize? } context 'when value size is less than n' do let(:arguments_list) do [ [5, 'こa'] ] end it_behaves_like 'a passing predicate' end context 'when value size is equal to n' do let(:arguments_list) do [ [5, 'こab'] ] end it_behaves_like 'a passing predicate' end context 'with value size is greater than n' do let(:arguments_list) do [ [5, 'こabc'] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/eql_spec.rb0000644000175000017500000000072013617270721021513 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates, '#eql?' do let(:predicate_name) { :eql? } context 'when value is equal to the arg' do let(:arguments_list) do [['Foo', 'Foo']] end it_behaves_like 'a passing predicate' end context 'with value is not equal to the arg' do let(:arguments_list) do [['Bar', 'Foo']] end it_behaves_like 'a failing predicate' end end dry-logic-1.0.5/spec/unit/predicates/number_spec.rb0000644000175000017500000000132313617270721022222 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#number?' do let(:predicate_name) { :number? } context 'when value is numerical' do let(:arguments_list) do [ ["34"], ["1.000004"], ["0"], [4], ["-15.24"], [-3.5] ] end it_behaves_like 'a passing predicate' end context 'with value is not numerical' do let(:arguments_list) do [ [''], ["-14px"], ["10,150.00"], [nil], [:symbol], [String] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/date_time_spec.rb0000644000175000017500000000116013617270721022664 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#date_time?' do let(:predicate_name) { :date_time? } context 'when value is a datetime' do let(:arguments_list) do [[DateTime.now]] end it_behaves_like 'a passing predicate' end context 'with value is not an integer' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/size_spec.rb0000644000175000017500000000230013617270721021700 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#size?' do let(:predicate_name) { :size? } context 'when value size is equal to n' do let(:arguments_list) do [ [[8], 2], [4, 'Jill'], [2, { 1 => 'st', 2 => 'nd' }], [8, 8], [1..8, 5] ] end it_behaves_like 'a passing predicate' end context 'when value size is greater than n' do let(:arguments_list) do [ [[1, 2], 3], [5, 'Jill'], [3, { 1 => 'st', 2 => 'nd' }], [1, 9], [1..5, 6] ] end it_behaves_like 'a failing predicate' end context 'with value size is less than n' do let(:arguments_list) do [ [[1, 2], 1], [3, 'Jill'], [1, { 1 => 'st', 2 => 'nd' }], [1, 7], [1..5, 4] ] end it_behaves_like 'a failing predicate' end context 'with an unsupported size' do it 'raises an error' do expect { Predicates[:size?].call('oops', 1) }.to raise_error(ArgumentError, /oops/) end end end end dry-logic-1.0.5/spec/unit/predicates/filled_spec.rb0000644000175000017500000000133513617270721022174 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#filled?' do let(:predicate_name) { :filled? } context 'when value is filled' do let(:arguments_list) do [ ['Jill'], [[1, 2, 3]], [{ name: 'John' }], [true], [false], ['1'], ['0'], [:symbol], [String] ] end it_behaves_like 'a passing predicate' end context 'with value is not filled' do let(:arguments_list) do [ [''], [[]], [{}], [nil] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/int_spec.rb0000644000175000017500000000117513617270721021531 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#int?' do let(:predicate_name) { :int? } context 'when value is an integer' do let(:arguments_list) do [ [1], [33], [7] ] end it_behaves_like 'a passing predicate' end context 'with value is not an integer' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/odd_spec.rb0000644000175000017500000000110313617270721021474 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#odd?' do let(:predicate_name) { :odd? } context 'when value is an odd int' do let(:arguments_list) do [ [13], [1], [1111] ] end it_behaves_like 'a passing predicate' end context 'with value is an even int' do let(:arguments_list) do [ [0], [2], [2222] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/date_spec.rb0000644000175000017500000000114013617270721021644 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#date?' do let(:predicate_name) { :date? } context 'when value is a date' do let(:arguments_list) do [[Date.today]] end it_behaves_like 'a passing predicate' end context 'with value is not an integer' do let(:arguments_list) do [ [''], [[]], [{}], [nil], [:symbol], [String], [1] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/min_size_spec.rb0000644000175000017500000000202413617270721022546 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#min_size?' do let(:predicate_name) { :min_size? } context 'when value size is greater than n' do let(:arguments_list) do [ [1, [1, 2]], [3, 'Jill'], [1, { 1 => 'st', 2 => 'nd' }], [7, 1], [4, 1..5] ] end it_behaves_like 'a passing predicate' end context 'when value size is equal to n' do let(:arguments_list) do [ [2, [1, 2]], [4, 'Jill'], [2, { 1 => 'st', 2 => 'nd' }], [8, 1], [5, 1..5] ] end it_behaves_like 'a passing predicate' end context 'with value size is less than n' do let(:arguments_list) do [ [3, [1, 2]], [5, 'Jill'], [3, { 1 => 'st', 2 => 'nd' }], [9, 1], [6, 1..5] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/predicates/bytesize_spec.rb0000644000175000017500000000175113617270721022575 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#bytesize?' do let(:predicate_name) { :bytesize? } context 'when value size is equal to n' do let(:arguments_list) do [ [4, 'こa'], [1..8, 'こa'] ] end it_behaves_like 'a passing predicate' end context 'when value size is greater than n' do let(:arguments_list) do [ [3, 'こa'], [1..3, 'こa'] ] end it_behaves_like 'a failing predicate' end context 'with value size is less than n' do let(:arguments_list) do [ [5, 'こa'], [5..10, 'こa'] ] end it_behaves_like 'a failing predicate' end context 'with an unsupported size' do it 'raises an error' do expect { Predicates[:bytesize?].call('oops', 1) }.to raise_error(ArgumentError, /oops/) end end end end dry-logic-1.0.5/spec/unit/predicates/respond_to_spec.rb0000644000175000017500000000115013617270721023104 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.describe Dry::Logic::Predicates do describe '#respond_to?' do let(:predicate_name) { :respond_to? } context 'when value responds to method' do let(:arguments_list) do [ [:method, Object], [:new, Hash] ] end it_behaves_like 'a passing predicate' end context 'when value does not respond to method' do let(:arguments_list) do [ [:foo, Object], [:bar, Hash] ] end it_behaves_like 'a failing predicate' end end end dry-logic-1.0.5/spec/unit/rule/0000755000175000017500000000000013617270721016220 5ustar utkarshutkarshdry-logic-1.0.5/spec/unit/rule/predicate_spec.rb0000644000175000017500000000246213617270721021523 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Rule::Predicate do subject(:rule) { Rule::Predicate.build(predicate) } let(:predicate) { str? } include_context 'predicates' it_behaves_like Rule describe '#name' do it 'returns predicate identifier' do expect(rule.name).to be(:str?) end end describe '#to_ast' do context 'without a result' do it 'returns rule ast' do expect(rule.to_ast).to eql([:predicate, [:str?, [[:input, Undefined]]]]) end it 'returns :failure with an id' do email = rule.with(id: :email) expect(email.(11).to_ast).to eql([:failure, [:email, [:predicate, [:str?, [[:input, 11]]]]]]) end end context 'with a result' do it 'returns success' do expect(rule.('foo')).to be_success end it 'returns failure ast' do expect(rule.(5).to_ast).to eql([:predicate, [:str?, [[:input, 5]]]]) end end context 'with a zero-arity predicate' do let(:predicate) { Module.new { def self.test?; true; end }.method(:test?) } it 'returns ast' do expect(rule.to_ast).to eql([:predicate, [:test?, []]]) end end end describe '#to_s' do it 'returns string representation' do expect(rule.curry('foo').to_s).to eql('str?("foo")') end end end dry-logic-1.0.5/spec/unit/operations/0000755000175000017500000000000013617270721017434 5ustar utkarshutkarshdry-logic-1.0.5/spec/unit/operations/negation_spec.rb0000644000175000017500000000235613617270721022605 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Negation do subject(:operation) { Operations::Negation.new(is_int) } include_context 'predicates' let(:is_int) { Rule::Predicate.build(int?) } describe '#call' do it 'negates its rule' do expect(operation.('19')).to be_success expect(operation.(17)).to be_failure end context 'double negation' do subject(:double_negation) { Operations::Negation.new(operation) } it 'works as rule' do expect(double_negation.('19')).to be_failure expect(double_negation.(17)).to be_success end end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:not, [:predicate, [:int?, [[:input, Undefined]]]]] ) end it 'returns result ast' do expect(operation.(17).to_ast).to eql( [:not, [:predicate, [:int?, [[:input, 17]]]]] ) end it 'returns result ast with an :id' do expect(operation.with(id: :age).(17).to_ast).to eql( [:failure, [:age, [:not, [:predicate, [:int?, [[:input, 17]]]]]]] ) end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('not(int?)') end end end dry-logic-1.0.5/spec/unit/operations/each_spec.rb0000644000175000017500000000255213617270721021677 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Each do subject(:operation) { Operations::Each.new(is_string) } include_context 'predicates' let(:is_string) { Rule::Predicate.build(str?) } describe '#call' do it 'applies its rules to all elements in the input' do expect(operation.(['Address'])).to be_success expect(operation.([nil, 'Address'])).to be_failure expect(operation.([:Address, 'Address'])).to be_failure end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql([:each, [:predicate, [:str?, [[:input, Undefined]]]]]) end it 'returns result ast' do expect(operation.([nil, 12, nil]).to_ast).to eql( [:set, [ [:key, [0, [:predicate, [:str?, [[:input, nil]]]]]], [:key, [1, [:predicate, [:str?, [[:input, 12]]]]]], [:key, [2, [:predicate, [:str?, [[:input, nil]]]]]] ]] ) end it 'returns failure result ast' do expect(operation.with(id: :tags).([nil, 'red', 12]).to_ast).to eql( [:failure, [:tags, [:set, [ [:key, [0, [:predicate, [:str?, [[:input, nil]]]]]], [:key, [2, [:predicate, [:str?, [[:input, 12]]]]]] ]]]] ) end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('each(str?)') end end end dry-logic-1.0.5/spec/unit/operations/xor_spec.rb0000644000175000017500000000330413617270721021603 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Xor do subject(:operation) { Operations::Xor.new(left, right) } include_context 'predicates' let(:left) { Rule::Predicate.build(array?) } let(:right) { Rule::Predicate.build(empty?) } let(:other) do Rule::Predicate.build(str?) end describe '#call' do it 'calls left and right' do expect(operation.(nil)).to be_success expect(operation.('')).to be_success expect(operation.([])).to be_failure end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:xor, [[:predicate, [:array?, [[:input, Undefined]]]], [:predicate, [:empty?, [[:input, Undefined]]]]]] ) end it 'returns result ast' do expect(operation.([]).to_ast).to eql( [:xor, [[:predicate, [:array?, [[:input, []]]]], [:predicate, [:empty?, [[:input, []]]]]]] ) end it 'returns failure result ast' do expect(operation.with(id: :name).([]).to_ast).to eql( [:failure, [:name, [:xor, [[:predicate, [:array?, [[:input, []]]]], [:predicate, [:empty?, [[:input, []]]]]]]]] ) end end describe '#and' do it 'creates conjunction with the other' do expect(operation.and(other).(nil)).to be_failure expect(operation.and(other).(19)).to be_failure expect(operation.and(other).('')).to be_success end end describe '#or' do it 'creates disjunction with the other' do expect(operation.or(other).([])).to be_failure expect(operation.or(other).('')).to be_success end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('array? XOR empty?') end end end dry-logic-1.0.5/spec/unit/operations/set_spec.rb0000644000175000017500000000222113617270721021563 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Set do subject(:operation) { Operations::Set.new(is_int, gt_18) } include_context 'predicates' let(:is_int) { Rule::Predicate.build(int?) } let(:gt_18) { Rule::Predicate.build(gt?, args: [18]) } describe '#call' do it 'applies all its rules to the input' do expect(operation.(19)).to be_success expect(operation.(17)).to be_failure end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:set, [[:predicate, [:int?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]] ) end it 'returns result ast' do expect(operation.(17).to_ast).to eql( [:set, [[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]] ) end it 'returns result ast with an :id' do expect(operation.with(id: :age).(17).to_ast).to eql( [:failure, [:age, [:set, [[:predicate, [:gt?, [[:num, 18], [:input, 17]]]]]]]] ) end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('set(int?, gt?(18))') end end end dry-logic-1.0.5/spec/unit/operations/key_spec.rb0000644000175000017500000000713713617270721021573 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Key do subject(:operation) { Operations::Key.new(predicate, name: :user) } include_context 'predicates' let(:predicate) do Rule::Predicate.build(key?).curry(:age) end describe '#call' do context 'with a plain predicate' do it 'returns a success for valid input' do expect(operation.(user: { age: 18 })).to be_success end it 'returns a failure for invalid input' do result = operation.(user: {}) expect(result).to be_failure expect(result.to_ast).to eql( [:failure, [:user, [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, {}]]]] ]]]] ) end end context 'with a set rule as predicate' do subject(:operation) do Operations::Key.new(predicate, name: :address) end let(:predicate) do Operations::Set.new(Rule::Predicate.build(key?).curry(:city), Rule::Predicate.build(key?).curry(:zipcode)) end it 'applies set rule to the value that passes' do result = operation.(address: { city: 'NYC', zipcode: '123' }) expect(result).to be_success end it 'applies set rule to the value that fails' do result = operation.(address: { city: 'NYC' }) expect(result).to be_failure expect(result.to_ast).to eql( [:failure, [:address, [:key, [:address, [:set, [ [:predicate, [:key?, [[:name, :zipcode], [:input, { city: 'NYC' }]]]] ]]]]]] ) end end context 'with an each rule as predicate' do subject(:operation) do Operations::Key.new(predicate, name: :nums) end let(:predicate) do Operations::Each.new(Rule::Predicate.build(str?)) end it 'applies each rule to the value that passses' do result = operation.(nums: %w(1 2 3)) expect(result).to be_success end it 'applies each rule to the value that fails' do failure = operation.(nums: [1, '3', 3]) expect(failure).to be_failure expect(failure.to_ast).to eql( [:failure, [:nums, [:key, [:nums, [:set, [ [:key, [0, [:predicate, [:str?, [[:input, 1]]]]]], [:key, [2, [:predicate, [:str?, [[:input, 3]]]]]] ]]]]]] ) end end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, Undefined]]]]]] ) end end describe '#ast' do it 'returns ast without the input' do expect(operation.ast).to eql( [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, Undefined]]]]]] ) end it 'returns ast with the input' do expect(operation.ast(user: 'jane')).to eql( [:key, [:user, [:predicate, [:key?, [[:name, :age], [:input, 'jane']]]]]] ) end end describe '#and' do subject(:operation) do Operations::Key.new(Rule::Predicate.build(str?), name: [:user, :name]) end let(:other) do Operations::Key.new(Rule::Predicate.build(filled?), name: [:user, :name]) end it 'returns and rule where value is passed to the right' do present_and_string = operation.and(other) expect(present_and_string.(user: { name: 'Jane' })).to be_success expect(present_and_string.(user: {})).to be_failure expect(present_and_string.(user: { name: 1 })).to be_failure end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('key[user](key?(:age))') end end end dry-logic-1.0.5/spec/unit/operations/and_spec.rb0000644000175000017500000000364413617270721021544 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::And do subject(:operation) { Operations::And.new(left, right) } include_context 'predicates' let(:left) { Rule::Predicate.build(int?) } let(:right) { Rule::Predicate.build(gt?).curry(18) } describe '#call' do it 'calls left and right' do expect(operation.(18)).to be_failure end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:and, [[:predicate, [:int?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]] ) end it 'returns result ast' do expect(operation.('18').to_ast).to eql( [:and, [[:predicate, [:int?, [[:input, '18']]]], [:hint, [:predicate, [:gt?, [[:num, 18], [:input, '18']]]]]]] ) expect(operation.with(hints: false).('18').to_ast).to eql( [:predicate, [:int?, [[:input, '18']]]] ) expect(operation.(18).to_ast).to eql( [:predicate, [:gt?, [[:num, 18], [:input, 18]]]] ) end it 'returns failure result ast' do expect(operation.with(id: :age).('18').to_ast).to eql( [:failure, [:age, [:and, [[:predicate, [:int?, [[:input, '18']]]], [:hint, [:predicate, [:gt?, [[:num, 18], [:input, '18']]]]]]]]] ) expect(operation.with(id: :age).(18).to_ast).to eql( [:failure, [:age, [:predicate, [:gt?, [[:num, 18], [:input, 18]]]]]] ) end end describe '#and' do let(:other) { Rule::Predicate.build(lt?).curry(30) } it 'creates and with the other' do expect(operation.and(other).(31)).to be_failure end end describe '#or' do let(:other) { Rule::Predicate.build(lt?).curry(14) } it 'creates or with the other' do expect(operation.or(other).(13)).to be_success end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('int? AND gt?(18)') end end end dry-logic-1.0.5/spec/unit/operations/check_spec.rb0000644000175000017500000000274013617270721022053 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Check do include_context 'predicates' describe '#call' do context 'with 1-level nesting' do subject(:operation) do Operations::Check.new(Rule::Predicate.build(eql?).curry(1), id: :compare, keys: [:num]) end it 'applies predicate to args extracted from the input' do expect(operation.(num: 1)).to be_success expect(operation.(num: 2)).to be_failure end end context 'with 2-levels nesting' do subject(:operation) do Operations::Check.new(Rule::Predicate.build(eql?), id: :compare, keys: [[:nums, :left], [:nums, :right]]) end it 'applies predicate to args extracted from the input' do expect(operation.(nums: { left: 1, right: 1 })).to be_success expect(operation.(nums: { left: 1, right: 2 })).to be_failure end it 'curries args properly' do result = operation.(nums: { left: 1, right: 2 }) expect(result.to_ast).to eql( [:failure, [:compare, [:check, [ [[:nums, :left], [:nums, :right]], [:predicate, [:eql?, [[:left, 1], [:right, 2]]]]] ]]] ) end end end describe '#to_ast' do subject(:operation) do Operations::Check.new(Rule::Predicate.build(str?), keys: [:email]) end it 'returns ast' do expect(operation.to_ast).to eql( [:check, [[:email], [:predicate, [:str?, [[:input, Undefined]]]]]] ) end end end dry-logic-1.0.5/spec/unit/operations/implication_spec.rb0000644000175000017500000000152413617270721023305 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Implication do subject(:operation) { Operations::Implication.new(left, right) } include_context 'predicates' let(:left) { Rule::Predicate.build(int?) } let(:right) { Rule::Predicate.build(gt?).curry(18) } describe '#call' do it 'calls left and right' do expect(operation.('19')).to be_success expect(operation.(19)).to be_success expect(operation.(18)).to be_failure end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:implication, [[:predicate, [:int?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]]] ) end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('int? THEN gt?(18)') end end end dry-logic-1.0.5/spec/unit/operations/attr_spec.rb0000644000175000017500000000153213617270721021746 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Attr do subject(:operation) { Operations::Attr.new(Rule::Predicate.build(str?), name: :name) } include_context 'predicates' let(:model) { Struct.new(:name) } describe '#call' do it 'applies predicate to the value' do expect(operation.(model.new('Jane'))).to be_success expect(operation.(model.new(nil))).to be_failure end end describe '#and' do let(:other) { Operations::Attr.new(Rule::Predicate.build(min_size?).curry(3), name: :name) } it 'returns and where value is passed to the right' do present_and_string = operation.and(other) expect(present_and_string.(model.new('Jane'))).to be_success expect(present_and_string.(model.new('Ja'))).to be_failure expect(present_and_string.(model.new(1))).to be_failure end end end dry-logic-1.0.5/spec/unit/operations/or_spec.rb0000644000175000017500000000374613617270721021425 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Operations::Or do subject(:operation) { Operations::Or.new(left, right) } include_context 'predicates' let(:left) { Rule::Predicate.build(nil?) } let(:right) { Rule::Predicate.build(gt?).curry(18) } let(:other) do Rule::Predicate.build(int?) & Rule::Predicate.build(lt?).curry(14) end describe '#call' do it 'calls left and right' do expect(operation.(nil)).to be_success expect(operation.(19)).to be_success expect(operation.(18)).to be_failure end end describe '#to_ast' do it 'returns ast' do expect(operation.to_ast).to eql( [:or, [ [:predicate, [:nil?, [[:input, Undefined]]]], [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]] ] ) end it 'returns result ast' do expect(operation.(17).to_ast).to eql( [:or, [ [:predicate, [:nil?, [[:input, 17]]]], [:predicate, [:gt?, [[:num, 18], [:input, 17]]]]] ] ) end it 'returns failure result ast' do expect(operation.with(id: :age).(17).to_ast).to eql( [:failure, [:age, [:or, [ [:predicate, [:nil?, [[:input, 17]]]], [:predicate, [:gt?, [[:num, 18], [:input, 17]]]]] ]]] ) end end describe '#and' do it 'creates and with the other' do expect(operation.and(other).(nil)).to be_failure expect(operation.and(other).(19)).to be_failure expect(operation.and(other).(13)).to be_failure expect(operation.and(other).(14)).to be_failure end end describe '#or' do it 'creates or with the other' do expect(operation.or(other).(nil)).to be_success expect(operation.or(other).(19)).to be_success expect(operation.or(other).(13)).to be_success expect(operation.or(other).(14)).to be_failure end end describe '#to_s' do it 'returns string representation' do expect(operation.to_s).to eql('nil? OR gt?(18)') end end end dry-logic-1.0.5/spec/spec_helper.rb0000644000175000017500000000105513617270721017111 0ustar utkarshutkarsh# frozen_string_literal: true if ENV['COVERAGE'] == 'true' require 'simplecov' SimpleCov.start do add_filter '/spec/' end end begin require 'pry-byebug' rescue LoadError; end require 'dry-logic' require 'dry/core/constants' require 'pathname' SPEC_ROOT = Pathname(__dir__) Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:require)) Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require)) include Dry::Logic include Dry::Core::Constants RSpec.configure do |config| config.disable_monkey_patching! config.warnings = true end dry-logic-1.0.5/spec/integration/0000755000175000017500000000000013617270721016615 5ustar utkarshutkarshdry-logic-1.0.5/spec/integration/rule_spec.rb0000644000175000017500000000307613617270721021131 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry-logic' RSpec.describe 'Rules' do specify 'defining an anonymous rule with an arbitrary predicate' do rule = Dry::Logic.Rule { |value| value.is_a?(Integer) } expect(rule.(1)).to be_success expect(rule[1]).to be(true) end specify 'defining a conjunction' do rule = Dry::Logic.Rule(&:even?) & Dry::Logic.Rule { |v| v > 4 } expect(rule.(3)).to be_failure expect(rule.(4)).to be_failure expect(rule.(5)).to be_failure expect(rule.(6)).to be_success end specify 'defining a disjunction' do rule = Dry::Logic.Rule { |v| v < 4 } | Dry::Logic.Rule { |v| v > 6 } expect(rule.(5)).to be_failure expect(rule.(3)).to be_success expect(rule.(7)).to be_success end specify 'defining an implication' do rule = Dry::Logic.Rule(&:empty?) > Dry::Logic.Rule { |v| v.is_a?(Array) } expect(rule.('foo')).to be_success expect(rule.([1, 2])).to be_success expect(rule.([])).to be_success expect(rule.('')).to be_failure end specify 'defining an exclusive disjunction' do rule = Dry::Logic.Rule(&:empty?) ^ Dry::Logic.Rule { |v| v.is_a?(Array) } expect(rule.('foo')).to be_failure expect(rule.([])).to be_failure expect(rule.([1, 2])).to be_success expect(rule.('')).to be_success end specify 'defining a rule with options' do rule = Dry::Logic::Rule(id: :empty?) { |value| value.empty? } expect(rule.('foo')).to be_failure expect(rule.('')).to be_success expect(rule.ast('foo')).to eql([:predicate, [:empty?, [[:value, 'foo']]]]) end end dry-logic-1.0.5/spec/integration/result_spec.rb0000644000175000017500000000333013617270721021471 0ustar utkarshutkarsh# frozen_string_literal: true RSpec.describe Result do include_context 'predicates' describe '#to_s' do shared_examples_for 'string representation' do it 'returns string representation' do expect(rule.(input).to_s).to eql(output) end end context 'with a predicate' do let(:rule) { Rule::Predicate.build(gt?, args: [18]) } let(:input) { 17 } let(:output) { 'gt?(18, 17)' } it_behaves_like 'string representation' end context 'with AND operation' do let(:rule) { Rule::Predicate.build(array?).and(Rule::Predicate.build(empty?)) } let(:input) { '' } let(:output) { 'array?("") AND empty?("")' } it_behaves_like 'string representation' end context 'with OR operation' do let(:rule) { Rule::Predicate.build(array?).or(Rule::Predicate.build(empty?)) } let(:input) { 123 } let(:output) { 'array?(123) OR empty?(123)' } it_behaves_like 'string representation' end context 'with XOR operation' do let(:rule) { Rule::Predicate.build(array?).xor(Rule::Predicate.build(empty?)) } let(:input) { [] } let(:output) { 'array?([]) XOR empty?([])' } it_behaves_like 'string representation' end context 'with THEN operation' do let(:rule) { Rule::Predicate.build(array?).then(Rule::Predicate.build(empty?)) } let(:input) { [1, 2, 3] } let(:output) { 'empty?([1, 2, 3])' } it_behaves_like 'string representation' end context 'with NOT operation' do let(:rule) { Operations::Negation.new(Rule::Predicate.build(array?)) } let(:input) { 'foo' } let(:output) { 'not(array?("foo"))' } it_behaves_like 'string representation' end end end dry-logic-1.0.5/spec/shared/0000755000175000017500000000000013617270721015540 5ustar utkarshutkarshdry-logic-1.0.5/spec/shared/predicates.rb0000644000175000017500000000255713617270721020221 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/predicates' RSpec.shared_examples 'predicates' do let(:nil?) { Dry::Logic::Predicates[:nil?] } let(:array?) { Dry::Logic::Predicates[:array?] } let(:empty?) { Dry::Logic::Predicates[:empty?] } let(:str?) { Dry::Logic::Predicates[:str?] } let(:true?) { Dry::Logic::Predicates[:true?] } let(:hash?) { Dry::Logic::Predicates[:hash?] } let(:int?) { Dry::Logic::Predicates[:int?] } let(:filled?) { Dry::Logic::Predicates[:filled?] } let(:min_size?) { Dry::Logic::Predicates[:min_size?] } let(:lt?) { Dry::Logic::Predicates[:lt?] } let(:gt?) { Dry::Logic::Predicates[:gt?] } let(:key?) { Dry::Logic::Predicates[:key?] } let(:attr?) { Dry::Logic::Predicates[:attr?] } let(:eql?) { Dry::Logic::Predicates[:eql?] } let(:size?) { Dry::Logic::Predicates[:size?] } let(:case?) { Dry::Logic::Predicates[:case?] } let(:equal?) { Dry::Logic::Predicates[:equal?] } end RSpec.shared_examples 'a passing predicate' do let(:predicate) { Dry::Logic::Predicates[predicate_name] } it do arguments_list.each do |args| expect(predicate.call(*args)).to be(true) end end end RSpec.shared_examples 'a failing predicate' do let(:predicate) { Dry::Logic::Predicates[predicate_name] } it do arguments_list.each do |args| expect(predicate.call(*args)).to be(false) end end end dry-logic-1.0.5/spec/shared/rule.rb0000644000175000017500000000346513617270721017044 0ustar utkarshutkarsh# frozen_string_literal: true shared_examples_for Dry::Logic::Rule do let(:arity) { 2 } let(:predicate) { double(:predicate, arity: arity, name: predicate_name) } let(:rule_type) { described_class } let(:predicate_name) { :good? } describe '#arity' do it 'returns its predicate arity' do rule = rule_type.build(predicate) expect(rule.arity).to be(2) end end describe '#parameters' do it 'returns a list of args with their names' do rule = rule_type.build(-> foo, bar { true }, args: [312]) expect(rule.parameters).to eql([[:req, :foo], [:req, :bar]]) end end describe '#call' do let(:arity) { 1 } it 'returns success for valid input' do rule = rule_type.build(predicate) expect(predicate).to receive(:[]).with(2).and_return(true) expect(rule.(2)).to be_success end it 'returns failure for invalid input' do rule = rule_type.build(predicate) expect(predicate).to receive(:[]).with(2).and_return(false) expect(rule.(2)).to be_failure end end describe '#[]' do let(:arity) { 1 } it 'delegates to its predicate' do rule = rule_type.build(predicate) expect(predicate).to receive(:[]).with(2).and_return(true) expect(rule[2]).to be(true) end end describe '#curry' do it 'returns a curried rule' do rule = rule_type.build(predicate).curry(3) expect(predicate).to receive(:[]).with(3, 2).and_return(true) expect(rule.args).to eql([3]) expect(rule.(2)).to be_success end it 'raises argument error when arity does not match' do expect(predicate).to receive(:arity).and_return(2) expect { rule_type.build(predicate).curry(3, 2, 1) }.to raise_error( ArgumentError, 'wrong number of arguments (3 for 2)' ) end end end dry-logic-1.0.5/spec/support/0000755000175000017500000000000013617270721016006 5ustar utkarshutkarshdry-logic-1.0.5/spec/support/mutant.rb0000644000175000017500000000025513617270721017645 0ustar utkarshutkarsh# frozen_string_literal: true module Mutant class Selector class Expression < self def call(_subject) integration.all_tests end end end end dry-logic-1.0.5/benchmarks/0000755000175000017500000000000013617270721015455 5ustar utkarshutkarshdry-logic-1.0.5/benchmarks/rule_application.rb0000644000175000017500000000174413617270721021342 0ustar utkarshutkarsh# frozen_string_literal: true require_relative 'setup' unless Dry::Logic::Rule.respond_to?(:build) Dry::Logic::Rule.singleton_class.alias_method(:build, :new) end predicates = Dry::Logic::Predicates type_check = Dry::Logic::Rule.build(predicates[:type?]).curry(Integer) int_check = Dry::Logic::Rule.build(predicates[:int?]) key_check = Dry::Logic::Rule.build(predicates[:key?]).curry(:user) with_user = { user: {} } without_user = {} comparison = Dry::Logic::Rule.build(predicates[:gteq?]).curry(18) Benchmark.ips do |x| x.report("type check - success") { type_check.(0) } x.report("type check - failure") { type_check.('0') } x.report("int check - success") { int_check.(0) } x.report("int check - failure") { int_check.('0') } x.report("key check - success") { key_check.(with_user) } x.report("key check - failure") { key_check.(without_user) } x.report("comparison - success") { comparison.(20) } x.report("comparison - failure") { comparison.(17) } x.compare! end dry-logic-1.0.5/benchmarks/setup.rb0000644000175000017500000000032313617270721017140 0ustar utkarshutkarsh# frozen_string_literal: true require 'benchmark/ips' require 'hotch' ENV['HOTCH_VIEWER'] ||= 'open' require 'dry/logic' require 'dry/logic/predicates' def profile(&block) Hotch(filter: 'Dry', &block) end dry-logic-1.0.5/.github/0000755000175000017500000000000013617270721014700 5ustar utkarshutkarshdry-logic-1.0.5/.github/workflows/0000755000175000017500000000000013617270721016735 5ustar utkarshutkarshdry-logic-1.0.5/.github/workflows/docsite.yml0000644000175000017500000000152013617270721021110 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project name: docsite on: push: paths: - docsite/** - .github/workflows/docsite.yml branches: - master - release-** tags: jobs: update-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: "2.6.x" - name: Install dependencies run: | gem install bundler bundle install --jobs 4 --retry 3 --without benchmarks sql - name: Symlink ossy run: mkdir -p bin && ln -sf "$(bundle show ossy)/bin/ossy" bin/ossy - name: Trigger dry-rb.org deploy env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{ secrets.GH_PAT }} run: bin/ossy github workflow dry-rb/dry-rb.org ci dry-logic-1.0.5/.github/workflows/ci.yml0000644000175000017500000000402613617270721020055 0ustar utkarshutkarshname: ci on: push: paths: - .github/workflows/ci.yml - lib/** - spec/** jobs: tests-mri: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: ["2.6.x", "2.5.x", "2.4.x"] include: - ruby: "2.6.x" coverage: "true" steps: - uses: actions/checkout@v1 - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} - name: Download test reporter if: "matrix.coverage == 'true'" run: | mkdir -p tmp/ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./tmp/cc-test-reporter chmod +x ./tmp/cc-test-reporter ./tmp/cc-test-reporter before-build - name: Run all tests env: COVERAGE: ${{matrix.coverage}} run: | gem install bundler bundle install --jobs 4 --retry 3 --without tools docs bundle exec rake - name: Send coverage results if: "matrix.coverage == 'true'" env: CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} GIT_COMMIT_SHA: ${{github.sha}} GIT_BRANCH: ${{github.ref}} GIT_COMMITTED_AT: ${{github.event.head_commit.timestamp}} run: | GIT_BRANCH=`ruby -e "puts ENV['GITHUB_REF'].split('/', 3).last"` \ GIT_COMMITTED_AT=`ruby -r time -e "puts Time.iso8601(ENV['GIT_COMMITTED_AT']).to_i"` \ ./tmp/cc-test-reporter after-build tests-others: runs-on: ubuntu-latest strategy: fail-fast: false matrix: image: ["jruby:9.2.8", "ruby:rc"] container: image: ${{matrix.image}} steps: - uses: actions/checkout@v1 - name: Install git run: | apt-get update apt-get install -y --no-install-recommends git - name: Run all tests run: | gem install bundler bundle install --jobs 4 --retry 3 --without tools docs bundle exec rspec dry-logic-1.0.5/.github/workflows/sync_configs.yml0000644000175000017500000000200713617270721022143 0ustar utkarshutkarsh# this file is managed by dry-rb/devtools project name: sync_configs on: repository_dispatch: jobs: sync-configs: runs-on: ubuntu-latest if: github.event.action == 'sync_configs' steps: - uses: actions/checkout@v1 - name: Update configuration files from devtools env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{ secrets.GH_PAT }} run: | git clone https://github.com/dry-rb/devtools.git tmp/devtools if [ -f ".github/workflows/custom_ci.yml" ]; then rsync -av --exclude '.github/workflows/ci.yml' tmp/devtools/shared/ . ; else rsync -av tmp/devtools/shared/ . ; fi git config --local user.email "dry-bot@dry-rb.org" git config --local user.name "dry-bot" git add -A git commit -m "[devtools] config sync" || echo "nothing changed" - name: Push changes uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GH_PAT }} dry-logic-1.0.5/.github/ISSUE_TEMPLATE/0000755000175000017500000000000013617270721017063 5ustar utkarshutkarshdry-logic-1.0.5/.github/ISSUE_TEMPLATE/---bug-report.md0000644000175000017500000000161413617270721021704 0ustar utkarshutkarsh--- name: "\U0001F41B Bug report" about: See CONTRIBUTING.md for more information title: '' labels: bug assignees: '' --- **Before you submit this: WE ONLY ACCEPT BUG REPORTS AND FEATURE REQUESTS** For more information see [our contribution guidelines](https://github.com/rom-rb/rom/blob/master/CONTRIBUTING.md) **Before you report** :warning: If you have a problem related to a schema, please **report it under [dry-schema issues](https://github.com/dry-rb/dry-schema/issues/new?assignees=&labels=bug&template=---bug-report.md&title=)** instead. **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Provide detailed steps to reproduce, an executable script would be best. **Expected behavior** A clear and concise description of what you expected to happen. **Your environment** - Affects my production application: **YES/NO** - Ruby version: ... - OS: ... dry-logic-1.0.5/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md0000644000175000017500000000022613617270721027321 0ustar utkarshutkarsh--- name: "⚠️ Please don't ask for support via issues" about: See CONTRIBUTING.md for more information title: '' labels: '' assignees: '' --- dry-logic-1.0.5/.github/ISSUE_TEMPLATE/---feature-request.md0000644000175000017500000000055613617270721022743 0ustar utkarshutkarsh--- name: "\U0001F6E0 Feature request" about: See CONTRIBUTING.md for more information title: '' labels: feature assignees: '' --- Summary of what the feature is supposed to do. ## Examples Code examples showing how the feature could be used. ## Resources Additional information, like a link to the discussion forum thread where the feature was discussed etc. dry-logic-1.0.5/lib/0000755000175000017500000000000013617270721014106 5ustar utkarshutkarshdry-logic-1.0.5/lib/dry-logic.rb0000644000175000017500000000006313617270721016323 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic' dry-logic-1.0.5/lib/dry/0000755000175000017500000000000013617270721014704 5ustar utkarshutkarshdry-logic-1.0.5/lib/dry/logic/0000755000175000017500000000000013617270721016001 5ustar utkarshutkarshdry-logic-1.0.5/lib/dry/logic/version.rb0000644000175000017500000000013113617270721020006 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Logic VERSION = '1.0.5' end end dry-logic-1.0.5/lib/dry/logic/appliable.rb0000644000175000017500000000076113617270721020263 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Logic module Appliable def id options[:id] end def result options[:result] end def applied? !result.nil? end def success? result.equal?(true) end def failure? !success? end def to_ast if applied? && id [success? ? :success : :failure, [id, ast]] else ast end end end end end dry-logic-1.0.5/lib/dry/logic/rule_compiler.rb0000644000175000017500000000346213617270721021174 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/constants' require 'dry/logic/rule' require 'dry/logic/rule/predicate' module Dry module Logic class RuleCompiler include Core::Constants attr_reader :predicates def initialize(predicates) @predicates = predicates end def call(ast) ast.map { |node| visit(node) } end def visit(node) name, nodes = node send(:"visit_#{name}", nodes) end def visit_check(node) keys, predicate = node Operations::Check.new(visit(predicate), keys: keys) end def visit_not(node) Operations::Negation.new(visit(node)) end def visit_key(node) name, predicate = node Operations::Key.new(visit(predicate), name: name) end def visit_attr(node) name, predicate = node Operations::Attr.new(visit(predicate), name: name) end def visit_set(node) Operations::Set.new(*call(node)) end def visit_each(node) Operations::Each.new(visit(node)) end def visit_predicate(node) name, params = node predicate = Rule::Predicate.build(predicates[name]) if params.size > 1 args = params.map(&:last).reject { |val| val == Undefined } predicate.curry(*args) else predicate end end def visit_and(node) left, right = node visit(left).and(visit(right)) end def visit_or(node) left, right = node visit(left).or(visit(right)) end def visit_xor(node) left, right = node visit(left).xor(visit(right)) end def visit_implication(node) left, right = node visit(left).then(visit(right)) end end end end dry-logic-1.0.5/lib/dry/logic/predicates.rb0000644000175000017500000001121313617270721020447 0ustar utkarshutkarsh# frozen_string_literal: true require 'bigdecimal' require 'bigdecimal/util' require 'date' module Dry module Logic module Predicates module Methods def [](name) method(name) end def type?(type, input) input.kind_of?(type) end def nil?(input) input.nil? end alias_method :none?, :nil? def key?(name, input) input.key?(name) end def attr?(name, input) input.respond_to?(name) end def empty?(input) case input when String, Array, Hash then input.empty? when nil then true else false end end def filled?(input) !empty?(input) end def bool?(input) input.is_a?(TrueClass) || input.is_a?(FalseClass) end def date?(input) input.is_a?(Date) end def date_time?(input) input.is_a?(DateTime) end def time?(input) input.is_a?(Time) end def number?(input) true if Float(input) rescue ArgumentError, TypeError false end def int?(input) input.is_a?(Integer) end def float?(input) input.is_a?(Float) end def decimal?(input) input.is_a?(BigDecimal) end def str?(input) input.is_a?(String) end def hash?(input) input.is_a?(Hash) end def array?(input) input.is_a?(Array) end def odd?(input) input.odd? end def even?(input) input.even? end def lt?(num, input) input < num end def gt?(num, input) input > num end def lteq?(num, input) !gt?(num, input) end def gteq?(num, input) !lt?(num, input) end def size?(size, input) case size when Integer then size.equal?(input.size) when Range, Array then size.include?(input.size) else raise ArgumentError, "+#{size}+ is not supported type for size? predicate." end end def min_size?(num, input) input.size >= num end def max_size?(num, input) input.size <= num end def bytesize?(size, input) case size when Integer then size.equal?(input.bytesize) when Range, Array then size.include?(input.bytesize) else raise ArgumentError, "+#{size}+ is not supported type for bytesize? predicate." end end def min_bytesize?(num, input) input.bytesize >= num end def max_bytesize?(num, input) input.bytesize <= num end def inclusion?(list, input) ::Kernel.warn 'inclusion is deprecated - use included_in instead.' included_in?(list, input) end def exclusion?(list, input) ::Kernel.warn 'exclusion is deprecated - use excluded_from instead.' excluded_from?(list, input) end def included_in?(list, input) list.include?(input) end def excluded_from?(list, input) !list.include?(input) end def includes?(value, input) if input.respond_to?(:include?) input.include?(value) else false end rescue TypeError false end def excludes?(value, input) !includes?(value, input) end def eql?(left, right) left.eql?(right) end def is?(left, right) left.equal?(right) end def not_eql?(left, right) !left.eql?(right) end def true?(value) value.equal?(true) end def false?(value) value.equal?(false) end def format?(regex, input) !input.nil? && regex.match?(input) end def case?(pattern, input) pattern === input end def uuid_v4?(input) uuid_v4_format = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i format?(uuid_v4_format, input) end def respond_to?(method, input) input.respond_to?(method) end def predicate(name, &block) define_singleton_method(name, &block) end end extend Methods def self.included(other) super other.extend(Methods) end end end end dry-logic-1.0.5/lib/dry/logic/result.rb0000644000175000017500000000315513617270721017650 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/constants' module Dry module Logic class Result include Core::Constants SUCCESS = Class.new { def success? true end def failure? false end }.new.freeze attr_reader :success attr_reader :id attr_reader :serializer def initialize(success, id = nil, &block) @success = success @id = id @serializer = block end def success? success end def failure? !success? end def type success? ? :success : :failure end def ast(input = Undefined) serializer.(input) end def to_ast if id [type, [id, ast]] else ast end end def to_s visit(to_ast) end private def visit(ast) __send__(:"visit_#{ast[0]}", ast[1]) end def visit_predicate(node) name, args = node if args.empty? name.to_s else "#{name}(#{args.map(&:last).map(&:inspect).join(', ')})" end end def visit_and(node) left, right = node "#{visit(left)} AND #{visit(right)}" end def visit_or(node) left, right = node "#{visit(left)} OR #{visit(right)}" end def visit_xor(node) left, right = node "#{visit(left)} XOR #{visit(right)}" end def visit_not(node) "not(#{visit(node)})" end def visit_hint(node) visit(node) end end end end dry-logic-1.0.5/lib/dry/logic/operators.rb0000644000175000017500000000072213617270721020345 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Logic module Operators def and(other) Operations::And.new(self, other) end alias & and def or(other) Operations::Or.new(self, other) end alias | or def xor(other) Operations::Xor.new(self, other) end alias ^ xor def then(other) Operations::Implication.new(self, other) end alias > then end end end dry-logic-1.0.5/lib/dry/logic/operations.rb0000644000175000017500000000065513617270721020517 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/and' require 'dry/logic/operations/or' require 'dry/logic/operations/xor' require 'dry/logic/operations/implication' require 'dry/logic/operations/negation' require 'dry/logic/operations/key' require 'dry/logic/operations/attr' require 'dry/logic/operations/each' require 'dry/logic/operations/set' require 'dry/logic/operations/check' require 'dry/logic/operators' dry-logic-1.0.5/lib/dry/logic/evaluator.rb0000644000175000017500000000172613617270721020336 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/equalizer' module Dry module Logic class Evaluator include Dry::Equalizer(:path) attr_reader :path class Set include Dry::Equalizer(:evaluators) attr_reader :evaluators def self.new(paths) super(paths.map { |path| Evaluator::Key.new(path) }) end def initialize(evaluators) @evaluators = evaluators end def call(input) evaluators.map { |evaluator| evaluator[input] } end alias_method :[], :call end class Key < Evaluator def call(input) path.reduce(input) { |a, e| a[e] } end alias_method :[], :call end class Attr < Evaluator def call(input) path.reduce(input) { |a, e| a.public_send(e) } end alias_method :[], :call end def initialize(path) @path = Array(path) end end end end dry-logic-1.0.5/lib/dry/logic/rule/0000755000175000017500000000000013617270721016750 5ustar utkarshutkarshdry-logic-1.0.5/lib/dry/logic/rule/interface.rb0000644000175000017500000000645313617270721021245 0ustar utkarshutkarsh# frozen_string_literal: true module Dry module Logic class Rule class Interface < ::Module attr_reader :arity attr_reader :curried def initialize(arity, curried) @arity = arity @curried = curried if !variable_arity? && curried > arity raise ArgumentError, "wrong number of arguments (#{curried} for #{arity})" end define_constructor if curried? if variable_arity? define_splat_application elsif constant? define_constant_application else define_fixed_application end end def constant? arity.zero? end def variable_arity? arity.equal?(-1) end def curried? !curried.zero? end def unapplied if variable_arity? -1 else arity - curried end end def name if constant? 'Constant' else arity_str = variable_arity? ? 'VariableArity' : "#{arity}Arity" curried_str = curried? ? "#{curried}Curried" : EMPTY_STRING "#{arity_str}#{curried_str}" end end def define_constructor assignment = if curried.equal?(1) '@arg0 = @args[0]' else "#{curried_args.join(', ')} = @args" end module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def initialize(*) super #{assignment} end RUBY end def define_constant_application module_exec do def call(*) if @predicate[] Result::SUCCESS else Result.new(false, id) { ast } end end def [](*) @predicate[] end end end def define_splat_application application = if curried? "@predicate[#{curried_args.join(', ')}, *input]" else '@predicate[*input]' end module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def call(*input) if #{application} Result::SUCCESS else Result.new(false, id) { ast(*input) } end end def [](*input) #{application} end RUBY end def define_fixed_application parameters = unapplied_args.join(', ') application = "@predicate[#{ (curried_args + unapplied_args).join(', ') }]" module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def call(#{parameters}) if #{application} Result::SUCCESS else Result.new(false, id) { ast(#{parameters}) } end end def [](#{parameters}) #{application} end RUBY end def curried_args @curried_args ||= ::Array.new(curried) { |i| "@arg#{i}" } end def unapplied_args @unapplied_args ||= ::Array.new(unapplied) { |i| "input#{i}" } end end end end end dry-logic-1.0.5/lib/dry/logic/rule/predicate.rb0000644000175000017500000000111313617270721021231 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/rule' module Dry module Logic class Rule::Predicate < Rule def self.specialize(arity, curried, base = Predicate) super end def type :predicate end def name predicate.name end def to_s if args.size > 0 "#{name}(#{args.map(&:inspect).join(', ')})" else "#{name}" end end def ast(input = Undefined) [type, [name, args_with_names(input)]] end alias_method :to_ast, :ast end end end dry-logic-1.0.5/lib/dry/logic/rule.rb0000644000175000017500000000466613617270721017311 0ustar utkarshutkarsh# frozen_string_literal: true require 'concurrent/map' require 'dry/core/constants' require 'dry/equalizer' require 'dry/logic/operations' require 'dry/logic/result' require 'dry/logic/rule/interface' module Dry module Logic def self.Rule(*args, **options, &block) if args.any? Rule.build(*args, **options) elsif block Rule.build(block, **options) end end class Rule include Core::Constants include Dry::Equalizer(:predicate, :options) include Operators attr_reader :predicate attr_reader :options attr_reader :args attr_reader :arity def self.interfaces @interfaces ||= ::Concurrent::Map.new end def self.specialize(arity, curried, base = Rule) base.interfaces.fetch_or_store([arity, curried]) do interface = Interface.new(arity, curried) klass = Class.new(base) { include interface } base.const_set("#{base.name.split('::').last}#{interface.name}", klass) klass end end def self.build(predicate, args: EMPTY_ARRAY, arity: predicate.arity, **options) specialize(arity, args.size).new(predicate, { args: args, arity: arity, **options }) end def initialize(predicate, options = EMPTY_HASH) @predicate = predicate @options = options @args = options[:args] || EMPTY_ARRAY @arity = options[:arity] || predicate.arity end def type :rule end def id options[:id] end def curry(*new_args) with(args: args + new_args) end def bind(object) if predicate.respond_to?(:bind) self.class.build(predicate.bind(object), **options) else self.class.build( -> *args { object.instance_exec(*args, &predicate) }, **options, arity: arity, parameters: parameters ) end end def eval_args(object) with(args: args.map { |arg| UnboundMethod === arg ? arg.bind(object).() : arg }) end def with(new_opts) self.class.build(predicate, **options, **new_opts) end def parameters options[:parameters] || predicate.parameters end def ast(input = Undefined) [:predicate, [id, args_with_names(input)]] end private def args_with_names(*input) parameters.map(&:last).zip(args + input) end end end end dry-logic-1.0.5/lib/dry/logic/operations/0000755000175000017500000000000013617270721020164 5ustar utkarshutkarshdry-logic-1.0.5/lib/dry/logic/operations/unary.rb0000644000175000017500000000067513617270721021657 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/abstract' module Dry module Logic module Operations class Unary < Abstract attr_reader :rule def initialize(*rules, **options) super @rule = rules.first end def ast(input = Undefined) [type, rule.ast(input)] end def to_s "#{type}(#{rule})" end end end end end dry-logic-1.0.5/lib/dry/logic/operations/attr.rb0000644000175000017500000000044513617270721021466 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/key' module Dry module Logic module Operations class Attr < Key def self.evaluator(name) Evaluator::Attr.new(name) end def type :attr end end end end end dry-logic-1.0.5/lib/dry/logic/operations/implication.rb0000644000175000017500000000132013617270721023015 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/binary' require 'dry/logic/result' module Dry module Logic module Operations class Implication < Binary def type :implication end def operator :then end def call(input) left_result = left.(input) if left_result.success? right_result = right.(input) Result.new(right_result.success?, id) { right_result.to_ast } else Result::SUCCESS end end def [](input) if left[input] right[input] else true end end end end end end dry-logic-1.0.5/lib/dry/logic/operations/set.rb0000644000175000017500000000141713617270721021307 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/abstract' require 'dry/logic/result' module Dry module Logic module Operations class Set < Abstract def type :set end def call(input) results = rules.map { |rule| rule.(input) } success = results.all?(&:success?) Result.new(success, id) do [type, results.select(&:failure?).map { |failure| failure.to_ast }] end end def [](input) rules.map { |rule| rule[input] }.all? end def ast(input = Undefined) [type, rules.map { |rule| rule.ast(input) }] end def to_s "#{type}(#{rules.map(&:to_s).join(', ')})" end end end end end dry-logic-1.0.5/lib/dry/logic/operations/key.rb0000644000175000017500000000270513617270721021305 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/unary' require 'dry/logic/evaluator' require 'dry/logic/result' module Dry module Logic module Operations class Key < Unary attr_reader :evaluator attr_reader :path def self.new(rules, **options) if options[:evaluator] super else name = options.fetch(:name) eval = options.fetch(:evaluator, evaluator(name)) super(rules, **options, evaluator: eval, path: name) end end def self.evaluator(name) Evaluator::Key.new(name) end def initialize(*rules, **options) super @evaluator = options[:evaluator] @path = options[:path] end def type :key end def call(hash) input = evaluator[hash] result = rule.(input) if result.success? Result::SUCCESS else Result.new(false, path) { [type, [path, result.to_ast]] } end end def [](hash) rule[evaluator[hash]] end def ast(input = Undefined) if input.equal?(Undefined) || !input.is_a?(Hash) [type, [path, rule.ast]] else [type, [path, rule.ast(evaluator[input])]] end end def to_s "#{type}[#{path}](#{rule})" end end end end end dry-logic-1.0.5/lib/dry/logic/operations/xor.rb0000644000175000017500000000104113617270721021315 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/binary' require 'dry/logic/result' module Dry module Logic module Operations class Xor < Binary def type :xor end alias_method :operator, :type def call(input) Result.new(self[input], id) { ast(input) } end def [](input) left[input] ^ right[input] end def ast(input = Undefined) [type, rules.map { |rule| rule.ast(input) }] end end end end end dry-logic-1.0.5/lib/dry/logic/operations/check.rb0000644000175000017500000000223113617270721021564 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/unary' require 'dry/logic/evaluator' require 'dry/logic/result' module Dry module Logic module Operations class Check < Unary attr_reader :evaluator def self.new(rule, **options) if options[:evaluator] super(rule, options) else keys = options.fetch(:keys) evaluator = Evaluator::Set.new(keys) super(rule, **options, evaluator: evaluator) end end def initialize(*rules, **options) super @evaluator = options[:evaluator] end def type :check end def call(input) *head, tail = evaluator[input] result = rule.curry(*head).(tail) if result.success? Result::SUCCESS else Result.new(false, id) { [type, [options[:keys], result.to_ast]] } end end def [](input) rule[*evaluator[input].reverse] end def ast(input = Undefined) [type, [options[:keys], rule.ast(input)]] end end end end end dry-logic-1.0.5/lib/dry/logic/operations/binary.rb0000644000175000017500000000101013617270721021765 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/abstract' module Dry module Logic module Operations class Binary < Abstract attr_reader :left attr_reader :right def initialize(*rules, **options) super @left, @right = rules end def ast(input = Undefined) [type, [left.ast(input), right.ast(input)]] end def to_s "#{left} #{operator.to_s.upcase} #{right}" end end end end end dry-logic-1.0.5/lib/dry/logic/operations/each.rb0000644000175000017500000000131713617270721021413 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/unary' require 'dry/logic/result' module Dry module Logic module Operations class Each < Unary def type :each end def call(input) results = input.map { |element| rule.(element) } success = results.all?(&:success?) Result.new(success, id) do failures = results .map .with_index { |result, idx| [:key, [idx, result.ast(input[idx])]] if result.failure? } .compact [:set, failures] end end def [](arr) arr.map { |input| rule[input] }.all? end end end end end dry-logic-1.0.5/lib/dry/logic/operations/negation.rb0000644000175000017500000000062313617270721022316 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/unary' require 'dry/logic/result' module Dry module Logic module Operations class Negation < Unary def type :not end def call(input) Result.new(rule.(input).failure?, id) { ast(input) } end def [](input) !rule[input] end end end end end dry-logic-1.0.5/lib/dry/logic/operations/abstract.rb0000644000175000017500000000156613617270721022324 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/core/constants' require 'dry/equalizer' require 'dry/logic/operators' module Dry module Logic module Operations class Abstract include Core::Constants include Dry::Equalizer(:rules, :options) include Operators attr_reader :rules attr_reader :options def initialize(*rules, **options) @rules = rules @options = options end def id options[:id] end def curry(*args) new(rules.map { |rule| rule.curry(*args) }, **options) end def new(rules, **new_options) self.class.new(*rules, **options, **new_options) end def with(new_options) new(rules, **options, **new_options) end def to_ast ast end end end end end dry-logic-1.0.5/lib/dry/logic/operations/or.rb0000644000175000017500000000135413617270721021134 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/binary' require 'dry/logic/result' module Dry module Logic module Operations class Or < Binary def type :or end alias_method :operator, :type def call(input) left_result = left.(input) if left_result.success? Result::SUCCESS else right_result = right.(input) if right_result.success? Result::SUCCESS else Result.new(false, id) { [:or, [left_result.to_ast, right_result.to_ast]] } end end end def [](input) left[input] || right[input] end end end end end dry-logic-1.0.5/lib/dry/logic/operations/and.rb0000644000175000017500000000174513617270721021262 0ustar utkarshutkarsh# frozen_string_literal: true require 'dry/logic/operations/binary' require 'dry/logic/result' module Dry module Logic module Operations class And < Binary attr_reader :hints def initialize(*, **) super @hints = options.fetch(:hints, true) end def type :and end alias operator type def call(input) left_result = left.(input) if left_result.success? right_result = right.(input) if right_result.success? Result::SUCCESS else Result.new(false, id) { right_result.ast(input) } end else Result.new(false, id) do left_ast = left_result.to_ast hints ? [type, [left_ast, [:hint, right.ast(input)]]] : left_ast end end end def [](input) left[input] && right[input] end end end end end dry-logic-1.0.5/lib/dry/logic.rb0000644000175000017500000000033613617270721016330 0ustar utkarshutkarsh# frozen_string_literal: true # A collection of micro-libraries, each intended to encapsulate # a common task in Ruby module Dry module Logic end end require 'dry/logic/rule/predicate' require 'dry/logic/operations' dry-logic-1.0.5/bin/0000755000175000017500000000000013617270721014110 5ustar utkarshutkarshdry-logic-1.0.5/bin/console0000755000175000017500000000025313617270721015500 0ustar utkarshutkarsh#!/usr/bin/env ruby # frozen_string_literal: true require "bundler/setup" require 'dry/logic' require 'dry/logic/predicates' include Dry::Logic require "irb" IRB.start dry-logic-1.0.5/dry-logic.gemspec0000644000175000017500000000214113617270721016574 0ustar utkarshutkarsh# frozen_string_literal: true require File.expand_path('../lib/dry/logic/version', __FILE__) Gem::Specification.new do |spec| spec.name = 'dry-logic' spec.version = Dry::Logic::VERSION spec.authors = ['Piotr Solnica'] spec.email = ['piotr.solnica@gmail.com'] spec.summary = 'Predicate logic with rule composition' spec.homepage = 'https://github.com/dry-rb/dry-logic' spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.4.0' spec.metadata = { 'source_code_uri' => 'https://github.com/dry-rb/dry-logic', 'changelog_uri' => 'https://github.com/dry-rb/dry-logic/blob/master/CHANGELOG.md' } spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0' spec.add_runtime_dependency 'dry-core', '~> 0.2' spec.add_runtime_dependency 'dry-equalizer', '~> 0.2' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' end