gamefic 2.4.0 → 3.0.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +41 -40
  3. data/.rspec-opal +2 -0
  4. data/.solargraph.yml +20 -3
  5. data/CHANGELOG.md +9 -0
  6. data/Rakefile +11 -1
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/gamefic.gemspec +5 -2
  10. data/lib/gamefic/action.rb +52 -183
  11. data/lib/gamefic/active/cue.rb +25 -0
  12. data/lib/gamefic/active/epic.rb +68 -0
  13. data/lib/gamefic/active/messaging.rb +43 -0
  14. data/lib/gamefic/active/take.rb +69 -0
  15. data/lib/gamefic/active.rb +95 -192
  16. data/lib/gamefic/actor.rb +2 -0
  17. data/lib/gamefic/block.rb +28 -0
  18. data/lib/gamefic/command.rb +16 -6
  19. data/lib/gamefic/core_ext/array.rb +4 -4
  20. data/lib/gamefic/core_ext/string.rb +10 -5
  21. data/lib/gamefic/describable.rb +39 -65
  22. data/lib/gamefic/dispatcher.rb +63 -32
  23. data/lib/gamefic/entity.rb +44 -19
  24. data/lib/gamefic/logging.rb +32 -0
  25. data/lib/gamefic/messenger.rb +66 -0
  26. data/lib/gamefic/narrative.rb +104 -0
  27. data/lib/gamefic/node.rb +44 -53
  28. data/lib/gamefic/plot.rb +60 -93
  29. data/lib/gamefic/props/default.rb +41 -0
  30. data/lib/gamefic/props/multiple_choice.rb +65 -0
  31. data/lib/gamefic/props/pause.rb +11 -0
  32. data/lib/gamefic/props/yes_or_no.rb +21 -0
  33. data/lib/gamefic/props.rb +10 -0
  34. data/lib/gamefic/query/base.rb +45 -126
  35. data/lib/gamefic/query/general.rb +46 -0
  36. data/lib/gamefic/query/result.rb +20 -0
  37. data/lib/gamefic/query/scoped.rb +41 -0
  38. data/lib/gamefic/query/text.rb +30 -31
  39. data/lib/gamefic/query.rb +7 -15
  40. data/lib/gamefic/response.rb +118 -0
  41. data/lib/gamefic/rulebook/calls.rb +90 -0
  42. data/lib/gamefic/rulebook/events.rb +79 -0
  43. data/lib/gamefic/rulebook/hooks.rb +57 -0
  44. data/lib/gamefic/rulebook/scenes.rb +68 -0
  45. data/lib/gamefic/rulebook.rb +139 -0
  46. data/lib/gamefic/scanner.rb +103 -0
  47. data/lib/gamefic/scene/activity.rb +9 -17
  48. data/lib/gamefic/scene/conclusion.rb +6 -5
  49. data/lib/gamefic/scene/default.rb +88 -0
  50. data/lib/gamefic/scene/multiple_choice.rb +14 -69
  51. data/lib/gamefic/scene/pause.rb +9 -13
  52. data/lib/gamefic/scene/yes_or_no.rb +6 -46
  53. data/lib/gamefic/scene.rb +11 -7
  54. data/lib/gamefic/scope/base.rb +44 -0
  55. data/lib/gamefic/scope/children.rb +16 -0
  56. data/lib/gamefic/scope/family.rb +20 -0
  57. data/lib/gamefic/scope/myself.rb +13 -0
  58. data/lib/gamefic/scope/parent.rb +13 -0
  59. data/lib/gamefic/scope/siblings.rb +14 -0
  60. data/lib/gamefic/scope.rb +8 -0
  61. data/lib/gamefic/scriptable/actions.rb +156 -0
  62. data/lib/gamefic/scriptable/entities.rb +76 -0
  63. data/lib/gamefic/scriptable/events.rb +65 -0
  64. data/lib/gamefic/scriptable/proxy.rb +55 -0
  65. data/lib/gamefic/scriptable/queries.rb +73 -0
  66. data/lib/gamefic/scriptable/scenes.rb +162 -0
  67. data/lib/gamefic/scriptable.rb +167 -73
  68. data/lib/gamefic/snapshot.rb +36 -0
  69. data/lib/gamefic/stage.rb +51 -0
  70. data/lib/gamefic/subplot.rb +51 -79
  71. data/lib/gamefic/syntax/template.rb +67 -0
  72. data/lib/gamefic/syntax.rb +102 -83
  73. data/lib/gamefic/vault.rb +50 -0
  74. data/lib/gamefic/version.rb +1 -1
  75. data/lib/gamefic.rb +26 -15
  76. data/spec-opal/spec_helper.rb +24 -0
  77. metadata +91 -29
  78. data/lib/gamefic/element.rb +0 -46
  79. data/lib/gamefic/keywords.rb +0 -52
  80. data/lib/gamefic/messaging.rb +0 -43
  81. data/lib/gamefic/plot/darkroom.rb +0 -120
  82. data/lib/gamefic/plot/host.rb +0 -42
  83. data/lib/gamefic/plot/snapshot.rb +0 -27
  84. data/lib/gamefic/query/children.rb +0 -9
  85. data/lib/gamefic/query/descendants.rb +0 -15
  86. data/lib/gamefic/query/external.rb +0 -39
  87. data/lib/gamefic/query/family.rb +0 -18
  88. data/lib/gamefic/query/itself.rb +0 -13
  89. data/lib/gamefic/query/matches.rb +0 -75
  90. data/lib/gamefic/query/parent.rb +0 -9
  91. data/lib/gamefic/query/siblings.rb +0 -13
  92. data/lib/gamefic/query/tree.rb +0 -17
  93. data/lib/gamefic/scene/base.rb +0 -142
  94. data/lib/gamefic/scene/multiple_scene.rb +0 -29
  95. data/lib/gamefic/serialize.rb +0 -196
  96. data/lib/gamefic/world/callbacks.rb +0 -135
  97. data/lib/gamefic/world/commands.rb +0 -181
  98. data/lib/gamefic/world/entities.rb +0 -98
  99. data/lib/gamefic/world/playbook.rb +0 -233
  100. data/lib/gamefic/world/players.rb +0 -37
  101. data/lib/gamefic/world/scenes.rb +0 -228
  102. data/lib/gamefic/world.rb +0 -18
