naturally 1.4.0 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://badge.fury.io/rb/naturally.png)](http://badge.fury.io/rb/naturally) [![Build Status](https://travis-ci.org/
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/naturally.png)](http://badge.fury.io/rb/naturally) [![Build Status](https://travis-ci.org/public-law/naturally.png)](https://travis-ci.org/public-law/naturally)
|
3
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/0ebf4ef97723f2622105/maintainability)](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:
|