rharriso/bower-rails

View on GitHub
lib/bower-rails/dsl.rb

Summary

Maintainability
B
5 hrs
Test Coverage
require 'json'
require 'fileutils'

module BowerRails
  class Dsl

    DEFAULT_DEPENDENCY_GROUP = :dependencies

    def self.evalute(root_path, filename)
      new(root_path).tap do |dsl|
        dsl.send(eval_file_method, File.join(root_path, filename))
      end
    end

    def self.eval_file_method
      BowerRails.use_gem_deps_for_bowerfile ? :eval_file_with_deps : :eval_file
    end

    attr_reader :dependencies, :root_path

    def initialize(root_path)
      @dependency_groups = []
      @root_path = root_path
      @bower_dependencies_list = []
      @dependencies = {}
      @resolutions = {}
      @ignored_dependencies = []
      @assets_path ||= "assets"
      @main_files = {}
      @current_group = nil
    end

    def asset(name, *args, &block)
      @asset_name = name
      group = @current_group || default_group
      options = Hash === args.last ? args.pop.dup : {}

      version = args.last || "latest"
      version = options[:ref] if options[:ref]

      options[:git] = "https://github.com/#{options[:github]}.git" if options[:github]

      if options[:git]
        version = if version == 'latest'
                    options[:git]
                  else
                    options[:git] + "#" + version
                  end
      end

      if options[:main_files]
        main_files(options[:main_files])
      end

      instance_eval(&block) if block_given?

      normalized_group_path = normalize_location_path(group.first, group_assets_path(group))
      @dependencies[normalized_group_path] ||= {}
      @dependencies[normalized_group_path][current_dependency_group_normalized] ||= {}
      @dependencies[normalized_group_path][current_dependency_group_normalized][name] = version
    end

    def dependency_group(name, options = {}, &block)

      assert_dependency_group_name name
      add_dependency_group name

      yield if block_given?

      remove_dependency_group!
    end

    def directories
      @dependencies.keys
    end

    def eval_file(file)
      instance_eval(File.open(file, 'rb') { |f| f.read }, file.to_s)
    end

    def eval_file_with_deps(file)
      Gem::Specification.map do |dep|
        bowerfile_in_dep = File.join(dep.gem_dir, 'Bowerfile')
        eval_file(bowerfile_in_dep) if bowerfile_in_dep != file && File.exist?(bowerfile_in_dep)
      end
      eval_file(file)
    end

    def final_assets_path
      groups.map do |group|
        [group.first.to_s, group_assets_path(group)]
      end.uniq
    end

    def generate_dotbowerrc
      contents = JSON.parse(File.read(File.join(root_path, '.bowerrc'))) rescue {}
      contents["directory"] = "bower_components"
      contents["ignoredDependencies"] = @ignored_dependencies
      JSON.pretty_generate(contents)
    end

    def group_assets_path group
      group.last[:assets_path]
    end

    def group(name, options = {}, &block)
      options[:assets_path] ||= @assets_path

      assert_asset_path options[:assets_path]
      assert_group_name name

      @current_group = add_group name, options
      yield if block_given?
    end

    def resolution(name, version)
      @resolutions[name] = version
    end

    def resolutions_with_root
      { :resolutions => @resolutions }
    end

    def ignored_dependency(name)
      @ignored_dependencies.push(name)
    end

    def write_bower_json
      @dependencies.each do |dir, data|

        FileUtils.mkdir_p dir unless File.directory? dir
        File.open(File.join(dir, "bower.json"), "w") do |f|
          data.merge!(resolutions_with_root)
          f.write(dependencies_to_json(data))
        end
      end
    end

    def write_dotbowerrc
      groups.map do |group|
        normalized_group_path = normalize_location_path(group.first, group_assets_path(group))
        File.open(File.join(normalized_group_path, ".bowerrc"), "w") do |f|
          f.write(generate_dotbowerrc)
          f.write("\n")
        end
      end
    end

    # accepts string or array to alter main option for bower.json
    def main_files(value = nil)
      if value
        @main_files[@asset_name] = Array(value)
      end
      @main_files
    end

    private

    # Stores the dependency group name in the stack
    #
    def add_dependency_group(dependency_group)
      @dependency_groups.push dependency_group.to_sym

      dependency_group
    end

    def add_group(*group)
      @groups = (groups << group) and return group
    end

    def assert_dependency_group_name(name)
      unless [:dependencies, :devDependencies].include?(normalize_dependency_group_name(name))
        raise ArgumentError, "Dependency group should be either dependencies or dev_dependencies, provided: #{name}"
      end
    end

    def assets_path(assets_path)
      assert_asset_path assets_path
      @assets_path = assets_path
    end

    def assert_asset_path(path)
      unless path.start_with?('assets', '/assets')
        raise ArgumentError, "Assets should be stored in /assets directory, try assets_path 'assets/#{path}' instead"
      end
    end

    def assert_group_name name
      raise ArgumentError, "Group name should be :lib or :vendor only" unless [:lib, :vendor].include?(name)
    end

    # Returns name for the current dependency from the stack
    #
    def current_dependency_group
      @dependency_groups.last || DEFAULT_DEPENDENCY_GROUP.to_sym
    end

    # Returns normalized current dependency group name
    #
    def current_dependency_group_normalized
      normalize_dependency_group_name current_dependency_group
    end

    def default_group
      [:vendor, { :assets_path => @assets_path }]
    end

    # Attempts to parse data from @dependencies to JSON
    #
    def dependencies_to_json(data)
      JSON.pretty_generate({
        :name => "dsl-generated-dependencies"
      }.merge(data))
    end

    def groups
      @groups ||= [default_group]
    end

    # Implementing ActiveSupport::Inflector camelize(:lower)
    #
    def normalize_dependency_group_name(name)
      segments = name.to_s.dup.downcase.split(/_/)
      [segments.shift, *segments.map{ |word| word.capitalize }].join('').to_sym
    end

    def normalize_location_path(loc, assets_path)
      File.join(root_path, loc.to_s, assets_path)
    end

    # Removes the dependency group name in the stack
    #
    def remove_dependency_group!
      @dependency_groups.pop
    end
  end
end