Class: Toys::Loader
- Inherits:
-
Object
- Object
- Toys::Loader
- Defined in:
- lib/toys/loader.rb,
lib/toys/loader/load_state.rb,
lib/toys/loader/tool_registry.rb
Overview
The Loader service loads tools from tool sources, and finds the appropriate tool given a set of command line arguments.
Constant Summary collapse
- FALLBACK_ROOT_PRIORITY =
The priority of the always-present fallback root tool. This is the priority of the root tool that will be returned from lookups if no sources have been added. If any source is added, it will have a higher priority than this, and its tools will supersede this fallback.
-999_999_999
Instance Attribute Summary collapse
-
#tool_name_splitter ⇒ Toys::ToolNameSplitter
readonly
The splitter that interprets delimiters in the tool names handled by this loader.
Instance Method Summary collapse
-
#has_subtools?(words) ⇒ boolean
Returns true if the given path has at least one subtool, even if they are hidden or non-runnable.
-
#initialize(source_list, tool_name_splitter: nil, middleware_stack: [], mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil, git_cache: nil, gems_util: nil) ⇒ Loader
constructor
Create a Loader.
-
#list_subtools(words, recursive: false, include_hidden: false, include_namespaces: false, include_non_runnable: false) ⇒ Array<Toys::ToolDefinition>
Returns a list of subtools for the given path, loading from their sources and ensuring they are finished.
-
#lookup(args) ⇒ Array(Toys::ToolDefinition,Array<String>)
Given a list of command line arguments, find the appropriate tool to handle the command, loading it from its source if necessary and ensuring it has been finished.
-
#lookup_specific(words) ⇒ Toys::ToolDefinition?
Given a tool name, looks up the specific tool, loading it from its source if necessary and ensuring it has been finished.
-
#resolve_sources ⇒ self
Ensures all root sources get resolved eagerly.
Constructor Details
#initialize(source_list, tool_name_splitter: nil, middleware_stack: [], mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil, git_cache: nil, gems_util: nil) ⇒ Loader
Create a Loader.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/toys/loader.rb', line 55 def initialize(source_list, tool_name_splitter: nil, middleware_stack: [], mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil, git_cache: nil, gems_util: nil) # This mutex serializes all loading. It could be held for arbitrary # amounts of time because it surrounds the loading of tools files. # Note that ToolRegistry does not have its own lock, so this mutex should # protect calls to @tool_registry. require "monitor" @mutex = ::Monitor.new # General references @mixin_lookup = mixin_lookup || ModuleLookup.new @template_lookup = template_lookup || ModuleLookup.new @tool_name_splitter = tool_name_splitter || ToolNameSplitter::DEFAULT @git_cache = git_cache @gems_util = gems_util # Set up the registry and priorities @stop_priority = FALLBACK_ROOT_PRIORITY @min_loaded_priority = HIGHEST_PRIORITY @tool_registry = ToolRegistry.new(middleware_stack: middleware_stack, middleware_lookup: middleware_lookup) # A root tool must always exist, at the lowest possible priority. @tool_registry.record_root(FALLBACK_ROOT_PRIORITY) @tool_registry.get_tool([], FALLBACK_ROOT_PRIORITY) # Worklist entries are pairs of either [source_spec, priority] or # [source_info, words]. The entries seeded here hold unresolved source # specs; the entries pushed back during a directory walk hold resolved # SourceInfo objects. @worklist = [] source_list.each_with_priority do |spec, priority| @worklist << [spec, priority] end end |
Instance Attribute Details
#tool_name_splitter ⇒ Toys::ToolNameSplitter (readonly)
The splitter that interprets delimiters in the tool names handled by this loader. Use it to convert a delimited name into words.
102 103 104 |
# File 'lib/toys/loader.rb', line 102 def tool_name_splitter @tool_name_splitter end |
Instance Method Details
#has_subtools?(words) ⇒ boolean
Returns true if the given path has at least one subtool, even if they are hidden or non-runnable. Loads from the sources if necessary.
210 211 212 213 |
# File 'lib/toys/loader.rb', line 210 def has_subtools?(words) # rubocop:disable Naming/PredicatePrefix load_for_prefix(words) each_definition_in_subtree(words) { |_tool| break :found } == :found # rubocop:disable Lint/UnreachableLoop end |
#list_subtools(words, recursive: false, include_hidden: false, include_namespaces: false, include_non_runnable: false) ⇒ Array<Toys::ToolDefinition>
Returns a list of subtools for the given path, loading from their sources and ensuring they are finished. The list will be sorted by name.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/toys/loader.rb', line 175 def list_subtools(words, recursive: false, include_hidden: false, include_namespaces: false, include_non_runnable: false) words_len = words.length found_tools = [] load_for_prefix(words) # We need to pull the entire subtree recursively even if we're not # returning recursive descendants, because we need the deeper data to # filter immediate children. If not returning recursive data, we'll # post-filter below. each_definition_in_subtree(words, recursive: true) do |tool| # each_definition_in_subtree iterates within the mutex, so this code # will execute within the mutex. if include_hidden || tool.full_name[words_len..].none? { |word| word.start_with?("_") } found_tools << tool.finish_definition(self) end end found_tools.sort_by!(&:full_name) found_tools = filter_non_runnable_tools(found_tools, include_namespaces, include_non_runnable) found_tools.select! { |tool| tool.full_name.length == words_len + 1 } unless recursive found_tools end |
#lookup(args) ⇒ Array(Toys::ToolDefinition,Array<String>)
Given a list of command line arguments, find the appropriate tool to handle the command, loading it from its source if necessary and ensuring it has been finished. This always returns a tool. If the specific tool path is not defined and cannot be found in any source, it finds the nearest namespace that would contain that tool, up to the root tool (which always exists.)
Returns a tuple of the found tool, and the array of remaining arguments that are not part of the tool name and should be passed as tool args.
121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/toys/loader.rb', line 121 def lookup(args) orig_prefix, args = find_orig_prefix(args) # Start looking for a tool with the entire prefix, and continue to # shorten it until a tool is found. Because the root tool always exists, # the final fallback of the empty prefix will always succeed. prefix = orig_prefix loop do tool = lookup_specific(prefix) return [tool, args.slice(prefix.length..-1)] if tool prefix = prefix.slice(0..-2) end end |
#lookup_specific(words) ⇒ Toys::ToolDefinition?
Given a tool name, looks up the specific tool, loading it from its source if necessary and ensuring it has been finished.
If there is an active tool, returns it; otherwise, returns the highest
priority tool that has been defined. If no tool has been defined with
the given name, returns nil.
149 150 151 152 153 154 |
# File 'lib/toys/loader.rb', line 149 def lookup_specific(words) load_for_prefix(words) @mutex.synchronize do @tool_registry.cur_definition(words)&.finish_definition(self) end end |
#resolve_sources ⇒ self
Ensures all root sources get resolved eagerly. Does not actually load any tools.
Can be called pre-emptively prior to other methods to prevent them from
raising ToolSourceError directly. (It is still possible for them
to raise ToolSourceError wrapped in a ContextualError if
a tool invokes another source via one of the load directives.)
227 228 229 |
# File 'lib/toys/loader.rb', line 227 def resolve_sources load_for_prefix(nil) end |