constrain 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b37ca58d51de33d6c5d6810c5b8e4b77e0d1d19355d4e381241b923f44918817
4
+ data.tar.gz: c64f19d94bb2e4dae6e4fbaf61a74d1fb6caee56f58a9006915a5749ee35b906
5
+ SHA512:
6
+ metadata.gz: bd6114ec7c7b5516c409ef41beae62d979c582f8f53c149634a1fc8ed8b4c595a7b3fd2d12d07a08fd2b7b587515adef890e6f68cdf6dd897d6ef79e8bad9c50
7
+ data.tar.gz: 9eec4bc8dac6976bbe2eb22e45bcf57589b5f1e04092596e1cb99f658c0b9144a68182eab73e0e5ae3eb9be9bb9434184f8c71122aaddba284d15f90cac4600e
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # Ignore auto-generated main file
14
+ /main
15
+
16
+ # Ignore Gemfile.lock. See https://stackoverflow.com/questions/4151495/should-gemfile-lock-be-included-in-gitignore
17
+ /Gemfile.lock
18
+
19
+ # Put your personal ignore files in /home/clr/.config/git/ignore
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.7.1
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in constrain.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Constrain
2
+
3
+ `Constrain` allows you to check if an object match a class expression. It is
4
+ typically used to check the type of method parameters and is an alternative to
5
+ using Ruby-3 .rbs files but with a different syntax and only dynamic checks
6
+
7
+ Constrain works with ruby-2 (and maybe ruby-3)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'constrain'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle install
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install constrain
24
+
25
+ ## Usage
26
+
27
+ You'll typically include the Constrain module and use the #constrain method to chech values:
28
+
29
+ include Constrain
30
+
31
+ # f takes a String and an array of Integer objects
32
+ def f(a, b)
33
+ constrain a, String
34
+ constrain b, [Integer]
35
+ end
36
+
37
+ The constrain instance method raises a Constrain::TypeError if the value
38
+ doesn't match the class expression. Constrain also defines the Constrain::check
39
+ class method that returns true/false depending on if the value match the
40
+ expression. Both methods raise a Constrain::Error if the expression is invalid
41
+
42
+ ### Class Expressions
43
+
44
+ Constrain#constrain and Constrain::check use class expressions composed of
45
+ Class objects, Proc objects, or arrays and hashes of class objects. Class
46
+ objects match if the value is an instance of the class:
47
+
48
+ constrain 42, Integer # Success
49
+ constrain 42, String # Failure
50
+
51
+ Note that NilClass and TrueClass and FalseClass are valid arguments and allows
52
+ you to do value comparison for those types:
53
+
54
+ constrain nil, Integer # Failure
55
+ constrain nil, Integer, NilClass # Success
56
+
57
+ Proc objects are called with the value as argument and should return truish or falsy:
58
+
59
+ proc = lambda { |value| value > 1 }
60
+ constrain 42, proc # Success
61
+ constrain 0, proc # Failure
62
+
63
+ Proc objects can check every aspect of an object and but you should not overuse
64
+ them as `Constrain` is throught of as a poor-man's type checker. More elaborate
65
+ constraints should be checked explicitly
66
+
67
+ Arrays match if the value is an Array and all its element match the given class expression:
68
+
69
+ constrain [42], [Integer] # Success
70
+ constrain [42], [String] # Failure
71
+
72
+ Arrays can be nested
73
+
74
+ constrain [[42]], [[Integer]]
75
+
76
+ Note that arrays are treated specially in hashes
77
+
78
+ Hashes match if value is a hash and every key/value pair match one of the given
79
+ key-class/value-class expressions:
80
+
81
+ constrain({"str" => 42}, String => Integer) # Success
82
+ constrain({"str" => 42}, String => String) # Failure
83
+
84
+ Note that the parenthesis are needed because otherwise the Ruby parser would
85
+ interpret the hash as block argument to #constrain
86
+
87
+ Hash keys or values can also be lists of class expressions that match if any
88
+ expression match. List are annotated as an array but contains more than one
89
+ element so that `[String, Symbol]` matches either a String or a Symbol value
90
+ while `[String]` matches an array of String objects:
91
+
92
+ constrain({ sym: 42 }, [Symbol, String] => Integer) # Success
93
+ constrain({ [sym] => 42 }, [Symbol, String] => Integer) # Failure
94
+
95
+ To specify an array of Symbol or String objects in hash keys or values, make
96
+ sure the list expression is enclosed in an array:
97
+
98
+ constrain({ [sym] => 42 }, [[Symbol, String]] => Integer) # Success
99
+
100
+ ## Contributing
101
+
102
+ Bug reports and pull requests are welcome on GitHub at https://github.com/clrgit/constrain.
103
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "constrain"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/constrain.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ require_relative 'lib/constrain/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "constrain"
5
+ spec.version = Constrain::VERSION
6
+ spec.authors = ["Claus Rasmussen"]
7
+ spec.email = ["claus.l.rasmussen@gmail.com"]
8
+
9
+ spec.summary = %q{Dynamic in-file type checking}
10
+ spec.description = %q{
11
+ Allows you check if an object match a class expression. It is typically
12
+ used to check the type of method paraameters. It is an alternative to using
13
+ Ruby-3 .rbs files but with a different syntax and only dynamic checks
14
+
15
+ Typically you'll include the Constrain module and use #constrain to check
16
+ the type of method parameters:
17
+
18
+ include Constrain
19
+
20
+ # f takes a String and an array of Integer objects. Raise a Constrain::Error
21
+ # if parameters doesn't have the expected types
22
+ def f(a, b)
23
+ constrain a, String
24
+ constrain b, [Integer]
25
+ end
26
+
27
+ Constrain works with ruby-2 (and maybe ruby-3)
28
+ }
29
+ spec.homepage = "http://www.nowhere.com/"
30
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
31
+
32
+ spec.metadata["homepage_uri"] = spec.homepage
33
+
34
+ # Specify which files should be added to the gem when it is released.
35
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
36
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
37
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
38
+ end
39
+ spec.bindir = "exe"
40
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
41
+ spec.require_paths = ["lib"]
42
+
43
+ # spec.add_dependency GEM [, VERSION]
44
+
45
+ # spec.add_development_dependency GEM [, VERSION]
46
+ spec.add_development_dependency "simplecov"
47
+ end
data/lib/constrain.rb ADDED
@@ -0,0 +1,73 @@
1
+ require "constrain/version"
2
+
3
+ module Constrain
4
+ # Raised on any error
5
+ class Error < StandardError; end
6
+
7
+ # Raised if types doesn't match a class expression
8
+ class TypeError < Error
9
+ def initialize(value, exprs)
10
+ super "Expected #{value.inspect} to match #{Constrain.fmt_exprs(exprs)}"
11
+ end
12
+ end
13
+
14
+ # Check that value matches one of the class expressions. Raises a
15
+ # Constrain::Error if the expression is invalid and a Constrain::TypeError if
16
+ # the value doesn't match
17
+ def constrain(value, *exprs)
18
+ return if exprs.any? { |expr| Constrain.check(value, expr) }
19
+ error = TypeError.new(value, exprs)
20
+ error.set_backtrace(caller[1..-1])
21
+ raise error
22
+ end
23
+
24
+ # Return true if the value matches the class expression. Raises a
25
+ # Constrain::Error if the expression is invalid
26
+ def self.check(value, expr)
27
+ case expr
28
+ when Class
29
+ value.is_a?(expr)
30
+ when Array
31
+ !expr.empty? or raise Error, "Empty array"
32
+ value.is_a?(Array) && value.all? { |elem| expr.any? { |e| check(elem, e) } }
33
+ when Hash
34
+ value.is_a?(Hash) && value.all? { |key, value|
35
+ expr.any? { |key_expr, value_expr|
36
+ [[key, key_expr], [value, value_expr]].all? { |value, expr|
37
+ if expr.is_a?(Array) && (expr.size > 1 || expr.first.is_a?(Array))
38
+ expr.any? { |e| check(value, e) }
39
+ else
40
+ check(value, expr)
41
+ end
42
+ }
43
+ }
44
+ }
45
+ when Proc
46
+ expr.call(value)
47
+ else
48
+ raise Error, "Illegal expression #{expr.inspect}"
49
+ end
50
+ end
51
+
52
+ # Render a class expression as a String. Same as
53
+ # <tt>exprs.map(&:inspect).join(", ")</tt> except that Proc objects are rendered as
54
+ # "Proc@<sourcefile>:<linenumber>"
55
+ def self.fmt_exprs(exprs)
56
+ exprs.map { |expr| fmt_expr(expr) }.join(", ")
57
+ end
58
+
59
+ # Render a class expression as a String. Same as +expr.inspect+ except that
60
+ # Proc objects are rendered as "Proc@<sourcefile>>:<linenumber>"
61
+ #
62
+ def self.fmt_expr(expr)
63
+ case expr
64
+ when Class; expr.to_s
65
+ when Array; "[" + expr.map { |expr| fmt_expr(expr) }.join(", ") + "]"
66
+ when Hash; "{" + expr.map { |k,v| "#{fmt_expr(k)} => #{fmt_expr(v)}" }.join(", ") + "}"
67
+ when Proc; "Proc@#{expr.source_location.first}:#{expr.source_location.last}"
68
+ else
69
+ raise Error, "Illegal expression"
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,3 @@
1
+ module Constrain
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: constrain
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Claus Rasmussen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-05-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: simplecov
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: "\n Allows you check if an object match a class expression. It is
28
+ typically\n used to check the type of method paraameters. It is an alternative
29
+ to using\n Ruby-3 .rbs files but with a different syntax and only dynamic checks\n
30
+ \ \n Typically you'll include the Constrain module and use #constrain to check\n
31
+ \ the type of method parameters:\n\n include Constrain\n\n # f takes
32
+ a String and an array of Integer objects. Raise a Constrain::Error\n # if parameters
33
+ doesn't have the expected types\n def f(a, b)\n constrain a, String\n
34
+ \ constrain b, [Integer]\n end\n\n Constrain works with ruby-2 (and
35
+ maybe ruby-3)\n "
36
+ email:
37
+ - claus.l.rasmussen@gmail.com
38
+ executables: []
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - ".gitignore"
43
+ - ".rspec"
44
+ - ".ruby-version"
45
+ - ".travis.yml"
46
+ - Gemfile
47
+ - README.md
48
+ - Rakefile
49
+ - bin/console
50
+ - bin/setup
51
+ - constrain.gemspec
52
+ - lib/constrain.rb
53
+ - lib/constrain/version.rb
54
+ homepage: http://www.nowhere.com/
55
+ licenses: []
56
+ metadata:
57
+ homepage_uri: http://www.nowhere.com/
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.3.0
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.1.4
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Dynamic in-file type checking
77
+ test_files: []