Class: Toys::Loader

Inherits:
Object
  • Object
show all
Includes:
MonitorMixin
Defined in:
lib/toys/loader.rb

Overview

The Loader service loads tools from configuration files, and finds the appropriate tool given a set of command line arguments.

This class is not thread-safe.

Instance Method Summary collapse

Constructor Details

#initialize(index_file_name: nil, preload_dir_name: nil, preload_file_name: nil, data_dir_name: nil, middleware_stack: [], extra_delimiters: "", mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil) ⇒ Loader

Create a Loader

Parameters:

  • index_file_name (String, nil) (defaults to: nil)

    A file with this name that appears in any configuration directory (not just a toplevel directory) is loaded first as a standalone configuration file. If not provided, standalone configuration files are disabled.

  • preload_file_name (String, nil) (defaults to: nil)

    A file with this name that appears in any configuration directory is preloaded before any tools in that configuration directory are defined.

  • preload_dir_name (String, nil) (defaults to: nil)

    A directory with this name that appears in any configuration directory is searched for Ruby files, which are preloaded before any tools in that configuration directory are defined.

  • data_dir_name (String, nil) (defaults to: nil)

    A directory with this name that appears in any configuration directory is added to the data directory search path for any tool file in that directory.

  • middleware_stack (Array<Toys::Middleware::Spec>) (defaults to: [])

    An array of middleware that will be used by default for all tools loaded by this loader.

  • extra_delimiters (String) (defaults to: "")

    A string containing characters that can function as delimiters in a tool name. Defaults to empty. Allowed characters are period, colon, and slash.

  • mixin_lookup (Toys::ModuleLookup) (defaults to: nil)

    A lookup for well-known mixin modules. Defaults to an empty lookup.

  • middleware_lookup (Toys::ModuleLookup) (defaults to: nil)

    A lookup for well-known middleware classes. Defaults to an empty lookup.

  • template_lookup (Toys::ModuleLookup) (defaults to: nil)

    A lookup for well-known template classes. Defaults to an empty lookup.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/toys/loader.rb', line 66

def initialize(index_file_name: nil, preload_dir_name: nil, preload_file_name: nil,
               data_dir_name: nil, middleware_stack: [], extra_delimiters: "",
               mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil)
  super()
  if index_file_name && ::File.extname(index_file_name) != ".rb"
    raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
  end
  @mixin_lookup = mixin_lookup || ModuleLookup.new
  @template_lookup = template_lookup || ModuleLookup.new
  @middleware_lookup = middleware_lookup || ModuleLookup.new
  @index_file_name = index_file_name
  @preload_file_name = preload_file_name
  @preload_dir_name = preload_dir_name
  @data_dir_name = data_dir_name
  @loading_started = false
  @worklist = []
  @tool_data = {}
  @max_priority = @min_priority = 0
  @middleware_stack = Middleware.resolve_specs(*middleware_stack)
  @delimiter_handler = DelimiterHandler.new(extra_delimiters)
  get_tool([], -999_999)
end

Instance Method Details

#add_block(high_priority: false, name: nil, &block) ⇒ self

Add a configuration block to the loader.

Parameters:

  • high_priority (Boolean) (defaults to: false)

    If true, add this block at the top of the priority list. Defaults to false, indicating the block should be at the bottom of the priority list.

  • name (String) (defaults to: nil)

    The source name that will be shown in documentation for tools defined in this block. If omitted, a default unique string will be generated.

  • block (Proc)

    The block of configuration, executed in the context of the tool DSL DSL::Tool.

Returns:

  • (self)


124
125
126
127
128
129
130
131
132
133
# File 'lib/toys/loader.rb', line 124

def add_block(high_priority: false, name: nil, &block)
  name ||= "(Code block #{block.object_id})"
  synchronize do
    raise "Cannot add a block after tool loading has started" if @loading_started
    priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
    source = SourceInfo.create_proc_root(block, name)
    @worklist << [source, [], priority]
  end
  self
end

#add_path(paths, high_priority: false) ⇒ self