data/lib/gamefic/node.rb CHANGED
@@ -1,58 +1,45 @@
1
- # Exception raised when setting a node's parent would cause
2
- # a circular reference, e.g., A -> A or A -> B -> A
3
- class CircularNodeReferenceError < RuntimeError; end
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
4
 
5
5
  module Gamefic
6
+ # Exception raised when setting a node's parent to an invalid object.
7
+ #
8
+ class NodeError < RuntimeError; end
9
+
10
+ # Parent/child relationships for objects.
11
+ #
6
12
  module Node
13
+ # The object's parent.
14
+ #
15
+ # @return [Node, nil]
16
+ attr_reader :parent
17
+
7
18
  # An array of the object's children.
8
19
  #
9
- # @return [Array]
20
+ # @return [Array<Node>]
10
21
  def children
11
- @children ||= []
12
- @children.clone
22
+ child_set.to_a.freeze
13
23
  end
14
24
 
15
25
  # Get a flat array of all descendants.
16
26
  #
17
- # @return [Array]
27
+ # @return [Array<Node>]
18
28
  def flatten
19
- array = Array.new
20
- children.each { |child|
21
- array = array + recurse_flatten(child)
22
- }
23
- array
24
- end
25
-
26
- # The object's parent.
27
- #
28
- # @return [Object]
29
- def parent
30
- @parent
29
+ children.flat_map { |child| [child] + child.flatten }
31
30
  end
32
31
 
33
32
  # Set the object's parent.
34
33
  #
34
+ # @param node [Node, nil]
35
35
  def parent=(node)
36
- return if node == @parent
37
- if node == self
38
- raise CircularNodeReferenceError.new("Node cannot be its own parent")
39
- end
40
- # Do not permit circular references
41
- if node != nil and node.parent == self
42
- node.parent = nil
43
- end
44
- if node != nil and flatten.include?(node)
45
- raise CircularNodeReferenceError.new("Node cannot be a child of a descendant")
46
- end
47
- if @parent != node
48
- if @parent != nil
49
- @parent.send(:rem_child, self)
50
- end
51
- @parent = node
52
- if @parent != nil
53
- @parent.send(:add_child, self)
54
- end
55
- end
36
+ return if node == parent
37
+
38
+ validate_parent node
39
+
40
+ parent&.rem_child self
41
+ @parent = node
42
+ parent&.add_child self
56
43
  end
57
44
 
58
45
  # Determine if external objects can interact with this object's children.
