sort_by_str 0.5.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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2010 The New York Times Company
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this library except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,35 @@
1
+ = sort_by_str
2
+
3
+ SQL-like sorts on your Enumerables.
4
+
5
+ == Getting Started
6
+
7
+ gem install sort_by_str
8
+ require 'sort_by_str'
9
+
10
+ == Usage
11
+
12
+ a = Date.parse('2010-08-22')
13
+ b = Date.parse('2010-08-23)
14
+
15
+ [a,b].sort_by_str('year ASC, day DESC')
16
+ => [b,a]
17
+
18
+ Call sort_by_str on your Enumerable with a SQL-style sort expression containing a list of fields.
19
+
20
+ A basic expression might look like like
21
+ 'year, month, day'.
22
+
23
+ Optional ASC (ascending) or DESC (descending) modifiers can also be used:
24
+ 'year ASC, day DESC'
25
+
26
+ <tt>send</tt> is used to extract values for comparison, so any valid method name
27
+ can be used in the expression.
28
+
29
+ == Author
30
+
31
+ Ben Koski, bkoski@nytimes.com
32
+
33
+ == Copyright
34
+
35
+ Copyright (c) 2010 The New York Times Company. See LICENSE for details.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sort_by_str"
8
+ gem.summary = %Q{SQL-like sorts on your Enumerables}
9
+ gem.description = %Q{SQL-like sorts on your Enumerables}
10
+ gem.email = "bkoski@nytimes.com"
11
+ gem.homepage = "http://github.com/nytimes/sort_by_str"
12
+ gem.authors = ["Ben Koski"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION')
47
+ version = File.read('VERSION')
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "sort_by_str #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,64 @@
1
+ module Enumerable
2
+
3
+ # Similar to sort_by, sort_by_str accepts a string containing a SQL-style sort expression.
4
+ #
5
+ # The simplest sort expression is a comma separated list of fields: <tt>'month,day,year'</tt>
6
+ #
7
+ # But can also include optional ASC (ascending) or DESC (descending) order modifiers.
8
+ # For example: <tt>'year DESC, month ASC, day ASC'</tt>
9
+ # If an order modifier is omitted for a field, an ASC sort is assumed.
10
+ #
11
+ # Field values are checked for comparision using a <tt>send</tt>, so any method name can be used.
12
+ def sort_by_str str
13
+ sort_parts = str.split(',').collect { |p| p.split(' ') }
14
+
15
+ # double-check structure of parsed data
16
+ raise ArgumentError, "'#{str}' doesn't appear correctly formatted." if sort_parts.empty?
17
+ sort_parts.each { |p| raise ArgumentError, "'#{str}' doesn't appear correctly formatted." if p.length > 2 }
18
+
19
+ # split into list of fields and sort directions for those fields
20
+ fields = sort_parts.collect { |p| p.first }
21
+ sort_directions = sort_parts.collect { |p| p.last.upcase == 'DESC' ? :desc : :asc } # if unspecified assumed to be an ASC sort
22
+
23
+ # From here we follow pattern of Schwartzian Transform used in sort_by (http://bit.ly/aPEsNO).
24
+ # This means that rather than doing a sort! directly on self, we cache values to be sorted into an array.
25
+ # So for example, given:
26
+ # a = Date.today
27
+ # b = Date.today + 1
28
+ # [a,b].sort_by_str('year ASC, day DESC')
29
+ #
30
+ # we transform to
31
+ # [[a, 2010, 22],[b,2010,23]]
32
+ # and then proceed with sort.
33
+ # Following sort, we collect list of first elements to produce final, sorted array.
34
+ # This prevents sort attrs from being called multiple times during sort
35
+ # which could be problematic if sort attr is an expensive calcuation.
36
+
37
+ # 1. collect data into intermediate arrays, data element first
38
+ sort_data = collect do |element|
39
+ data = [element]
40
+ fields.each { |f| data << element.send(f) }
41
+ data
42
+ end
43
+
44
+ # 2. actually sort the data -- a,b are limited to a[1..-1] so as to exclude first [data] element
45
+ sort_data.sort! do |a,b|
46
+ cmp = 0
47
+ sort_directions.zip(a[1..-1], b[1..-1]) do |field_data|
48
+ direction, a_val, b_val = field_data
49
+ if direction == :desc
50
+ cmp = b_val <=> a_val
51
+ else
52
+ cmp = a_val <=> b_val
53
+ end
54
+ break if cmp != 0
55
+ end
56
+
57
+ cmp
58
+ end
59
+
60
+ # 3. now that data is sorted, reduce back to list of data elements
61
+ sort_data.collect { |element| element.first }
62
+ end
63
+
64
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'ostruct'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'sort_by_str'
9
+
10
+ class Test::Unit::TestCase
11
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ class SortByStrTest < Test::Unit::TestCase
4
+
5
+ context "with valid sort expression" do
6
+
7
+ setup do
8
+ @red_10 = OpenStruct.new(:color => 'red', :size => 10)
9
+ @blue_10 = OpenStruct.new(:color => 'blue', :size => 10)
10
+ @red_15 = OpenStruct.new(:color => 'red', :size => 15)
11
+
12
+ @all_data = [@red_10, @blue_10, @red_15]
13
+ end
14
+
15
+ should "sort correctly on a single-field sort" do
16
+ assert_equal [@blue_10, @red_15], [@red_15, @blue_10].sort_by_str('size')
17
+ end
18
+
19
+ should "sort correctly on a multi-field sort" do
20
+ assert_equal [@blue_10, @red_10, @red_15], @all_data.sort_by_str('size, color')
21
+ end
22
+
23
+ should "respect DESC modifier" do
24
+ assert_equal [@red_15, @blue_10, @red_10], @all_data.sort_by_str('size DESC, color')
25
+ end
26
+
27
+ should "respect ASC modifier" do
28
+ assert_equal [@blue_10, @red_10, @red_15], @all_data.sort_by_str('size ASC, color')
29
+ end
30
+
31
+ should "sort correctly even if extra spaces are supplied" do
32
+ assert_equal [@blue_10, @red_10, @red_15], @all_data.sort_by_str('size ASC , color ')
33
+ end
34
+
35
+ end
36
+
37
+ context "with invalid sort expression" do
38
+ setup do
39
+ @data = [OpenStruct.new(:size => 10), OpenStruct.new(:size => 15)]
40
+ end
41
+
42
+ should "raise an ArgumentError if too many tokens are supplied for a field" do
43
+ assert_raises(ArgumentError) { @data.sort('size ASC DESC')}
44
+ end
45
+
46
+ should "raise an ArgumentError if no fields are supplied" do
47
+ assert_raises(ArgumentError) { @data.sort('') }
48
+ end
49
+
50
+ should "raise an ArgumentError if no tokens are supplied for a field" do
51
+ assert_raises(ArgumentError) { @data.sort('size ASC, ,size DESC')}
52
+ end
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sort_by_str
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Koski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-08-24 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: SQL-like sorts on your Enumerables
26
+ email: bkoski@nytimes.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - lib/sort_by_str.rb
42
+ - test/test_helper.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/nytimes/sort_by_str
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: SQL-like sorts on your Enumerables
71
+ test_files:
72
+ - test/test_helper.rb
73
+ - test/test_sort_by_str.rb