grumlin 0.15.4 → 0.16.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e796fc1b5bff2cb474d068ad2a05a2b8a195df1981842745d5c90798ab5e02b7
4
- data.tar.gz: 39cd9ac6bbedfb6a8707d306c6a6e0223856028162778bb665865e387dcf2bc3
3
+ metadata.gz: ce713ebd08c676dfe38124e0286963ea18ee8d5d51f678096fd5f2ae8c28c51c
4
+ data.tar.gz: 6cd25c9ac701a2276fdb470b55b4ebaf123323521c6cd8d4d87365b68d604426
5
5
  SHA512:
6
- metadata.gz: 072df70e88f7192272cad5c6127740ef10d52b5057f42a557e9f4ea722e55f53d222025e7b4d7607666d4c5038d3e630abb55faec7f05c640efed53d442a2b57
7
- data.tar.gz: 32ac57244365458e56c9b2c2705500c87254ba523454817b67f88637e07444d5e0b2abfa361540693f0dedf578b7e5f346c8a2515e536c69ad86e5ee09fa7bf3
6
+ metadata.gz: 81b5b522b5d183240d27cb36285f448e4918386cd267ea84fcffb2cc763d156d272844a4ffab2e00999d5c5be129930d905220506e07702ff150c49ac7388d1b
7
+ data.tar.gz: f4d56b0a66d91f8f454964a8d42de46fa0b6b85595c48e67e09875e01bfdb96eb131e0c6cb5ec395ebeb7dda59c45ff67c445f88e75a12a03436689d9ba14279
data/.rubocop.yml CHANGED
@@ -60,6 +60,9 @@ RSpec/MultipleExpectations:
60
60
  RSpec/DescribeClass:
61
61
  Enabled: false
62
62
 
63
+ RSpec/MultipleMemoizedHelpers:
64
+ Max: 7
65
+
63
66
  Style/WordArray:
64
67
  Exclude:
65
68
  - spec/**/*_spec.rb
@@ -77,3 +80,10 @@ Style/Documentation:
77
80
 
78
81
  Style/MultilineBlockChain:
79
82
  Enabled: false
