gamefic 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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