fractional_indexing 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +21 -0
- data/LICENSE +121 -0
- data/README.md +57 -0
- data/Rakefile +12 -0
- data/lib/fractional_indexing/version.rb +5 -0
- data/lib/fractional_indexing.rb +247 -0
- data/sig/fractional_indexing.rbs +4 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1ec000613bb56c79ff44198f1fc0e45be734922f89c22b5ce9fb5ec5ec0ae2df
|
4
|
+
data.tar.gz: ead7373a2c426ff75274a5b0fb87cf3f5e8332075ae4e2c70fa071d1795c5ea8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8914f607fd7028a06c1082901baa30aba1330f354643af03cf1a2b99985cd239378ba96aebf17c8a241bf2dbab2550fb9111086047e45456998a07392e1494af
|
7
|
+
data.tar.gz: 2b2996841ca207d20a26670d30f4dffeef721334ab6ceb2a5143abd543f400ee43b8289867c5057c8827e4f5ddf7d934003316ca13cccad133b7c6b14c5a5cff
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fractional_indexing (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.18.0)
|
10
|
+
rake (13.0.6)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
x86_64-darwin-20
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
fractional_indexing!
|
17
|
+
minitest (~> 5.0)
|
18
|
+
rake (~> 13.0)
|
19
|
+
|
20
|
+
BUNDLED WITH
|
21
|
+
2.4.6
|
data/LICENSE
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
Creative Commons Legal Code
|
2
|
+
|
3
|
+
CC0 1.0 Universal
|
4
|
+
|
5
|
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
6
|
+
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
7
|
+
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
8
|
+
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
9
|
+
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
10
|
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
11
|
+
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
12
|
+
HEREUNDER.
|
13
|
+
|
14
|
+
Statement of Purpose
|
15
|
+
|
16
|
+
The laws of most jurisdictions throughout the world automatically confer
|
17
|
+
exclusive Copyright and Related Rights (defined below) upon the creator
|
18
|
+
and subsequent owner(s) (each and all, an "owner") of an original work of
|
19
|
+
authorship and/or a database (each, a "Work").
|
20
|
+
|
21
|
+
Certain owners wish to permanently relinquish those rights to a Work for
|
22
|
+
the purpose of contributing to a commons of creative, cultural and
|
23
|
+
scientific works ("Commons") that the public can reliably and without fear
|
24
|
+
of later claims of infringement build upon, modify, incorporate in other
|
25
|
+
works, reuse and redistribute as freely as possible in any form whatsoever
|
26
|
+
and for any purposes, including without limitation commercial purposes.
|
27
|
+
These owners may contribute to the Commons to promote the ideal of a free
|
28
|
+
culture and the further production of creative, cultural and scientific
|
29
|
+
works, or to gain reputation or greater distribution for their Work in
|
30
|
+
part through the use and efforts of others.
|
31
|
+
|
32
|
+
For these and/or other purposes and motivations, and without any
|
33
|
+
expectation of additional consideration or compensation, the person
|
34
|
+
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
35
|
+
is an owner of Copyright and Related Rights in the Work, voluntarily
|
36
|
+
elects to apply CC0 to the Work and publicly distribute the Work under its
|
37
|
+
terms, with knowledge of his or her Copyright and Related Rights in the
|
38
|
+
Work and the meaning and intended legal effect of CC0 on those rights.
|
39
|
+
|
40
|
+
1. Copyright and Related Rights. A Work made available under CC0 may be
|
41
|
+
protected by copyright and related or neighboring rights ("Copyright and
|
42
|
+
Related Rights"). Copyright and Related Rights include, but are not
|
43
|
+
limited to, the following:
|
44
|
+
|
45
|
+
i. the right to reproduce, adapt, distribute, perform, display,
|
46
|
+
communicate, and translate a Work;
|
47
|
+
ii. moral rights retained by the original author(s) and/or performer(s);
|
48
|
+
iii. publicity and privacy rights pertaining to a person's image or
|
49
|
+
likeness depicted in a Work;
|
50
|
+
iv. rights protecting against unfair competition in regards to a Work,
|
51
|
+
subject to the limitations in paragraph 4(a), below;
|
52
|
+
v. rights protecting the extraction, dissemination, use and reuse of data
|
53
|
+
in a Work;
|
54
|
+
vi. database rights (such as those arising under Directive 96/9/EC of the
|
55
|
+
European Parliament and of the Council of 11 March 1996 on the legal
|
56
|
+
protection of databases, and under any national implementation
|
57
|
+
thereof, including any amended or successor version of such
|
58
|
+
directive); and
|
59
|
+
vii. other similar, equivalent or corresponding rights throughout the
|
60
|
+
world based on applicable law or treaty, and any national
|
61
|
+
implementations thereof.
|
62
|
+
|
63
|
+
2. Waiver. To the greatest extent permitted by, but not in contravention
|
64
|
+
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
65
|
+
irrevocably and unconditionally waives, abandons, and surrenders all of
|
66
|
+
Affirmer's Copyright and Related Rights and associated claims and causes
|
67
|
+
of action, whether now known or unknown (including existing as well as
|
68
|
+
future claims and causes of action), in the Work (i) in all territories
|
69
|
+
worldwide, (ii) for the maximum duration provided by applicable law or
|
70
|
+
treaty (including future time extensions), (iii) in any current or future
|
71
|
+
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
72
|
+
including without limitation commercial, advertising or promotional
|
73
|
+
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
74
|
+
member of the public at large and to the detriment of Affirmer's heirs and
|
75
|
+
successors, fully intending that such Waiver shall not be subject to
|
76
|
+
revocation, rescission, cancellation, termination, or any other legal or
|
77
|
+
equitable action to disrupt the quiet enjoyment of the Work by the public
|
78
|
+
as contemplated by Affirmer's express Statement of Purpose.
|
79
|
+
|
80
|
+
3. Public License Fallback. Should any part of the Waiver for any reason
|
81
|
+
be judged legally invalid or ineffective under applicable law, then the
|
82
|
+
Waiver shall be preserved to the maximum extent permitted taking into
|
83
|
+
account Affirmer's express Statement of Purpose. In addition, to the
|
84
|
+
extent the Waiver is so judged Affirmer hereby grants to each affected
|
85
|
+
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
86
|
+
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
87
|
+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
88
|
+
maximum duration provided by applicable law or treaty (including future
|
89
|
+
time extensions), (iii) in any current or future medium and for any number
|
90
|
+
of copies, and (iv) for any purpose whatsoever, including without
|
91
|
+
limitation commercial, advertising or promotional purposes (the
|
92
|
+
"License"). The License shall be deemed effective as of the date CC0 was
|
93
|
+
applied by Affirmer to the Work. Should any part of the License for any
|
94
|
+
reason be judged legally invalid or ineffective under applicable law, such
|
95
|
+
partial invalidity or ineffectiveness shall not invalidate the remainder
|
96
|
+
of the License, and in such case Affirmer hereby affirms that he or she
|
97
|
+
will not (i) exercise any of his or her remaining Copyright and Related
|
98
|
+
Rights in the Work or (ii) assert any associated claims and causes of
|
99
|
+
action with respect to the Work, in either case contrary to Affirmer's
|
100
|
+
express Statement of Purpose.
|
101
|
+
|
102
|
+
4. Limitations and Disclaimers.
|
103
|
+
|
104
|
+
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
105
|
+
surrendered, licensed or otherwise affected by this document.
|
106
|
+
b. Affirmer offers the Work as-is and makes no representations or
|
107
|
+
warranties of any kind concerning the Work, express, implied,
|
108
|
+
statutory or otherwise, including without limitation warranties of
|
109
|
+
title, merchantability, fitness for a particular purpose, non
|
110
|
+
infringement, or the absence of latent or other defects, accuracy, or
|
111
|
+
the present or absence of errors, whether or not discoverable, all to
|
112
|
+
the greatest extent permissible under applicable law.
|
113
|
+
c. Affirmer disclaims responsibility for clearing rights of other persons
|
114
|
+
that may apply to the Work or any use thereof, including without
|
115
|
+
limitation any person's Copyright and Related Rights in the Work.
|
116
|
+
Further, Affirmer disclaims responsibility for obtaining any necessary
|
117
|
+
consents, permissions or other rights required for any use of the
|
118
|
+
Work.
|
119
|
+
d. Affirmer understands and acknowledges that Creative Commons is not a
|
120
|
+
party to this document and has no duty or obligation with respect to
|
121
|
+
this CC0 or use of the Work.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Fractional Indexing
|
2
|
+
|
3
|
+
This is based on [Implementing Fractional Indexing
|
4
|
+
](https://observablehq.com/@dgreensp/implementing-fractional-indexing) by [David Greenspan
|
5
|
+
](https://github.com/dgreensp).
|
6
|
+
|
7
|
+
Fractional indexing is a technique to create an ordering that can be used for [Realtime Editing of Ordered Sequences](https://www.figma.com/blog/realtime-editing-of-ordered-sequences/).
|
8
|
+
|
9
|
+
This implementation includes variable-length integers, and the prepend/append optimization described in David's article.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Install the gem and add to the application's Gemfile by executing:
|
14
|
+
|
15
|
+
$ bundle add fractional_indexing
|
16
|
+
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
18
|
+
|
19
|
+
$ gem install fractional_indexing
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require "fractional_indexing"
|
25
|
+
|
26
|
+
first = FractionalIndexing.generate_key_between(None, None)
|
27
|
+
raise unless first == 'a0'
|
28
|
+
|
29
|
+
# Insert after 1st
|
30
|
+
second = FractionalIndexing.generate_key_between(first, None)
|
31
|
+
raise unless second == 'a1'
|
32
|
+
|
33
|
+
# Insert after 2nd
|
34
|
+
third = FractionalIndexing.generate_key_between(second, None)
|
35
|
+
raise unless third == 'a2'
|
36
|
+
|
37
|
+
# Insert before 1st
|
38
|
+
zeroth = FractionalIndexing.generate_key_between(None, first)
|
39
|
+
raise unless zeroth == 'Zz'
|
40
|
+
|
41
|
+
# Insert in between 2nd and 3rd. Midpoint
|
42
|
+
second_and_half = FractionalIndexing.generate_key_between(second, third)
|
43
|
+
raise unless second_and_half == 'a1V'
|
44
|
+
```
|
45
|
+
|
46
|
+
## Other Languages
|
47
|
+
|
48
|
+
This is a Ruby port of the [Python port](https://github.com/httpie/fractional-indexing-python) of the [original JavaScript implementation](https://github.com/rocicorp/fractional-indexing).
|
49
|
+
That means that this implementation is byte-for-byte compatible with:
|
50
|
+
|
51
|
+
| Language | Repo |
|
52
|
+
| ---------- | ---------------------------------------------------- |
|
53
|
+
| JavaScript | https://github.com/rocicorp/fractional-indexing |
|
54
|
+
| Go | https://github.com/rocicorp/fracdex |
|
55
|
+
| Python | https://github.com/httpie/fractional-indexing-python |
|
56
|
+
|
57
|
+
The code was ported entirely by ChatGPT.
|
data/Rakefile
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Provides functions for generating ordering strings
|
4
|
+
# <https://observablehq.com/@dgreensp/implementing-fractional-indexing>
|
5
|
+
# <https://github.com/aalin/fractional_indexing.rb>
|
6
|
+
# Ported from Python using ChatGPT:
|
7
|
+
# <https://github.com/httpie/fractional-indexing-python>
|
8
|
+
|
9
|
+
require_relative "fractional_indexing/version"
|
10
|
+
require "bigdecimal"
|
11
|
+
|
12
|
+
module FractionalIndexing
|
13
|
+
class Error < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
BASE_62_DIGITS =
|
17
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
18
|
+
SMALLEST_INTEGER = "A00000000000000000000000000"
|
19
|
+
INTEGER_ZERO = "a0"
|
20
|
+
|
21
|
+
def self.midpoint(a, b, digits)
|
22
|
+
# `a` may be empty string, `b` is null or non-empty string.
|
23
|
+
# `a < b` lexicographically if `b` is non-null.
|
24
|
+
# no trailing zeros allowed.
|
25
|
+
# digits is a string such as '0123456789' for base 10. Digits must be in
|
26
|
+
# ascending character code order!
|
27
|
+
raise Error, "#{a} >= #{b}" if b && a >= b
|
28
|
+
raise Error, "trailing zero" if (a[-1] == "0") || (b && (b[-1] == "0"))
|
29
|
+
if b
|
30
|
+
# remove longest common prefix. pad `a` with 0s as we
|
31
|
+
# go. note that we don't need to pad `b`, because it can't
|
32
|
+
# end before `a` while traversing the common prefix.
|
33
|
+
n = 0
|
34
|
+
a = a.ljust(b.length, "0")
|
35
|
+
for i in 0..(b.length - 1)
|
36
|
+
if a[i] == b[i]
|
37
|
+
n += 1
|
38
|
+
else
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
return b[0..(n - 1)] + midpoint(a[n..-1], b[n..-1], digits) if n > 0
|
44
|
+
end
|
45
|
+
|
46
|
+
# first digits (or lack of digit) are different
|
47
|
+
digit_a = a.empty? ? 0 : digits.index(a[0])
|
48
|
+
digit_b = b.nil? ? digits.length : digits.index(b[0])
|
49
|
+
if digit_b - digit_a > 1
|
50
|
+
min_digit = (0.5 * (digit_a + digit_b)).round
|
51
|
+
return digits[min_digit]
|
52
|
+
else
|
53
|
+
if b && b.length > 1
|
54
|
+
return b[0]
|
55
|
+
else
|
56
|
+
# `b` is null or has length 1 (a single digit).
|
57
|
+
# the first digit of `a` is the previous digit to `b`,
|
58
|
+
# or 9 if `b` is null.
|
59
|
+
# given, for example, midpoint('49', '5'), return
|
60
|
+
# '4' + midpoint('9', null), which will become
|
61
|
+
# '4' + '9' + midpoint('', null), which is '495'
|
62
|
+
digit_a = a.empty? ? 0 : digits.index(a[0])
|
63
|
+
return digits[digit_a] + midpoint(a[1..-1], nil, digits)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.validate_integer(i)
|
69
|
+
unless i.length == get_integer_length(i[0])
|
70
|
+
raise Error, "invalid integer part of order key: #{i}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.get_integer_length(head)
|
75
|
+
if ('a'..'z').cover?(head)
|
76
|
+
return head.ord - 'a'.ord + 2
|
77
|
+
elsif ('A'..'Z').cover?(head)
|
78
|
+
return 'Z'.ord - head.ord + 2
|
79
|
+
end
|
80
|
+
raise Error, "invalid order key head: " + head
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.get_integer_part(key)
|
84
|
+
integer_part_length = get_integer_length(key[0])
|
85
|
+
raise Error, "invalid order key: #{key}" if integer_part_length > key.size
|
86
|
+
|
87
|
+
key[0...integer_part_length]
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.validate_order_key(key)
|
91
|
+
raise Error, "invalid order key: #{key}" if key == SMALLEST_INTEGER
|
92
|
+
|
93
|
+
# get_integer_part() will throw if the first character is bad,
|
94
|
+
# or the key is too short. we'd call it to check these things
|
95
|
+
# even if we didn't need the result
|
96
|
+
i = get_integer_part(key)
|
97
|
+
f = key[i.size..]
|
98
|
+
raise Error, "invalid order key: #{key}" if f[-1] == "0"
|
99
|
+
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.increment_integer(x, digits)
|
104
|
+
validate_integer(x)
|
105
|
+
head, *digs = x.chars
|
106
|
+
carry = true
|
107
|
+
digs.reverse_each do |d|
|
108
|
+
i = digits.index(d) + 1
|
109
|
+
if i == digits.size
|
110
|
+
d.replace('0')
|
111
|
+
else
|
112
|
+
d.replace(digits[i])
|
113
|
+
carry = false
|
114
|
+
break
|
115
|
+
end
|
116
|
+
end
|
117
|
+
if carry
|
118
|
+
if head == 'Z'
|
119
|
+
return 'a0'
|
120
|
+
elsif head == 'z'
|
121
|
+
return nil
|
122
|
+
end
|
123
|
+
h = (head.ord + 1).chr
|
124
|
+
if h > 'a'
|
125
|
+
digs.push('0')
|
126
|
+
else
|
127
|
+
digs.pop
|
128
|
+
end
|
129
|
+
return h + digs.join
|
130
|
+
else
|
131
|
+
return head + digs.join
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.decrement_integer(x, digits)
|
136
|
+
validate_integer(x)
|
137
|
+
head, *digs = x.chars
|
138
|
+
borrow = true
|
139
|
+
digs.reverse_each do |d|
|
140
|
+
i = digits.index(d) - 1
|
141
|
+
if i == -1
|
142
|
+
d.replace(digits[-1])
|
143
|
+
else
|
144
|
+
d.replace(digits[i])
|
145
|
+
borrow = false
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
149
|
+
if borrow
|
150
|
+
if head == 'a'
|
151
|
+
return 'Z' + digits[-1]
|
152
|
+
elsif head == 'A'
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
h = (head.ord - 1).chr
|
156
|
+
if h < 'Z'
|
157
|
+
digs.push(digits[-1])
|
158
|
+
else
|
159
|
+
digs.pop
|
160
|
+
end
|
161
|
+
return h + digs.join
|
162
|
+
else
|
163
|
+
return head + digs.join
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.generate_key_between(a, b, digits = BASE_62_DIGITS)
|
168
|
+
validate_order_key(a) if a
|
169
|
+
validate_order_key(b) if b
|
170
|
+
raise "a >= b" if a && b && a >= b
|
171
|
+
|
172
|
+
if a == nil
|
173
|
+
return INTEGER_ZERO if b == nil
|
174
|
+
ib = get_integer_part(b)
|
175
|
+
fb = b[ib.length..-1]
|
176
|
+
return ib + midpoint("", fb, digits) if ib == SMALLEST_INTEGER
|
177
|
+
return ib if ib < b
|
178
|
+
res = decrement_integer(ib, digits)
|
179
|
+
raise "cannot decrement any more" if res == nil
|
180
|
+
return res
|
181
|
+
end
|
182
|
+
|
183
|
+
if b == nil
|
184
|
+
ia = get_integer_part(a)
|
185
|
+
fa = a[ia.length..-1]
|
186
|
+
i = increment_integer(ia, digits)
|
187
|
+
return ia + midpoint(fa, nil, digits) if i == nil
|
188
|
+
return i
|
189
|
+
end
|
190
|
+
|
191
|
+
ia = get_integer_part(a)
|
192
|
+
fa = a[ia.length..-1]
|
193
|
+
ib = get_integer_part(b)
|
194
|
+
fb = b[ib.length..-1]
|
195
|
+
return ia + midpoint(fa, fb, digits) if ia == ib
|
196
|
+
i = increment_integer(ia, digits)
|
197
|
+
raise "cannot increment any more" if i == nil
|
198
|
+
|
199
|
+
return i if i < b
|
200
|
+
|
201
|
+
return ia + midpoint(fa, nil, digits)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns an array of n distinct keys in sorted order.
|
205
|
+
# If a and b are both nil, returns [a0, a1, ...]
|
206
|
+
# If one or the other is nil, returns consecutive "integer"
|
207
|
+
# keys. Otherwise, returns relatively short keys between.
|
208
|
+
def self.generate_n_keys_between(a, b, n, digits = BASE_62_DIGITS)
|
209
|
+
return [] if n.zero?
|
210
|
+
|
211
|
+
return [generate_key_between(a, b, digits)] if n == 1
|
212
|
+
|
213
|
+
unless b
|
214
|
+
c = generate_key_between(a, b, digits)
|
215
|
+
result = [c]
|
216
|
+
(n - 1).times do
|
217
|
+
c = generate_key_between(c, b, digits)
|
218
|
+
result << c
|
219
|
+
end
|
220
|
+
return result
|
221
|
+
end
|
222
|
+
|
223
|
+
unless a
|
224
|
+
c = generate_key_between(a, b, digits)
|
225
|
+
result = [c]
|
226
|
+
(n - 1).times do
|
227
|
+
c = generate_key_between(a, c, digits)
|
228
|
+
result << c
|
229
|
+
end
|
230
|
+
return result.reverse
|
231
|
+
end
|
232
|
+
|
233
|
+
mid = n / 2
|
234
|
+
c = generate_key_between(a, b, digits)
|
235
|
+
[
|
236
|
+
*generate_n_keys_between(a, c, mid.floor, digits),
|
237
|
+
c,
|
238
|
+
*generate_n_keys_between(c, b, n - mid.floor - 1, digits)
|
239
|
+
]
|
240
|
+
end
|
241
|
+
|
242
|
+
# Rounds a float to an integer using decimal.Decimal.quantize with
|
243
|
+
# decimal.ROUND_HALF_UP rounding method.
|
244
|
+
def self.round_half_up(n)
|
245
|
+
(n.to_d.round(0, half: :up)).to_i
|
246
|
+
end
|
247
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fractional_indexing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andreas Alin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- andreas.alin@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- Gemfile
|
21
|
+
- Gemfile.lock
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- lib/fractional_indexing.rb
|
26
|
+
- lib/fractional_indexing/version.rb
|
27
|
+
- sig/fractional_indexing.rbs
|
28
|
+
homepage: https://github.com/aalin/fractional_indexing.rb
|
29
|
+
licenses:
|
30
|
+
- " CC0-1.0"
|
31
|
+
metadata:
|
32
|
+
homepage_uri: https://github.com/aalin/fractional_indexing.rb
|
33
|
+
source_code_uri: https://github.com/aalin/fractional_indexing.rb
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.6.0
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubygems_version: 3.4.6
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: Fractional Indexing in Ruby
|
53
|
+
test_files: []
|