83
+
84
+
85
+ # TODO:
86
+ # Style/SymbolArray:
87
+ # EnforcedStyle: brackets
88
+ # Style/WordArray:
89
+ # EnforcedStyle: brackets
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.16.0] - 2022-03-11
2
+
3
+ - Query building is rewritten from scratch. No public APIs were changed. [Details](https://github.com/babbel/grumlin/pull/64)
4
+ - Add support for [TextP](https://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/process/traversal/TextP.html)
5
+
1
6
  ## [0.15.4] - 2022-01-20
2
7
 
3
8
  - Move step and expression definitions to a yaml file for better diffs
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.15.4)
4
+ grumlin (0.16.1)
5
5
  async-pool (~> 0.3)
6
6
  async-websocket (~> 0.19)
7
7
  oj (~> 3.12)
@@ -28,7 +28,7 @@ GEM
28
28
  protocol-http (~> 0.22.0)
29
29
  protocol-http1 (~> 0.14.0)
30
30
  protocol-http2 (~> 0.14.0)
31
- async-io (1.32.2)
31
+ async-io (1.33.0)
32
32
  async
33
33
  async-pool (0.3.9)
34
34
  async (>= 1.25)
data/lib/definitions.yml CHANGED
@@ -32,17 +32,21 @@ steps:
32
32
  - in
33
33
  - inE
34
34
  - inV
35
+ - inject
35
36
  - is
36
37
  - label
37
38
  - limit
38
39
  - map
40
+ - none
39
41
  - not
42
+ - option
40
43
  - or
41
44
  - order
42
45
  - out
43
46
  - outE
44
47
  - outV
45
48
  - path
49
+ - profile
46
50
  - project
47
51
  - properties
48
52
  - property
@@ -68,6 +72,7 @@ steps:
68
72
  - V
69
73
  - addE
70
74
  - addV
75
+ - inject
71
76
  configuration:
72
77
  - withSack
73
78
  - withSideEffect
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Action
5
+ START_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
6
+ CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
7
+ REGULAR_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze
8
+
9
+ ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS
10
+
11
+ attr_reader :name, :args, :params, :shortcuts, :next_step, :configuration_steps, :previous_step
12
+
13
+ def initialize(name, args: [], params: {}, previous_step: nil, shortcuts: {}, pool: Grumlin.default_pool)
14
+ @name = name.to_sym
15
+ @args = args # TODO: add recursive validation: only json types or Action
16
+ @params = params # TODO: add recursive validation: only json types
17
+ @previous_step = previous_step
18
+ @shortcuts = shortcuts
19
+ @pool = pool
20
+ end
21
+
22
+ ALL_STEPS.each do |step|
23
+ define_method step do |*args, **params|
24
+ step(step, *args, **params)
25
+ end
26
+ end
27
+
28
+ def step(name, *args, **params)
29
+ Action.new(name, args: args, params: params, previous_step: self, shortcuts: @shortcuts, pool: @pool)
30
+ end
31
+
32
+ def configuration_step?
33
+ CONFIGURATION_STEPS.include?(@name)
34
+ end
35
+
36
+ def start_step?
37
+ START_STEPS.include?(@name)
38
+ end
39
+
40
+ def regular_step?
41
+ REGULAR_STEPS.include?(@name)
42
+ end
43
+
44
+ def supported_step?
45
+ ALL_STEPS.include?(@name)
46
+ end
47
+
48
+ def shortcut?
49
+ @shortcuts.key?(@name)
50
+ end
51
+
52
+ def arguments
53
+ @arguments ||= [*@args].tap do |args|
54
+ args << @params if @params.any?
55
+ end
56
+ end
57
+
58
+ def method_missing(name, *args, **params)
59
+ return step(name, *args, **params) if @shortcuts.key?(name)
60
+
61
+ super
62
+ end
63
+
64
+ def ==(other)
65
+ self.class == other.class &&
66
+ @name == other.name &&
67
+ @args == other.args &&
68
+ @params == other.params &&
69
+ @previous_step == other.previous_step &&
70
+ @shortcuts == other.shortcuts
71
+ end
72
+
73
+ def steps
74
+ @steps ||= Steps.from(self)
75
+ end
76
+
77
+ def to_s(**params)
78
+ StepsSerializers::String.new(steps, **params).serialize
79
+ end
80
+
81
+ # TODO: add human readable mode
82
+ def inspect
83
+ conf_steps, regular_steps = StepsSerializers::HumanReadableBytecode.new(steps).serialize
84
+ "#{conf_steps.any? ? conf_steps : nil}#{regular_steps}"
85
+ end
86
+
87
+ def bytecode(no_return: false)
88
+ StepsSerializers::Bytecode.new(steps, no_return: no_return)
89
+ end
90
+
91
+ def next
92
+ to_enum.next
93
+ end
94
+
95
+ def hasNext # rubocop:disable Naming/MethodName
96
+ to_enum.peek
97
+ true
98
+ rescue StopIteration
99
+ false
100
+ end
101
+
102
+ def to_enum
103
+ @to_enum ||= toList.to_enum
104
+ end
105
+
106
+ def toList
107
+ @pool.acquire do |client|
108
+ client.write(bytecode)
109
+ end
110
+ end
111
+
112
+ def iterate
113
+ @pool.acquire do |client|
114
+ client.write(bytecode(no_return: true))
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def respond_to_missing?(name, _include_private = false)
121
+ @shortcuts.key?(name)
122
+ end
123
+ end
124
+ end
@@ -97,7 +97,7 @@ module Grumlin
97
97
  def write(bytecode)
98
98
  raise NotConnectedError unless connected?
99
99
 
100
- request = to_query(bytecode.to_bytecode)
100
+ request = to_query(bytecode)
101
101
  channel = @request_dispatcher.add_request(request)
102
102
  @transport.write(request)
103
103
 
@@ -130,7 +130,7 @@ module Grumlin
130
130
  op: "bytecode",
131
131
  processor: "traversal",
132
132
  args: {
133
- gremlin: bytecode,
133
+ gremlin: { :@type => "g:Bytecode", :@value => bytecode.serialize },
134
134
  aliases: { g: :g }
135
135
  }
136
136
  }
@@ -2,28 +2,23 @@
2
2
 
3
3
  module Grumlin
4
4
  module Expressions
5
- module P
6
- class << self
7
- class Predicate < TypedValue
8
- def initialize(name, args:, arg_type: nil)
9
- super(type: "P")
10
- @name = name
11
- @args = args
12
- @arg_type = arg_type
13
- end
5
+ class P
6
+ class Predicate
7
+ attr_reader :namespace, :name, :value, :type
14
8
 
15
- def value
16
- @value ||= {
17
- predicate: @name,
18
- value: TypedValue.new(type: @arg_type, value: @args).to_bytecode
19
- }
20
- end
9
+ def initialize(namespace, name, value:, type: nil)
10
+ @namespace = namespace
11
+ @name = name
12
+ @value = value
13
+ @type = type
21
14
  end
15
+ end
22
16
 
