unbounded 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c98cc5d255568b4124a3ae5e87269348e2971136
4
+ data.tar.gz: ae0242ba93586dc599b2bf606b216dddcd19054b
5
+ SHA512:
6
+ metadata.gz: 0fcc4b3258a7fd16b7c193c342f99e6c271bdb6a2be04e5e5b822d7471f46ef8c030ed4c6d8b259c7e2f8c6600a19e05d4dad1edf25b416f8556322698027fa8
7
+ data.tar.gz: 4e72d60a82c534ba8b1d84cc833900c473de8f62b19293b5a2f89dbf06f5e7bcc8ffa9c05f165c1ad2971e8aa8fb89b025ae66ec5e42a0ae18c74f7db514d55c
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.pryrc ADDED
@@ -0,0 +1,4 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'unbounded/version'
4
+ require 'unbounded'
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --protected
2
+ --private
3
+ lib/**/*.rb
4
+ -M redcarpet
5
+ -m markdown
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ source 'https://rubygems.org'
3
+
4
+ # Specify your gem's dependencies in unbounded.gemspec
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Alexa Grey
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Unbounded
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'unbounded'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```shell
16
+ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```shell
22
+ gem install unbounded
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Create an unbounded range by passing nil as one of the end points:
28
+
29
+ ```ruby
30
+ normal_style = Unbounded::Range.new(0, nil) # => 0..INFINITY
31
+ ```
32
+
33
+ ... Or using postgres-style ranges
34
+
35
+ ```ruby
36
+ postgres_style = Unbounded::Range.new('[0,100)') # => 0...100
37
+ ```
38
+
39
+ Alternatively, convert a vanilla range:
40
+
41
+ ```ruby
42
+ converted = (1..3).unbounded
43
+ converted.kind_of? Unbounded::Range # => true
44
+ ```
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create new Pull Request
53
+
54
+ ## Todo
55
+
56
+ * invertable ranges (at least for numeric)
57
+
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rspec/core/rake_task"
6
+ require 'yard'
7
+
8
+ RSpec::Core::RakeTask.new('spec')
9
+
10
+ desc 'Generate docs'
11
+ YARD::Rake::YardocTask.new(:doc) do |t|
12
+ t.files = ['lib/**/*.rb']
13
+ t.options = ['--protected', '--private', '-r', 'README.md']
14
+ end
15
+
16
+ namespace :doc do
17
+ desc 'Generate and publish YARD docs to github pages.'
18
+ task :publish => ['doc'] do
19
+ Dir.chdir(File.dirname(__FILE__) + '/../doc') do
20
+ system %Q{git add .}
21
+ system %Q{git add -u}
22
+ system %Q{git commit -m 'Generating docs for version #{version}.'}
23
+ system %Q{git push origin gh-pages}
24
+ end
25
+ end
26
+ end
27
+
28
+ task :default => :spec
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+
3
+ module Unbounded
4
+ # Methods for parsing alternative range formats
5
+ # Presently there are two:
6
+ #
7
+ # * {POSTGRES_FORMAT}
8
+ # * {SIMPLE_FORMAT}
9
+ module Formats
10
+ extend ActiveSupport::Concern
11
+
12
+ # @!parse include Unbounded::Utility
13
+ include Unbounded::Utility
14
+
15
+ # A regular expression that loosely describes the format used within Postgres
16
+ # for their range types. They look something like:
17
+ #
18
+ # * `"[4,6]"` == `4..6`
19
+ # * `"[1,9)"` == `1...9`
20
+ #
21
+ # An endpoint can be omitted to create an unbounded type:
22
+ #
23
+ # * `"[1,]"` == `1..INFINITY`
24
+ # * `"[,0)"` == `-INFINITY...0`
25
+ #
26
+ # @see http://www.postgresql.org/docs/9.2/static/rangetypes.html for more information
27
+ # @return [Regexp]
28
+ POSTGRES_FORMAT=/^
29
+ (?<lower_inc>[\[\(])
30
+ (?<minimum>\S*)
31
+ ,\s*
32
+ (?<maximum>\S*)
33
+ (?<upper_inc>[\)\]])
34
+ $/x
35
+
36
+ # A regular expression for parsing the simple format used in
37
+ # Range.new shorthand, e.g. `'1..3'`, `'4...10'`
38
+ # @return [Regexp]
39
+ SIMPLE_FORMAT=/^(?<minimum>[^\.]+)(?<inclusiveness>\.\.\.?)(?<maximum>[^\.]+)$/
40
+
41
+ # @param [Array] args arguments from {Unbounded::Range#initialize}
42
+ # @return [OpenStruct]
43
+ def parse_for_range(args)
44
+ hashed = case args.length
45
+ when 0 then parse_standard_range_options(nil, nil)
46
+ when 1 then parse_string_for_range_options(args.shift)
47
+ when 2..3 then parse_standard_range_options(*args)
48
+ end.presence or raise ArgumentError, "Don't know how to parse arguments: #{args.inspect}"
49
+
50
+
51
+ OpenStruct.new(hashed).tap do |o|
52
+ if o.format == :postgres
53
+ alter_by_postgres_style! o
54
+ elsif o.format == :simple
55
+ alter_by_simple_style! o
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+ # Parse the standard range initialization options
62
+ # @see Range#initialize
63
+ # @param [Object] min
64
+ # @param [Object] max
65
+ # @param [Boolean] exclude_end
66
+ # @return [{format: :standard, minimum: Numeric, maximum: Numeric, exclude_end: Boolean}]
67
+ def parse_standard_range_options(min, max, exclude_end = false)
68
+ {
69
+ :format => :standard,
70
+ :minimum => min.presence || NINFINITY,
71
+ :maximum => max.presence || INFINITY,
72
+ :exclude_end => !!exclude_end
73
+ }
74
+ end
75
+
76
+ # Parse string using {POSTGRES_FORMAT} or {SIMPLE_FORMAT}.
77
+ # @param [#to_s] s
78
+ # @return [Hash]
79
+ def parse_string_for_range_options(s)
80
+ case s.to_s
81
+ when POSTGRES_FORMAT
82
+ match_to_hash(Regexp.last_match).merge(:format => :postgres)
83
+ when SIMPLE_FORMAT
84
+ match_to_hash(Regexp.last_match).merge(:format => :simple)
85
+ end
86
+ end
87
+
88
+ # Modify the options slightly for postgres formats.
89
+ # @param [OpenStruct] options
90
+ # @return [void]
91
+ def alter_by_postgres_style!(options)
92
+ options.minimum = numerify(options.minimum, NINFINITY)
93
+ options.maximum = numerify(options.maximum, INFINITY)
94
+ options.exclude_end = options.upper_inc == ')'
95
+
96
+ if options.lower_inc == '('
97
+ options.minimum += 1
98
+ end
99
+ end
100
+
101
+ # Modify the options slightly to account for simple (e.g. 1..3) formatting
102
+ # @param [OpenStruct] options
103
+ # @return [void]
104
+ def alter_by_simple_style!(options)
105
+ options.minimum = numerify(options.minimum, NINFINITY)
106
+ options.maximum = numerify(options.maximum, INFINITY)
107
+ options.exclude_end = options.inclusiveness.length == 3
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ module Unbounded
4
+ # An error created when attempting to iterate over a (non-lazy)
5
+ # version of an {Unbounded::Range}, and the current ruby version
6
+ # does not support `#lazy`.
7
+
8
+ class InfiniteEnumerator < TypeError
9
+ end
10
+ end
@@ -0,0 +1,188 @@
1
+ # encoding: utf-8
2
+
3
+ module Unbounded
4
+ # An unbounded (infinite) range extension for the standard Ruby Range class.
5
+ class Range < ::Range
6
+ include Formats
7
+
8
+ RANGE_FORMATS[:humanized] = proc { |first, last| ::Unbounded::Range.humanized(first, last) }
9
+ RANGE_FORMATS[:postgres] = proc { |first, last| ::Unbounded::Range.postgres(first, last) }
10
+
11
+ # @overload initialize(range_start, range_minimum, exclude_end = false)
12
+ # Create a range the traditional way, à la `Range.new`.
13
+ # @param [#succ] range_start
14
+ # @param [#succ] range_end
15
+ # @param [Boolean] exclude_end
16
+ # @overload initialize(range_string)
17
+ # Create a range in the same manner as you would in PostgreSQL
18
+ # @param [String] range_string
19
+ def initialize(*args)
20
+ parsed = parse_for_range(args)
21
+
22
+ @format = parsed.format
23
+
24
+ super(parsed.minimum, parsed.maximum, parsed.exclude_end)
25
+ end
26
+
27
+ # @todo i18n support
28
+ # @return [String] a more human-readable format
29
+ def humanized
30
+ case unbounded?
31
+ when :infinite
32
+ "infinite"
33
+ when :maximum
34
+ "#{self.begin}+"
35
+ when :minimum
36
+ exclude_end? ? "fewer than #{self.end}" : "#{self.end} or fewer"
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ # @return [Boolean]
43
+ def infinite?
44
+ numeric? && ( ( unbounded_minimum | unbounded_maximum ) == 3 )
45
+ end
46
+
47
+ # @return [Boolean] whether this range is numeric
48
+ def numeric?
49
+ self.begin.kind_of?(Numeric) && self.end.kind_of?(Numeric)
50
+ end
51
+
52
+ # For compatibility with {Unbounded::RangeExtension#unbounded}
53
+ # unded?
54
+ # @return [self]
55
+ def unbounded
56
+ self
57
+ end
58
+
59
+ # Check whether this range is unbounded in some way.
60
+ # @return [Symbol,nil] returns a symbol for the type of unboundedness, nil otherwise
61
+ def unbounded?(unbounded_type = nil)
62
+ return false unless numeric?
63
+
64
+ unboundedness = case unbounded_minimum | unbounded_maximum
65
+ when 3 then :infinite
66
+ when 2 then :maximum
67
+ when 1 then :minimum
68
+ end
69
+
70
+ return unbounded_type && unboundedness ? ( unboundedness == :infinite ) || ( unboundedness == unbounded_type ) : unboundedness
71
+ end
72
+
73
+ # @!group Enumerable Overrides
74
+ # Same as `Enumerable#count`, except in cases where the collection is {Range#unbounded?}.
75
+ # @note This would not be true in some cases, e.g. checking for negative
76
+ # numbers on a range of 0 to infinity.
77
+ # @return [Integer, INFINITY] Infinity when unbounded, super otherwise
78
+ def count(*args, &block)
79
+ unbounded? ? INFINITY : super
80
+ end
81
+
82
+ # @overload first
83
+ # @return [Numeric] equivalent to `#min`
84
+ # @overload first(n)
85
+ # @param [Integer] n
86
+ # @return [Array<#succ>] first n-elements of the range
87
+ def first(*args)
88
+ n = args.shift
89
+
90
+ if unbounded_minimum? && (n.nil? || n.kind_of?(Integer))
91
+ if n.nil?
92
+ min
93
+ else
94
+ Array.new(n, min)
95
+ end
96
+ else
97
+ return n.nil? ? super() : super(n)
98
+ end
99
+ end
100
+
101
+ # @overload last
102
+ # @return [Numeric] equivalent to {#max}
103
+ # @overload last(n)
104
+ # @param [Integer] n
105
+ # @return [Array<#succ>] last n-elements of the range
106
+ def last(*args)
107
+ n = args.shift
108
+
109
+ if unbounded? && (n.nil? || n.kind_of?(Integer))
110
+ if n.nil?
111
+ max
112
+ elsif unbounded? == :minimum # To prevent `cannot iterate from Float`
113
+ new_minimum = max - (n - 1)
114
+
115
+ (new_minimum..max).to_a
116
+ else
117
+ Array.new(n, max) # Array of Infinity
118
+ end
119
+ else
120
+ return n.nil? ? super() : super(n)
121
+ end
122
+ end
123
+
124
+ # Overload for `Range#max` to account for "unbounded_maximum? && exclude_end?" edge case
125
+ def max
126
+ super
127
+ rescue TypeError
128
+ self.end
129
+ end
130
+
131
+ # The default implementation of `Range#minmax` for needlessly uses `#each`,
132
+ # which can cause infinite enumeration.
133
+ # @return [Array(Numeric, Numeric)]
134
+ def minmax
135
+ [min, unbounded? ? INFINITY : max]
136
+ end
137
+ # @!endgroup
138
+
139
+ class << self
140
+ # @param [Numeric] first
141
+ # @param [Numeric] last
142
+ # @return [String]
143
+ def humanized(first, last)
144
+ return new(first, last).humanized
145
+ end
146
+
147
+ private
148
+ # @param [Symbol] type should be :min or :max
149
+ # @param [Integer] int_to_return integer to return for bitwise comparison in {#unbounded?}
150
+ # @return [void]
151
+ # @!macro [attach] unbounded_endpoint
152
+ # @!method unbounded_$1imum
153
+ # Determine whether the $1 is unbounded.
154
+ # @!visibility private
155
+ # @return [$2,0] $2 if unbounded, 0 if otherwise
156
+ #
157
+ # @!method unbounded_$1imum?
158
+ # Predicate method for {#unbounded_$1imum} that checks against zero
159
+ # @return [Integer,nil]
160
+ #
161
+ # @!method finite_$1imum?
162
+ # Inverse complement of {#unbounded_$1imum?}
163
+ # @return [Boolean]
164
+ def unbounded_endpoint(type, int_to_return)
165
+ attr_name = type == :min ? "begin" : "end"
166
+
167
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
168
+ def unbounded_#{type}imum
169
+ self.#{attr_name}.respond_to?(:infinite?) && self.#{attr_name}.infinite? ? #{int_to_return} : 0
170
+ end
171
+
172
+ def unbounded_#{type}imum?
173
+ unbounded_#{type}imum.nonzero?
174
+ end
175
+
176
+ def finite_#{type}imum?
177
+ !unbounded_#{type}imum?
178
+ end
179
+
180
+ private :unbounded_#{type}imum
181
+ RUBY
182
+ end
183
+ end
184
+
185
+ unbounded_endpoint :min, 1
186
+ unbounded_endpoint :max, 2
187
+ end
188
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ module Unbounded
4
+ # Extensions for the standard Ruby `Range` class.
5
+ module RangeExtension
6
+ extend ::ActiveSupport::Concern
7
+
8
+ # @return [String] humanized string of the range
9
+ def humanized
10
+ "#{self.begin} \u2013 #{self.end}"
11
+ end
12
+
13
+ # Transform this into an Unbounded::Range.
14
+ # @return [Unbounded::Range]
15
+ def unbounded
16
+ ::Unbounded::Range.new(self.min, self.max, exclude_end?)
17
+ end
18
+ end
19
+ end
20
+
21
+ Range.send(:include, Unbounded::RangeExtension)
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module Unbounded
4
+ # Shared utility methods
5
+ module Utility
6
+ extend ActiveSupport::Concern
7
+
8
+ # Convert matchdata to a hash.
9
+ # @param [MatchData] match
10
+ # @return [Hash]
11
+ def match_to_hash(match)
12
+ Hash[match.names.zip(match.captures)]
13
+ end
14
+
15
+ # @param [Object] thing something to numerify
16
+ # @param [Object] default_value a value to return if `thing` fails to numerify.
17
+ # If it is `:original`, it will return the value of `thing` as-is.
18
+ # @return [Numeric, Object, nil]
19
+ def numerify(thing, default_value = nil)
20
+ return thing if thing.is_a?(Numeric)
21
+
22
+ if thing.respond_to?(:include?) && thing.include?('.')
23
+ Float(thing)
24
+ else
25
+ Integer(thing)
26
+ end
27
+ rescue ArgumentError, TypeError
28
+ if default_value == :original
29
+ thing
30
+ else
31
+ default_value
32
+ end
33
+ end
34
+
35
+ extend self
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ module Unbounded
4
+ # Current library version.
5
+ # @return [Gem::Version]
6
+ VERSION = Gem::Version.new("0.0.1")
7
+ end
data/lib/unbounded.rb ADDED
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/object/try'
6
+ require 'active_support/core_ext/range'
7
+
8
+ require 'ostruct'
9
+
10
+ require 'unbounded/version'
11
+
12
+ # A library for working with unbounded (infinite) ranges,
13
+ # with support for PostgreSQL-style notation.
14
+ module Unbounded
15
+ # Infinity
16
+ INFINITY = 1.0 / 0.0
17
+
18
+ # Negative infinity
19
+ NINFINITY = -INFINITY
20
+
21
+ autoload :Formats, 'unbounded/formats'
22
+ autoload :InfiniteEnumerator, 'unbounded/infinite_enumerator'
23
+ autoload :Range, 'unbounded/range'
24
+ autoload :Utility, 'unbounded/utility'
25
+
26
+ require 'unbounded/range_extension'
27
+
28
+ # @!parse extend Unbounded::Utility
29
+ extend Unbounded::Utility
30
+
31
+ # @return [Unbounded::Range]
32
+ def Unbounded.range(*args, &block)
33
+ Unbounded::Range.new(*args, &block)
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
6
+ # This file was generated by the `rspec --init` command. Conventionally, all
7
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
8
+ # Require this file using `require "spec_helper"` to ensure that it is only
9
+ # loaded once.
10
+ #
11
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
12
+ require 'bundler/setup'
13
+ require 'unbounded'
14
+ require 'pry'
15
+
16
+ Dir["./spec/support/**/*.rb"].sort.each {|f| require f }
17
+
18
+ RSpec.configure do |config|
19
+ config.treat_symbols_as_metadata_keys_with_true_values = true
20
+ config.run_all_when_everything_filtered = true
21
+ config.filter_run :focus
22
+ config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
23
+
24
+ # Run specs in random order to surface order dependencies. If you find an
25
+ # order dependency and want to debug it, you can fix the order by providing
26
+ # the seed, which is printed after each run.
27
+ # --seed 1234
28
+ config.order = 'random'
29
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_context 'postgres', pg: :true do
6
+ subject { described_class.new example.metadata[:postgres] }
7
+
8
+ let(:equivalent_range) { example.metadata[:equiv] }
9
+
10
+ it '== an equivalent range' do
11
+ should == equivalent_range
12
+ end
13
+
14
+ it_behaves_like 'postgres range exclusivity'
15
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'a finite maximum' do
6
+ describe '#last' do
7
+ specify 'given no parameters, it returns max' do
8
+ subject.last.should eq subject.max
9
+ end
10
+
11
+ specify 'given an integer, it defers to super' do
12
+ subject.last(10).should be_a_kind_of Array
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'a finite minimum' do
6
+ describe '#first' do
7
+ specify 'given no parameters, it returns #min' do
8
+ subject.first.should == subject.min
9
+ end
10
+
11
+ specify 'given an integer, it defers to super' do
12
+ subject.first(10).should be_a_kind_of Array
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'an excluded end' do
6
+ it { should be_exclude_end }
7
+
8
+ it { should_not include subject.end }
9
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'an infinite range' do
6
+ it { should be_infinite }
7
+
8
+ it_has_behavior 'humanized (infinite)'
9
+
10
+ it 'it should include any number' do
11
+ should include infinity, negative_infinity, *(positive_numbers + negative_numbers)
12
+ end
13
+
14
+ it_should_behave_like 'any unbounded range'
15
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'an unbounded maximum' do
6
+ it_should_behave_like 'any unbounded range'
7
+
8
+ it_has_behavior 'humanized (n+)'
9
+
10
+ it 'should include any positive number' do
11
+ should include *positive_numbers
12
+ end
13
+
14
+ it 'should not include negative numbers' do
15
+ should_not include *negative_numbers
16
+ end
17
+
18
+ describe '#last' do
19
+ specify 'given no parameters, it should be INFINITY' do
20
+ subject.last.should == infinity
21
+ end
22
+
23
+ specify 'given an integer, it returns Array.new(n, INFINITY)' do
24
+ subject.last(10).should == Array.new(10, infinity)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'an unbounded minimum' do
6
+ it_should_behave_like 'any unbounded range'
7
+
8
+ it 'should include any negative number' do
9
+ should include *negative_numbers
10
+ end
11
+
12
+ it 'should not include positive numbers' do
13
+ should_not include *positive_numbers
14
+ end
15
+
16
+ describe '#first' do
17
+ specify 'given no parameters, it should be -INFINITY' do
18
+ subject.first.should == negative_infinity
19
+ end
20
+
21
+ specify 'given an integer, it returns Array.new(n, -INFINITY)' do
22
+ subject.first(10).should == Array.new(10, negative_infinity)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'any unbounded range' do
6
+ it { should be_unbounded }
7
+
8
+ it { should be_numeric }
9
+
10
+ describe '#count' do
11
+ its(:count) { should be_infinite }
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'humanized string' do
6
+ describe '#humanized' do
7
+ it 'humanizes correctly' do
8
+ subject.humanized.should match expected_format
9
+ end
10
+ end
11
+ end
12
+
13
+ shared_examples_for 'humanized (n or fewer)' do
14
+ let(:expected_format) { %r,\d+ or fewer, }
15
+ include_examples 'humanized string'
16
+ end
17
+
18
+ shared_examples_for "humanized (hyphenated)" do
19
+ let(:expected_format) { %r,\d+ #{"\u2013"} \d+, }
20
+ include_examples 'humanized string'
21
+ end
22
+
23
+ shared_examples_for 'humanized (fewer than n)' do
24
+ let(:expected_format) { %r,fewer than \d+, }
25
+ include_examples 'humanized string'
26
+ end
27
+
28
+ shared_examples_for 'humanized (infinite)' do
29
+ let(:expected_format) { %r,infinite, }
30
+ include_examples 'humanized string'
31
+ end
32
+
33
+ shared_examples_for 'humanized (n+)' do
34
+ let(:expected_format) { %r,\d+\+, }
35
+ include_examples 'humanized string'
36
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'numerification' do |opts = {}|
6
+ if opts.key?(:want)
7
+ let(:wanted) { opts[:want] }
8
+
9
+ it 'converts to the expected value' do
10
+ should == wanted
11
+ end
12
+ elsif opts.key?(:default)
13
+ if opts[:default] == :original
14
+ it 'should return the original' do
15
+ should eq provided
16
+ end
17
+ else
18
+ it 'returns the default' do
19
+ should eq default
20
+ end
21
+ end
22
+ else
23
+ it 'returns nil' do
24
+ should be_nil
25
+ end
26
+ end
27
+
28
+ let(:provided) do
29
+ opts.fetch(:provide) do
30
+ opts.fetch(:want).presence.to_s
31
+ end
32
+ end
33
+
34
+ let (:default) { opts[:default] }
35
+
36
+ subject { dummy_class.numerify(provided, default) }
37
+
38
+ if opts.key? :klass
39
+ let(:klass) { opts[:klass] }
40
+
41
+ it "coerces to a #{opts[:klass]}" do
42
+ should be_a_kind_of klass
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ shared_examples_for 'postgres range exclusivity' do |*args|
6
+ exclusivity = args.shift || {}
7
+
8
+ exclusivity = OpenStruct.new exclusivity
9
+
10
+ exclusivity.max = metadata.fetch(:postgres, '').end_with?(')') if exclusivity.min.nil?
11
+
12
+ exclusivity.min = metadata.fetch(:postgres, '').start_with?('(') if exclusivity.min.nil?
13
+
14
+ let!(:provided_range_start) { ( example.metadata.fetch(:postgres, '')[/^.(\d)+,/, 1] || 0).to_i }
15
+
16
+ let!(:expected_range_start) { provided_range_start + (exclusivity.min ? 1 : 0) }
17
+
18
+ if exclusivity.min
19
+ specify 'it should have a different start than provided' do
20
+ subject.min.should_not == provided_range_start
21
+ subject.min.should == expected_range_start
22
+ end
23
+
24
+ specify 'it should not include the provided minimum' do
25
+ subject.should_not include provided_range_start
26
+ end
27
+ else
28
+
29
+ specify 'it should have the provided minimum' do
30
+ subject.min.should == provided_range_start
31
+ end
32
+
33
+ end
34
+
35
+ if exclusivity.max
36
+ specify 'the end is excluded' do
37
+ subject.should_not include subject.end
38
+ end
39
+ else
40
+ specify 'the end is included' do
41
+ subject.should include subject.max
42
+ end
43
+ end
44
+
45
+ its(:exclude_end?) { should exclusivity.max ? be_true : be_false }
46
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Unbounded::RangeExtension do
6
+ let(:standard_range) { 1..10 }
7
+
8
+ subject { standard_range }
9
+
10
+ it_has_behavior 'humanized (hyphenated)'
11
+
12
+ describe '#unbounded' do
13
+ its(:unbounded) { should be_a_kind_of Unbounded::Range }
14
+ its(:unbounded) { should == standard_range }
15
+ end
16
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Unbounded::Range do
6
+ let(:negative_infinity) { Unbounded::NINFINITY }
7
+ let(:infinity) { Unbounded::INFINITY }
8
+ let(:positive_numbers) { [42, 2**64] }
9
+ let(:negative_numbers) { [-2**64, -42] }
10
+
11
+ describe '#unbounded' do
12
+ specify 'returns self' do
13
+ subject.unbounded.should be subject
14
+ end
15
+ end
16
+
17
+ describe '.humanized' do
18
+ specify { described_class.humanized(0, 10).should be_a_kind_of String }
19
+ end
20
+
21
+ describe '.new' do
22
+ context 'given ()' do
23
+ it_should_behave_like 'an infinite range'
24
+ end
25
+
26
+ context 'given (nil, nil)' do
27
+ subject { described_class.new nil, nil }
28
+
29
+ it_should_behave_like 'an infinite range'
30
+ end
31
+
32
+ context 'given (0, nil)' do
33
+ subject { described_class.new 0, nil }
34
+
35
+ it_should_behave_like 'a finite minimum'
36
+
37
+ it_should_behave_like 'an unbounded maximum'
38
+
39
+ it_has_behavior 'humanized (n+)'
40
+ end
41
+
42
+ context 'given (0, nil, true)' do
43
+ subject { described_class.new 0, nil, true }
44
+
45
+ it_should_behave_like 'an excluded end'
46
+
47
+ it_should_behave_like 'a finite minimum'
48
+
49
+ it_should_behave_like 'an unbounded maximum'
50
+
51
+ it_has_behavior 'humanized (n+)'
52
+
53
+ describe '#minmax' do
54
+ specify 'the last element is infinity' do
55
+ subject.minmax.last == infinity
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'given (nil, 0)' do
61
+ subject { described_class.new nil, 0 }
62
+
63
+ it_should_behave_like 'an unbounded minimum'
64
+
65
+ it_should_behave_like 'a finite maximum'
66
+
67
+ it_has_behavior 'humanized (n or fewer)'
68
+ end
69
+
70
+ context 'given (nil, 0, true)' do
71
+ subject { described_class.new nil, 0, true }
72
+
73
+ it_should_behave_like 'an excluded end'
74
+
75
+ it_should_behave_like 'an unbounded minimum'
76
+
77
+ it_should_behave_like 'a finite maximum'
78
+
79
+ it_has_behavior 'humanized (fewer than n)'
80
+ end
81
+
82
+ context 'with a postgres-style string argument' do
83
+ context '()', :pg,
84
+ :start_excluded,
85
+ :end_excluded,
86
+ postgres: '(1,10)',
87
+ equiv: 2...10
88
+
89
+ context '[)', :pg,
90
+ :end_excluded,
91
+ start_excluded: false,
92
+ postgres: '[1,10)',
93
+ equiv: 1...10
94
+
95
+ context '(]', :pg,
96
+ :start_excluded,
97
+ end_excluded: false,
98
+ postgres: '(1,10]',
99
+ equiv: 2..10
100
+
101
+ context '[]', :pg,
102
+ end_excluded: false,
103
+ start_excluded: false,
104
+ postgres: '[1,10]',
105
+ equiv: 1..10
106
+ end
107
+
108
+ context 'with a simple-style string argument' do
109
+ context 'given 0...Infinity' do
110
+ subject { described_class.new('0...Infinity') }
111
+
112
+ it_should_behave_like 'a finite minimum'
113
+ it_should_behave_like 'an unbounded maximum'
114
+ it_should_behave_like 'an excluded end'
115
+ end
116
+
117
+ context 'given 1..10' do
118
+ subject { described_class.new('1..10') }
119
+
120
+ it_should_behave_like 'a finite minimum'
121
+ it_should_behave_like 'a finite maximum'
122
+
123
+ it_has_behavior 'humanized (hyphenated)'
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Unbounded::Utility do
6
+ let(:dummy_class) { Module.new { extend Unbounded::Utility } }
7
+
8
+ describe '#match_to_hash' do
9
+ let(:regex) { %r:^(?<first_word>\w+)[^\w]*(?<second_word>\w+): }
10
+ let(:test_string) { 'Hello, world!' }
11
+ let(:match_data) { test_string.match(regex) }
12
+
13
+ subject { dummy_class.match_to_hash(match_data) }
14
+
15
+ it 'converts `MatchData` to a hash' do
16
+ subject.should be_a_kind_of Hash
17
+ end
18
+
19
+ it 'has the expected contents' do
20
+ should include 'first_word' => 'Hello', 'second_word' => 'world'
21
+ end
22
+ end
23
+
24
+ describe '#numerify' do
25
+ context 'given a numeric value' do
26
+ it 'returns the same' do
27
+ dummy_class.numerify(1).should eq 1
28
+ dummy_class.numerify(2.0).should eq 2.0
29
+ end
30
+ end
31
+
32
+ context 'given a float-containing string' do
33
+ it_has_behavior 'numerification', :want => 1.0, :klass => Float
34
+ end
35
+
36
+ context 'given an integer-containing string' do
37
+ it_has_behavior 'numerification', :want => 1, :klass => Integer
38
+ end
39
+
40
+ context 'on a failed conversion' do
41
+ context 'given no default' do
42
+ it_has_behavior 'numerification', :provide => 'anything else'
43
+ end
44
+
45
+ context 'default: :original' do
46
+ it_has_behavior 'numerification', :provide => 'some string', :default => :original
47
+ end
48
+
49
+ context 'default: some other string' do
50
+ it_has_behavior 'numerification', :provide => 'some string', :default => 'some other string'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Unbounded do
6
+ describe '.range' do
7
+ specify 'creates a range' do
8
+ described_class.range(nil, nil).should be_a_kind_of Unbounded::Range
9
+ end
10
+ end
11
+ end
data/unbounded.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'unbounded/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "unbounded"
8
+ spec.version = Unbounded::VERSION
9
+ spec.authors = ["Alexa Grey"]
10
+ spec.email = ["devel@mouse.vc"]
11
+ spec.description = %q{Unbounded (infinite) ranges, ruby-style}
12
+ spec.summary = %q{Unbounded (infinite) ranges, ruby-style}
13
+ spec.homepage = "https://github.com/scryptmouse/unbounded"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "active_support", ">= 3.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec", "~> 2.14"
26
+ spec.add_development_dependency "pry"
27
+ spec.add_development_dependency "simplecov"
28
+ spec.add_development_dependency 'yard'
29
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unbounded
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexa Grey
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: active_support
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Unbounded (infinite) ranges, ruby-style
112
+ email:
113
+ - devel@mouse.vc
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .pryrc
120
+ - .rspec
121
+ - .yardopts
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - lib/unbounded.rb
127
+ - lib/unbounded/formats.rb
128
+ - lib/unbounded/infinite_enumerator.rb
129
+ - lib/unbounded/range.rb
130
+ - lib/unbounded/range_extension.rb
131
+ - lib/unbounded/utility.rb
132
+ - lib/unbounded/version.rb
133
+ - spec/spec_helper.rb
134
+ - spec/support/shared_context_for_postgres_style_ranges.rb
135
+ - spec/support/shared_examples_for_a_finite_maximum.rb
136
+ - spec/support/shared_examples_for_a_finite_minimum.rb
137
+ - spec/support/shared_examples_for_an_excluded_end.rb
138
+ - spec/support/shared_examples_for_an_infinite_range.rb
139
+ - spec/support/shared_examples_for_an_unbounded_maximum.rb
140
+ - spec/support/shared_examples_for_an_unbounded_minimum.rb
141
+ - spec/support/shared_examples_for_any_unbounded_range.rb
142
+ - spec/support/shared_examples_for_humanized_string.rb
143
+ - spec/support/shared_examples_for_numerification.rb
144
+ - spec/support/shared_examples_for_postgres_range_exclusivity.rb
145
+ - spec/unbounded/range_extension_spec.rb
146
+ - spec/unbounded/range_spec.rb
147
+ - spec/unbounded/utility_spec.rb
148
+ - spec/unbounded_spec.rb
149
+ - unbounded.gemspec
150
+ homepage: https://github.com/scryptmouse/unbounded
151
+ licenses:
152
+ - MIT
153
+ metadata: {}
154
+ post_install_message:
155
+ rdoc_options: []
156
+ require_paths:
157
+ - lib
158
+ required_ruby_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ required_rubygems_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - '>='
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ requirements: []
169
+ rubyforge_project:
170
+ rubygems_version: 2.0.3
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: Unbounded (infinite) ranges, ruby-style
174
+ test_files:
175
+ - spec/spec_helper.rb
176
+ - spec/support/shared_context_for_postgres_style_ranges.rb
177
+ - spec/support/shared_examples_for_a_finite_maximum.rb
178
+ - spec/support/shared_examples_for_a_finite_minimum.rb
179
+ - spec/support/shared_examples_for_an_excluded_end.rb
180
+ - spec/support/shared_examples_for_an_infinite_range.rb
181
+ - spec/support/shared_examples_for_an_unbounded_maximum.rb
182
+ - spec/support/shared_examples_for_an_unbounded_minimum.rb
183
+ - spec/support/shared_examples_for_any_unbounded_range.rb
184
+ - spec/support/shared_examples_for_humanized_string.rb
185
+ - spec/support/shared_examples_for_numerification.rb
186
+ - spec/support/shared_examples_for_postgres_range_exclusivity.rb
187
+ - spec/unbounded/range_extension_spec.rb
188
+ - spec/unbounded/range_spec.rb
189
+ - spec/unbounded/utility_spec.rb
190
+ - spec/unbounded_spec.rb
191
+ has_rdoc: