class Middleman::Builder

Constants

SORT_ORDER

Sort order, images, fonts, js/css and finally everything else.

Attributes

app[R]

Make app & events available to `after_build` callbacks.

events[R]

Make app & events available to `after_build` callbacks.

thor[RW]

Reference to the Thor class.

Public Class Methods

new(app, opts={}) click to toggle source

Create a new Builder instance. @param [Middleman::Application] app The app to build. @param [Hash] opts The builder options

# File lib/middleman-core/builder.rb, line 29
def initialize(app, opts={})
  @app = app
  @source_dir = Pathname(File.join(@app.root, @app.config[:source]))
  @build_dir = Pathname(@app.config[:build_dir])

  if @build_dir.expand_path.relative_path_from(@source_dir).to_s =~ /\A[.\/]+\Z/
    raise ":build_dir (#{@build_dir}) cannot be a parent of :source_dir (#{@source_dir})"
  end

  @glob = opts.fetch(:glob)
  @cleaning = opts.fetch(:clean)
  @parallel = opts.fetch(:parallel, true)

  rack_app = ::Middleman::Rack.new(@app).to_app
  @rack = ::Rack::MockRequest.new(rack_app)

  @callbacks = ::Middleman::CallbackManager.new
  @callbacks.install_methods!(self, [:on_build_event])
end

Public Instance Methods

binary_encode(string) click to toggle source
# File lib/middleman-core/builder.rb, line 290
def binary_encode(string)
  string.force_encoding('ascii-8bit') if string.respond_to?(:force_encoding)
  string
end
clean!() click to toggle source
# File lib/middleman-core/builder.rb, line 278
def clean!
  to_remove = @to_clean.reject do |f|
    app.config[:skip_build_clean].call(f.to_s)
  end

  to_remove.each do |f|
    FileUtils.rm(f)
    trigger(:deleted, f)
  end
end
export_file!(output_file, source) click to toggle source
# File lib/middleman-core/builder.rb, line 195
def export_file!(output_file, source)
  # ::Middleman::Util.instrument "write_file", output_file: output_file do
  source = write_tempfile(output_file, source.to_s) if source.is_a? String

  method, source_path = if source.is_a? Tempfile
    [::FileUtils.method(:mv), source.path]
  else
    [::FileUtils.method(:cp), source.to_s]
  end

  mode = which_mode(output_file, source_path)

  if mode == :created || mode == :updated
    ::FileUtils.mkdir_p(output_file.dirname)
    method.call(source_path, output_file.to_s)
  end

  source.unlink if source.is_a? Tempfile

  trigger(mode, output_file)
  # end
end
output_files() click to toggle source
# File lib/middleman-core/builder.rb, line 112
def output_files
  logger.debug '== Building files'

  resources = @app.sitemap.resources
                  .reject { |resource| resource.ext == '.css' }
                  .sort_by { |resource| SORT_ORDER.index(resource.ext) || 100 }

  if @glob
    resources = resources.select do |resource|
      if defined?(::File::FNM_EXTGLOB)
        File.fnmatch(@glob, resource.destination_path, ::File::FNM_EXTGLOB)
      else
        File.fnmatch(@glob, resource.destination_path)
      end
    end
  end

  output_resources(resources)
end
output_resource(resource) click to toggle source
# File lib/middleman-core/builder.rb, line 222
def output_resource(resource)
  ::Middleman::Util.instrument 'builder.output.resource', path: File.basename(resource.destination_path) do
    output_file = @build_dir + resource.destination_path.gsub('%20', ' ')

    begin
      if resource.binary?
        export_file!(output_file, resource.file_descriptor[:full_path])
      else
        response = @rack.get(::URI.escape(resource.request_path))

        # If we get a response, save it to a tempfile.
        if response.status == 200
          export_file!(output_file, binary_encode(response.body))
        else
          trigger(:error, output_file, response.body)
          return false
        end
      end
    rescue => e
      trigger(:error, output_file, "#{e}\n#{e.backtrace.join("\n")}")
      return false
    end

    output_file
  end