17
+ class << self
23
18
  # TODO: support more predicates
24
19
  %i[eq neq].each do |predicate|
25
20
  define_method predicate do |*args|
26
- Predicate.new(predicate, args: args[0])
21
+ Predicate.new("P", predicate, value: args[0])
27
22
  end
28
23
  end
29
24
 
@@ -36,7 +31,7 @@ module Grumlin
36
31
  else
37
32
  args.to_a
38
33
  end
39
- Predicate.new(predicate, args: args, arg_type: "List")
34
+ Predicate.new("P", predicate, value: args, type: "List")
40
35
  end
41
36
  end
42
37
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Expressions
5
+ class TextP < P
6
+ class << self
7
+ %i[containing endingWith notContaining notEndingWith notStartingWith startingWith].each do |predicate|
8
+ define_method predicate do |*args|
9
+ P::Predicate.new("TextP", predicate, value: args[0])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,16 +2,30 @@
2
2
 
3
3
  module Grumlin
4
4
  module Expressions
5
- module WithOptions
5
+ class WithOptions
6
6
  WITH_OPTIONS = Grumlin.definitions.dig(:expressions, :with_options).freeze
7
7
 
8
8
  class << self
9
9
  WITH_OPTIONS.each do |k, v|
10
10
  define_method k do
11
- v
11
+ name = "@#{k}"
12
+ return instance_variable_get(name) if instance_variable_defined?(name)
13
+
14
+ instance_variable_set(name, WithOptions.new(k, v))
12
15
  end
13
16
  end
14
17
  end
18
+
19
+ attr_reader :name, :value
20
+
21
+ def initialize(name, value)
22
+ @name = name
23
+ @value = value
24
+ end
25
+
26
+ def to_s
27
+ "WithOptions.#{@name}"
28
+ end
15
29
  end
16
30
  end
17
31
  end
@@ -4,11 +4,11 @@ module Grumlin
4
4
  module Repository
5
5
  module InstanceMethods
6
6
  def __
7
- with_shortcuts(Grumlin::Expressions::U)
7
+ TraversalStart.new(self.class.shortcuts)
8
8
  end
9
9
 
10
10
  def g
11
- with_shortcuts(Grumlin::Traversal.new)
11
+ TraversalStart.new(self.class.shortcuts)
12
12
  end
13
13
  end
14
14
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Shortcut
5
+ extend Forwardable
6
+
7
+ attr_reader :name, :block
8
+
9
+ def_delegator :@block, :arity
10
+ def_delegator :@block, :source_location
11
+
12
+ def initialize(name, &block)
13
+ @name = name
14
+ @block = block
15
+ end
16
+
17
+ def ==(other)
18
+ @name == other.name && @block == other.block
19
+ end
20
+
21
+ # TODO: to_s, inspect, preview
22
+
23
+ def apply(object, *args, **params)
24
+ object.instance_exec(*args, **params, &@block)
25
+ end
26
+ end
27
+ end
@@ -5,13 +5,17 @@ module Grumlin
5
5
  module Properties
6
6
  extend Grumlin::Shortcuts
7
7
 
8
- shortcut :props do |*_args, **props|
8
+ shortcut :props do |props|
9
+ next if props.nil? # TODO: fixme, add proper support for **params
10
+
9
11
  props.reduce(self) do |tt, (prop, value)|
10
12
  tt.property(prop, value)
11
13
  end
12
14
  end
13
15
 
14
- shortcut :hasAll do |*, **props|
16
+ shortcut :hasAll do |props|
17
+ next if props.nil? # TODO: fixme, add proper support for **params
18
+
15
19
  props.reduce(self) do |tt, (prop, value)|
16
20
  tt.has(prop, value)
17
21
  end
@@ -2,14 +2,7 @@
2
2
 
3
3
  module Grumlin
4
4
  module Shortcuts
5
- module InstanceMethods
6
- def with_shortcuts(obj)
7
- ShortcutProxy.new(obj, self.class.shortcuts, parent: self)
8
- end
9
- end
10
-
11
5
  def self.extended(base)
12
- base.include(InstanceMethods)
13
6
  base.include(Grumlin::Expressions)
14
7
  end
15
8
 
@@ -18,22 +11,28 @@ module Grumlin
18
11
  subclass.shortcuts_from(self)
19
12
  end
20
13
 
