extrarange 1.0.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.
- data/LICENSE +20 -0
- data/README.markdown +115 -0
- data/lib/extrarange/core_ext/range.rb +5 -0
- data/lib/extrarange/range_math.rb +119 -0
- data/lib/extrarange/range_with_math.rb +16 -0
- data/lib/extrarange/sparse_range.rb +198 -0
- data/lib/extrarange.rb +2 -0
- metadata +74 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2010 Timothy Elliott, Alan Franzoni
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Extrarange: enhancing the Ruby range experience.
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## SparseRange
|
|
5
|
+
|
|
6
|
+
The SparseRange class lets you easily collect many ranges into one. It accepts
|
|
7
|
+
individual values (3, 4, 5) as well as ranges (2..3000) and will fold them
|
|
8
|
+
together to efficiently work with them as a single set of ranges.
|
|
9
|
+
|
|
10
|
+
Once you have inserted your values and ranges, you can perform logic operations
|
|
11
|
+
such as union and intersection. This is particularly useful for finding
|
|
12
|
+
overlapping time spans.
|
|
13
|
+
|
|
14
|
+
I took care to allow for non-sequential ranges, such as ranges of floats, to
|
|
15
|
+
work with this class. Non-sequential ranges are ranges for data types that don't
|
|
16
|
+
have a succ method.
|
|
17
|
+
|
|
18
|
+
The class aims at being a drop-in replacement of a standard range; it exposes the very
|
|
19
|
+
same interface of the builtin ruby range. There're currently some caveats, check them below.
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
|
|
23
|
+
Require and include the module
|
|
24
|
+
|
|
25
|
+
require 'extrarange'
|
|
26
|
+
include ExtraRange
|
|
27
|
+
|
|
28
|
+
Initialize a range with gaps, the order doesn't matter, and overlapping values
|
|
29
|
+
are folded together:
|
|
30
|
+
|
|
31
|
+
extrarange = SparseRange.new 55...900, 1..34, 22, 23
|
|
32
|
+
|
|
33
|
+
Add values to a range with gaps:
|
|
34
|
+
|
|
35
|
+
extrarange << 0.4
|
|
36
|
+
extrarange.add(Time.now...(Time.now + 5000))
|
|
37
|
+
|
|
38
|
+
Delete values from a range with gaps:
|
|
39
|
+
|
|
40
|
+
extrarange.delete(Date.today - 15)
|
|
41
|
+
extrarange.delete(50..3000)
|
|
42
|
+
|
|
43
|
+
Fast calculation of the size of a range with gaps:
|
|
44
|
+
|
|
45
|
+
extrarange.size
|
|
46
|
+
|
|
47
|
+
Use logic operations on a range with gaps:
|
|
48
|
+
|
|
49
|
+
# returns a range with gaps of all overlapping ranges
|
|
50
|
+
extrarange_one & extrarange_two
|
|
51
|
+
extrarange & ((Date.today - 30)..Date.today)
|
|
52
|
+
|
|
53
|
+
# returns a range with gaps of all ranges combined
|
|
54
|
+
extrarange_one | extrarange_two
|
|
55
|
+
|
|
56
|
+
I created this class in order to simplify the calculation of billing periods in
|
|
57
|
+
a rails project that uses a audit table:
|
|
58
|
+
|
|
59
|
+
require 'extrarange'
|
|
60
|
+
include ExtraRange
|
|
61
|
+
|
|
62
|
+
def get_premium_time_spans
|
|
63
|
+
premium_time_spans = SparseRange.new
|
|
64
|
+
|
|
65
|
+
# Get all events from an audit table, i.e. every time a customer toggles
|
|
66
|
+
# the premium flag on their account.
|
|
67
|
+
premium_toggles = Customer.audits.premium_column
|
|
68
|
+
|
|
69
|
+
# We only care when premium was on, so eat the first event if it was a
|
|
70
|
+
# toggle to a non-premium account.
|
|
71
|
+
premium_toggles.shift unless premium_toggles.first.value
|
|
72
|
+
|
|
73
|
+
premium_toggles.each_slice(2) do |toggle_pair|
|
|
74
|
+
# If we only got one element in the slice then inject a fake toggle
|
|
75
|
+
# at the end of the span
|
|
76
|
+
span_end = toggle_pair[1] ? toggle_pair[1].created_at : Time.now
|
|
77
|
+
|
|
78
|
+
premium_time_spans.add(toggle_pair[0].created_at..span_end)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
premium_time_spans
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
premium_time_spans = get_premium_time_spans
|
|
85
|
+
|
|
86
|
+
# Calculate how long the customer had a premium account in January
|
|
87
|
+
january = Time.local(2010, 1, 1)...Time.local(2010, 2, 1)
|
|
88
|
+
puts "January Premium: #{(premium_time_spans & january).size} seconds"
|
|
89
|
+
|
|
90
|
+
# Calculate how long the customer had a premium account during the two last
|
|
91
|
+
# service outages
|
|
92
|
+
disk_recovery = Time.local(2009, 12, 24, 15, 30)..Time.local(2009, 12, 24, 16)
|
|
93
|
+
net_outage = Time.local(2010, 2, 1, 12, 15)..Time.local(2010, 2, 2, 2, 30)
|
|
94
|
+
outages = SparseRange.new disk_recovery, net_outage
|
|
95
|
+
|
|
96
|
+
puts "Outage Premium: #{(premium_time_spans & outages).size} seconds"
|
|
97
|
+
|
|
98
|
+
### Caveats
|
|
99
|
+
- SparseRange is currently mutable. Be careful whenever using it as an hash key as it may produce unexpected results.
|
|
100
|
+
- If *any* range within the sparse range doesn't support min/max (e.g. reverse ranges like 3..1), then the sparse
|
|
101
|
+
range won't support them as well. I hope to fix such behaviour in a future version.
|
|
102
|
+
- cover? is unoptimized and may take a lot of time in large sparse ranges.
|
|
103
|
+
|
|
104
|
+
## Credits
|
|
105
|
+
|
|
106
|
+
This is a fork of the [range_with_gaps](https://github.com/ender672/range_with_gaps) gem by Timothy Elliott.
|
|
107
|
+
|
|
108
|
+
## TODO
|
|
109
|
+
|
|
110
|
+
- Create an immutable version of SparseRange
|
|
111
|
+
- Create another edition of Ruby builtin range, supporting an optional external succ/comparator/whatever we may need to build an
|
|
112
|
+
arbitrary range
|
|
113
|
+
- Create things like cyclic ranges, etc.
|
|
114
|
+
|
|
115
|
+
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module ExtraRange
|
|
2
|
+
module RangeMath
|
|
3
|
+
# From Ruby Facets. Returns true if this range overlaps with another range.
|
|
4
|
+
def overlap?(enum)
|
|
5
|
+
include?(enum.first) or enum.include?(first)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Returns true if this range completely covers the given range.
|
|
9
|
+
def mask?(enum)
|
|
10
|
+
higher_or_equal_rhs_as?(enum) && include?(enum.first)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns a new range containing all elements that are common to both.
|
|
14
|
+
def &(enum)
|
|
15
|
+
enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable"
|
|
16
|
+
return nil unless overlap?(enum)
|
|
17
|
+
|
|
18
|
+
first_val = [first, enum.first].max
|
|
19
|
+
hi = lower_or_equal_rhs_as?(enum) ? self : enum
|
|
20
|
+
|
|
21
|
+
self.class.new first_val, hi.last, hi.exclude_end?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns a new range built by merging self and the given range. If they are
|
|
25
|
+
# non-overlapping then we return an array of ranges.
|
|
26
|
+
def |(enum)
|
|
27
|
+
if overlap?(enum) || adjacent?(enum)
|
|
28
|
+
first_val = [first, enum.first].min
|
|
29
|
+
hi = higher_or_equal_rhs_as?(enum) ? self : enum
|
|
30
|
+
|
|
31
|
+
self.class.new first_val, hi.last, hi.exclude_end?
|
|
32
|
+
else
|
|
33
|
+
return self, enum
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Return true if this range and the other range are adjacent to each other.
|
|
38
|
+
# Non-sequential ranges that exclude an end can not be adjacent.
|
|
39
|
+
def adjacent?(enum)
|
|
40
|
+
adjacent_before?(enum) || enum.adjacent_before?(self)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns a new range by subtracting the given range from self. If all
|
|
44
|
+
# elements are removed then returns nil. If the subtracting range results
|
|
45
|
+
# in a split of self then we return two ranges in a sorted array. Only works
|
|
46
|
+
# on ranges that represent a sequence of values and respond to succ.
|
|
47
|
+
def -(enum)
|
|
48
|
+
return self.dup unless overlap?(enum)
|
|
49
|
+
|
|
50
|
+
if enum.mask?(self)
|
|
51
|
+
nil
|
|
52
|
+
elsif first >= enum.first
|
|
53
|
+
ltrim enum
|
|
54
|
+
elsif lower_or_equal_rhs_as?(enum)
|
|
55
|
+
rtrim enum
|
|
56
|
+
else
|
|
57
|
+
[rtrim(enum), ltrim(enum)]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Return the physical dimension of the range. Only works with ranges that
|
|
62
|
+
# represent a sequence. This can be confusing for ranges of floats, since
|
|
63
|
+
# (0.0..42.0) will result in the same size as (0.0...42.0). Won't work with
|
|
64
|
+
# ranges that don't support the minus operator.
|
|
65
|
+
def size
|
|
66
|
+
(sequential? ? one_after_max : last) - first
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns true if this range has nothing. This only happens with ranges such
|
|
70
|
+
# as (0...0)
|
|
71
|
+
def empty?
|
|
72
|
+
last == first && exclude_end?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Uses the given enumerable to left trim this range
|
|
78
|
+
def ltrim(enum)
|
|
79
|
+
first_val = sequential? ? enum.one_after_max : enum.last
|
|
80
|
+
self.class.new first_val, last, exclude_end?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Uses the given enumerable to right trim this range
|
|
84
|
+
def rtrim(enum)
|
|
85
|
+
self.class.new first, enum.first, true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Return true if this range is sequential and its elements respond to succ.
|
|
89
|
+
def sequential?
|
|
90
|
+
first.respond_to?(:succ)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Return true if this range has a higher or equal rhs as the given range
|
|
94
|
+
def higher_or_equal_rhs_as?(enum)
|
|
95
|
+
last > enum.last || (last == enum.last && (!exclude_end? || enum.exclude_end?))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Return true if this range has a lower or equal rhs as the given range
|
|
99
|
+
def lower_or_equal_rhs_as?(enum)
|
|
100
|
+
last < enum.last || (last == enum.last && (exclude_end? || !enum.exclude_end?))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
protected
|
|
104
|
+
|
|
105
|
+
# Return the value that is one after the max value. This allows us to compare
|
|
106
|
+
# ranges with our without exclude_end? set to true.
|
|
107
|
+
def one_after_max
|
|
108
|
+
exclude_end? ? last : last.succ
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def adjacent_before?(enum)
|
|
112
|
+
if sequential?
|
|
113
|
+
one_after_max == enum.first
|
|
114
|
+
else
|
|
115
|
+
last == enum.first && !exclude_end?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'extrarange/range_math'
|
|
2
|
+
|
|
3
|
+
module ExtraRange
|
|
4
|
+
class RangeWithMath < Range
|
|
5
|
+
include ExtraRange::RangeMath
|
|
6
|
+
|
|
7
|
+
def self.from_range(range)
|
|
8
|
+
new range.begin, range.end, range.exclude_end?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_range
|
|
12
|
+
Range.new self.begin, self.end, exclude_end?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
require 'extrarange/range_math'
|
|
2
|
+
require 'extrarange/range_with_math'
|
|
3
|
+
|
|
4
|
+
module ExtraRange
|
|
5
|
+
class SparseRange
|
|
6
|
+
include ExtraRange::RangeMath
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
def initialize(*ranges)
|
|
10
|
+
@ranges = []
|
|
11
|
+
ranges.each { |r| self.add r }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def begin
|
|
15
|
+
@ranges.first().begin
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
alias first begin
|
|
19
|
+
|
|
20
|
+
def end
|
|
21
|
+
@ranges.last().end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
alias last end
|
|
25
|
+
|
|
26
|
+
def exclude_end?
|
|
27
|
+
@ranges.last().exclude_end?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def dup
|
|
31
|
+
self.class.new *to_a
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ==(rset)
|
|
35
|
+
@ranges == rset.instance_variable_get(:@ranges)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add(o)
|
|
39
|
+
case o
|
|
40
|
+
when Range then
|
|
41
|
+
_add(o)
|
|
42
|
+
when self.class then
|
|
43
|
+
o.each_range { |b| add b }
|
|
44
|
+
when Enumerable then
|
|
45
|
+
o.each { |b| add b }
|
|
46
|
+
else
|
|
47
|
+
add(o..o)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
alias << add
|
|
54
|
+
|
|
55
|
+
def delete(o)
|
|
56
|
+
case o
|
|
57
|
+
when Range then
|
|
58
|
+
_delete(o)
|
|
59
|
+
when self.class then
|
|
60
|
+
o.each_range { |s| delete s }
|
|
61
|
+
when Enumerable then
|
|
62
|
+
o.each { |s| delete s }
|
|
63
|
+
else
|
|
64
|
+
delete(o..o)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
return self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def |(enum)
|
|
71
|
+
dup.add(enum)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
alias + |
|
|
75
|
+
alias union |
|
|
76
|
+
|
|
77
|
+
def -(enum)
|
|
78
|
+
dup.delete(enum)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias difference -
|
|
82
|
+
|
|
83
|
+
def &(o)
|
|
84
|
+
ret = self.class.new
|
|
85
|
+
|
|
86
|
+
case o
|
|
87
|
+
when Range
|
|
88
|
+
@ranges.each do |r|
|
|
89
|
+
intersection = r & o
|
|
90
|
+
ret.add intersection if intersection
|
|
91
|
+
end
|
|
92
|
+
ret
|
|
93
|
+
when self.class
|
|
94
|
+
o.each_range do |i|
|
|
95
|
+
intersection = self & i
|
|
96
|
+
ret.add intersection if intersection
|
|
97
|
+
end
|
|
98
|
+
ret
|
|
99
|
+
when Enumerable
|
|
100
|
+
o.each do |i|
|
|
101
|
+
intersection = self & i
|
|
102
|
+
ret.add intersection if intersection
|
|
103
|
+
end
|
|
104
|
+
ret
|
|
105
|
+
else
|
|
106
|
+
self&(o..o)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
alias intersection &
|
|
111
|
+
|
|
112
|
+
# BEWARE! SparseRange is mutable!
|
|
113
|
+
# it would be better to change its behaviour to return new sparseranges or to
|
|
114
|
+
# create a separate ImmutableSparseRange class.
|
|
115
|
+
def hash
|
|
116
|
+
return @ranges.hash
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def max(&block)
|
|
120
|
+
maxes = @ranges.collect {|r| r.max(&block)}
|
|
121
|
+
(maxes.include? nil) ? nil : maxes.max(&block)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def min(&block)
|
|
125
|
+
mins = @ranges.collect {|r| r.min(&block)}
|
|
126
|
+
(mins.include? nil) ? nil : mins.min(&block)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def include?(elem)
|
|
130
|
+
@ranges.any? { |r| r.include? elem }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
alias member? include?
|
|
134
|
+
|
|
135
|
+
# currently unoptimized yet working version.
|
|
136
|
+
# TODO: optimize this method to use the builtin cover? method for ranges if
|
|
137
|
+
# they're available, i.e. we're running on Ruby 1.9.1+
|
|
138
|
+
alias cover? include?
|
|
139
|
+
|
|
140
|
+
def to_a
|
|
141
|
+
@ranges.map { |r| r.to_a }.flatten
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def each
|
|
145
|
+
to_a.each{ |o| yield o }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def size
|
|
149
|
+
@ranges.inject(0) { |sum, n| sum + n.size }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def empty?
|
|
153
|
+
@ranges.empty?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def each_range
|
|
157
|
+
@ranges.map { |r| r.to_range }.each { |o| yield o }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def step(n=1, &block)
|
|
161
|
+
enumerator = to_a.select.with_index.select { |_, index| (index % n) == 0 }.collect { |x, _| x }
|
|
162
|
+
if block_given?
|
|
163
|
+
enumerator.each(&block)
|
|
164
|
+
else
|
|
165
|
+
return enumerator
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
def _add(new_range)
|
|
172
|
+
new_range = ExtraRange::RangeWithMath.from_range new_range
|
|
173
|
+
return if new_range.empty?
|
|
174
|
+
|
|
175
|
+
inserted = false
|
|
176
|
+
|
|
177
|
+
@ranges.each_with_index do |r, i|
|
|
178
|
+
if r.overlap?(new_range) || r.adjacent?(new_range)
|
|
179
|
+
new_range = new_range | r
|
|
180
|
+
@ranges[i] = nil
|
|
181
|
+
elsif r.first > new_range.first
|
|
182
|
+
@ranges.insert(i, new_range)
|
|
183
|
+
inserted = true
|
|
184
|
+
break
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
@ranges << new_range unless inserted
|
|
189
|
+
@ranges.compact!
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def _delete(o)
|
|
193
|
+
@ranges.map! { |r| r - ExtraRange::RangeWithMath.from_range(o) }
|
|
194
|
+
@ranges.flatten!
|
|
195
|
+
@ranges.compact!
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
data/lib/extrarange.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: extrarange
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 23
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
- 0
|
|
10
|
+
version: 1.0.0
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Alan Franzoni
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2010-02-18 00:00:00 Z
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: The SparseRange class lets you easily collect many ranges into one.You can perform logic operations such as union and intersection on ranges with gaps. Can replace a standard range.
|
|
22
|
+
email:
|
|
23
|
+
- username@franzoni.eu
|
|
24
|
+
executables: []
|
|
25
|
+
|
|
26
|
+
extensions: []
|
|
27
|
+
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
|
|
30
|
+
files:
|
|
31
|
+
- lib/extrarange.rb
|
|
32
|
+
- lib/extrarange/range_with_math.rb
|
|
33
|
+
- lib/extrarange/range_math.rb
|
|
34
|
+
- lib/extrarange/sparse_range.rb
|
|
35
|
+
- lib/extrarange/core_ext/range.rb
|
|
36
|
+
- LICENSE
|
|
37
|
+
- README.markdown
|
|
38
|
+
homepage: http://extrarange.franzoni.eu
|
|
39
|
+
licenses: []
|
|
40
|
+
|
|
41
|
+
post_install_message:
|
|
42
|
+
rdoc_options: []
|
|
43
|
+
|
|
44
|
+
require_paths:
|
|
45
|
+
- lib
|
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
|
+
none: false
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
hash: 3
|
|
52
|
+
segments:
|
|
53
|
+
- 0
|
|
54
|
+
version: "0"
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
none: false
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
hash: 17
|
|
61
|
+
segments:
|
|
62
|
+
- 1
|
|
63
|
+
- 3
|
|
64
|
+
- 5
|
|
65
|
+
version: 1.3.5
|
|
66
|
+
requirements: []
|
|
67
|
+
|
|
68
|
+
rubyforge_project:
|
|
69
|
+
rubygems_version: 1.8.24
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 3
|
|
72
|
+
summary: Extended Range functionality
|
|
73
|
+
test_files: []
|
|
74
|
+
|