unbounded 0.0.1

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
+ 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: