naturally 1.4.0 → 2.2.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 +5 -5
- data/.travis.yml +2 -2
- data/Gemfile +1 -1
- data/README.md +10 -6
- data/lib/naturally.rb +32 -7
- data/lib/naturally/segment.rb +3 -2
- data/lib/naturally/version.rb +1 -1
- data/naturally.gemspec +2 -2
- data/spec/naturally_spec.rb +72 -14
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 71953ca154c0b7db095ccc5f8f877afed7142eb0bcf59a5118779ba2c8f79aa1
|
4
|
+
data.tar.gz: 302ae86df325c4e526e1b8158dc669c497b27ae9b5ffe60d32bdcb206285eb46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7f607e695090c2d12a67b1410960312fbc4d9b7d046f1dea91513653fd00da13e01ebf92c1405efdb498a32029baae4733ddd022cee78becf67db7b4988bedd
|
7
|
+
data.tar.gz: 3110d73168c9797628ad7490e1713785921abbce1651d54e9d8e1e2c6e2c65d9e5cbf77ad1c8b693ac49d2d4a7741ffb1198480f17a1f4b1fa9af2ca264d4c5f
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# Naturally
|
2
|
-
[](http://badge.fury.io/rb/naturally) [](http://badge.fury.io/rb/naturally) [](https://travis-ci.org/public-law/naturally)
|
3
|
+
[](https://codeclimate.com/github/dogweather/naturally/maintainability)
|
3
4
|
|
4
|
-
Natural (version number) sorting with support for **legal document numbering**, **college course codes**, and **Unicode**.
|
5
|
-
See Jeff Atwood's [Sorting for Humans: Natural Sort Order](http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html) and the
|
5
|
+
Natural ("version number") sorting with support for **legal document numbering**, **college course codes**, and **Unicode**.
|
6
|
+
See Jeff Atwood's [Sorting for Humans: Natural Sort Order](http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html) and the Public.Law post [Counting to 10 in Californian](https://blog.public.law/2012/08/07/counting-from-1-to-10-in-californian/).
|
6
7
|
|
7
|
-
##Installation
|
8
|
+
## Installation
|
8
9
|
|
9
10
|
```Shell
|
10
11
|
$ gem install naturally
|
@@ -15,8 +16,11 @@ $ gem install naturally
|
|
15
16
|
```Ruby
|
16
17
|
require 'naturally'
|
17
18
|
|
18
|
-
# Sort a simple array of strings
|
19
|
+
# Sort a simple array of strings with legal numbering
|
19
20
|
Naturally.sort(["336", "335a", "335", "335.1"]) # => ["335", "335.1", "335a", "336"]
|
21
|
+
|
22
|
+
# Sort version numbers
|
23
|
+
Naturally.sort(["13.10", "13.04", "10.10", "10.04.4"]) # => ["10.04.4", "10.10", "13.04", "13.10"]
|
20
24
|
```
|
21
25
|
|
22
26
|
Usually the library is used to sort an array of objects:
|
@@ -37,7 +41,7 @@ releases = [
|
|
37
41
|
]
|
38
42
|
|
39
43
|
# Sort by version number
|
40
|
-
sorted = Naturally.
|
44
|
+
sorted = Naturally.sort releases, by: :version
|
41
45
|
|
42
46
|
# Check what we have
|
43
47
|
expect(sorted.map(&:name)).to eq [
|
data/lib/naturally.rb
CHANGED
@@ -3,26 +3,39 @@ require 'naturally/segment'
|
|
3
3
|
# A module which performs natural sorting on a variety of number
|
4
4
|
# formats. (See the specs for examples.)
|
5
5
|
module Naturally
|
6
|
-
# Perform a natural sort.
|
6
|
+
# Perform a natural sort. Supports two syntaxes:
|
7
|
+
#
|
8
|
+
# 1. sort(objects) # Simple arrays
|
9
|
+
# 2. sort(objects, by: some_attribute) # Complex objects
|
7
10
|
#
|
8
11
|
# @param [Array<String>] an_array the list of numbers to sort.
|
12
|
+
# @param [by] (optional) an attribute of the array by which to sort.
|
9
13
|
# @return [Array<String>] the numbers sorted naturally.
|
10
|
-
def self.sort(an_array)
|
11
|
-
|
14
|
+
def self.sort(an_array, by:nil)
|
15
|
+
if by.nil?
|
16
|
+
an_array.sort_by { |x| normalize(x) }
|
17
|
+
else
|
18
|
+
self.sort_by(an_array, by)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
# Sort an array of objects "naturally" by a given attribute.
|
23
|
+
# If block is given, attribute is ignored and each object
|
24
|
+
# is yielded to the block to obtain the sort key.
|
15
25
|
#
|
16
26
|
# @param [Array<Object>] an_array the list of objects to sort.
|
17
27
|
# @param [Symbol] an_attribute the attribute by which to sort.
|
28
|
+
# @param [Block] &block a block that should evaluate to the
|
29
|
+
# sort key for the yielded object
|
18
30
|
# @return [Array<Object>] the objects in natural sort order.
|
19
|
-
def self.sort_by(an_array, an_attribute)
|
31
|
+
def self.sort_by(an_array, an_attribute=nil, &block)
|
32
|
+
return sort_by_block(an_array, &block) if block_given?
|
20
33
|
an_array.sort_by { |obj| normalize(obj.send(an_attribute)) }
|
21
34
|
end
|
22
35
|
|
23
|
-
# Convert the given number an array of {Segment}s.
|
36
|
+
# Convert the given number to an array of {Segment}s.
|
24
37
|
# This enables it to be sorted against other arrays
|
25
|
-
# by the
|
38
|
+
# by the built-in #sort method.
|
26
39
|
#
|
27
40
|
# For example, '1.2a.3' becomes
|
28
41
|
# [Segment<'1'>, Segment<'2a'>, Segment<'3'>]
|
@@ -32,7 +45,19 @@ module Naturally
|
|
32
45
|
# @return [Array<Segment>] an array of Segments which
|
33
46
|
# can be sorted naturally via a standard #sort.
|
34
47
|
def self.normalize(complex_number)
|
35
|
-
tokens = complex_number.to_s.scan(/\p{Word}+/)
|
48
|
+
tokens = complex_number.to_s.gsub(/\_/,'').scan(/\p{Word}+/)
|
36
49
|
tokens.map { |t| Segment.new(t) }
|
37
50
|
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# Sort an array of objects "naturally", yielding each object
|
54
|
+
# to the block to obtain the sort key.
|
55
|
+
#
|
56
|
+
# @param [Array<Object>] an_array the list of objects to sort.
|
57
|
+
# @param [Block] &block a block that should evaluate to the
|
58
|
+
# sort key for the yielded object
|
59
|
+
# @return [Array<Object>] the objects in natural sort order.
|
60
|
+
def self.sort_by_block(an_array, &block)
|
61
|
+
an_array.sort_by { |obj| normalize(yield(obj)) }
|
62
|
+
end
|
38
63
|
end
|
data/lib/naturally/segment.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Naturally
|
2
2
|
# An entity which can be compared to other like elements for
|
3
3
|
# sorting. It's an object representing
|
4
|
-
# a value which implements the {Comparable} interface
|
4
|
+
# a value which implements the {Comparable} interface which can
|
5
|
+
# convert itself to an array.
|
5
6
|
class Segment
|
6
7
|
include Comparable
|
7
8
|
|
@@ -25,7 +26,7 @@ module Naturally
|
|
25
26
|
# @example a college course code
|
26
27
|
# Segment.new('MATH101').to_array #=> [:str, "MATH", 101]
|
27
28
|
#
|
28
|
-
# @example Section 633a of the
|
29
|
+
# @example Section 633a of the U.S. Age Discrimination in Employment Act
|
29
30
|
# Segment.new('633a').to_array #=> [:int, 633, "a"]
|
30
31
|
def to_array
|
31
32
|
# TODO: Refactor, probably via polymorphism
|
data/lib/naturally/version.rb
CHANGED
data/naturally.gemspec
CHANGED
@@ -7,11 +7,11 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.version = Naturally::VERSION
|
8
8
|
gem.license = 'MIT'
|
9
9
|
gem.authors = ["Robb Shecter"]
|
10
|
-
gem.email = ["robb@
|
10
|
+
gem.email = ["robb@public.law"]
|
11
11
|
gem.summary = %q{Sorts numbers according to the way people are used to seeing them.}
|
12
12
|
gem.description = %q{Natural Sorting with support for legal numbering, course numbers, and other number/letter mixes.}
|
13
13
|
gem.homepage = "http://github.com/dogweather/naturally"
|
14
|
-
gem.required_ruby_version = '>= 2.
|
14
|
+
gem.required_ruby_version = '>= 2.0'
|
15
15
|
|
16
16
|
gem.files = `git ls-files`.split($/)
|
17
17
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/spec/naturally_spec.rb
CHANGED
@@ -1,92 +1,128 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
require 'naturally'
|
2
3
|
|
3
4
|
describe Naturally do
|
4
|
-
|
5
|
+
# Just a helper for these tests
|
6
|
+
def it_sorts(opts = {})
|
7
|
+
this = opts[:this]
|
8
|
+
to_this = opts[:to_this]
|
5
9
|
actual = Naturally.sort(this)
|
6
10
|
expect(actual).to eq(to_this)
|
7
11
|
end
|
8
12
|
|
13
|
+
|
9
14
|
describe '#sort' do
|
15
|
+
it 'supports a nicer by: syntax' do
|
16
|
+
UbuntuVersion ||= Struct.new(:name, :version)
|
17
|
+
releases = [
|
18
|
+
UbuntuVersion.new('Saucy Salamander', '13.10'),
|
19
|
+
UbuntuVersion.new('Raring Ringtail', '13.04'),
|
20
|
+
UbuntuVersion.new('Precise Pangolin', '12.04.4'),
|
21
|
+
UbuntuVersion.new('Maverick Meerkat', '10.10'),
|
22
|
+
UbuntuVersion.new('Quantal Quetzal', '12.10'),
|
23
|
+
UbuntuVersion.new('Lucid Lynx', '10.04.4')
|
24
|
+
]
|
25
|
+
|
26
|
+
actual = Naturally.sort releases, by: :version
|
27
|
+
|
28
|
+
expect(actual.map(&:name)).to eq [
|
29
|
+
'Lucid Lynx',
|
30
|
+
'Maverick Meerkat',
|
31
|
+
'Precise Pangolin',
|
32
|
+
'Quantal Quetzal',
|
33
|
+
'Raring Ringtail',
|
34
|
+
'Saucy Salamander'
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
10
39
|
it 'sorts an array of strings nicely as if they were legal numbers' do
|
11
40
|
it_sorts(
|
12
|
-
this:
|
41
|
+
this: %w(676 676.1 676.11 676.12 676.2 676.3 676.9 676.10),
|
13
42
|
to_this: %w(676 676.1 676.2 676.3 676.9 676.10 676.11 676.12)
|
14
43
|
)
|
15
44
|
end
|
16
45
|
|
17
46
|
it 'sorts a more complex list of strings' do
|
18
47
|
it_sorts(
|
19
|
-
this:
|
48
|
+
this: %w(350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.45 354.5),
|
20
49
|
to_this: %w(350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.5 354.45)
|
21
50
|
)
|
22
51
|
end
|
23
52
|
|
24
53
|
it 'sorts when numbers have letters in them' do
|
25
54
|
it_sorts(
|
26
|
-
this:
|
55
|
+
this: %w(335 335.1 336a 336 337 337a 337.1 337.15 337.2),
|
27
56
|
to_this: %w(335 335.1 336 336a 337 337.1 337.2 337.15 337a)
|
28
57
|
)
|
29
58
|
end
|
30
59
|
|
31
60
|
it 'sorts when numbers have unicode letters in them' do
|
32
61
|
it_sorts(
|
33
|
-
this:
|
62
|
+
this: %w(335 335.1 336a 336 337 337я 337.1 337.15 337.2),
|
34
63
|
to_this: %w(335 335.1 336 336a 337 337.1 337.2 337.15 337я)
|
35
64
|
)
|
36
65
|
end
|
37
66
|
|
38
67
|
it 'sorts when letters have numbers in them' do
|
39
68
|
it_sorts(
|
40
|
-
this:
|
69
|
+
this: %w(PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, PBLI, SBP1, SBP3),
|
41
70
|
to_this: %w(PBLI, PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, SBP1, SBP3)
|
42
71
|
)
|
43
72
|
end
|
44
73
|
|
45
74
|
it 'sorts when letters have numbers and unicode characters in them' do
|
46
75
|
it_sorts(
|
47
|
-
this:
|
76
|
+
this: %w(АБ4, АБ2, АБ10, АБ12, АБ1, АБ3, АД8, АД5, АЩФ12, АЩФ8, ЫВА1),
|
48
77
|
to_this: %w(АБ1, АБ2, АБ3, АБ4, АБ10, АБ12, АД5, АД8, АЩФ8, АЩФ12, ЫВА1)
|
49
78
|
)
|
50
79
|
end
|
51
80
|
|
52
81
|
it 'sorts double digits with letters correctly' do
|
53
82
|
it_sorts(
|
54
|
-
this:
|
83
|
+
this: %w(12a 12b 12c 13a 13b 2 3 4 5 10 11 12),
|
55
84
|
to_this: %w(2 3 4 5 10 11 12 12a 12b 12c 13a 13b)
|
56
85
|
)
|
57
86
|
end
|
58
87
|
|
59
88
|
it 'sorts double digits with unicode letters correctly' do
|
60
89
|
it_sorts(
|
61
|
-
this:
|
90
|
+
this: %w(12а 12б 12в 13а 13б 2 3 4 5 10 11 12),
|
62
91
|
to_this: %w(2 3 4 5 10 11 12 12а 12б 12в 13а 13б)
|
63
92
|
)
|
64
93
|
end
|
65
94
|
|
95
|
+
it 'sorts strings suffixed with underscore and numbers correctly' do
|
96
|
+
it_sorts(
|
97
|
+
this: %w(item_10 item_11 item_1 item_7 item_5 item_3 item_4 item_6 item_2),
|
98
|
+
to_this: %w(item_1 item_2 item_3 item_4 item_5 item_6 item_7 item_10 item_11)
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
66
102
|
it 'sorts letters with digits correctly' do
|
67
103
|
it_sorts(
|
68
|
-
this:
|
104
|
+
this: %w(1 a 2 b 3 c),
|
69
105
|
to_this: %w(1 2 3 a b c)
|
70
106
|
)
|
71
107
|
end
|
72
108
|
|
73
109
|
it 'sorts complex numbers with digits correctly' do
|
74
110
|
it_sorts(
|
75
|
-
this:
|
111
|
+
this: %w(1 a 2 b 3 c 1.1 a.1 1.2 a.2 1.3 a.3 b.1 ),
|
76
112
|
to_this: %w(1 1.1 1.2 1.3 2 3 a a.1 a.2 a.3 b b.1 c)
|
77
113
|
)
|
78
114
|
end
|
79
115
|
|
80
116
|
it 'sorts complex mixes of numbers and digits correctly' do
|
81
117
|
it_sorts(
|
82
|
-
this:
|
118
|
+
this: %w( 1.a.1 1.1 ),
|
83
119
|
to_this: %w( 1.1 1.a.1 )
|
84
120
|
)
|
85
121
|
end
|
86
122
|
|
87
123
|
it 'sorts complex mixes of numbers and digits correctly' do
|
88
124
|
it_sorts(
|
89
|
-
this:
|
125
|
+
this: %w( 1a1 1aa aaa ),
|
90
126
|
to_this: %w( 1aa 1a1 aaa )
|
91
127
|
)
|
92
128
|
end
|
@@ -94,7 +130,7 @@ describe Naturally do
|
|
94
130
|
|
95
131
|
describe '#sort_naturally_by' do
|
96
132
|
it 'sorts by an attribute' do
|
97
|
-
UbuntuVersion
|
133
|
+
UbuntuVersion ||= Struct.new(:name, :version)
|
98
134
|
releases = [
|
99
135
|
UbuntuVersion.new('Saucy Salamander', '13.10'),
|
100
136
|
UbuntuVersion.new('Raring Ringtail', '13.04'),
|
@@ -150,4 +186,26 @@ describe Naturally do
|
|
150
186
|
]
|
151
187
|
end
|
152
188
|
end
|
189
|
+
|
190
|
+
describe '#sort_naturally_by_block' do
|
191
|
+
it 'sorts using a block' do
|
192
|
+
releases = [
|
193
|
+
{:name => 'Saucy Salamander', :version => '13.10'},
|
194
|
+
{:name => 'Raring Ringtail', :version => '13.04'},
|
195
|
+
{:name => 'Precise Pangolin', :version => '12.04.4'},
|
196
|
+
{:name => 'Maverick Meerkat', :version => '10.10'},
|
197
|
+
{:name => 'Quantal Quetzal', :version => '12.10'},
|
198
|
+
{:name => 'Lucid Lynx', :version => '10.04.4'}
|
199
|
+
]
|
200
|
+
actual = Naturally.sort_by(releases){|r| r[:version]}
|
201
|
+
expect(actual.map{|r| r[:name]}).to eq [
|
202
|
+
'Lucid Lynx',
|
203
|
+
'Maverick Meerkat',
|
204
|
+
'Precise Pangolin',
|
205
|
+
'Quantal Quetzal',
|
206
|
+
'Raring Ringtail',
|
207
|
+
'Saucy Salamander'
|
208
|
+
]
|
209
|
+
end
|
210
|
+
end
|
153
211
|
end
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: naturally
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robb Shecter
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Natural Sorting with support for legal numbering, course numbers, and
|
14
14
|
other number/letter mixes.
|
15
15
|
email:
|
16
|
-
- robb@
|
16
|
+
- robb@public.law
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
@@ -33,7 +33,7 @@ homepage: http://github.com/dogweather/naturally
|
|
33
33
|
licenses:
|
34
34
|
- MIT
|
35
35
|
metadata: {}
|
36
|
-
post_install_message:
|
36
|
+
post_install_message:
|
37
37
|
rdoc_options: []
|
38
38
|
require_paths:
|
39
39
|
- lib
|
@@ -41,16 +41,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
41
|
requirements:
|
42
42
|
- - ">="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: '2.
|
44
|
+
version: '2.0'
|
45
45
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: '0'
|
50
50
|
requirements: []
|
51
|
-
|
52
|
-
|
53
|
-
signing_key:
|
51
|
+
rubygems_version: 3.2.5
|
52
|
+
signing_key:
|
54
53
|
specification_version: 4
|
55
54
|
summary: Sorts numbers according to the way people are used to seeing them.
|
56
55
|
test_files:
|