tidied 0.0.1 → 0.1.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.
- 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
|