21
- def shortcut(name, &block)
14
+ def shortcut(name, shortcut = nil, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
22
15
  name = name.to_sym
23
16
  # TODO: blocklist of names to avoid conflicts with standard methods?
24
- if Grumlin::AnonymousStep::SUPPORTED_STEPS.include?(name)
17
+ if Grumlin::Action::REGULAR_STEPS.include?(name)
25
18
  raise ArgumentError,
26
19
  "cannot use names of standard gremlin steps"
27
20
  end
28
21
 
29
- raise ArgumentError, "shortcut '#{name}' already exists" if shortcuts.key?(name) && shortcuts[name] != block
22
+ if (shortcut.nil? && block.nil?) || (shortcut && block)
23
+ raise ArgumentError, "either shortcut or block must be passed"
24
+ end
25
+
26
+ shortcut ||= Shortcut.new(name, &block)
27
+
28
+ raise ArgumentError, "shortcut '#{name}' already exists" if shortcuts.key?(name) && shortcuts[name] != shortcut
30
29
 
31
- shortcuts[name] = block
30
+ shortcuts[name] = shortcut
32
31
  end
33
32
 
34
33
  def shortcuts_from(other_shortcuts)
35
- other_shortcuts.shortcuts.each do |name, block|
36
- shortcut(name, &block)
34
+ other_shortcuts.shortcuts.each do |name, shortcut|
35
+ shortcut(name, shortcut)
37
36
  end
38
37
  end
39
38
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class ShortcutsApplyer
5
+ class << self
6
+ def call(steps)
7
+ new.call(steps)
8
+ end
9
+ end
10
+
11
+ def call(steps)
12
+ return steps unless steps.uses_shortcuts?
13
+
14
+ shortcuts = steps.shortcuts
15
+
16
+ configuration_steps = process_steps(steps.configuration_steps, shortcuts)
17
+ regular_steps = process_steps(steps.steps, shortcuts)
18
+
19
+ Steps.new(shortcuts).tap do |processed_steps|
20
+ (configuration_steps + regular_steps).each do |step|
21
+ processed_steps.add(step.name, step.arguments)
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def process_steps(steps, shortcuts) # rubocop:disable Metrics/AbcSize
29
+ steps.each_with_object([]) do |step, result|
30
+ arguments = step.arguments.map do |arg|
31
+ arg.is_a?(Steps) ? ShortcutsApplyer.call(arg) : arg
32
+ end
33
+
34
+ if shortcuts.include?(step.name)
35
+ t = TraversalStart.new(shortcuts)
36
+ action = shortcuts[step.name].apply(t, *arguments)
37
+ next if action.nil? || action == t # Shortcut did not add any steps
38
+
39
+ new_steps = ShortcutsApplyer.call(Steps.from(action))
40
+ result.concat(new_steps.configuration_steps)
41
+ result.concat(new_steps.steps)
42
+ else
43
+ result << StepData.new(step.name, arguments)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class StepData
5
+ attr_reader :name, :arguments
6
+
7
+ def initialize(name, arguments)
8
+ @name = name
9
+ @arguments = arguments
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class &&
14
+ @name == other.name &&
15
+ @arguments == other.arguments
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Steps
5
+ CONFIGURATION_STEPS = Action::CONFIGURATION_STEPS
6
+ ALL_STEPS = Action::ALL_STEPS
7
+
8
+ def self.from(action)
9
+ raise ArgumentError, "expected: #{Action}, given: #{action.class}" unless action.is_a?(Action)
10
+
11
+ shortcuts = action.shortcuts
12
+ actions = []
13
+
14
+ until action.nil?
15
+ actions.unshift(action)
16
+ action = action.previous_step
17
+ end
18
+
19
+ new(shortcuts).tap do |chain|
20
+ actions.each do |act|
21
+ chain.add(act.name, act.arguments)
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_reader :configuration_steps, :steps, :shortcuts
27
+
28
+ def initialize(shortcuts, configuration_steps: [], steps: [])
29
+ @shortcuts = shortcuts
30
+ @configuration_steps = configuration_steps
31
+ @steps = steps
32
+ end
33
+
34
+ def add(name, arguments)
35
+ return add_configuration_step(name, arguments) if CONFIGURATION_STEPS.include?(name)
36
+
37
+ StepData.new(name, cast_arguments(arguments)).tap do |step|
38
+ @steps << step
39
+ end
40
+ end
41
+
42
+ def uses_shortcuts?
43
+ shortcuts?(@configuration_steps) || shortcuts?(@steps)
44
+ end
45
+
46
+ def ==(other)
47
+ self.class == other.class &&
48
+ @shortcuts == other.shortcuts &&
49
+ @configuration_steps == other.configuration_steps &&
50
+ @steps == other.steps
51
+ end
52
+
53
+ # TODO: add #bytecode, to_s, inspect
54
+
55
+ private
56
+
57
+ def shortcuts?(steps_ary)
58
+ steps_ary.any? do |step|
59
+ @shortcuts.include?(step.name) || step.arguments.any? do |arg|
60
+ arg.is_a?(Steps) ? arg.uses_shortcuts? : false
61
+ end
62
+ end
63
+ end
64
+
65
+ def add_configuration_step(name, arguments)
66
+ raise ArgumentError, "cannot use configuration steps after start step was used" unless @steps.empty?
67
+
68
+ StepData.new(name, cast_arguments(arguments)).tap do |step|
69
+ @configuration_steps << step
70
+ end
71
+ end
72
+
73
+ def cast_arguments(arguments)
74
+ arguments.map { |arg| arg.is_a?(Action) ? Steps.from(arg) : arg }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class Bytecode < Serializer
6
+ # constructor params: no_return: true|false, default false
7
+ # TODO: add pretty
8
+
9
+ NONE_STEP = StepData.new("none", [])
10
+
11
+ def serialize
12
+ steps = ShortcutsApplyer.call(@steps)
13
+ no_return = @params[:no_return] || false
14
+
15
+ {
16
+ step: (steps.steps + (no_return ? [NONE_STEP] : [])).map { |s| serialize_step(s) }
17
+ }.tap do |v|
18
+ v.merge!(source: steps.configuration_steps.map { |s| serialize_step(s) }) if steps.configuration_steps.any?
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def serialize_step(step)
25
+ [step.name, *step.arguments.map { |arg| serialize_arg(arg) }]
26
+ end
27
+
28
+ def serialize_arg(arg)
29
+ return serialize_typed_value(arg) if arg.is_a?(TypedValue)
30
+ return serialize_predicate(arg) if arg.is_a?(Expressions::P::Predicate)
31
+ return arg.value if arg.is_a?(Expressions::WithOptions)
32
+
33
+ return arg unless arg.is_a?(Steps)
34
+
35
+ { :@type => "g:Bytecode", :@value => Bytecode.new(arg, **@params.merge(no_return: false)).serialize }
36
+ end
37
+
38
+ def serialize_typed_value(value)
39
+ return value.value if value.type.nil?
40
+
41
+ {
42
+ "@type": "g:#{value.type}",
43
+ "@value": value.value
44
+ }
45
+ end
46
+
47
+ def serialize_predicate(value)
48
+ {
49
+ "@type": "g:#{value.namespace}",
50
+ "@value": {
51
+ predicate: value.name,
52
+ value: if value.type.nil?
53
+ value.value
54
+ else
55
+ {
56
+ "@type": "g:#{value.type}",
57
+ "@value": value.value
58
+ }
59
+ end
60
+ }
61
+ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class HumanReadableBytecode < Serializer
6
+ def serialize
7
+ steps = ShortcutsApplyer.call(@steps)
8
+ [serialize_steps(steps.configuration_steps), serialize_steps(steps.steps)]
9
+ end
10
+
11
+ def serialize_steps(steps)
12
+ steps.map { |s| serialize_step(s) }
13
+ end
14
+
15
+ private
16
+
17
+ def serialize_step(step)
18
+ [step.name, *step.arguments.map { |arg| serialize_arg(arg) }]
19
+ end
20
+
21
+ def serialize_arg(arg)
22
+ return arg.to_s if arg.is_a?(TypedValue)
23
+ return serialize_predicate(arg) if arg.is_a?(Expressions::P::Predicate)
24
+ return arg.value if arg.is_a?(Expressions::WithOptions)
25
+
26
+ return arg unless arg.is_a?(Steps)
27
+
28
+ HumanReadableBytecode.new(arg, **@params.merge(no_return: false)).serialize[1]
29
+ end
30
+
31
+ def serialize_predicate(arg)
32
+ "#{arg.name}(#{arg.value})"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class Serializer
6
+ def initialize(steps, **params)
7
+ @steps = steps
8
+ @params = params
9
+ end
10
+
11
+ def serialize
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class String < Serializer
6
+ # constructor params: apply_shortcuts: true|false, default: false
7
+ # constructor params: anonymous: true|false, default: false
8
+ # TODO: add pretty
9
+
10
+ def serialize
11
+ steps = @params[:apply_shortcuts] ? ShortcutsApplyer.call(@steps) : @steps
12
+
13
+ configuration_steps = serialize_steps(steps.configuration_steps)
14
+ regular_steps = serialize_steps(steps.steps)
15
+
16
+ "#{prefix}.#{(configuration_steps + regular_steps).join(".")}"
17
+ end
18
+
19
+ private
20
+
21
+ def prefix
22
+ @prefix ||= @params[:anonymous] ? "__" : "g"
23
+ end
24
+
25
+ def serialize_arg(arg)
26
+ return "\"#{arg}\"" if arg.is_a?(::String) || arg.is_a?(Symbol)
27
+ return "#{arg.type}.#{arg.value}" if arg.is_a?(Grumlin::TypedValue)
28
+ return arg.to_s if arg.is_a?(Grumlin::Expressions::WithOptions)
29
+
30
+ return arg unless arg.is_a?(Steps)
31
+
32
+ StepsSerializers::String.new(arg, anonymous: true, **@params).serialize
33
+ end
34
+
35
+ def serialize_steps(steps)
36
+ steps.map do |step|
37
+ "#{step.name}(#{step.arguments.map { |a| serialize_arg(a) }.join(", ")})"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/grumlin/sugar.rb CHANGED
@@ -6,12 +6,12 @@ module Grumlin
6
6
  base.include(Grumlin::Expressions)
7
7
  end
8
8
 
9
- def __
10
- Grumlin::Expressions::U
9
+ def __(shortcuts = {})
10
+ Grumlin::TraversalStart.new(shortcuts) # TODO: allow only regular and start steps
11
11
  end
12
12
 
13
- def g
14
- Grumlin::Traversal.new
13
+ def g(shortcuts = {})
14
+ Grumlin::TraversalStart.new(shortcuts)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class TraversalStart
5
+ START_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
6
+ REGULAR_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze
7
+ CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
8
+
9
+ ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS
10
+
11
+ ALL_STEPS.each do |step|
12
+ define_method step do |*args, **params|
13
+ step(step, *args, **params)
14
+ end
15
+ end
16
+
17
+ attr_reader :shortcuts
18
+
19
+ def initialize(shortcuts)
20
+ @shortcuts = shortcuts
21
+ end
22
+
23
+ def step(name, *args, **params)
24
+ Action.new(name, args: args, params: params, shortcuts: @shortcuts)
25
+ end
26
+
27
+ def method_missing(name, *args, **params)
28
+ return step(name, *args, **params) if @shortcuts.key?(name)
29
+
30
+ super
31
+ end
32
+
33
+ def __
34
+ TraversalStart.new(@shortcuts) # TODO: allow only regular and start steps
35
+ end
36
+
37
+ def to_s(*)
38
+ self.class.to_s
39
+ end
40
+
41
+ def inspect
42
+ self.class.inspect
43
+ end
44
+
45
+ private
46
+
47
+ def respond_to_missing?(name, _include_private = false)
48
+ @shortcuts.key?(name)
49
+ end
50
+ end
51
+ end
@@ -9,17 +9,6 @@ module Grumlin
9
9
  @value = value
10
10
  end
11
11
 
12
- def to_bytecode
13
- @to_bytecode ||= if type.nil?
14
- value
15
- else
16
- {
17
- "@type": "g:#{type}",
18
- "@value": value
19
- }
20
- end
21
- end
22
-
23
12
  def inspect
24
13
  "<#{type}.#{value}>"
25
14
  end
@@ -27,9 +16,5 @@ module Grumlin
27
16
  def to_s
28
17
  inspect
29
18
  end
30
-
31
- def to_readable_bytecode
32
- inspect
33
- end
34
19
  end
35
20
  end
@@ -16,6 +16,8 @@ module Grumlin
16
16
  "g:Double" => ->(value) { cast_double(value) },
17
17
  "g:Direction" => ->(value) { value },
18
18
  # "g:VertexProperty"=> ->(value) { value }, # TODO: implement me
19
+ "g:TraversalMetrics" => ->(value) { cast_map(value[:@value]) },
20
+ "g:Metrics" => ->(value) { cast_map(value[:@value]) },
19
21
  "g:T" => ->(value) { value.to_sym }
20
22
  }.freeze
21
23
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.15.4"
4
+ VERSION = "0.16.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grumlin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.4
4
+ version: 0.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-20 00:00:00.000000000 Z
11
+ date: 2022-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool
@@ -98,8 +98,7 @@ files:
98
98
  - lib/async/channel.rb
99
99
  - lib/definitions.yml
100
100
  - lib/grumlin.rb
101
- - lib/grumlin/anonymous_step.rb
102
- - lib/grumlin/bytecode.rb
101
+ - lib/grumlin/action.rb
103
102
  - lib/grumlin/client.rb
104
103
  - lib/grumlin/edge.rb
105
104
  - lib/grumlin/expressions/expression.rb
@@ -109,22 +108,28 @@ files:
109
108
  - lib/grumlin/expressions/pop.rb
110
109
  - lib/grumlin/expressions/scope.rb
111
110
  - lib/grumlin/expressions/t.rb
112
- - lib/grumlin/expressions/u.rb
111
+ - lib/grumlin/expressions/text_p.rb
113
112
  - lib/grumlin/expressions/with_options.rb
114
113
  - lib/grumlin/path.rb
115
114
  - lib/grumlin/property.rb
116
115
  - lib/grumlin/repository.rb
117
116
  - lib/grumlin/request_dispatcher.rb
118
- - lib/grumlin/shortcut_proxy.rb
117
+ - lib/grumlin/shortcut.rb
119
118
  - lib/grumlin/shortcuts.rb
120
119
  - lib/grumlin/shortcuts/properties.rb
121
- - lib/grumlin/step.rb
120
+ - lib/grumlin/shortcuts_applyer.rb
121
+ - lib/grumlin/step_data.rb
122
+ - lib/grumlin/steps.rb
123
+ - lib/grumlin/steps_serializers/bytecode.rb
124
+ - lib/grumlin/steps_serializers/human_readable_bytecode.rb
125
+ - lib/grumlin/steps_serializers/serializer.rb
126
+ - lib/grumlin/steps_serializers/string.rb
122
127
  - lib/grumlin/sugar.rb
123
128
  - lib/grumlin/test/rspec.rb
124
129
  - lib/grumlin/test/rspec/db_cleaner_context.rb
125
130
  - lib/grumlin/test/rspec/gremlin_context.rb
126
131
  - lib/grumlin/transport.rb
127
- - lib/grumlin/traversal.rb
132
+ - lib/grumlin/traversal_start.rb
128
133
  - lib/grumlin/traverser.rb
129
134
  - lib/grumlin/typed_value.rb
130
135
  - lib/grumlin/typing.rb
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- class AnonymousStep
5
- attr_reader :name, :previous_step, :configuration_steps
6
-
7
- SUPPORTED_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze
8
-
9
- def initialize(name, *args, configuration_steps: [], previous_step: nil, **params)
10
- @name = name
11
- @previous_step = previous_step
12
- @args = args
13
- @params = params
14
- @configuration_steps = configuration_steps
15
- end
16
-
17
- SUPPORTED_STEPS.each do |step|
18
- define_method(step) do |*args, **params|
19
- step(step, *args, **params)
20
- end
21
- end
22
-
23
- def step(name, *args, **params)
24
- self.class.new(name, *args, previous_step: self, configuration_steps: configuration_steps, **params)
25
- end
26
-
27
- def inspect
28
- bytecode.inspect
29
- end
30
-
31
- def to_s
32
- inspect
33
- end
34
-
35
- def bytecode(no_return: false)
36
- @bytecode ||= Bytecode.new(self, no_return: no_return)
37
- end
38
-
39
- def args
40
- [*@args].tap do |args|
41
- args << @params if @params.any?
42
- end
43
- end
44
- end
45
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- # Incapsulates logic of converting step chains and step arguments to queries that can be sent to the server
5
- # and to human readable strings.
6
- class Bytecode < TypedValue
7
- class NoneStep
8
- def to_bytecode
9
- ["none"]
10
- end
11
- end
12
-
13
- NONE_STEP = NoneStep.new
14
-
15
- def initialize(step, no_return: false)
16
- super(type: "Bytecode")
17
- @step = step
18
- @no_return = no_return
19
- end
20
-
21
- def inspect
22
- configuration_steps = @step.configuration_steps.map do |s|
23
- serialize_arg(s, serialization_method: :to_readable_bytecode)
24
- end
25
- "#{configuration_steps.any? ? configuration_steps : nil}#{to_readable_bytecode}"
26
- end
27
-
28
- def to_s
29
- inspect
30
- end
31
-
32
- def to_readable_bytecode
33
- @to_readable_bytecode ||= steps.map { |s| serialize_arg(s, serialization_method: :to_readable_bytecode) }
34
- end
35
-
36
- def value
37
- @value ||= { step: (steps + (@no_return ? [NONE_STEP] : [])).map { |s| serialize_arg(s) } }.tap do |v|
38
- v.merge!(source: @step.configuration_steps.map { |s| serialize_arg(s) }) if @step.configuration_steps.any?
39
- end
40
- end
41
-
42
- private
43
-
44
- # Serializes step or a step argument to either an executable query or a human readable string representation
45
- # depending on the `serialization_method` parameter. It should be either `:to_readable_bytecode` for human readable
46
- # representation or `:to_bytecode` for query.
47
- def serialize_arg(arg, serialization_method: :to_bytecode)
48
- return arg.public_send(serialization_method) if arg.respond_to?(serialization_method)
49
- return arg unless arg.is_a?(AnonymousStep)
50
-
51
- arg.args.each.with_object([arg.name.to_s]) do |a, res|
52
- res << if a.respond_to?(:bytecode)
53
- a.bytecode.public_send(serialization_method)
54
- else
55
- serialize_arg(a, serialization_method: serialization_method)
56
- end
57
- end
58
- end
59
-
60
- def steps
61
- @steps ||= [].tap do |result|
62
- step = @step
63
- until step.nil?
64
- result.unshift(step)
65
- step = step.previous_step
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- module Expressions
5
- # The module is called U because Underscore and implements __
6
- module U
7
- class << self
8
- Grumlin::AnonymousStep::SUPPORTED_STEPS.each do |step|
9
- define_method step do |*args, **params|
10
- AnonymousStep.new(step, *args, **params)
11
- end
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- class ShortcutProxy
5
- extend Forwardable
6
-
7
- attr_reader :object, :shortcuts
8
-
9
- # shortcuts: {"name": ->(arg) {}}
10
- def initialize(object, shortcuts, parent: nil)
11
- @object = object
12
- @shortcuts = shortcuts
13
- @parent = parent
14
- end
15
-
16
- def method_missing(name, *args, **params)
17
- return @parent.public_send(name, *args, **params) if %i[__ g].include?(name) && !@parent.nil?
18
-
19
- return wrap_result(@object.public_send(name, *args, **params)) if @object.respond_to?(name)
20
-
21
- return wrap_result(instance_exec(*args, **params, &@shortcuts[name])) if @shortcuts.key?(name)
22
-
23
- super
24
- end
25
-
26
- # For some reason the interpreter thinks it's private
27
- public def respond_to_missing?(name, include_private = false) # rubocop:disable Style/AccessModifierDeclarations
28
- name = name.to_sym
29
-
30
- (%i[__ g].include?(name) &&
31
- @parent.respond_to?(name)) ||
32
- @object.respond_to?(name) ||
33
- @shortcuts.key?(name) ||
34
- super
35
- end
36
-
37
- def_delegator :@object, :to_s
38
-
39
- def inspect
40
- @object.inspect
41
- end
42
-
43
- private
44
-
45
- def wrap_result(result)
46
- if result.is_a?(AnonymousStep) || result.is_a?(Traversal)
47
- return self.class.new(result, @shortcuts, parent: @parent)
48
- end
49
-
50
- result
51
- end
52
- end
53
- end
data/lib/grumlin/step.rb DELETED
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- class Step < AnonymousStep
5
- attr_reader :client
6
-
7
- def initialize(pool, name, *args, configuration_steps: [], previous_step: nil, **params)
8
- super(name, *args, previous_step: previous_step, configuration_steps: configuration_steps, **params)
9
- @pool = pool
10
- end
11
-
12
- def next
13
- to_enum.next
14
- end
15
-
16
- def hasNext # rubocop:disable Naming/MethodName
17
- to_enum.peek
18
- true
19
- rescue StopIteration
20
- false
21
- end
22
-
23
- def to_enum
24
- @to_enum ||= toList.to_enum
25
- end
26
-
27
- def toList
28
- @pool.acquire do |client|
29
- client.write(bytecode)
30
- end
31
- end
32
-
33
- def iterate
34
- @pool.acquire do |client|
35
- client.write(bytecode(no_return: true))
36
- end
37
- end
38
-
39
- def step(step_name, *args, **params)
40
- self.class.new(@pool, step_name, *args, previous_step: self, configuration_steps: @configuration_steps, **params)
41
- end
42
- end
43
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- class Traversal
5
- SUPPORTED_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
6
-
7
- CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
8
-
9
- attr_reader :configuration_steps
10
-
11
- def initialize(pool = Grumlin.default_pool, configuration_steps: [])
12
- @pool = pool
13
- @configuration_steps = configuration_steps
14
- end
15
-
16
- def inspect
17
- "#<#{self.class}>"
18
- end
19
-
20
- def to_s
21
- inspect
22
- end
23
-
24
- CONFIGURATION_STEPS.each do |step|
25
- define_method step do |*args, **params|
26
- self.class.new(@pool, configuration_steps: @configuration_steps + [AnonymousStep.new(step, *args, **params)])
27
- end
28
- end
29
-
30
- SUPPORTED_STEPS.each do |step|
31
- define_method step do |*args, **params|
32
- Step.new(@pool, step, *args, configuration_steps: @configuration_steps, **params)
33
- end
34
- end
35
- end
36
- end