technicalpickles/jeweler

View on GitHub
lib/jeweler/generator.rb

Summary

Maintainability
B
6 hrs
Test Coverage
require 'git'
require 'github_api'
require 'highline/import'
require 'erb'

require 'net/http'
require 'uri'

require 'fileutils'
require 'pathname'

require 'jeweler/version'

class Jeweler
  class NoGitUserName < StandardError
  end
  class NoGitUserEmail < StandardError
  end
  class FileInTheWay < StandardError
  end
  class NoGitHubRepoNameGiven < StandardError
  end
  class NoGitHubUser < StandardError
  end
  class GitInitFailed < StandardError
  end
  class GitRepoCreationFailed < StandardError
  end

  # Generator for creating a jeweler-enabled project
  class Generator
    require 'jeweler/generator/options'
    require 'jeweler/generator/application'

    require 'jeweler/generator/github_mixin'

    require 'jeweler/generator/bacon_mixin'
    require 'jeweler/generator/micronaut_mixin'
    require 'jeweler/generator/minitest_mixin'
    require 'jeweler/generator/rspec_mixin'
    require 'jeweler/generator/shoulda_mixin'
    require 'jeweler/generator/testspec_mixin'
    require 'jeweler/generator/testunit_mixin'
    require 'jeweler/generator/riot_mixin'
    require 'jeweler/generator/shindo_mixin'

    require 'jeweler/generator/rdoc_mixin'
    require 'jeweler/generator/yard_mixin'

    attr_accessor :target_dir, :user_name, :user_email, :summary, :homepage,
                  :description, :project_name, :github_username,
                  :repo, :should_create_remote_repo,
                  :testing_framework, :documentation_framework,
                  :should_use_cucumber, :should_use_bundler,
                  :should_setup_rubyforge, :should_use_reek, :should_use_roodi,
                  :development_dependencies,
                  :options,
                  :git_remote

    def initialize(options = {})
      self.options = options
      extracted_directory = nil

      self.project_name   = options[:project_name]
      if project_name.nil? || project_name.squeeze.strip == ''
        raise NoGitHubRepoNameGiven
      else
        path = File.split(project_name)

        if path.size > 1
          extracted_directory = File.join(path[0..-1])
          self.project_name = path.last
        end
      end

      self.development_dependencies = []
      self.testing_framework = options[:testing_framework]
      self.documentation_framework = options[:documentation_framework]
      begin
        generator_mixin_name = "#{testing_framework.to_s.capitalize}Mixin"
        generator_mixin = self.class.const_get(generator_mixin_name)
        extend generator_mixin
      rescue NameError => e
        raise ArgumentError, "Unsupported testing framework (#{testing_framework})"
      end

      begin
        generator_mixin_name = "#{documentation_framework.to_s.capitalize}Mixin"
        generator_mixin = self.class.const_get(generator_mixin_name)
        extend generator_mixin
      rescue NameError => e
        raise ArgumentError, "Unsupported documentation framework (#{documentation_framework})"
      end

      self.target_dir             = options[:directory] || extracted_directory || project_name

      self.summary                = options[:summary] || 'TODO: one-line summary of your gem'
      self.description            = options[:description] || 'TODO: longer description of your gem'
      self.should_use_cucumber    = options[:use_cucumber]
      self.should_use_reek        = options[:use_reek]
      self.should_use_roodi       = options[:use_roodi]
      self.should_setup_rubyforge = options[:rubyforge]
      self.should_use_bundler     = options[:use_bundler]

      development_dependencies << ['cucumber', '>= 0'] if should_use_cucumber

      # TODO: make bundler optional?
      development_dependencies << ['bundler', '~> 1.0']
      development_dependencies << ['jeweler', "~> #{Jeweler::Version::STRING}"]
      development_dependencies << ['simplecov', '>= 0']

      development_dependencies << ['reek', '~> 1.2.8'] if should_use_reek
      development_dependencies << ['roodi', '~> 2.1.0'] if should_use_roodi

      self.user_name       = options[:user_name]
      self.user_email      = options[:user_email]
      self.homepage        = options[:homepage]

      self.git_remote      = options[:git_remote]

      raise NoGitUserName unless user_name
      raise NoGitUserEmail unless user_email

      extend GithubMixin
    end

    def run
      create_files
      create_version_control
      $stdout.puts "Jeweler has prepared your gem in #{target_dir}"
      if should_create_remote_repo
        create_and_push_repo
        $stdout.puts "Jeweler has pushed your repo to #{git_remote}"
      end
    end

    def constant_name
      project_name.split(/[-_]/).collect(&:capitalize).join
    end

    def lib_filename
      "#{project_name}.rb"
    end

    def require_name
      project_name
    end

    def file_name_prefix
      project_name.tr('-', '_')
    end

    def lib_dir
      'lib'
    end

    def feature_filename
      "#{project_name}.feature"
    end

    def steps_filename
      "#{project_name}_steps.rb"
    end

    def features_dir
      'features'
    end

    def features_support_dir
      File.join(features_dir, 'support')
    end

    def features_steps_dir
      File.join(features_dir, 'step_definitions')
    end

    private

    def create_files
      if File.exist?(target_dir) || File.directory?(target_dir)
        raise FileInTheWay, "The directory #{target_dir} already exists, aborting. Maybe move it out of the way before continuing?"
      else
        FileUtils.mkdir target_dir
      end

      output_template_in_target '.gitignore'
      output_template_in_target 'Rakefile'
      output_template_in_target 'Gemfile' if should_use_bundler
      output_template_in_target 'LICENSE.txt'
      output_template_in_target 'README.rdoc'
      output_template_in_target '.document'

      mkdir_in_target           lib_dir
      touch_in_target           File.join(lib_dir, lib_filename)

      mkdir_in_target           test_dir
      output_template_in_target File.join(testing_framework.to_s, 'helper.rb'),
                                File.join(test_dir, test_helper_filename)
      output_template_in_target File.join(testing_framework.to_s, 'flunking.rb'),
                                File.join(test_dir, test_filename)

      if testing_framework == :rspec
        output_template_in_target File.join(testing_framework.to_s, '.rspec'),
                                  '.rspec'

      end

      if should_use_cucumber
        mkdir_in_target           features_dir
        output_template_in_target File.join(%w(features default.feature)), File.join('features', feature_filename)

        mkdir_in_target           features_support_dir
        output_template_in_target File.join(features_support_dir, 'env.rb')

        mkdir_in_target           features_steps_dir
        touch_in_target           File.join(features_steps_dir, steps_filename)
      end
    end

    def render_template(source)
      template_contents = File.read(File.join(template_dir, source))
      template          = ERB.new(template_contents, nil, '<>')

      # squish extraneous whitespace from some of the conditionals
      template.result(binding).gsub(/\n\n\n+/, "\n\n")
    end

    def output_template_in_target(source, destination = source)
      final_destination = File.join(target_dir, destination)
      template_result   = render_template(source)

      File.open(final_destination, 'w') { |file| file.write(template_result) }

      $stdout.puts "\tcreate\t#{destination}"
    end

    def template_dir
      File.join(File.dirname(__FILE__), 'templates')
    end

    def mkdir_in_target(directory)
      final_destination = File.join(target_dir, directory)

      FileUtils.mkdir final_destination

      $stdout.puts "\tcreate\t#{directory}"
    end

    def touch_in_target(destination)
      final_destination = File.join(target_dir, destination)
      FileUtils.touch final_destination
      $stdout.puts "\tcreate\t#{destination}"
    end

    def create_version_control
      Dir.chdir(target_dir) do
        begin
          @repo = Git.init
        rescue Git::GitExecuteError => e
          raise GitInitFailed, 'Encountered an error during gitification. Maybe the repo already exists, or has already been pushed to?'
        end

        begin
          @repo.add('.')
        rescue Git::GitExecuteError => e
          # raise GitAddFailed, "There was some problem adding this directory to the git changeset"
          raise
        end

        begin
          @repo.commit "Initial commit to #{project_name}."
        rescue Git::GitExecuteError => e
          raise
        end

        begin
          @repo.add_remote('origin', git_remote)
        rescue Git::GitExecuteError => e
          puts 'Encountered an error while adding origin remote. Maybe you have some weird settings in ~/.gitconfig?'
          raise
        end
      end
    end

    def create_and_push_repo
      puts 'Please provide your Github password to create the Github repository'
      begin
        login = github_username
        password = ask('Password: ') { |q| q.echo = false }
        github = Github.new(login: login.strip, password: password.strip)
        github.repos.create(name: project_name, description: summary)
      rescue Github::Error::Unauthorized
        puts 'Wrong login/password! Please try again'
        retry
      rescue Github::Error::UnprocessableEntity
        raise GitRepoCreationFailed, "Can't create that repo. Does it already exist?"
      end
      # TODO: do a HEAD request to see when it's ready?
      @repo.push('origin')
    end
  end
end