naturally 1.3.1 → 1.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a36e33ade747d0c59b3788ba0bb22c1f6defb184
4
- data.tar.gz: a81707fb161b155dc47bed22462cf8a9ed734fe5
3
+ metadata.gz: 11b862eddf78c3c623300fc93d643a1b0081f815
4
+ data.tar.gz: 576b16fec163a92e756c98c1df4f3b1af8ebab31
5
5
  SHA512:
6
- metadata.gz: 7536fef931a0c593bc9918bff1e6407922a18cc339f8e0d96edf8e97db941232809238749fe6fd65f9fb9264cd8fa89d16d01cef8e64afe30301def32f6726f3
7
- data.tar.gz: 00f5d433456e0516da7229ab2c64b9c5bbb448aa556e58252115a564d431078dfe19ca8fa1d28b5fd6cfaeee89e3e600db59f3c27d67f9f94e58fb5995a45888
6
+ metadata.gz: 08f910f8cd088cb92969c6f1d257cc53a2178e54894639bac1b361651c986f312e90c6cd42eacb202b89fc664dbf1108d76b474578a40e0a35462bb6ab4f23fe
7
+ data.tar.gz: 1f389ebe3746dc8d2f5c80cd35eb0dcdf4e408fd5f9116945e3355bdf2e037ecff46a60ca458bb3e02df5a701b3847460c184d4ba29012248aae2a09b32e26c9
data/.travis.yml CHANGED
@@ -1,6 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.9.3"
4
- - "2.0.0"
5
- - "2.1.0"
6
- - "2.1.1"
3
+ - 2.1
4
+ - 2.2
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  group :test do
4
- gem 'rspec', '~> 2.0'
4
+ gem 'rspec', '~> 3.0'
5
5
  gem 'rake', '~> 10.0'
6
6
  end
7
7
 
data/README.md CHANGED
@@ -1,29 +1,8 @@
1
1
  # Naturally
