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
@@ -0,0 +1,57 @@
1
+ module Yuriita
2
+ class Query
3
+ include Enumerable
4
+
5
+ attr_reader :inputs
6
+
7
+ def initialize(inputs: [])
8
+ @inputs = inputs
9
+ end
10
+
11
+ def ==(other)
12
+ other.is_a?(self.class) && other.inputs == inputs
13
+ end
14
+
15
+ def keywords
16
+ inputs.select{ |input| input.is_a?(Yuriita::Inputs::Keyword) }
17
+ end
18
+
19
+ def each(&block)
20
+ block or return enum_for(__method__) { size }
21
+ inputs.each(&block)
22
+ self
23
+ end
24
+
25
+ def add_input(input)
26
+ inputs << input
27
+ self
28
+ end
29
+ alias << add_input
30
+
31
+ def delete(input)
32
+ inputs.delete(input)
33
+ self
34
+ end
35
+
36
+ def delete_if
37
+ block_given? or return enum_for(__method__) { size }
38
+ select { |input| yield input }.each { |input| inputs.delete(input) }
39
+ self
40
+ end
41
+
42
+ def size
43
+ inputs.size
44
+ end
45
+ alias length size
46
+
47
+ def initialize_dup(original)
48
+ super
49
+ @inputs = original.instance_variable_get(:@inputs).dup
50
+ end
51
+
52
+ def initialize_clone(original)
53
+ super
54
+ @inputs = original.instance_variable_get(:@inputs).clone
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ module Yuriita
2
+ class QueryBuilder
3
+ def self.build(input)
4
+ new.build(input)
5
+ end
6
+
7
+ def initialize(lexer: Lexer, parser: Parser)
8
+ @lexer = Lexer
9
+ @parser = Parser
10
+ end
11
+
12
+ def build(input)
13
+ tokens = lexer.lex(input)
14
+ parser.parse(tokens)
15
+ rescue RLTK::LexingError, RLTK::NotInLanguage, RLTK::BadToken, EOFError
16
+ raise ParseError
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :lexer, :parser
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ module Yuriita
2
+ class Result
3
+ def self.error(exception)
4
+ new(exception: exception)
5
+ end
6
+
7
+ def self.success(relation)
8
+ new(relation: relation)
9
+ end
10
+
11
+ attr_reader :relation, :exception
12
+
13
+ def initialize(relation: nil, exception: nil)
14
+ @relation = relation
15
+ @exception = exception
16
+
17
+ if @relation.nil? && @exception.nil?
18
+ raise ArgumentError, "Must provide a relation or an exception"
19
+ end
20
+ end
21
+
22
+ def successful?
23
+ exception.nil?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ module Yuriita
2
+ class Route
3
+ attr_reader :collection
4
+
5
+ def initialize(collection, matcher, to)
6
+ @collection = collection
7
+ @matcher = matcher
8
+ @to = to
9
+ end
10
+
11
+ def match?(input)
12
+ case matcher
13
+ when String
14
+ # TODO better matching
15
+ # This won't work for string matching becuase to_s will use quotes
16
+ # Could we parse it and compare the queries? This would maybe let us
17
+ # catch parse exceptions
18
+ #
19
+ # If we do parse the matcher we could warn if it resulted in more than
20
+ # two inputs.
21
+ input.to_s == matcher
22
+ when Proc
23
+ matcher.call(input)
24
+ else
25
+ matcher.match?(input)
26
+ end
27
+ end
28
+
29
+ def action
30
+ to
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :matcher, :to
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ module Yuriita
2
+ class Router
3
+ def initialize
4
+ @routes = []
5
+ end
6
+
7
+ def append_route(collection, matcher, to:)
8
+ routes.append Route.new(collection, matcher, to)
9
+ end
10
+
11
+ def route(input, relation)
12
+ if route = match_for(input)
13
+ collection = route.collection
14
+ collection.dispatch(route.action, input, relation)
15
+ end
16
+ end
17
+
18
+ def match_for(input)
19
+ routes.detect { |route| route.match?(input) }
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :routes
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ module Yuriita
2
+ module Routing
3
+ extend ::ActiveSupport::Concern
4
+
5
+ included do
6
+ cattr_accessor :router, default: Yuriita::Router.new
7
+ end
8
+
9
+ class_methods do
10
+ def routing(matcher, to:)
11
+ router.append_route(self, matcher, to: to)
12
+ end
13
+
14
+ def route(input, relation)
15
+ router.route(input, relation)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Yuriita
2
+ class Runner
3
+ def initialize(relation:, configuration:, **options)
4
+ @relation = relation
5
+ @configuration = configuration
6
+ @executor = options.fetch(:executor, Executor)
7
+ @assembler = options.fetch(:assembler, Assembler)
8
+ end
9
+
10
+ def run(query)
11
+ clauses = assembler.new(configuration).build(query)
12
+ executor.new(clauses).run(relation)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :relation, :configuration, :executor, :assembler
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require "active_model"
2
+
3
+ module Yuriita
4
+ class Search
5
+ include ActiveModel::Model
6
+
7
+ def initialize(table, param_key)
8
+ define_singleton_method(param_key) do
9
+ table.q
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module Yuriita
2
+ class SearchFilter
3
+ attr_reader :input
4
+
5
+ def initialize(input:, combination:, &block)
6
+ @input = input
7
+ @combination = combination
8
+ @block = block
9
+ end
10
+
11
+ def apply(relation, keywords)
12
+ relations = keywords.map do |keyword|
13
+ block.call(relation, keyword.to_s)
14
+ end
15
+
16
+ combination.new(
17
+ base_relation: relation,
18
+ relations: relations,
19
+ ).combine
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :combination, :block
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Yuriita
2
+ module Selects
3
+ class AllOrExplicit
4
+ def initialize(options:, query:)
5
+ @options = options
6
+ @query = query
7
+ end
8
+
9
+ def filters
10
+ active_options.map(&:filter)
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :options, :query
16
+
17
+ def active_options
18
+ explicit_options.presence || options
19
+ end
20
+
21
+ def explicit_options
22
+ options.select do |option|
23
+ query.include?(option.input)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ module Yuriita
2
+ module Selects
3
+ class Exclusive
4
+ def initialize(options:, default:, query:)
5
+ @options = options
6
+ @default = default
7
+ @query = query
8
+ end
9
+
10
+ def filter
11
+ active_option.filter
12
+ end
13
+
14
+ def selected?(option)
15
+ active_option == option
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :options, :default, :query
21
+
22
+ def active_option
23
+ chosen_option || default
24
+ end
25
+
26
+ def chosen_option
27
+ input = last_matching_input
28
+ if input.present?
29
+ option_for(input)
30
+ end
31
+ end
32
+
33
+ def last_matching_input
34
+ matching_inputs.last
35
+ end
36
+
37
+ def matching_inputs
38
+ query.select do |input|
39
+ options.any? { |option| option.input == input }
40
+ end
41
+ end
42
+
43
+ def option_for(input)
44
+ options.detect { |option| option.input == input }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module Yuriita
2
+ module Selects
3
+ class Multiple
4
+ def initialize(options:, query:)
5
+ @options = options
6
+ @query = query
7
+ end
8
+
9
+ def filters
10
+ active_options.map(&:filter)
11
+ end
12
+
13
+ def selected?(option)
14
+ active_options.include?(option)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :options, :query
20
+
21
+ def active_options
22
+ options.select do |option|
23
+ query.include?(option.input)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ module Yuriita
2
+ module Selects
3
+ class Single
4
+ def initialize(options:, query:)
5
+ @options = options
6
+ @query = query
7
+ end
8
+
9
+ def filter
10
+ if active_option.present?
11
+ active_option.filter
12
+ end
13
+ end
14
+
15
+ def selected?(option)
16
+ if active_option.present?
17
+ active_option == option
18
+ else
19
+ false
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :options, :query
26
+
27
+ def active_option
28
+ input = last_matching_input
29
+ if input.present?
30
+ option_for(input)
31
+ end
32
+ end
33
+
34
+ def last_matching_input
35
+ matching_inputs.last
36
+ end
37
+
38
+ def matching_inputs
39
+ query.select do |input|
40
+ options.any? { |option| option.input == input }
41
+ end
42
+ end
43
+
44
+ def option_for(input)
45
+ options.detect { |option| option.input == input }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,84 @@
1
+ require "active_support"
2
+ require "active_model"
3
+ require "yuriita/runner"
4
+
5
+ module Yuriita
6
+ class Table
7
+ include ActiveModel::Model
8
+
9
+ SPACE = " ".freeze
10
+ EMPTY_STRING = "".freeze
11
+
12
+ def initialize(
13
+ relation:,
14
+ params:,
15
+ configuration:,
16
+ param_key: :q
17
+ )
18
+ @relation = relation
19
+ @params = params
20
+ @configuration = configuration
21
+ @param_key = param_key
22
+ @search = Yuriita::Search.new(self, @param_key)
23
+ end
24
+
25
+ attr_reader :search
26
+
27
+ def q
28
+ output =
29
+ if user_input?
30
+ user_input
31
+ else
32
+ default_input
33
+ end
34
+
35
+ if output.blank?
36
+ EMPTY_STRING
37
+ else
38
+ output + SPACE
39
+ end
40
+ end
41
+
42
+ def items
43
+ Runner.new(relation: relation, configuration: configuration).run(query)
44
+ end
45
+
46
+ def filtered?
47
+ user_input? && user_query != default_query
48
+ end
49
+
50
+ def default_input
51
+ configuration.default_input
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :relation, :params, :configuration, :param_key
57
+
58
+ def query
59
+ if user_input?
60
+ user_query
61
+ else
62
+ default_query
63
+ end
64
+ end
65
+
66
+ def user_input
67
+ params.fetch(param_key, EMPTY_STRING)
68
+ end
69
+
70
+ def user_input?
71
+ params.key?(param_key)
72
+ end
73
+
74
+ def default_query
75
+ QueryBuilder.build(default_input)
76
+ end
77
+
78
+ def user_query
79
+ QueryBuilder.build(user_input)
80
+ rescue ParseError
81
+ Query.new
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module Yuriita
2
+ VERSION = "0.2.1"
3
+ end
data/lib/yuriita.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "zeitwerk"
2
+ loader = Zeitwerk::Loader.for_gem
3
+ loader.ignore("#{__dir__}/yuri-ita.rb")
4
+ loader.collapse("#{__dir__}/yuriita/errors")
5
+ loader.setup
6
+
7
+ module Yuriita
8
+ def self.filter(relation, input, configuration)
9
+ query = build_query(input)
10
+ relation = Runner.new(relation: relation, configuration: configuration).run(query)
11
+ Result.success(relation)
12
+ rescue ParseError => exception
13
+ Result.error(exception)
14
+ end
15
+
16
+ def self.build_query(input)
17
+ QueryBuilder.build(input)
18
+ end
19
+ end