@@ -64,31 +51,35 @@ module Gamefic
64
51
  true
65
52
  end
66
53
 
54
+ # True if this node is the other's parent.
55
+ #
56
+ # @param other [Node]
57
+ def has?(other)
58
+ other.parent == self
59
+ end
60
+
67
61
  protected
68
62
 
69
63
  def add_child(node)
70
- children
71
- @children.push(node)
64
+ child_set.add node
72
65
  end
73
66
 
74
67
  def rem_child(node)
75
- children
76
- @children.delete(node)
68
+ child_set.delete node
77
69
  end
78
70
 
79
- def concat_children(children)
80
- children.concat(children)
71
+ private
72
+
73
+ def child_set
74
+ @child_set ||= Set.new
81
75
  end
82
76
 
83
- private
77
+ def validate_parent(node)
78
+ raise NodeError, 'Parent must be a Node' unless node.is_a?(Node) || node.nil?
79
+
80
+ raise NodeError, "Node cannot be its own parent" if node == self
84
81
 
85
- def recurse_flatten(node)
86
- array = Array.new
87
- array.push(node)
88
- node.children.each { |child|
89
- array = array + recurse_flatten(child)
90
- }
91
- return array
82
+ raise NodeError, 'Node cannot be a child of a descendant' if flatten.include?(node)
92
83
  end
93
84
  end
94
85
  end
data/lib/gamefic/plot.rb CHANGED
@@ -1,114 +1,81 @@
1
- require 'gamefic/query'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Gamefic
4
- # A plot controls the game narrative and manages the world model.
5
- # Authors typically build plots through scripts that are executed in a
6
- # special container called a stage. All of the elements that compose the
7
- # narrative (characters, locations, scenes, etc.) reside in the stage's
8
- # scope. Game engines use the plot to receive game data and process user
9
- # input.
4
+ # The plot is the central narrative. It provides a script interface with
5
+ # methods for creating entities, actions, scenes, and hooks.
10
6
  #
11
- class Plot
12
- autoload :Snapshot, 'gamefic/plot/snapshot'
13
- autoload :Darkroom, 'gamefic/plot/darkroom'
14
- autoload :Host, 'gamefic/plot/host'
15
-
16
- # @return [Hash]
17
- attr_reader :metadata
7
+ class Plot < Narrative
8
+ def ready
9
+ super
10
+ subplots.each(&:ready)
11
+ players.each(&:start_take)
12
+ subplots.delete_if(&:concluding?)
13
+ players.select(&:concluding?).each { |plyr| rulebook.run_player_conclude_blocks plyr }
14
+ end
18
15
 
19
- attr_reader :static
16
+ def update
17
+ players.each(&:finish_take)
18
+ super
19
+ subplots.each(&:update)
20
+ end
20
21
 
21
- include World
22
- include Scriptable
23
- # @!parse extend Scriptable::ClassMethods
24
- include Snapshot
25
- include Host
26
- include Serialize
22
+ # Remove an actor from the game.
23
+ #
24
+ # Calling `uncast` on the plot will also remove the actor from its
25
+ # subplots.
26
+ #
27
+ # @param actor [Actor]
28
+ # @return [Actor]
29
+ def uncast actor
30
+ subplots.each { |sp| sp.uncast actor }
31
+ super
32
+ end
27
33
 
28
- exclude_from_serial [:@static]
34
+ # Get an array of all the current subplots.
35
+ #
36
+ # @return [Array<Subplot>]
37
+ def subplots
38
+ @subplots ||= []
39
+ end
29
40
 
30
- # @param metadata [Hash]
31
- def initialize metadata: {}
32
- @metadata = metadata
33
- run_scripts
34
- @static = [self] + scene_classes + entities
41
+ # Start a new subplot based on the provided class.
42
+ #
43
+ # @param subplot_class [Class<Gamefic::Subplot>] The Subplot class
44
+ # @param introduce [Gamefic::Actor, Array<Gamefic::Actor>] Players to introduce
45
+ # @param config [Hash] Subplot configuration
46
+ # @return [Gamefic::Subplot]
47
+ def branch subplot_class = Gamefic::Subplot, introduce: [], **config
48
+ subplot_class.new(self, introduce: introduce, **config)
49
+ .tap { |sub| subplots.push sub }
35
50
  end
36
51
 
37
- def plot
38
- self
52
+ def save
53
+ Snapshot.save self
39
54
  end
