tidied 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -0
- data/lib/tidied.rb +32 -2
- data/lib/tidied/filtering.rb +64 -0
- data/lib/tidied/sorting.rb +134 -0
- data/lib/tidied/version.rb +2 -2
- data/tidied.gemspec +1 -2
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f28badb45e6138ff37f9aa60a877fd335a217f8b2feda6e0a3c323a317f3812
|
4
|
+
data.tar.gz: 77d1682eb7ea5c1d52512d6dfdfa78e97c10b9aad076657ae3ccfc3e75c8b999
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d1525ca8780bd9692f7e7c6e22f5f6154cf949ece328db960d52b47ffc8fe85d5d4b5c51f25f14d4ed0b3266d186a330beaece8581d6e8fd19fea56c26a6582
|
7
|
+
data.tar.gz: 7ce14f95efbc480cc073ab4be43c9a4c25ad0aaae0075e876e7d7efeb64fd8f48d5a3294dabc31b6fea5618708841aaccb66a2958a7b02b271cc163d2584d6c4
|
data/Gemfile.lock
CHANGED
@@ -2,10 +2,13 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
tidied (0.1.0)
|
5
|
+
anyway_config (>= 2.0.0)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
8
9
|
specs:
|
10
|
+
anyway_config (2.1.0)
|
11
|
+
ruby-next-core (>= 0.11.0)
|
9
12
|
ast (2.4.1)
|
10
13
|
diff-lcs (1.4.4)
|
11
14
|
parallel (1.20.1)
|
@@ -39,6 +42,7 @@ GEM
|
|
39
42
|
unicode-display_width (>= 1.4.0, < 3.0)
|
40
43
|
rubocop-ast (1.4.0)
|
41
44
|
parser (>= 2.7.1.5)
|
45
|
+
ruby-next-core (0.12.0)
|
42
46
|
ruby-progressbar (1.11.0)
|
43
47
|
unicode-display_width (2.0.0)
|
44
48
|
|
data/lib/tidied.rb
CHANGED
@@ -1,8 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "tidied/version"
|
4
|
+
require_relative "tidied/config"
|
5
|
+
require "forwardable"
|
6
|
+
require_relative "tidied/sorting"
|
7
|
+
require_relative "tidied/filtering"
|
8
|
+
|
9
|
+
class Tidied
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@view, :first_name, :last_name
|
4
12
|
|
5
|
-
module Tidied
|
6
13
|
class Error < StandardError; end
|
7
|
-
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def config
|
17
|
+
@config ||= Config.new
|
18
|
+
yield @config if block_given?
|
19
|
+
@config
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield(config) if block_given?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(collection)
|
28
|
+
@collection = collection
|
29
|
+
end
|
30
|
+
|
31
|
+
def sort(*instructions)
|
32
|
+
Sorting.new(*instructions).execute(@view)
|
33
|
+
end
|
34
|
+
|
35
|
+
def filter(*instructions)
|
36
|
+
Filtering.new(*instructions).execute(@view)
|
37
|
+
end
|
8
38
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
class Tidied
|
6
|
+
class Filtering
|
7
|
+
def initialize(*instructions)
|
8
|
+
@defaults = {
|
9
|
+
operator: :==,
|
10
|
+
accessor: :itself,
|
11
|
+
value: nil,
|
12
|
+
case_sensitive: true
|
13
|
+
}
|
14
|
+
@instructions = instructions.map do |instruction|
|
15
|
+
@defaults.merge(instruction)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(collection)
|
20
|
+
return false unless collection.respond_to? :select
|
21
|
+
|
22
|
+
@instructions.reduce(collection) do |output, instruction|
|
23
|
+
output.select do |item|
|
24
|
+
raw_value = access_value(from: item, at: instruction[:accessor])
|
25
|
+
attribute_value = process_value(from: raw_value, given: instruction)
|
26
|
+
|
27
|
+
next false if not attribute_value.respond_to?(instruction[:operator])
|
28
|
+
|
29
|
+
operator_method = attribute_value.method(instruction[:operator])
|
30
|
+
query_value = process_value(from: instruction[:value], given: instruction)
|
31
|
+
|
32
|
+
if operator_method.arity.zero?
|
33
|
+
operator_method.call
|
34
|
+
elsif operator_method.arity == 1
|
35
|
+
operator_method.call(query_value)
|
36
|
+
elsif operator_method.arity.abs.positive?
|
37
|
+
operator_method.call(*query_value)
|
38
|
+
else
|
39
|
+
raise Error.new("invalid operator `#{instruction[:operator]}` for `#{attribute_value}` at `#{instruction[:accessor]}`")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def access_value(from:, at:)
|
48
|
+
path = at.to_s.split('.')
|
49
|
+
path.reduce(from) do |object, signal|
|
50
|
+
break nil unless object.respond_to? signal
|
51
|
+
|
52
|
+
object.public_send(signal)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def process_value(from:, given:)
|
57
|
+
return from.map { |v| process_value(from: v, given: given) } if from.respond_to?(:each)
|
58
|
+
return from if not from.is_a?(String) || from.is_a?(Symbol)
|
59
|
+
return from.downcase if not given[:case_sensitive]
|
60
|
+
|
61
|
+
from
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
class Tidied
|
6
|
+
class Sorting
|
7
|
+
def initialize(*instructions)
|
8
|
+
@defaults = {
|
9
|
+
direction: :ascending,
|
10
|
+
nils: :small,
|
11
|
+
accessor: :itself,
|
12
|
+
case_sensitive: true,
|
13
|
+
normalized: false,
|
14
|
+
natural: false
|
15
|
+
}
|
16
|
+
@instructions = instructions.map do |instruction|
|
17
|
+
@defaults.merge(instruction)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def execute(collection)
|
22
|
+
collection.sort_by do |item|
|
23
|
+
@instructions.map do |instruction|
|
24
|
+
raw_value = access_value(from: item, at: instruction[:accessor])
|
25
|
+
processed_value = process_value(from: raw_value, given: instruction)
|
26
|
+
numeric_value = numeric_value(from: processed_value)
|
27
|
+
direction_multiplier = direction_multiplier(given: instruction[:direction])
|
28
|
+
nils_multiplier = nils_multiplier(given: instruction[:nils])
|
29
|
+
|
30
|
+
# p({ raw_value: raw_value, processed_value: processed_value, numeric_value: numeric_value, direction_multiplier: direction_multiplier, nils_multiplier: nils_multiplier })
|
31
|
+
|
32
|
+
if numeric_value.nil? # [1, 0] [-1, 0]
|
33
|
+
[direction_multiplier * nils_multiplier, 0]
|
34
|
+
else # [0, n] [0, -n]
|
35
|
+
[0, numeric_value * direction_multiplier]
|
36
|
+
end # [-1, 0] [0, -n] [0, n] [1, 0]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def access_value(from:, at:)
|
44
|
+
path = at.to_s.split('.')
|
45
|
+
path.reduce(from) do |object, signal|
|
46
|
+
break nil unless object.respond_to? signal
|
47
|
+
|
48
|
+
object.public_send(signal)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_value(from:, given:)
|
53
|
+
return from if not from.is_a?(String) || from.is_a?(Symbol)
|
54
|
+
return from.downcase if not given[:case_sensitive]
|
55
|
+
return normalize(from) + from if given[:normalized]
|
56
|
+
return segment(from) if given[:natural]
|
57
|
+
|
58
|
+
from
|
59
|
+
end
|
60
|
+
|
61
|
+
def numeric_value(from:)
|
62
|
+
return from if from.is_a?(Numeric)
|
63
|
+
return 1 if from == true
|
64
|
+
return 0 if from == false
|
65
|
+
|
66
|
+
if from.is_a?(String) || from.is_a?(Symbol)
|
67
|
+
string_to_numeric_value(from)
|
68
|
+
elsif from.is_a?(Date)
|
69
|
+
time_to_numeric_value(Time.new(from.year, from.month, from.day, 00, 00, 00, 00))
|
70
|
+
elsif from.respond_to?(:to_time)
|
71
|
+
time_to_numeric_value(from.to_time)
|
72
|
+
elsif from.respond_to?(:map)
|
73
|
+
segment_array_to_numeric_value(from)
|
74
|
+
else
|
75
|
+
from
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def direction_multiplier(given:)
|
80
|
+
return -1 if given == :descending
|
81
|
+
|
82
|
+
1
|
83
|
+
end
|
84
|
+
|
85
|
+
def nils_multiplier(given:)
|
86
|
+
return -1 if given == :small
|
87
|
+
|
88
|
+
1
|
89
|
+
end
|
90
|
+
|
91
|
+
def string_to_numeric_value(string)
|
92
|
+
string # "aB09ü""
|
93
|
+
.chars # ["a", "B", "0", "9", "ü"]
|
94
|
+
.map { |char| char.ord.to_s.rjust(3, '0') } # ["097", "066", "048", "057", "252"]
|
95
|
+
.insert(1, '.') # ["097", ".", "066", "048", "057", "252"]
|
96
|
+
.reduce(&:concat) # "097.066048057252"
|
97
|
+
.to_r # (24266512014313/2500000000000)
|
98
|
+
end
|
99
|
+
|
100
|
+
def time_to_numeric_value(time) # https://stackoverflow.com/a/30604935/2884386
|
101
|
+
time # 2000-01-01 00:00:00 +0000
|
102
|
+
.utc # 2000-01-01 00:00:00 UTC
|
103
|
+
.to_f # 946684800.0
|
104
|
+
.*(1000) # 946684800000.0
|
105
|
+
.round # 946684800000
|
106
|
+
end
|
107
|
+
|
108
|
+
def segment_array_to_numeric_value(segments)
|
109
|
+
segments # ["a", 12, "b", 34, "c"]
|
110
|
+
.map { |x| x.is_a?(Numeric) ? x : x.ord } # [97, 12, 98, 34, 99]
|
111
|
+
.map { |n| (n + 1).to_s.rjust(3, '0') } # ["098", "013", "099", "035", "100"]
|
112
|
+
.insert(1, '.') # ["098", ".", "013", "099", "035", "100"]
|
113
|
+
.join # "098.013099035100"
|
114
|
+
.to_r # (980130990351/100000000000)
|
115
|
+
end
|
116
|
+
|
117
|
+
def normalize(string) # https://github.com/grosser/sort_alphabetical
|
118
|
+
string # "Äaáäßs"
|
119
|
+
.unicode_normalize(:nfd) # "Äaáäßs"
|
120
|
+
.downcase(:fold) # "äaáässs"
|
121
|
+
.chars # ["a", "̈", "a", "a", "́", "a", "̈", "s", "s", "s"]
|
122
|
+
.select { |char| char =~ /[[:ascii:]]/ } # ["a", "a", "a", "a", "s", "s", "s"]
|
123
|
+
.join # "aaaasss"
|
124
|
+
end
|
125
|
+
|
126
|
+
def segment(string) # https://stackoverflow.com/a/15170063/2884386
|
127
|
+
digits_or_not_digit_regex = /[[:digit:]]+|[^[:digit:]]/
|
128
|
+
|
129
|
+
string # "ab12cd34,-56"
|
130
|
+
.scan(digits_or_not_digit_regex) # ["a", "b", "12", "c", "d", "34", ",", "-", "56"]
|
131
|
+
.map { |a| a =~ /[[:digit:]]+/ ? a.to_i : a } # ["a", "b", 12, "c", "d", 34, ",", "-", 56]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/tidied/version.rb
CHANGED
data/tidied.gemspec
CHANGED
@@ -28,8 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
|
31
|
-
|
32
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
31
|
+
spec.add_dependency "anyway_config", ">= 2.0.0"
|
33
32
|
|
34
33
|
# For more information and examples about making a new gem, checkout our
|
35
34
|
# guide at: https://bundler.io/guides/creating_gem.html
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tidied
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fractaledmind
|
@@ -9,7 +9,21 @@ autorequire:
|
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2021-01-20 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: anyway_config
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.0
|
13
27
|
description: Filter, sort, and/or paginate your data, whether it comes from a database
|
14
28
|
or is a simple Ruby collection.
|
15
29
|
email:
|
@@ -30,6 +44,8 @@ files:
|
|
30
44
|
- bin/console
|
31
45
|
- bin/setup
|
32
46
|
- lib/tidied.rb
|
47
|
+
- lib/tidied/filtering.rb
|
48
|
+
- lib/tidied/sorting.rb
|
33
49
|
- lib/tidied/version.rb
|
34
50
|
- tidied.gemspec
|
35
51
|
homepage: https://github.com/fractaledmind/tidied
|