class Opal::Builder

def self.build(*args, &block)

def self.build(*args, &block)
  new.build(*args, &block)
end

def self.extensions

All the extensions supported by registered processors
def self.extensions
  @extensions ||= []
end

def self.processors

The registered processors
def self.processors
  @processors ||= []
end

def self.register_processor(processor, processor_extensions)


processing.
The processor is able to recognize paths suitable for its type of
## `.match?(path)`

- Opal's compiler options
- the asset's filename
- the unprocessed source
The processor will be instantiated passing:
## `.new(source, filename, compiler_options)`

map.
An instance of `::Opal::SourceMap::File` representing the processd asset's source
## `#source_map`

The processed source
## `#to_s`

be safely ignored
An array of entities that are autoloaded and their compile-time load failure can
## `#autoloads`

An array of string containing the logic paths of required directories
## `#required_trees`

An array of string containing the logic paths of required assets
## `#requires`

A processor will respond to:
Register a builder processor and the supported extensions.
@public
def self.register_processor(processor, processor_extensions)
  return if processors.include?(processor)
  processors << processor
  processor_extensions.each { |ext| extensions << ext }
end

def already_processed

def already_processed
  @already_processed ||= Set.new
end

def append_paths(*paths)

def append_paths(*paths)
  path_reader.append_paths(*paths)
end

def build(path, options = {})

def build(path, options = {})
  build_str(source_for(path), path, options)
end

def build_require(path, options = {})

def build_require(path, options = {})
  process_require(path, [], options)
end

def build_str(source, rel_path, options = {})

def build_str(source, rel_path, options = {})
  return if source.nil?
  abs_path = expand_path(rel_path)
  rel_path = expand_ext(rel_path)
  asset = processor_for(source, rel_path, abs_path, false, options)
  requires = preload + asset.requires + tree_requires(asset, abs_path)
  # Don't automatically load modules required by the module
  process_requires(rel_path, requires, asset.autoloads, options.merge(load: false))
  processed << asset
  self
end

def dependent_files

Return a list of dependent files, for watching purposes
def dependent_files
  processed.map(&:abs_path).compact.select { |fn| File.exist?(fn) }
end

def esm?

def esm?
  @compiler_options[:esm]
end

def expand_ext(path)

def expand_ext(path)
  abs_path = path_reader.expand(path)
  if abs_path
    File.join(
      File.dirname(path),
      File.basename(abs_path)
    )
  else
    path
  end
end

def expand_path(path)

def expand_path(path)
  return if stub?(path)
  (path_reader.expand(path) || File.expand_path(path)).to_s
end

def extensions

def extensions
  ::Opal::Builder.extensions
end

def initialize(options = nil)

def initialize(options = nil)
  (options || {}).each_pair do |k, v|
    public_send("#{k}=", v)
  end
  @stubs                    ||= []
  @preload                  ||= []
  @processors               ||= ::Opal::Builder.processors
  @path_reader              ||= PathReader.new(Opal.paths, extensions.map { |e| [".#{e}", ".js.#{e}"] }.flatten)
  @prerequired              ||= []
  @compiler_options         ||= Opal::Config.compiler_options
  @missing_require_severity ||= Opal::Config.missing_require_severity
  @cache                    ||= Opal.cache
  @scheduler                ||= Opal.builder_scheduler
  if @scheduler.respond_to? :new
    @scheduler = @scheduler.new(self)
  end
  @processed = []
end

def initialize_copy(other)

def initialize_copy(other)
  super
  @stubs = other.stubs.dup
  @preload = other.preload.dup
  @processors = other.processors.dup
  @path_reader = other.path_reader.dup
  @prerequired = other.prerequired.dup
  @compiler_options = other.compiler_options.dup
  @missing_require_severity = other.missing_require_severity.to_sym
  @processed = other.processed.dup
  @scheduler = other.scheduler.dup.tap { |i| i.builder = self }
end

def output_extension

to ESM mode only if the extension is "mjs"
Output extension, to be used by runners. At least Node.JS switches
def output_extension
  if esm?
    'mjs'
  else
    'js'
  end
end

def process_require(rel_path, autoloads, options)

def process_require(rel_path, autoloads, options)
  return if already_processed.include?(rel_path)
  already_processed << rel_path
  asset = process_require_threadsafely(rel_path, autoloads, options)
  processed << asset if asset
end

def process_require_threadsafely(rel_path, autoloads, options)

def process_require_threadsafely(rel_path, autoloads, options)
  return if prerequired.include?(rel_path)
  autoload = autoloads.include? rel_path
  source = stub?(rel_path) ? '' : read(rel_path, autoload)
  # The handling is delegated to the runtime
  return if source.nil?
  abs_path = expand_path(rel_path)
  rel_path = expand_ext(rel_path)
  asset = processor_for(source, rel_path, abs_path, autoload, options.merge(requirable: true))
  process_requires(
    rel_path,
    asset.requires + tree_requires(asset, abs_path),
    asset.autoloads,
    options
  )
  asset
end

def process_requires(rel_path, requires, autoloads, options)

def process_requires(rel_path, requires, autoloads, options)
  @scheduler.process_requires(rel_path, requires, autoloads, options)
end

def processor_for(source, rel_path, abs_path, autoload, options)

def processor_for(source, rel_path, abs_path, autoload, options)
  processor = processors.find { |p| p.match? abs_path }
  if !processor && !autoload
    raise(ProcessorNotFound, "can't find processor for rel_path: " \
                             "#{rel_path.inspect}, "\
                             "abs_path: #{abs_path.inspect}, "\
                             "source: #{source.inspect}, "\
                             "processors: #{processors.inspect}"
         )
  end
  options = options.merge(cache: cache)
  processor.new(source, rel_path, abs_path, @compiler_options.merge(options))
end

def read(path, autoload)

def read(path, autoload)
  path_reader.read(path) || begin
    print_list = ->(list) { "- #{list.join("\n- ")}\n" }
    message = "can't find file: #{path.inspect} in:\n" +
              print_list[path_reader.paths] +
              "\nWith the following extensions:\n" +
              print_list[path_reader.extensions] +
              "\nAnd the following processors:\n" +
              print_list[processors]
    unless autoload
      case missing_require_severity
      when :error   then raise MissingRequire, message
      when :warning then warn message
      when :ignore  then # noop
      end
    end
    nil
  end
end

def source_for(path)

Retrieve the source for a given path the same way #build would do.
def source_for(path)
  read(path, false)
end

def source_map

def source_map
  ::Opal::SourceMap::Index.new(processed.map(&:source_map), join: "\n")
end

def stub?(path)

def stub?(path)
  stubs.include?(path)
end

def to_s

def to_s
  processed.map(&:to_s).join("\n")
end

def tree_requires(asset, asset_path)

def tree_requires(asset, asset_path)
  dirname = asset_path.to_s.empty? ? Pathname.pwd : Pathname(asset_path).expand_path.dirname
  abs_base_paths = path_reader.paths.map { |p| File.expand_path(p) }
  asset.required_trees.flat_map do |tree|
    abs_tree_path = dirname.join(tree).expand_path.to_s
    abs_base_path = abs_base_paths.find { |p| abs_tree_path.start_with?(p) }
    if abs_base_path
      abs_base_path = Pathname(abs_base_path)
      entries_glob  = Pathname(abs_tree_path).join('**', "*{.js,}.{#{extensions.join ','}}")
      Pathname.glob(entries_glob).map { |file| file.relative_path_from(abs_base_path).to_s }
    else
      [] # the tree is not part of any known base path
    end
  end
end