40
55
 
41
- # Get an Array of the Plot's current Syntaxes.
42
- #
43
- # @return [Array<Syntax>]
44
- def syntaxes
45
- playbook.syntaxes
56
+ def inspect
57
+ "#<#{self.class}>"
46
58
  end
47
59
 
48
- # Prepare the Plot for the next turn of gameplay.
49
- # This method is typically called by the Engine that manages game
50
- # execution.
51
- #
52
- def ready
53
- playbook.freeze
54
- call_ready
55
- call_player_ready
56
- subplots.each { |s| s.ready }
57
- players.each do |p|
58
- p.state.replace(
59
- p.scene.state
60
- .merge({
61
- messages: p.messages,
62
- last_prompt: p.last_prompt,
63
- last_input: p.last_input,
64
- queue: p.queue
65
- })
66
- .merge(p.state)
67
- )
68
- p.output.replace(p.state)
69
- p.state.clear
70
- end
60
+ def detach
61
+ cache = [@rulebook]
62
+ @rulebook = nil
63
+ cache.concat subplots.map(&:detach)
64
+ cache
71
65
  end
72
66
 
73
- # Update the Plot's current turn of gameplay.
74
- # This method is typically called by the Engine that manages game
75
- # execution.
76
- #
77
- def update
78
- entities.each { |e| e.flush }
79
- call_before_player_update
80
- players.each do |p|
81
- next unless p.scene
82
- p.last_input = p.queue.last
83
- p.last_prompt = p.scene.prompt
84
- p.scene.update
85
- if p.scene.is_a?(Scene::Conclusion)
86
- player_conclude_procs.each do |proc|
87
- proc.call p
88
- end
89
- end
90
- end
91
- call_player_update
92
- call_update
93
- subplots.delete_if(&:concluded?)
94
- subplots.each(&:update)
67
+ def attach(cache)
68
+ super(cache.shift)
69
+ subplots.each { |subplot| subplot.attach cache.shift }
95
70
  end
96
71
 
97
- # Send a message to a group of entities.
98
- #
99
- # @param entities [Array<Entity>]
100
- # @param message [String]
101
- def tell entities, message
102
- entities.each { |entity|
103
- entity.tell message
104
- }
72
+ def hydrate
73
+ super
74
+ subplots.each(&:hydrate)
105
75
  end
106
- end
107
- end
108
76
 
109
- module Gamefic
110
- # @yieldself [Gamefic::Plot]
111
- def self.script &block
112
- Gamefic::Plot.script &block
77
+ def self.restore data
78
+ Snapshot.restore data
79
+ end
113
80
  end