Add a configuration file/directory to the loader.

Parameters:

  • paths (String, Array<String>)

    One or more paths to add.

  • high_priority (Boolean) (defaults to: false)

    If true, add this path at the top of the priority list. Defaults to false, indicating the new path should be at the bottom of the priority list.

Returns:

  • (self)


98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/toys/loader.rb', line 98

def add_path(paths, high_priority: false)
  paths = Array(paths)
  synchronize do
    raise "Cannot add a path after tool loading has started" if @loading_started
    priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
    paths.each do |path|
      source = SourceInfo.create_path_root(path)
      @worklist << [source, [], priority]
    end
  end
  self
end

#has_subtools?(words) ⇒ Boolean

Returns true if the given path has at least one subtool. Loads from the configuration if necessary.

Parameters:

  • words (Array<String>)

    The name of the parent tool

Returns:

  • (Boolean)


214
215
216
217
218
219
220
221
222
223
# File 'lib/toys/loader.rb', line 214

def has_subtools?(words) # rubocop:disable Naming/PredicateName
  load_for_prefix(words)
  len = words.length
  tool_data_snapshot.each do |n, td|
    if !n.empty? && n.length > len && n.slice(0, len) == words && !td.empty?
      return true
    end
  end
  false
end

#list_subtools(words, recursive: false, include_hidden: false) ⇒ Array<Toys::Tool>

Returns a list of subtools for the given path, loading from the configuration if necessary.

Parameters:

  • words (Array<String>)

    The name of the parent tool

  • recursive (Boolean) (defaults to: false)

    If true, return all subtools recursively rather than just the immediate children (the default)

  • include_hidden (Boolean) (defaults to: false)

    If true, include hidden subtools, e.g. names beginning with underscores.

Returns:



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/toys/loader.rb', line 189

def list_subtools(words, recursive: false, include_hidden: false)
  load_for_prefix(words)
  found_tools = []
  len = words.length
  tool_data_snapshot.each do |n, td|
    next if n.empty?
    if recursive
      next if n.length <= len || n.slice(0, len) != words
    else
      next unless n.slice(0..-2) == words
    end
    tool = td.cur_definition
    found_tools << tool unless tool.nil?
  end
  sort_tools_by_name(found_tools)
  include_hidden ? found_tools : filter_hidden_subtools(found_tools)
end

#lookup(args) ⇒ Array(Toys::Tool,Array<String>)

Given a list of command line arguments, find the appropriate tool to handle the command, loading it from the configuration if necessary. This always returns a tool. If the specific tool path is not defined and cannot be found in any configuration, it finds the nearest namespace that would contain that tool, up to the root tool.

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.

Parameters:

  • args (Array<String>)

    Command line arguments

Returns:



148
149
150
151
152
153
154
155
156
# File 'lib/toys/loader.rb', line 148

def lookup(args)
  orig_prefix, args = @delimiter_handler.find_orig_prefix(args)
  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::Tool?

Given a tool name, looks up the specific tool, loading it from the configuration if necessary.

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.

Parameters:

  • words (Array<String>)

    The tool name

Returns:

  • (Toys::Tool)

    if the tool was found

  • (nil)

    if no such tool exists



170
171
172
173
174
175
176
# File 'lib/toys/loader.rb', line 170

def lookup_specific(words)
  words = @delimiter_handler.split_path(words.first) if words.size == 1
  load_for_prefix(words)
  tool = get_tool_data(words).cur_definition
  finish_definitions_in_tree(words) if tool
  tool
end

#split_path(str) ⇒ Array<String>

Splits the given path using the delimiters configured in this Loader. You may pass in either an array of strings, or a single string possibly delimited by path separators. Always returns an array of strings.

Parameters:

  • str (String, Symbol, Array<String,Symbol>)

    The path to split.

Returns:

  • (Array<String>)


233
234
235
236
# File 'lib/toys/loader.rb', line 233

def split_path(str)
  return str.map(&:to_s) if str.is_a?(::Array)
  @delimiter_handler.split_path(str.to_s)
end