yuri-ita 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/lib/yuri-ita.rb +1 -0
- data/lib/yuriita/and_combination.rb +22 -0
- data/lib/yuriita/assembler.rb +15 -0
- data/lib/yuriita/clauses/dynamic.rb +18 -0
- data/lib/yuriita/clauses/filter.rb +19 -0
- data/lib/yuriita/clauses/identity.rb +9 -0
- data/lib/yuriita/clauses/search.rb +20 -0
- data/lib/yuriita/collection.rb +60 -0
- data/lib/yuriita/configuration.rb +31 -0
- data/lib/yuriita/definitions/dynamic.rb +38 -0
- data/lib/yuriita/definitions/exclusive.rb +32 -0
- data/lib/yuriita/definitions/multiple.rb +28 -0
- data/lib/yuriita/definitions/scope.rb +33 -0
- data/lib/yuriita/definitions/single.rb +31 -0
- data/lib/yuriita/dynamic_filter.rb +18 -0
- data/lib/yuriita/errors/collection/action_not_found.rb +14 -0
- data/lib/yuriita/errors/error.rb +4 -0
- data/lib/yuriita/errors/parse_error.rb +4 -0
- data/lib/yuriita/executor.rb +17 -0
- data/lib/yuriita/expression_filter.rb +18 -0
- data/lib/yuriita/inputs/expression.rb +13 -0
- data/lib/yuriita/inputs/keyword.rb +9 -0
- data/lib/yuriita/lexer.rb +21 -0
- data/lib/yuriita/option.rb +19 -0
- data/lib/yuriita/or_combination.rb +23 -0
- data/lib/yuriita/parser.rb +61 -0
- data/lib/yuriita/query.rb +57 -0
- data/lib/yuriita/query_builder.rb +23 -0
- data/lib/yuriita/result.rb +26 -0
- data/lib/yuriita/route.rb +37 -0
- data/lib/yuriita/router.rb +26 -0
- data/lib/yuriita/routing.rb +19 -0
- data/lib/yuriita/runner.rb +19 -0
- data/lib/yuriita/search.rb +13 -0
- data/lib/yuriita/search_filter.rb +26 -0
- data/lib/yuriita/selects/all_or_explicit.rb +28 -0
- data/lib/yuriita/selects/exclusive.rb +48 -0
- data/lib/yuriita/selects/multiple.rb +28 -0
- data/lib/yuriita/selects/single.rb +49 -0
- data/lib/yuriita/table.rb +84 -0
- data/lib/yuriita/version.rb +3 -0
- data/lib/yuriita.rb +19 -0
- metadata +230 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 413f3447fff9ac44f83651cf97d96ce1ec2a80b850b9e88ef142e1afd2bc3519
|
4
|
+
data.tar.gz: 0d85e9307246eeca631a8ea73b252ddb545912b9c0acb8972d25970046074087
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7d1ba86975041460af1ace033804af6a3f9b0d592a300a69ebf3028a33e16f748cab9ef7302bb6931b8fdfa71b142481d05097f62505af2b42ef0a1b7c61b57a
|
7
|
+
data.tar.gz: b320a83a41391c6fa86972ddec4e2d730de2c27e1028cae68227cf41f8b08fd072054ae828d5949ba3e14f9ef61698fee93cb126f3a3709d37281dab7976a479
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2020 Ebrahim Kobeissi and thoughtbot, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/lib/yuri-ita.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "yuriita"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class AndCombination
|
3
|
+
def initialize(base_relation:, relations:)
|
4
|
+
@base_relation = base_relation
|
5
|
+
@relations = relations
|
6
|
+
end
|
7
|
+
|
8
|
+
def combine
|
9
|
+
base_relation.merge(combined_relations)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :base_relation, :relations
|
15
|
+
|
16
|
+
def combined_relations
|
17
|
+
relations.reduce do |chain, relation|
|
18
|
+
chain.merge(relation)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class Assembler
|
3
|
+
attr_reader :configuration
|
4
|
+
|
5
|
+
def initialize(configuration)
|
6
|
+
@configuration = configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(query)
|
10
|
+
configuration.map do |definition|
|
11
|
+
definition.apply(query: query)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Clauses
|
3
|
+
class Dynamic
|
4
|
+
def initialize(filter:, input:)
|
5
|
+
@filter = filter
|
6
|
+
@input = input
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(relation)
|
10
|
+
filter.apply(relation, input)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :filter, :input
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Clauses
|
3
|
+
class Filter
|
4
|
+
def initialize(filters:, combination:)
|
5
|
+
@filters = filters
|
6
|
+
@combination = combination
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(relation)
|
10
|
+
relations = filters.map { |filter| filter.apply(relation) }
|
11
|
+
combination.new(base_relation: relation, relations: relations).combine
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :filters, :combination
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Clauses
|
3
|
+
class Search
|
4
|
+
def initialize(filters:, keywords:, combination:)
|
5
|
+
@filters = filters
|
6
|
+
@keywords = keywords
|
7
|
+
@combination = combination
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply(relation)
|
11
|
+
relations = filters.map { |filter| filter.apply(relation, keywords) }
|
12
|
+
combination.new(base_relation: relation, relations: relations).combine
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :filters, :keywords, :combination
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class Collection
|
3
|
+
include Yuriita::Routing
|
4
|
+
|
5
|
+
attr_reader :input
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def dispatch(name, input, relation)
|
9
|
+
new(input).dispatch(name, relation)
|
10
|
+
end
|
11
|
+
|
12
|
+
def action_methods
|
13
|
+
@action_methods ||= begin
|
14
|
+
internal_methods = superclass.public_instance_methods(true)
|
15
|
+
methods = (public_instance_methods(true) - internal_methods)
|
16
|
+
|
17
|
+
methods.map!(&:to_s)
|
18
|
+
|
19
|
+
methods.to_set
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(input)
|
25
|
+
@input = input
|
26
|
+
end
|
27
|
+
|
28
|
+
def dispatch(name, relation)
|
29
|
+
process(name, relation)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def process(action, *args)
|
35
|
+
action = action.to_s
|
36
|
+
|
37
|
+
unless method_name = find_method_name(action)
|
38
|
+
raise ActionNotFound.new("The action '#{action}' could not be found for #{self.class.name}", self, action)
|
39
|
+
end
|
40
|
+
|
41
|
+
process_action(method_name, *args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_action(method_name, *args)
|
45
|
+
send_action(method_name, *args)
|
46
|
+
end
|
47
|
+
alias send_action send
|
48
|
+
|
49
|
+
|
50
|
+
def find_method_name(action)
|
51
|
+
if action_method?(action)
|
52
|
+
action
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def action_method?(name)
|
57
|
+
self.class.action_methods.include?(name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class Configuration
|
3
|
+
include Enumerable
|
4
|
+
EMPTY_STRING = "".freeze
|
5
|
+
|
6
|
+
attr_reader :default_input
|
7
|
+
|
8
|
+
def initialize(definitions, default_input: EMPTY_STRING)
|
9
|
+
@definitions = definitions
|
10
|
+
@default_input = default_input
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_definition(key)
|
14
|
+
definitions.fetch(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
block or return enum_for(__method__) { size }
|
19
|
+
definitions.each_value(&block)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
definitions.size
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :definitions
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Definitions
|
3
|
+
class Dynamic
|
4
|
+
def initialize(filter:)
|
5
|
+
@filter = filter
|
6
|
+
end
|
7
|
+
|
8
|
+
def apply(query:)
|
9
|
+
input = select_input(query)
|
10
|
+
|
11
|
+
if input.present?
|
12
|
+
Clauses::Dynamic.new(filter: filter, input: input)
|
13
|
+
else
|
14
|
+
Clauses::Identity.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :filter
|
21
|
+
|
22
|
+
def select_input(query)
|
23
|
+
matching_inputs(query).last
|
24
|
+
end
|
25
|
+
|
26
|
+
def matching_inputs(query)
|
27
|
+
query.select do |input|
|
28
|
+
case input
|
29
|
+
when Inputs::Expression
|
30
|
+
input.qualifier == filter.qualifier
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Definitions
|
3
|
+
class Exclusive
|
4
|
+
attr_reader :options, :default
|
5
|
+
|
6
|
+
def initialize(options:, default:)
|
7
|
+
@options = options
|
8
|
+
@default = default
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply(query:)
|
12
|
+
filter = selected_filter(query)
|
13
|
+
|
14
|
+
Clauses::Filter.new(filters: [filter], combination: combination)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def combination
|
20
|
+
AndCombination
|
21
|
+
end
|
22
|
+
|
23
|
+
def selected_filter(query)
|
24
|
+
Selects::Exclusive.new(
|
25
|
+
options: options,
|
26
|
+
default: default,
|
27
|
+
query: query,
|
28
|
+
).filter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Definitions
|
3
|
+
class Multiple
|
4
|
+
attr_reader :options, :combination
|
5
|
+
|
6
|
+
def initialize(options:, combination:)
|
7
|
+
@options = options
|
8
|
+
@combination = combination
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply(query:)
|
12
|
+
filters = selected_filters(query)
|
13
|
+
|
14
|
+
if filters.present?
|
15
|
+
Clauses::Filter.new(filters: filters, combination: combination)
|
16
|
+
else
|
17
|
+
Clauses::Identity.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def selected_filters(query)
|
24
|
+
Selects::Multiple.new(options: options, query: query).filters
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Definitions
|
3
|
+
class Scope
|
4
|
+
attr_reader :options, :combination
|
5
|
+
|
6
|
+
def initialize(options:, combination:)
|
7
|
+
@options = options
|
8
|
+
@combination = combination
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply(query:)
|
12
|
+
filters = selected_filters(query)
|
13
|
+
keywords = query.keywords
|
14
|
+
|
15
|
+
if keywords.present?
|
16
|
+
Clauses::Search.new(
|
17
|
+
filters: filters,
|
18
|
+
keywords: keywords,
|
19
|
+
combination: combination,
|
20
|
+
)
|
21
|
+
else
|
22
|
+
Clauses::Identity.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def selected_filters(query)
|
29
|
+
Selects::AllOrExplicit.new(options: options, query: query).filters
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Yuriita
|
2
|
+
module Definitions
|
3
|
+
class Single
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(options:)
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply(query:)
|
11
|
+
filter = selected_filter(query)
|
12
|
+
|
13
|
+
if filter.present?
|
14
|
+
Clauses::Filter.new(filters: [filter], combination: combination)
|
15
|
+
else
|
16
|
+
Clauses::Identity.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def combination
|
23
|
+
AndCombination
|
24
|
+
end
|
25
|
+
|
26
|
+
def selected_filter(query)
|
27
|
+
Selects::Single.new(options: options, query: query).filter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class DynamicFilter
|
3
|
+
attr_reader :qualifier
|
4
|
+
|
5
|
+
def initialize(qualifier:, &block)
|
6
|
+
@qualifier = qualifier
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply(relation, input)
|
11
|
+
block.call(relation, input)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :block
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class Collection
|
3
|
+
class ActionNotFound < Yuriita::Error
|
4
|
+
attr_reader :collection, :action
|
5
|
+
|
6
|
+
def initialize(message = nil, collection = nil, action = nil)
|
7
|
+
@collection = collection
|
8
|
+
@action = action
|
9
|
+
super(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class Executor
|
3
|
+
def initialize(clauses)
|
4
|
+
@clauses = clauses
|
5
|
+
end
|
6
|
+
|
7
|
+
def run(relation)
|
8
|
+
clauses.reduce(relation) do |chain, clause|
|
9
|
+
chain.merge(clause.apply(relation))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :clauses
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "rltk/lexer"
|
2
|
+
|
3
|
+
module Yuriita
|
4
|
+
class Lexer < RLTK::Lexer
|
5
|
+
DATETIME_REGEX = %r{
|
6
|
+
([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])
|
7
|
+
(
|
8
|
+
T
|
9
|
+
((2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9]))
|
10
|
+
(Z|[+-](2[0-3]|[01][0-9])(:[0-5][0-9])?)
|
11
|
+
)?
|
12
|
+
}x
|
13
|
+
|
14
|
+
rule(/[ \t\f]+/) { :SPACE }
|
15
|
+
rule(/:/) { :COLON }
|
16
|
+
rule(/>/) { :GT }
|
17
|
+
rule(DATETIME_REGEX) { |d| [:DATETIME, DateTime.parse(d)] }
|
18
|
+
rule(/[^ >\t\f:"]+/) { |t| [:WORD, t] }
|
19
|
+
rule(/"/) { :QUOTE }
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class Option
|
3
|
+
attr_reader :name, :filter
|
4
|
+
|
5
|
+
def initialize(name:, filter:)
|
6
|
+
@name = name
|
7
|
+
@filter = filter
|
8
|
+
end
|
9
|
+
|
10
|
+
def input
|
11
|
+
filter.input
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
other.is_a?(self.class) && other.name == name
|
16
|
+
end
|
17
|
+
alias_method :eql?, :==
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Yuriita
|
2
|
+
class OrCombination
|
3
|
+
def initialize(base_relation:, relations:)
|
4
|
+
@base_relation = base_relation
|
5
|
+
@relations = relations
|
6
|
+
end
|
7
|
+
|
8
|
+
def combine
|
9
|
+
base_relation.merge(combined_relations)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :base_relation, :relations
|
15
|
+
|
16
|
+
def combined_relations
|
17
|
+
relations.reduce do |chain, relation|
|
18
|
+
chain.or(relation)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "rltk/parser"
|
2
|
+
|
3
|
+
module Yuriita
|
4
|
+
class Parser < RLTK::Parser
|
5
|
+
left :SPACE
|
6
|
+
|
7
|
+
production(:query) do
|
8
|
+
clause("SPACE? .input_list SPACE?") do |inputs|
|
9
|
+
Query.new(inputs: inputs)
|
10
|
+
end
|
11
|
+
clause("SPACE?") { |_| Query.new }
|
12
|
+
end
|
13
|
+
|
14
|
+
production(:input_list) do
|
15
|
+
clause(".input") { |input| [input] }
|
16
|
+
clause(".input_list SPACE .input") { |head, tail| head + [tail] }
|
17
|
+
end
|
18
|
+
|
19
|
+
production(:input) do
|
20
|
+
clause(".keyword") { |keyword| keyword }
|
21
|
+
clause(".expression") { |expression| expression }
|
22
|
+
end
|
23
|
+
|
24
|
+
production(:keyword) do
|
25
|
+
clause(:WORD) { |word| Inputs::Keyword.new(word) }
|
26
|
+
clause("QUOTE SPACE? .phrase SPACE? QUOTE") do |phrase|
|
27
|
+
Inputs::Keyword.new(phrase)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
production(:expression) do
|
32
|
+
clause(".qualifier COLON .term") do |qualifier, term|
|
33
|
+
Inputs::Expression.new(qualifier, term)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
production(:scope_field) do
|
38
|
+
clause(:WORD) { |word| word }
|
39
|
+
end
|
40
|
+
|
41
|
+
production(:qualifier) do
|
42
|
+
clause(:WORD) { |word| word }
|
43
|
+
end
|
44
|
+
|
45
|
+
production(:order) do
|
46
|
+
clause(:WORD) { |word| word }
|
47
|
+
end
|
48
|
+
|
49
|
+
production(:term) do
|
50
|
+
clause(:WORD) { |word| word }
|
51
|
+
clause("QUOTE SPACE? .phrase SPACE? QUOTE") { |phrase| phrase }
|
52
|
+
end
|
53
|
+
|
54
|
+
production(:phrase) do
|
55
|
+
clause(:WORD) { |word| word }
|
56
|
+
clause(".phrase SPACE .WORD") { |phrase, word| phrase + " " + word }
|
57
|
+
end
|
58
|
+
|
59
|
+
finalize
|
60
|
+
end
|
61
|
+
end
|