114
81
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Props
5
+ # A collection of data related to a scene. Scenes define which Props class
6
+ # they use. Props can be accessed in a scene's on_start and on_finish
7
+ # callbacks.
8
+ #
9
+ # Props::Default includes the most common attributes that a scene requires.
10
+ # Scenes are able but not required to subclass it. Some scenes, like
11
+ # MultipleChoice, use specialized Props subclasses, but in many cases,
12
+ # Props::Default is sufficient.
13
+ #
14
+ class Default
15
+ # @return [String]
16
+ attr_writer :prompt
17
+
18
+ # @return [String]
19
+ attr_accessor :input
20
+
21
+ # A freeform dictionary of objects related to the scene. Plots can pass
22
+ # opts to be included in the context when they cue scenes.
23
+ #
24
+ # @return [Hash]
25
+ attr_reader :context
26
+ alias data context
27
+
28
+ # @param scene [Scene, nil]
29
+ # @param context [Hash]
30
+ def initialize name, type, **context
31
+ @scene_name = name
32
+ @scene_type = type
33
+ @context = context
34
+ end
35
+
36
+ def prompt
37
+ @prompt ||= '>'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Props
5
+ # Props for MultipleChoice scenes.
6
+ #
7
+ class MultipleChoice < Default
8
+ # A message to send the player for an invalid choice. A formatting
9
+ # token named %<input>s can be used to inject the user input.
10
+ #
11
+ # @return [String]
12
+ attr_writer :invalid_message
13
+
14
+ # The array of available options.
15
+ #
16
+ # @return [Array<String>]
17
+ def options
18
+ @options ||= []
19
+ end
20
+
21
+ def invalid_message
22
+ @invalid_message ||= '"%<input>s" is not a valid choice.'
23
+ end
24
+
25
+ # The zero-based index of the selected option.
26
+ #
27
+ # @return [Integer, nil]
28
+ def index
29
+ return nil unless input
30
+
31
+ @index ||= index_by_number || index_by_text
32
+ end
33
+
34
+ # The one-based index of the selected option.
35
+ #
36
+ # @return [Integer, nil]
37
+ def number
38
+ return nil unless index
39
+
40
+ index + 1
41
+ end
42
+
43
+ # The full text of the selected option.
44
+ #
45
+ # @return [String, nil]
46
+ def selection
47
+ return nil unless index
48
+
49
+ options[index]
50
+ end
51
+
52
+ private
53
+
54
+ def index_by_number
55
+ return input.to_i - 1 if input.match(/^\d+$/) && options[input.to_i - 1]
56
+
57
+ nil
58
+ end
59
+
60
+ def index_by_text
61
+ options.find_index { |text| input.downcase == text.downcase }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module Gamefic
2
+ module Props
3
+ # Props for Pause scenes.
4
+ #
5
+ class Pause < Default
6
+ def prompt
7
+ @prompt ||= 'Press enter to continue...'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Props
5
+ # A MultipleChoice variant that only allows Yes or No.
6
+ #
7
+ class YesOrNo < MultipleChoice
8
+ def yes?
9
+ selection == 'Yes'
10
+ end
11
+
12
+ def no?
13
+ selection == 'No'
14
+ end
15
+
16
+ def options
17
+ @options ||= %w[Yes No].freeze
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gamefic
4
+ module Props
5
+ require 'gamefic/props/default'
6
+ require 'gamefic/props/multiple_choice'
7
+ require 'gamefic/props/pause'
8
+ require 'gamefic/props/yes_or_no'
9
+ end
10
+ end
@@ -1,160 +1,79 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Gamefic
2
4
  module Query
5
+ # A base class for entity-based queries that can be applied to responses.
6
+ # Each query represents an attempt to match an argument in a command to a
7
+ # game entity.
8
+ #
3
9
  class Base
4
- NEST_REGEXP = / in | on | of | from | inside | from inside /
5
-
10
+ # @return [Array<Object>]
6
11
  attr_reader :arguments
7
12
 
8
- def initialize *args
9
- @arguments = args
10
- end
11
-
12
- # Determine whether the query allows ambiguous entity references.
13
- # If false, actions that use this query will only be valid if the token
14
- # passed into it resolves to a single entity. If true, actions will
15
- # accept an array of matching entities instead.
16
- # Queries are not ambiguous by default (ambiguous? == false).
17
- #
18
13
  # @return [Boolean]
19
- def ambiguous?
20
- false
21
- end
14
+ attr_reader :ambiguous
22
15
 
23
- # Subclasses should override this method with the logic required to collect
24
- # all entities that exist in the query's context.
16
+ # @raise [ArgumentError] if any of the arguments are nil
25
17
  #
26
- # @return [Array<Object>]
27
- def context_from(subject)
28
- []
29
- end
18
+ # @param arguments [Array<Object>]
19
+ # @param ambiguous [Boolean]
20
+ def initialize *arguments, ambiguous: false
21
+ raise ArgumentError, "nil argument in query" if arguments.any?(&:nil?)
30
22
 
31
- # Get a collection of objects that exist in the subject's context and
32
- # match the provided token. The result is provided as a Matches object.
33
- #
34
- # @param subject [Entity]
35
- # @param token [String]
36
- # @param continued [Boolean]
37
- # @return [Gamefic::Query::Matches]
38
- def resolve(subject, token, continued: false)
39
- available = context_from(subject)
40
- return Matches.new([], '', token) if available.empty?
41
- if continued
42
- return Matches.execute(available, token, continued: continued)
43
- elsif nested?(token)
44
- drill = denest(available, token).select { |e| accept?(e) }
45
- return Matches.new(drill, token, '') unless drill.length != 1
46
- return Matches.new([], '', token)
47
- end
48
- result = available.select { |e| e.specified?(token) }
49
- result = available.select { |e| e.specified?(token, fuzzy: true) } if result.empty?
50
- result.keep_if { |e| accept? e }
51
- Matches.new(result, (result.empty? ? '' : token), (result.empty? ? token : ''))
23
+ @arguments = arguments
24
+ @ambiguous = ambiguous
52
25
  end
53
26
 
