yuri-ita 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +19 -0
  3. data/lib/yuri-ita.rb +1 -0
  4. data/lib/yuriita/and_combination.rb +22 -0
  5. data/lib/yuriita/assembler.rb +15 -0
  6. data/lib/yuriita/clauses/dynamic.rb +18 -0
  7. data/lib/yuriita/clauses/filter.rb +19 -0
  8. data/lib/yuriita/clauses/identity.rb +9 -0
  9. data/lib/yuriita/clauses/search.rb +20 -0
  10. data/lib/yuriita/collection.rb +60 -0
  11. data/lib/yuriita/configuration.rb +31 -0
  12. data/lib/yuriita/definitions/dynamic.rb +38 -0
  13. data/lib/yuriita/definitions/exclusive.rb +32 -0
  14. data/lib/yuriita/definitions/multiple.rb +28 -0
  15. data/lib/yuriita/definitions/scope.rb +33 -0
  16. data/lib/yuriita/definitions/single.rb +31 -0
  17. data/lib/yuriita/dynamic_filter.rb +18 -0
  18. data/lib/yuriita/errors/collection/action_not_found.rb +14 -0
  19. data/lib/yuriita/errors/error.rb +4 -0
  20. data/lib/yuriita/errors/parse_error.rb +4 -0
  21. data/lib/yuriita/executor.rb +17 -0
  22. data/lib/yuriita/expression_filter.rb +18 -0
  23. data/lib/yuriita/inputs/expression.rb +13 -0
  24. data/lib/yuriita/inputs/keyword.rb +9 -0
  25. data/lib/yuriita/lexer.rb +21 -0
  26. data/lib/yuriita/option.rb +19 -0
  27. data/lib/yuriita/or_combination.rb +23 -0
  28. data/lib/yuriita/parser.rb +61 -0
  29. data/lib/yuriita/query.rb +57 -0
  30. data/lib/yuriita/query_builder.rb +23 -0
  31. data/lib/yuriita/result.rb +26 -0
  32. data/lib/yuriita/route.rb +37 -0
  33. data/lib/yuriita/router.rb +26 -0
  34. data/lib/yuriita/routing.rb +19 -0
  35. data/lib/yuriita/runner.rb +19 -0
  36. data/lib/yuriita/search.rb +13 -0
  37. data/lib/yuriita/search_filter.rb +26 -0
  38. data/lib/yuriita/selects/all_or_explicit.rb +28 -0
  39. data/lib/yuriita/selects/exclusive.rb +48 -0
  40. data/lib/yuriita/selects/multiple.rb +28 -0
  41. data/lib/yuriita/selects/single.rb +49 -0
  42. data/lib/yuriita/table.rb +84 -0
  43. data/lib/yuriita/version.rb +3 -0
  44. data/lib/yuriita.rb +19 -0
  45. 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,9 @@
1
+ module Yuriita
2
+ module Clauses
3
+ class Identity
4
+ def apply(relation)
5
+ relation
6
+ end
7
+ end
8
+ end
9
+ 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,4 @@
1
+ module Yuriita
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Yuriita
2
+ class ParseError < Yuriita::Error
3
+ end
4
+ 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,18 @@
1
+ module Yuriita
2
+ class ExpressionFilter
3
+ attr_reader :input
4
+
5
+ def initialize(input:, &block)
6
+ @input = input
7
+ @block = block
8
+ end
9
+
10
+ def apply(relation)
11
+ block.call(relation)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :block
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Yuriita
2
+ module Inputs
3
+ Expression = Struct.new(:qualifier, :term) do
4
+ def to_s
5
+ if term.match?(" ")
6
+ "#{qualifier}:\"#{term}\""
7
+ else
8
+ "#{qualifier}:#{term}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Yuriita
2
+ module Inputs
3
+ Keyword = Struct.new(:value) do
4
+ def to_s
5
+ value
6
+ end
7
+ end
8
+ end
9
+ 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