end
output_resources(resources) click to toggle source
# File lib/middleman-core/builder.rb, line 133
def output_resources(resources)
  results = if @parallel
    ::Parallel.map(resources, &method(:output_resource))
  else
    resources.map(&method(:output_resource))
  end

  @has_error = true if results.any? { |r| r == false }

  if @cleaning && !@has_error
    results.each do |p|
      next unless p.exist?

      # handle UTF-8-MAC filename on MacOS
      cleaned_name = if RUBY_PLATFORM =~ /darwin/
        p.to_s.encode('UTF-8', 'UTF-8-MAC')
      else
        p
      end

      @to_clean.delete(Pathname(cleaned_name))
    end
  end

  resources
end
prerender_css() click to toggle source
# File lib/middleman-core/builder.rb, line 90
def prerender_css
  logger.debug '== Prerendering CSS'

  css_files = ::Middleman::Util.instrument 'builder.prerender.output' do
    resources = @app.sitemap.resources.select { |resource| resource.ext == '.css' }
    output_resources(resources)
  end

  ::Middleman::Util.instrument 'builder.prerender.check-files' do
    # Double-check for compass sprites
    unless @app.files.find_new_files!.empty?
      logger.debug '== Checking for Compass sprites'
      @app.sitemap.ensure_resource_list_updated!
    end
  end

  css_files
end
queue_current_paths() click to toggle source
# File lib/middleman-core/builder.rb, line 253
def queue_current_paths
  @to_clean = []

  return unless File.exist?(@app.config[:build_dir])

  paths = ::Middleman::Util.all_files_under(@app.config[:build_dir]).map do |path|
    Pathname(path)
  end

  @to_clean = paths.select do |path|
    path.realpath.relative_path_from(@build_dir.realpath).to_s !~ /\/\./ || path.to_s =~ /\.(htaccess|htpasswd)/
  end

  # handle UTF-8-MAC filename on MacOS
  @to_clean = @to_clean.map do |path|
    if RUBY_PLATFORM =~ /darwin/
      Pathname(path.to_s.encode('UTF-8', 'UTF-8-MAC'))
    else
      Pathname(path)
    end
  end
end
run!() click to toggle source
# File lib/middleman-core/builder.rb, line 52
def run!
  @has_error = false
  @events = {}

  ::Middleman::Util.instrument 'builder.before' do
    @app.execute_callbacks(:before_build, [self])
  end

  ::Middleman::Util.instrument 'builder.queue' do
    queue_current_paths if @cleaning
  end

  ::Middleman::Util.instrument 'builder.prerender' do
    prerender_css
  end

  ::Middleman::Profiling.start

  ::Middleman::Util.instrument 'builder.output' do
    output_files
  end

  ::Middleman::Profiling.report('build')

  ::Middleman::Util.instrument 'builder.clean' do
    clean! if @cleaning
  end

  ::Middleman::Util.instrument 'builder.after' do
    @app.execute_callbacks(:after_build, [self])
  end

  !@has_error
end
trigger(event_type, target, extra=nil) click to toggle source
# File lib/middleman-core/builder.rb, line 296
def trigger(event_type, target, extra=nil)
  @events[event_type] ||= []
  @events[event_type] << target

  execute_callbacks(:on_build_event, [event_type, target, extra])
end
which_mode(output_file, source) click to toggle source
# File lib/middleman-core/builder.rb, line 165
def which_mode(output_file, source)
  if !output_file.exist?
    :created
  else
    FileUtils.compare_file(source.to_s, output_file.to_s) ? :identical : :updated
  end
end
write_tempfile(output_file, contents) click to toggle source
# File lib/middleman-core/builder.rb, line 178
def write_tempfile(output_file, contents)
  file = Tempfile.new([
                        File.basename(output_file),
                        File.extname(output_file)
                      ])
  file.binmode
  file.write(contents)
  file.close
  File.chmod(0644, file)
  file
end