2
2
  [![Gem Version](https://badge.fury.io/rb/naturally.png)](http://badge.fury.io/rb/naturally) [![Build Status](https://travis-ci.org/dogweather/naturally.png)](https://travis-ci.org/dogweather/naturally) [![Code Climate](https://codeclimate.com/github/dogweather/naturally.png)](https://codeclimate.com/github/dogweather/naturally)
3
3
 
4
- Natural (version number) sorting with added support for **legal document numbering** 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 Weblaws.org post [Counting to 10 in Californian](http://www.weblaws.org/blog/2012/08/counting-from-1-to-10-in-californian/).
6
-
7
- The core of the search is [from here](https://github.com/ahoward/version_sorter). It's since been extended to handle the particular types of numbers that come up in statutes, such
8
- as *335.1, 336, 336a*, etc.
9
-
10
- `Naturally` will also sort "numbers" in college course code format such as
11
- *MATH101, MATH102, ...*. See the specs for examples.
12
-
13
-
14
- ## Installation
15
-
16
- Add this line to your application's Gemfile:
17
-
18
- gem 'naturally'
19
-
20
- And then execute:
21
-
22
- $ bundle
23
-
24
- Or install it outside of bundler with:
25
-
26
- $ gem install naturally
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 WebLaws.org post [Counting to 10 in Californian](http://www.weblaws.org/blog/2012/08/counting-from-1-to-10-in-californian/).
27
6
 
28
7
 
29
8
  ## Usage
@@ -68,6 +47,11 @@ expect(sorted.map(&:name)).to eq [
68
47
 
69
48
  See [the spec for more examples](https://github.com/dogweather/naturally/blob/master/spec/naturally_spec.rb) of what Naturally can sort.
70
49
 
50
+ ## Related Work
51
+
52
+ * [ahoward/version_sorter](https://github.com/ahoward/version_sorter), the starting point for the `naturally` gem.
53
+ * [GitHub's Version sorter](https://github.com/github/version_sorter)
54
+
71
55
 
72
56
  ## Contributing
73
57
 
data/lib/naturally.rb CHANGED
@@ -1,98 +1,43 @@
1
- # encoding: utf-8
2
-
3
- require 'naturally/version'
4
-
1
+ require 'naturally/segment'
2
+
3
+ # A module which performs natural sorting on a variety of number
4
+ # formats. (See the specs for examples.)
5
+ #
6
+ # It achieves this by capitalizing on Ruby's behavior when
7
+ # comparing arrays: The module sorts arrays of segmented numbers such as
8
+ # ['1.9', '1.9a', '1.10'] by comparing them in their array forms.
9
+ # I.e., approximately [['1', '9'], ['1, '9a'], ['1', '10']]
5
10
  module Naturally
6
11
  # Perform a natural sort.
7
12
  #
8
13
  # @param [Array<String>] an_array the list of numbers to sort.
9
14
  # @return [Array<String>] the numbers sorted naturally.
10
15
  def self.sort(an_array)
11
- return an_array.sort_by { |x| normalize(x) }
16
+ an_array.sort_by { |x| normalize(x) }
12
17
  end
13
18
 
19
+ # Sort an array of objects "naturally" by a given attribute.
20
+ #
21
+ # @param [Array<Object>] an_array the list of objects to sort.
22
+ # @param [Symbol] an_attribute the attribute by which to sort.
23
+ # @return [Array<Object>] the objects in natural sort order.
14
24
  def self.sort_by(an_array, an_attribute)
15
- an_array.sort_by{|i| Naturally.normalize(i.send(an_attribute))}
25
+ an_array.sort_by { |obj| normalize(obj.send(an_attribute)) }
16
26
  end
17
27
 
18
- # Convert the given number into an object that can be sorted
19
- # naturally. This object is an array of {NumberElement} instances.
28
+ # Convert the given number an array of {Segment}s.
29
+ # This enables it to be sorted against other arrays
30
+ # by the standard #sort method.
20
31
  #
21
- # @param [String] number the number in complex form such as 1.2a.3.
22
- # @return [Array<NumberElement>] an array of NumberElements which is
23
- # able to be sorted naturally via a normal 'sort'.
24
- def self.normalize(number)
25
- number.to_s.scan(%r/\p{Word}+/o).map { |i| NumberElement.new(i) }
26
- end
27
-
28
- private
29
-
30
- # An entity which can be compared to other like elements for
31
- # sorting in an array. It's an object representing
32
- # a value which implements the {Comparable} interface.
33
- class NumberElement
34
- include Comparable
35
- attr_accessor :val
36
-
37
- def initialize(v)
38
- @val = v
39
- end
40
-
41
- def <=>(other)
42
- if both_are_integers_without_letters(other)
43
- return @val.to_i <=> other.val.to_i
44
- end
45
-
46
- if either_is_numbers_followed_by_letters(other)
47
- return simple_normalize(@val) <=> simple_normalize(other.val)
48
- end
49
-
50
- if either_is_letters_followed_by_numbers(other)
51
- return reverse_simple_normalize(@val) <=> reverse_simple_normalize(other.val)
52
- end
53
-
54
- @val <=> other.val
55
- end
56
-
57
- def either_is_letters_followed_by_numbers(other)
58
- letters_with_numbers? || other.letters_with_numbers?
59
- end
60
-
61
- def either_is_numbers_followed_by_letters(other)
62
- numbers_with_letters? || other.numbers_with_letters?
63
- end
64
-
65
- def both_are_integers_without_letters(other)
66
- pure_integer? && other.pure_integer?
67
- end
68
-
69
- def pure_integer?
70
- @val =~ /^\d+$/
71
- end
72
-
73
- def numbers_with_letters?
74
- val =~ /^\d+\p{Alpha}+$/
75
- end
76
-
77
- def letters_with_numbers?
78
- val =~ /^\p{Alpha}+\d+$/
79
- end
80
-
81
- def simple_normalize(n)
82
- if n =~ /^(\d+)(\p{Alpha}+)$/
83
- [$1.to_i, $2]
84
- else
85
- [n.to_i]
86
- end
87
- end
88
-
89
- def reverse_simple_normalize(n)
90
- if n =~ /^(\p{Alpha}+)(\d+)$/
91
- [$1, $2.to_i]
92
- else
93
- [n.to_s]
94
- end
95
- end
96
-
32
+ # For example:
33
+ # '1.2a.3' becomes [Segment<'1'>, Segment<'2a'>, Segment<'3'>]
34
+ #
35
+ # @param [String] complex_number the number in a hierarchical form
36
+ # such as 1.2a.3.
37
+ # @return [Array<Segment>] an array of Segments which
38
+ # can be sorted naturally via a standard #sort.
39
+ def self.normalize(complex_number)
40
+ tokens = complex_number.to_s.scan(/\p{Word}+/)
41
+ tokens.map { |t| Segment.new(t) }
97
42
  end
98
43
  end
@@ -0,0 +1,28 @@
1
+ module Naturally
2
+ # An entity which can be compared to other like elements for
3
+ # sorting. It's an object representing
4
+ # a value which implements the {Comparable} interface.
5
+ class Segment
6
+ include Comparable
7
+
8
+ def initialize(v)
9
+ @val = v
10
+ end
11
+
12
+ def <=>(other)
13
+ to_array <=> other.to_array
14
+ end
15
+
16
+ def to_array
17
+ if @val =~ /^(\p{Digit}+)(\p{Alpha}+)$/
18
+ [$1.to_i, $2]
19
+ elsif @val =~ /^(\p{Alpha}+)(\p{Digit}+)$/
20
+ [$1, $2.to_i]
21
+ elsif @val =~ /^\p{Digit}+$/
22
+ [@val.to_i]
23
+ else
24
+ [@val]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Naturally
2
- VERSION = '1.3.1'
2
+ VERSION = '1.3.2'
3
3
  end
data/naturally.gemspec CHANGED
@@ -6,10 +6,11 @@ require 'naturally/version'
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = "naturally"
8
8
  gem.version = Naturally::VERSION
9
+ gem.license = 'MIT'
9
10
  gem.authors = ["Robb Shecter"]
10
11
  gem.email = ["robb@weblaws.org"]
11
12
  gem.summary = %q{Sorts numbers according to the way people are used to seeing them.}
12
- gem.description = %q{Natural Sorting with support for legal numbering}
13
+ gem.description = %q{Natural Sorting with support for legal numbering, course numbers, and other number/letter mixes.}
13
14
  gem.homepage = "http://github.com/dogweather/naturally"
14
15
 
15
16
  gem.files = `git ls-files`.split($/)
@@ -1,55 +1,67 @@
1
1
  # encoding: utf-8
2
-
3
2
  require 'naturally'
4
3
 
5
4
  describe Naturally do
5
+ def it_sorts(this:, to_this:)
6
+ actual = Naturally.sort(this)
7
+ expect(actual).to eq(to_this)
8
+ end
9
+
6
10
  describe '#sort' do
7
11
  it 'sorts an array of strings nicely as if they were legal numbers' do
8
- a = %w[676 676.1 676.11 676.12 676.2 676.3 676.9 676.10]
9
- b = %w[676 676.1 676.2 676.3 676.9 676.10 676.11 676.12]
10
- Naturally.sort(a).should == b
12
+ it_sorts(
13
+ this: %w(676 676.1 676.11 676.12 676.2 676.3 676.9 676.10),
14
+ to_this: %w(676 676.1 676.2 676.3 676.9 676.10 676.11 676.12)
15
+ )
11
16
  end
12
17
 
13
18
  it 'sorts a more complex list of strings' do
14
- a = %w[350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.45 354.5]
15
- b = %w[350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.5 354.45]
16
- Naturally.sort(a).should == b
19
+ it_sorts(
20
+ this: %w(350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.45 354.5),
21
+ to_this: %w(350 351 352 352.1 352.5 353.1 354 354.3 354.4 354.5 354.45)
22
+ )
17
23
  end
18
24
 
19
25
  it 'sorts when numbers have letters in them' do
20
- a = %w[335 335.1 336a 336 337 337a 337.1 337.15 337.2]
21
- b = %w[335 335.1 336 336a 337 337.1 337.2 337.15 337a]
22
- Naturally.sort(a).should == b
26
+ it_sorts(
27
+ this: %w(335 335.1 336a 336 337 337a 337.1 337.15 337.2),
28
+ to_this: %w(335 335.1 336 336a 337 337.1 337.2 337.15 337a)
29
+ )
23
30
  end
24
31
 
25
32
  it 'sorts when numbers have unicode letters in them' do
26
- a = %w[335 335.1 336a 336 337 337я 337.1 337.15 337.2]
27
- b = %w[335 335.1 336 336a 337 337.1 337.2 337.15 337я]
28
- Naturally.sort(a).should == b
33
+ it_sorts(
34
+ this: %w(335 335.1 336a 336 337 337я 337.1 337.15 337.2),
35
+ to_this: %w(335 335.1 336 336a 337 337.1 337.2 337.15 337я)
36
+ )
29
37
  end
30
38
 
31
39
  it 'sorts when letters have numbers in them' do
32
- a = %w[PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, PBLI, SBP1, SBP3]
33
- b = %w[PBLI, PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, SBP1, SBP3]
34
- Naturally.sort(a).should == b
40
+ it_sorts(
41
+ this: %w(PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, PBLI, SBP1, SBP3),
42
+ to_this: %w(PBLI, PC1, PC3, PC5, PC7, PC9, PC10, PC11, PC12, PC13, PC14, PROF2, SBP1, SBP3)
43
+ )
35
44
  end
36
45
 
37
46
  it 'sorts when letters have numbers and unicode characters in them' do
38
- a = %w[АБ4, АБ2, АБ10, АБ12, АБ1, АБ3, АД8, АД5, АЩФ12, АЩФ8, ЫВА1]
39
- b = %w[АБ1, АБ2, АБ3, АБ4, АБ10, АБ12, АД5, АД8, АЩФ8, АЩФ12, ЫВА1]
40
- Naturally.sort(a).should == b
47
+ it_sorts(
48
+ this: %w(АБ4, АБ2, АБ10, АБ12, АБ1, АБ3, АД8, АД5, АЩФ12, АЩФ8, ЫВА1),
49
+ to_this: %w(АБ1, АБ2, АБ3, АБ4, АБ10, АБ12, АД5, АД8, АЩФ8, АЩФ12, ЫВА1)
50
+ )
41
51
  end
42
52
 
43
53
  it 'sorts double digits with letters correctly' do
44
- a = %w[12a 12b 12c 13a 13b 2 3 4 5 10 11 12]
45
- b = %w[2 3 4 5 10 11 12 12a 12b 12c 13a 13b]
46
- Naturally.sort(a).should == b
54
+ it_sorts(
55
+ this: %w(12a 12b 12c 13a 13b 2 3 4 5 10 11 12),
56
+ to_this: %w(2 3 4 5 10 11 12 12a 12b 12c 13a 13b)
57
+ )
47
58
  end
48
59
 
49
60
  it 'sorts double digits with unicode letters correctly' do
50
- a = %w[12а 12б 12в 13а 13б 2 3 4 5 10 11 12]
51
- b = %w[2 3 4 5 10 11 12 12а 12б 12в 13а 13б]
52
- Naturally.sort(a).should == b
61
+ it_sorts(
62
+ this: %w(12а 12б 12в 13а 13б 2 3 4 5 10 11 12),
63
+ to_this: %w(2 3 4 5 10 11 12 12а 12б 12в 13а 13б)
64
+ )
53
65
  end
54
66
  end
55
67
 
@@ -64,8 +76,8 @@ describe Naturally do
64
76
  UbuntuVersion.new('Quantal Quetzal', '12.10'),
65
77
  UbuntuVersion.new('Lucid Lynx', '10.04.4')
66
78
  ]
67
- sorted = Naturally.sort_by(releases, :version)
68
- expect(sorted.map(&:name)).to eq [
79
+ actual = Naturally.sort_by(releases, :version)
80
+ expect(actual.map(&:name)).to eq [
69
81
  'Lucid Lynx',
70
82
  'Maverick Meerkat',
71
83
  'Precise Pangolin',
@@ -86,8 +98,8 @@ describe Naturally do
86
98
  Thing.new('2.1', 'Калуга'),
87
99
  Thing.new('1.3', 'Васюки')
88
100
  ]
89
- sorted = objects.sort_by{ |o| Naturally.normalize(o.name) }
90
- sorted.map{|o| o.name}.should == %w[
101
+ actual = objects.sort_by { |o| Naturally.normalize(o.name) }
102
+ expect(actual.map(&:name)).to eq %w(
91
103
  Брест
92
104
  Будапешт
93
105
  Васюки
@@ -95,7 +107,7 @@ describe Naturally do
95
107
  Киев
96
108
  Москва
97
109
  Париж
98
- ]
110
+ )
99
111
  end
100
112
  end
101
113
  end
metadata CHANGED
@@ -1,16 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: naturally
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robb Shecter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-07 00:00:00.000000000 Z
11
+ date: 2015-03-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Natural Sorting with support for legal numbering
13
+ description: Natural Sorting with support for legal numbering, course numbers, and
14
+ other number/letter mixes.
14
15
  email:
15
16
  - robb@weblaws.org
16
17
  executables: []
@@ -24,11 +25,13 @@ files:
24
25
  - README.md
25
26
  - Rakefile
26
27
  - lib/naturally.rb
28
+ - lib/naturally/segment.rb
27
29
  - lib/naturally/version.rb
28
30
  - naturally.gemspec
29
31
  - spec/naturally_spec.rb
30
32
  homepage: http://github.com/dogweather/naturally
31
- licenses: []
33
+ licenses:
34
+ - MIT
32
35
  metadata: {}
33
36
  post_install_message:
34
37
  rdoc_options: []
@@ -46,10 +49,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
49
  version: '0'
47
50
  requirements: []
48
51
  rubyforge_project:
49
- rubygems_version: 2.2.2
52
+ rubygems_version: 2.4.6
50
53
  signing_key:
51
54
  specification_version: 4
52
55
  summary: Sorts numbers according to the way people are used to seeing them.
53
56
  test_files:
54
57
  - spec/naturally_spec.rb
55
- has_rdoc: