# =========================================================================== # SC::Manifest Buildtasks # copyright 2011, Strobe Inc. and Apple Inc. all rights reserved # ===========================================================================

# Tasks invoked while building Manifest objects. You can override these # tasks in your buildfiles.

require File.dirname(__FILE__) + '/helpers/file_rule_list'

namespace :manifest do

desc "Invoked just before a manifest object is built to setup standard properties"
task :prepare do |task, env|
  require 'tempfile'

  manifest = env[:manifest]
  target   = env[:target]

  # make sure a language was set
  manifest[:language] ||= :en

  # build_root is target.build_root + language + build_number
  manifest[:build_root] = File.join(target[:build_root],
    manifest[:language].to_s, target[:build_number].to_s)

  # staging_root is target.staging_root + language + build_number
  manifest[:staging_root] = File.join(target[:staging_root],
    manifest[:language].to_s, target[:build_number].to_s)

  # cache_root is target.cache_root + language + build_number
  manifest[:cache_root] = File.join(target[:cache_root],
    manifest[:language].to_s, target[:build_number].to_s)

  # url_root
  manifest[:url_root] =
    [target[:url_root], manifest[:language], target[:build_number]].join('/')

  # index_root
  manifest[:index_root] =
    [target[:index_root], manifest[:language], target[:build_number]].join('/')

  # source_root
  manifest[:source_root] = target[:source_root]
end

desc "Actually builds a manifest.  This will catalog all entries and then filter them"
task :build => %w(catalog hide_buildfiles localize prepare_build_tasks:all)

desc "Builds a manifest, this adds a copy file entry for every whitelisted file in the source"
task :catalog do |t, env|
  target   = env[:target]
  manifest = env[:manifest]

  source_root = target[:source_root]

  file_rule_list = SproutCore::FileRuleList.new

  Dir.glob("#{Dir.pwd}/#{SC.env[:whitelist_name]}").each do |path|
    file_rule_list.read_json(path, :allow) if File.file? path
  end

  Dir.glob("#{Dir.pwd}/#{SC.env[:blacklist_name]}").each do |path|
    if File.file? path
      file_rule_list.allow_by_default = true
      file_rule_list.read_json(path, :deny)
    end
  end

  Dir.glob("#{Dir.pwd}/#{SC.env[:accept_name]}").each do |path|
    file_rule_list.read(path) if File.file? path
  end

  number_rejected_entries = 0

  Dir.glob("#{source_root}/**/*").each do |path|
    next unless File.file?(path)
    next if target.target_directory?(path)

    if file_rule_list.include?(target[:target_name], path)
      filename = path.sub /^#{Regexp.escape source_root}\//, ''
      filename = filename.split(::File::SEPARATOR).join('/')
      manifest.add_entry filename, :original => true # entry:prepare will fill in the rest
    else
      number_rejected_entries += 1
    end
  end

    if number_rejected_entries > 0
      SC.logger.warn "The Whitelist file rejected #{number_rejected_entries} file(s) from #{target[:target_name]}"
    end
end

desc "hides structural files that do not belong in build include Buildfiles and debug or fixtures if turned off"
task :hide_buildfiles => [:catalog] do |task, env|
  manifest = env[:manifest]

  # these directories are to be excluded unless CONFIG.load_"dirname" = true
  exclude_dirnames = %w(debug tests fixtures protocols).reject do |k|
    CONFIG[:"load_#{k}"]
  end
  exclude_dirnames.concat [*CONFIG[:exclude]]

  # loop through entries and hide those that do not below...
  manifest.entries.each do |entry|

    # if in /dirname or /foo.lproj/dirname -- hide it!
    exclude_dirnames.each do |dirname|
      if entry[:filename] =~ /^(([^\/]+)\.lproj\/)?#{dirname}\/.+$/
        entry.hide!
        next
      end
    end

    # allow if it is a handlebars template
    next if entry[:ext] == "handlebars"

    # otherwise, allow if inside lproj
    next if entry.localized? || entry[:filename] =~ /^.+\.lproj\/.+$/

    # allow if in tests, fixtures or debug as well...
    next if entry[:filename] =~ /^(resources|tests|fixtures|debug)\/.+$/

    # or skip if ext not js
    entry.hide! if entry[:ext] != 'js'
  end
end

desc "localizes files. reject any files from other languages"
task :localize => [:catalog, :hide_buildfiles] do |task, env|
  target   = env[:target]
  manifest = env[:manifest]

  seen = {} # already seen entries...
  preferred_language = target.config[:preferred_language] || :en

  manifest.entries.each do |entry|

    # Is a localized resource!
    if entry[:filename] =~ /^([^\/]+)\.lproj\/(.+)$/
      entry[:language] = (SC::Target::LONG_LANGUAGE_MAP[$1.to_s.downcase.to_sym]) || $1.to_sym
      entry[:localized] = true

      # remove .lproj dir from build paths as well..
      lang_dir = "#{$1}.lproj/"
      sub_str = (entry[:ext] == 'js') ? 'lproj/' : ''
      entry[:filename  ] = entry[:filename].sub(lang_dir, sub_str)
      entry[:build_path] = entry[:build_path].sub(lang_dir, sub_str)
      entry[:url]        = entry[:url].sub(lang_dir, sub_str)

      # if this is part of the current language, always include...
      # hide any preferred_language entry...
      if entry[:language] == manifest[:language]
        if seen[entry[:filename]]
          seen[entry[:filename]].hide!
        else
          seen[entry[:filename]] = entry
        end

      # if this is a preferred_language, hide unless we've seen one
      elsif entry[:language].to_s == preferred_language.to_s
        if seen[entry[:filename]]
          entry.hide!
        else
          seen[entry[:filename]] = entry
        end

      # Otherwise, hide it...
      else
        entry.hide!
      end

    # Not a localized resource
    else
      entry[:language] = manifest[:language]
      entry[:localized] = false
    end
  end
end

namespace :prepare_build_tasks do

  desc "main entrypoint for preparing all build tasks.  This should invoke all needed tasks"
  task :all => %w(css json handlebars javascript module_info sass less combine string_wrap minify string_wrap html strings tests packed split)

  desc "executes prerequisites needed before one of the subtasks can be invoked.  All subtasks that have this as a prereq"
  task :setup => %w(manifest:catalog manifest:hide_buildfiles manifest:localize)

  desc "create builder tasks for all unit tests based on file extension."
  task :tests => :setup do |task, env|
    manifest = env[:manifest]

    # Generate test entries
    test_entries = []
    entries_by_dirname = {} # for building composites...
    manifest.entries.each do |entry|
      next unless entry[:filename] =~ /^tests\//

      # if this is a js file, add js transform first to handle sc_static()
      # etc.
      if entry[:ext] == 'js'
        entry = manifest.add_transform entry,
          :build_task => 'build:javascript'
      end

      # Add transform to build into test.
      test_entries << manifest.add_transform(entry,
        :build_task => "build:test",
        :entry_type => :test,
        :ext        => 'html')

      # Strip off dirnames, saving each by dirname...
      dirname = entry[:filename]
      while (dirname = dirname.sub(/\/?[^\/]+$/,'')).size > 0
        (entries_by_dirname[dirname] ||= []) << entry
      end
    end

    # Generate composite entries for each directory...
    entries_by_dirname.each do |dirname, entries|
      filename = "#{dirname}.html"
      manifest.add_composite filename,
        :build_task     => "build:test",
        :entry_type     => :test,
        :ext            => 'html',
        :source_entries => entries,
        :hide_entries   => false
    end

    # Add summary entry
    if CONFIG[:load_tests]
      manifest.add_entry 'tests/-index.json',
        :composite      => true,
        :source_entries => test_entries,
        :build_task     => 'build:test_index',
        :entry_type     => :resource
    end
  end
  task :javascript => :tests # IMPORTANT! to avoid JS including unit tests.
  # task :html       => :tests # IMPORTANT! to avoid HTML including tests

  desc "scans for javascript files, annotates them and prepares combined entries for each output target"
  task :javascript => :setup do |task, env|
    manifest = env[:manifest]
    target = env[:target]
    config   = CONFIG

    # select all original entries with with ext of css
    entries = manifest.entries.select do |e|
      e.original? && e[:ext] == 'js'
    end

    # add transform & tag with build directives.
    entries.each do |entry|
      entry = manifest.add_transform entry,
        :lazy_instantiation => config[:lazy_instantiation],
        :notify_onload => !config[:combine_javascript],
        :filename   => ['source', entry[:filename]].join('/'),
        :build_path => File.join(manifest[:build_root], 'source', entry[:filename]),
        :url => [manifest[:url_root], 'source', entry[:filename]].join("/"),
        :build_task => 'build:javascript',
        :resource   => 'javascript',
        :entry_type => :javascript

      entry.discover_build_directives!
    end
  end

  desc "scans for json files, processing SC directives like sc_static"
  task :json => :setup do |task, env|
    manifest = env[:manifest]
    target = env[:target]
    config   = CONFIG

    # select all original entries with with ext of css
    entries = manifest.entries.select do |e|
      e.original? && e[:ext] == 'json'
    end

    # add transform & tag with build directives.
    entries.each do |entry|
      entry = manifest.add_transform entry,
        :build_task => 'build:json'
    end
  end

  desc "scans for css files, creates a transform and annotates them"
  task :css => :setup do |task, env|
    manifest = env[:manifest]
    config = env[:target].config

    # select all original entries with with ext of css
    entries = manifest.entries.select do |e|
      e.original? && ['css', 'scss'].include?(e[:ext])
    end

    # add transform & tag with build directives.
    entries.each do |entry|
      filename = entry[:filename]

      # We want it to appear as CSS
      filename.gsub! /\.scss$/, '.css'

      entry = manifest.add_transform entry,
        :filename   => ['source', filename].join('/'),
        :build_path => File.join(manifest[:build_root], 'source', entry[:filename]),
        :url => [manifest[:url_root], 'source', entry[:filename]].join("/"),
        :build_task => 'build:css',
        :resource   => 'stylesheet',
        :entry_type => :css,
        :ext => 'css',
        :from_scss => (entry[:ext] == 'scss')   # for testing
      entry.discover_build_directives!
    end
  end

  desc "scans for image files, creates a transform and annotates them"
  task :images => :setup do |task, env|
    manifest = env[:manifest]
    config = env[:target].config

    # select all original entries with with ext of png, jpg, jpeg or gif
    entries = manifest.entries.select do |e|
      e.original? && ['png', 'jpg', 'jpeg', 'gif'].include?(e[:ext])
    end

    # add transform & tag with build directives.
    entries.each do |entry|
      entry = manifest.add_transform entry,
        :filename   => ['source', entry[:filename]].join('/'),
        :build_path => File.join(manifest[:build_root], 'source', entry[:filename]),
        :url => [manifest[:url_root], 'source', entry[:filename]].join("/"),
        :build_task => 'build:image',
        :entry_type => :image
    end

  end

  desc "scans for Handlebars templates and converts them to JavaScript"
  task :handlebars => %w(setup) do |task, env|
    manifest = env[:manifest]

    entries = manifest.entries.select do |e|
      e[:ext] == 'handlebars'
    end

    entries.each do |entry|
      entry = manifest.add_transform entry,
        :filename => ['source', entry[:filename]].join('/'),
        :build_path => File.join(manifest[:build_root], 'source', entry[:filename].ext('js')),
        :url => [manifest[:url_root], 'source', entry[:filename]].join("/"),
        :build_task => 'build:handlebars',
        :resource   => 'javascript',
        :entry_type => :javascript
    end
  end

  desc "adds a module_info.js entry for all deferred and prefetched modules"
  task :module_info => %w(setup) do |task, env|
    target   = env[:target]
    manifest = env[:manifest]
    config   = CONFIG

    # Populate module_info for all deferred_modules frameworks.
    # Add :debug_required and :test_required depending on
    # the build mode.
    debug = config[:load_debug]
    test = config[:load_tests]

    # find all of the modules required by this target
    targets = target.modules({ :debug => debug, :test => test, :theme => true })

    unless targets.size == 0

      source_paths = []

      targets.each do |target|
        m = target.manifest_for(manifest.variation)
        m.build!

        m.entries.each do |entry|
          # The entry may have been built directly; we need to make sure it was
          # staged because we check staging_path
          entry.stage!
          case entry[:entry_type]
          when :css
            source_paths << entry[:staging_path]
          when :javascript
            source_paths << entry[:staging_path]
          end
        end
      end

      manifest.add_entry 'module_info.js',
        :dynamic        => true, # required to get correct timestamp for cacheable_url
        :build_task     => 'build:module_info',
        :resource       => 'javascript',
        :entry_type     => :javascript,
        :composite      => true,
        :target         => target,
        :targets        => targets,
        :variation      => manifest.variation,
        :debug          => debug,
        :test           => test,
        :theme          => true,
        :source_paths   => source_paths
    end
  end

  task :module_info => :tests # IMPORTANT! to avoid JS including unit tests.

  desc "generates combined entries for CSS"
  task :chance => %w(setup images javascript module_info css sass less) do |task, env|
    config = CONFIG
    manifest = env[:manifest]
    target = manifest.target

    sprited = CONFIG[:use_sprites]
    minify = !SC.env[:dont_minify] && (CONFIG[:minify_css].nil? ? CONFIG[:minify] : CONFIG[:minify_css])

    # the image files will be shared between all css_entries-- that is,
    # all instances of Chance created
    global_chance_entries = []

    # For each "resource" a separate css entry will be created.
    css_entries = {}

    manifest.entries.each do |entry|
      # Chance needs to know about image files so it can embed as data URIs in the
      # CSS. For this reason, if Chance is enabled, we need to send entries for image
      # files to the 'build:chance' buildtask.
      is_chance_file = ['.png', '.jpg', '.jpeg', '.gif'].include?(File.extname(entry[:filename]))

      next if entry[:resource].nil? and not is_chance_file

      if is_chance_file
        global_chance_entries << entry
      elsif entry[:entry_type] == :css
        (css_entries[entry[:resource]] ||= []) << entry
        entry.hide! if config[:combine_stylesheets]
      end

    end

    chance_instances = []
    all_chance_entries = []
    timestamps = []

    # build combined CSS entry
    css_entries.each do |resource_name, entries|
      # Send image files to the build task if Chance is being used
      entries.concat global_chance_entries

      # We create the Chance Instance now, even though we don't run the whole thing.
      # This could incur a performance penalty to build manifests, but it is the only
      # way to know what sprties, etc. need to be put into the manifest.
      #
      # Still, Chance has to get created sometime.
      #
      # To help, we cache the Chance Instance based on a key we generate. The Chance Factory does
      # this for us. It will automatically handle when things change, for the most part (though
      # the builder still has to tell Chance to double-check things, etc.)
      opts = { 
        # the value of $theme
        :theme => CONFIG[:css_theme],

        # whether it should minify
        :minify => minify,

        # charset for CSS files
        :css_charset => CONFIG[:css_charset],

        # whether it should optimize sprites. This is opt-in, and comes from the buildfile.
        :optimize_sprites => CONFIG[:optimize_sprites],

        # Whether it should pad slices for debugging when writing them to the sprite. This is opt-out
        # in development, but is off during production.
        :pad_sprites_for_debugging => CONFIG[:pad_sprites_for_debugging],

        # a unique identifier for the instance. we can share across localizations; this is
        # merely used to prevent conflicts within the SAME CSS file: Chance makes sure
        # all rules for generated images include this key. We'll use the target name.
        :instance_id => target[:target_name]
      }
      chance_key = manifest[:staging_root] + "/" + resource_name

      chance = Chance::ChanceFactory.instance_for_key(chance_key, opts)
      chance_files = {}

      timestamp = 0
      has_2x_entries = false
      entries.each do |entry|
        timestamp = [entry.timestamp, timestamp].max

        # unfortunately, we have to stage the source entries NOW. But only if they require
        # building to be used. This applies primarily to sass and less entries.
        if entry[:build_required]
          src_path = entry.stage![:staging_path]
        else
          src_path = entry[:source_path]
        end

        next unless File.exist?(src_path)

        Chance.add_file src_path
        # Also remove source/ from the filename for sc_require and @import
        chance_files[entry.filename.sub(/^source\//, '')] = src_path

        has_2x_entries = true if src_path.include?("@2x")
      end

      Chance::ChanceFactory.update_instance(chance_key, opts, chance_files)

      timestamps << timestamp
      chance_instances << chance

      # ADD A 2X version
      # Because the @2x file is not a _real_ composite entry, and as such has
      # no true source entries (because we don't want to stage them as that
      # adversely impacts performance), we need to give a set of source paths
      # for the entry to compare mtimes with to know if it needs to update.
      entry_source_paths = entries.map {|e| e[:source_path] }

      add_chance_file = lambda {|entry_name, chance_file, entry_type|
        manifest.add_entry entry_name,
          :variation       => manifest.variation,
          :build_task      => 'build:chance_file',
          :entry_type      => entry_type,
          :combined        => true,

          :source_entries  => entries,
          :ordered_entries => SC::Helpers::EntrySorter.sort(entries),

          :chance_file     => chance_file,
          :chance_instance => chance,

          # For cache-busting, we must support timestamped urls, but the entry
          # will be unable to calculate the timestamp for this on its own. So, we
          # must supply the calculated timestamp.
          :timestamp       => timestamp,

          :source_paths => entry_source_paths,
          :resource_name => resource_name,

          # So that modules, etc. can figure out what resource it belongs to
          :resource => resource_name,
          :minified => minify,

          # So that it can easily be recognized as an @2x entry.
          :x2 => entry_name.include?("@2x")
      }

      chance_file = "chance" + (sprited ? "-sprited" : "") + ".css"

      add_chance_file.call(resource_name + ".css", chance_file, :css)

      # We only want to add the 2x version if there is a need for it.
      # NOTE: the HTML builder will need to pick the normal version if it
      # cannot find the @2x version.
      if has_2x_entries
        chance_2x_file = "chance" + (sprited ? "-sprited" : "") + "@2x.css"
        add_chance_file.call(resource_name + "@2x.css", chance_2x_file, :css)
      end

      if sprited
        chance.sprite_names.each {|name|
          add_chance_file.call(resource_name + "-" + name, name, :image);
        }

        chance.sprite_names({:x2 => true}).each {|name|
          add_chance_file.call(resource_name + "-" + name, name, :image);
        }
      end
    end

  end

  desc "generates combined entries for javascript"
  task :combine => %w(setup chance javascript module_info) do |task, env|
    config = env[:target].config
    manifest = env[:manifest]
    config   = CONFIG

    javascript_entries = {}

    manifest.entries.each do |entry|
      # we can only combine entries with a resource property.
      next if entry[:resource].nil?

      if entry[:entry_type] == :javascript
        (javascript_entries[entry[:resource]] ||= []) << entry
      end
    end

    # build combined JS entry
    javascript_entries.each do |resource_name, entries|
      resource_name = resource_name.ext('js')

      if resource_name == 'javascript.js'
        pf = ['source/lproj/layout.js', 'source/lproj/strings.js', 'source/core.js', 'source/utils.js']
        if manifest.target.target_type == :app
          target_name = manifest.target.target_name.to_s.split('/')[-1]
          pf.insert(2, "source/#{target_name}.js")
        end
      else
        pf = []
      end

      manifest.add_composite resource_name,
        :build_task      => 'build:combine',
        :source_entries  => entries,
        :top_level_lazy_instantiation => config[:lazy_instantiation],
        :hide_entries    => config[:combine_javascript],
        :ordered_entries => SC::Helpers::EntrySorter.sort(entries, pf),
        :entry_type      => :javascript,
        :combined        => true
    end
  end

  desc "Wraps the javascript.js file into a string if the target is a prefetched module"
  task :string_wrap => %w(setup css javascript module_info sass less combine minify) do |task, env|
    manifest = env[:manifest]
    target   = env[:target]

    next unless target[:target_type] == :module

    entry = manifest.entry_for "javascript.js"

    next if not entry
    transform = manifest.add_composite 'javascript-strings.js',
      :build_task     => 'build:string_wrap',
      :entry_type     => :javascript,
      :hide_entries   => false,
      :source_entries => [entry],

      # carry forward minification so we can do a last-minute security check
      # and not reject this file.
      :minified       => entry.minified?,
      :packed         => entry.packed? # carry forward
  end

  desc "adds a packed entry including javascript.js from required targets"
  task :packed => %w(setup combine) do |task, env|
    target   = env[:target]
    manifest = env[:manifest]

    # Only include a packed entry _if_ it is an app.
    if target[:target_type] == :app
      # Handle JavaScript version.  get all required targets and find their
      # javascript.js.  Build packed js from that.
      targets = target.expand_required_targets({ :theme => true }) + [target]
      entries = targets.map do |target|
        m = target.manifest_for(manifest.variation).build!

        # need to find the version that is not minified
        entry = m.entry_for('javascript.js')
        entry = entry.source_entry while entry && entry.minified?
        entry
      end

      entries.compact!
      manifest.add_composite 'javascript-packed.js',
        :build_task        => 'build:combine',
        :source_entries    => entries,
        :hide_entries      => false,
        :entry_type        => :javascript,
        :combined          => true,
        :ordered_entries   => entries, # orderd by load order
        :targets           => targets,
        :packed            => true

    end
  end

  task :minify => :packed # IMPORTANT: don't want minified version

  desc "adds a packed entry including stylesheet.css from required targets"
  task :packed => %w(setup combine) do |task, env|
    target   = env[:target]
    manifest = env[:manifest]

    # packed entries only for apps now.
    if target[:target_type] == :app

      combine_entries = [{
        :resource => 'stylesheet',
        :fallback => nil
      }, {
        :resource => 'stylesheet@2x',
        :fallback => 'stylesheet'
      }]

      combine_entries.each {|combine_entry|
        resource = combine_entry[:resource]
        fallback = combine_entry[:fallback]

        # Handle CSS version.  get all required targets and find their
        # stylesheet.css.  Build packed css from that.
        targets = target.expand_required_targets({ :theme => true }) + [target]
        entries = targets.map do |target|
          m = target.manifest_for(manifest.variation).build!
          entry = m.entry_for(resource + ".css")

          if entry.nil? and not fallback.nil?
            entry = m.entry_for(fallback + ".css")
          end

          entry
        end

        entries.compact!

        next if entries.length == 0
        manifest.add_composite resource + '-packed.css',
          :build_task        => 'build:combine',
          :source_entries    => entries,
          :hide_entries      => false,
          :entry_type        => :css,
          :combined          => true,
          :ordered_entries   => entries, # ordered by load order
          :targets           => targets,
          :packed            => true

      }
    end

  end

  desc "splits packed CSS files into chunks based on selector count because internet explorer is shitty"
  task :split => %w(packed) do |task, env|
    # The split_css transform, which we add to stylesheet-packed, splits CSS based on number
    # of selectors (because IE has a maximum of ~4096 per file). It actually ends up adding
    # more manifest entries, which is UBER HACKY!!! But, what can you do when you don't
    # know what files you'll have to create until you've already started building?
    target = env[:target]
    manifest = env[:manifest]

    if target[:target_type] == :app
      # We don't need to do @2x: it is safari only.
      resource = "stylesheet-packed.css"
      entry = manifest.entry_for(resource)

      # There may not be a stylesheet-packed.
      if not entry.nil?        
        entry = manifest.add_transform entry,
          :build_task => 'build:split_css'
      end
    end
  end

  task :minify => :split # IMPORTANT: don't want minified version

  #Create builder tasks for sass and less in a DRY way
  [:sass, :less].each do |csscompiler|
    desc sprintf("create a builder task for all %s files to create css files", csscompiler.to_s)
    task csscompiler => :setup do |task, env|
      manifest = env[:manifest]

      manifest.entries.each do |entry|
        next unless entry[:ext] == csscompiler.to_s

        manifest.add_transform entry,
          :filename   => ['source', entry[:filename]].join('/'),
          :build_path => File.join(manifest[:build_root], 'source', entry[:filename]),
          :url => [manifest[:url_root], 'source', entry[:filename]].join("/"),
          :build_task => 'build:'+csscompiler.to_s,
          :entry_type => :css,
          :build_required => true, # tells the :chance step that it needs to stage it before it can use it
          :ext        => 'css',
          :resource   => 'stylesheet',
          :required   => []
      end
    end
  end

  desc "find all html-generating files, annotate and combine them"
  task :html => :setup do |task, env|
    target   = env[:target]
    manifest = env[:manifest]
    config   = CONFIG

    # select all entries with proper extensions
    known_ext = %w(rhtml erb haml)
    entries = manifest.entries.select do |e|
      (e[:entry_type] == :html) || (e[:entry_type].nil? && known_ext.include?(e[:ext]))
    end

    # tag entry with build directives and sort by resource
    entries_by_resource = {}

    entries.each do |entry|
      entry[:entry_type] = :html
      entry[:resource] = 'index'

      entry[:render_task] = case entry[:ext]
      when 'rhtml'
        'render:erubis'
      when 'erb'
        "render:erubis"
      when 'haml'
        'render:haml'
      end

      # items beginning with an underscore are partials.  do not build
      if entry[:filename] =~ /^_/
        entry.hide!
        entry[:is_partial] = true

      # not a partial
      else
        # use a custom scan method since discover_build_directives! is too
        # general...
        entry.scan_source(/<%\s*sc_resource\(?\s*['"](.+)['"]\s*\)?/) do |m|
          entry.resource = m[0].ext ''
        end
        (entries_by_resource[entry[:resource]] ||= []) << entry
      end
    end

    # even if no resource was found for the index.html, add one anyway if
    # the target is loadable
    if target.loadable? && entries_by_resource['index'].nil?
      entries_by_resource['index'] = []
    end

    # Now, build combined entry for each resource
    entries_by_resource.each do |resource_name, entries|
      resource_name = resource_name.ext('html')
      is_index = resource_name == 'index.html'

      # compute the friendly_url assuming normal install process
      friendly_url = [target[:index_root]]
      m_language = manifest[:language].to_sym
      t_preferred = (target.config[:preferred_language] || :en).to_sym
      if is_index
        friendly_url << m_language.to_s unless t_preferred == m_language
      else
        friendly_url << m_language.to_s
        friendly_url << resource_name
      end
      friendly_url = friendly_url.join('/')

      is_pref_lang = (manifest[:language] == config[:preferred_language])
      is_hidden = !target.loadable? && is_index
      overwrite_current = config[:overwrite_current]

      # index.html entries get generated three times.  Once for inside the
      # build dir, once for the language and once for the entire target name
      # Note that you must generate an index.html entry for all three even
      # if you won't actually use it because other index.html entries may
      # reference it
      (is_index ? 3 : 1).times do |rep_cnt|

        manifest.add_composite resource_name,
          :entry_type => :html,
          :combined => true,
          :build_task => 'build:html',
          :source_entries => entries, # make independent
          :hidden     =>  is_hidden,
          :include_required_targets => target.loadable? && is_index,
          :friendly_url => friendly_url,
          :is_index   => is_index

        # if this is the index, setup next rep
        if is_index
          resource_name = File.join('..', resource_name)
          is_hidden = true if !target.loadable? || !overwrite_current
          is_hidden = true if (rep_cnt>=2) && !is_pref_lang
        end
      end
    end
  end

  desc "creates transform entries for all css and Js entries to minify them if needed"
  task :minify => %w(setup javascript module_info css combine sass less html) do |task, env|
    manifest = env[:manifest]
    config   = CONFIG

    minify_javascript = config[:minify_javascript]
    minify_javascript = config[:minify] if minify_javascript.nil?

    minify_html = config[:minify_html]
    minify_html = config[:minify] if minify_html.nil?

    if SC.env[:dont_minify]
      minify_javascript = false
      minify_html = false
    end

    manifest.entries.dup.each do |entry|
      case entry[:entry_type]
        # NOTE: CSS IS MINIFIED BY CHANCE. So we ignore it here.

      when :html
        if minify_html
          manifest.add_transform entry,
              :build_task => 'build:minify:html',
              :entry_type => :html,
              :minified => true
        end
      when :javascript
        if minify_javascript
          manifest.add_transform entry,
            :build_task => 'build:minify:javascript',
            :entry_type => :javascript,
            :minified   => true,
            :combined   => entry.combined?,  # carry forward
            :packed     => entry.packed? # carry forward
        end
      end
    end
  end

  desc "adds a loc strings entry that generates a yaml file server-side functions can use"
  task :strings => %w(setup javascript module_info) do |task, env|
    manifest = env[:manifest]

    # find the lproj/strings.js file...
    if entry = (manifest.entry_for('source/lproj/strings.js') || manifest.entry_for('source/lproj/strings.js', :hidden => true))
      manifest.add_transform entry,
        :filename   => 'strings.yaml',
        :build_path => File.join(manifest[:build_root], 'strings.yaml'),
        :staging_path => File.join(manifest[:staging_root], 'strings.yaml'),
        :url        => [manifest[:url_root], 'strings.yaml'].join('/'),
        :build_task => 'build:strings',
        :ext        => 'yaml',
        :entry_type => :strings,
        :hide_entry => false,
        :hidden     => true
    end
  end

  desc "..."
  task :image => :setup do
  end
end

end