54
- def include?(subject, object)
55
- return false unless accept?(object)
56
- result = context_from(subject)
57
- result.include?(object)
27
+ # @param subject [Gamefic::Entity]
28
+ # @param token [String]
29
+ # @return [Result]
30
+ def query(subject, token)
31
+ raise "#query not implemented for #{self.class}"
58
32
  end
59
33
 
60
- # A ranking of how precise the query's arguments are.
61
- #
62
- # Query precision is a factor in calculating Action#rank.
63
- #
64
34
  # @return [Integer]
65
35
  def precision
66
- if @precision.nil?
67
- @precision = 1
68
- arguments.each { |a|
69
- if a.is_a?(Class)
70
- @precision += 100
71
- elsif a.is_a?(Gamefic::Entity)
72
- @precision += 1000
73
- end
74
- }
75
- @precision
76
- end
77
- @precision
36
+ @precision ||= calculate_precision
78
37
  end
79
- alias rank precision
80
38
 
81
- def signature
82
- "#{self.class.to_s.split('::').last.downcase}(#{simplify_arguments.join(', ')})"
39
+ def ambiguous?
40
+ @ambiguous
83
41
  end
84
42
 
85
- # Determine whether the specified entity passes the query's arguments.
86
- #
87
- # @param [Entity]
88
- # @return [Boolean]
89
- def accept?(entity)
90
- result = true
91
- arguments.each do |a|
92
- result = if a.is_a?(Symbol)
93
- (entity.send(a) != false)
94
- elsif a.is_a?(Regexp)
95
- !entity.to_s.match(a).nil?
96
- elsif a.is_a?(Module) || a.is_a?(Class)
97
- entity.is_a?(a)
43
+ private
44
+
45
+ def calculate_precision
46
+ @arguments.sum(@ambiguous ? -1000 : 0) do |arg|
47
+ case arg
48
+ when Entity, Scriptable::Proxy::Agent
49
+ 1000
50
+ when Class, Module
51
+ class_depth(arg) * 100
98
52
  else
99
- (entity == a)
53
+ 1
100
54
  end
101
- break if result == false
102
55
  end
103
- result
104
56
  end
105
57
 
106
- protected
58
+ def class_depth klass
59
+ return 1 unless klass.is_a?(Class)
107
60
 
108
- # Return an array of the entity's children. If the child is accessible,
109
- # recursively append its children.
110
- # The result will NOT include the original entity itself.
111
- #
112
- # @param [Entity]
113
- # @return [Array<Object>]
114
- def subquery_accessible entity
115
- return [] if entity.nil?
116
- result = []
117
- if entity.accessible?
118
- entity.children.each do |c|
119
- result.push c
120
- result.concat subquery_accessible(c)
121
- end
122
- end
123
- result
61
+ depth = 1
62
+ sup = klass
63
+ depth += 1 while (sup = sup.superclass)
64
+ depth
124
65
  end
125
66
 
126
- private
67
+ def ambiguous_result scan
68
+ return Result.new(nil, scan.token) if scan.matched.empty?
127
69
 
128
- def simplify_arguments
129
- arguments.map do |a|
130
- if a.is_a?(Class) || a.is_a?(Object)
131
- a.to_s.split('::').last.downcase
132
- else
133
- a.to_s.downcase
134
- end
135
- end
70
+ Result.new(scan.matched, scan.remainder)
136
71
  end
137
72
 
138
- def nested?(token)
139
- !token.match(NEST_REGEXP).nil?
140
- end
73
+ def unambiguous_result scan
74
+ return Result.new(nil, scan.token) unless scan.matched.one?
141
75
 
142
- def denest(objects, token)
143
- parts = token.split(NEST_REGEXP)
144
- current = parts.pop
145
- last_result = objects.select { |e| e.specified?(current) }
146
- last_result = objects.select { |e| e.specified?(current, fuzzy: true) } if last_result.empty?
147
- until parts.empty?
148
- current = "#{parts.last} #{current}"
149
- result = last_result.select { |e| e.specified?(current) }
150
- result = last_result.select { |e| e.specified?(current, fuzzy: true) } if result.empty?
151
- break if result.empty?
152
- parts.pop
153
- last_result = result
154
- end
155
- return [] if last_result.empty? or last_result.length > 1
156
- return last_result if parts.empty?
157
- denest(last_result[0].children, parts.join(' '))
76
+ Result.new(scan.matched.first, scan.remainder)
158
77
  end
159
78
  end
160
79
  end