circle-cli/circle-cli

View on GitHub
lib/circle/cli/app.rb

Summary

Maintainability
A
45 mins
Test Coverage
require 'launchy'
require 'circle/cli/repo'
require 'circle/cli/project'
require 'circle/cli/watcher'

module Circle
  module CLI
    class App < Thor
      CIRCLE_URL = 'https://circleci.com/account/api'

      LOGIN_HELP = <<-EOMSG
1. Press [enter], and you'll be taken CircleCI.
2. Enter a name for your new token.
3. Click 'Create new token'.
4. Come back to your prompt and paste in your new token.
5. Press enter to complete the process.
      EOMSG

      NO_TOKEN_MESSAGE = <<-EOMSG
CircleCI token hasn't been configured. Run the following command to login:

  $ circle login
      EOMSG

      default_task :status
      class_option :repo, default: '.', desc: 'path to repo'

      desc 'status', 'show CircleCI build result'
      method_option :branch, desc: 'branch name'
      method_option :watch, desc: 'watch the build', type: :boolean, default: false
      method_option :poll, default: 5, desc: 'polling frequency', type: :numeric
      def status
        validate_repo!
        validate_latest!

        watcher = Watcher.new do
          display_status
        end

        watcher.to_preload do
          project.clear_cache!
          project.latest.preload
        end

        if options[:watch]
          watcher.poll(options[:poll])
        else
          watcher.display
        end
      end

      desc 'watch', 'an alias for `circle --watch`', hide: true
      def watch
        invoke :status, [], watch: true
      end

      desc 'overview', 'list recent builds and their statuses for all branches'
      method_option :watch, desc: 'watch the list of builds', type: :boolean, default: false
      method_option :poll, default: 5, desc: 'polling frequency', type: :numeric
      def overview
        validate_repo!
        abort! 'No recent builds.' if project.recent_builds.empty?

        watcher = Watcher.new do
          display_builds(project.recent_builds)
        end

        watcher.to_preload do
          project.clear_cache!
          project.recent_builds
        end

        if options[:watch]
          watcher.poll(options[:poll])
        else
          watcher.display
        end
      end

      desc 'open', 'open CircleCI build'
      method_option :branch, desc: 'branch name'
      def open
        validate_repo!
        validate_latest!
        Launchy.open latest[:build_url]
      end

      desc 'build', 'trigger a build on circle ci'
      method_option :branch, desc: 'branch name'
      def build
        validate_repo!
        project.build!
        say "A build has been triggered.\n\n", :green
        invoke :status, [], watch: true
      end

      desc 'cancel', 'cancel most recent build'
      method_option :branch, desc: 'branch name'
      def cancel
        validate_repo!
        validate_latest!
        latest.cancel! unless latest.finished?
        invoke :status
        say "\nThe build has been cancelled.", :red unless latest.finished?
      end

      desc 'token', 'view or edit CircleCI token'
      def token(value = nil)
        if value
          repo.circle_token = value
        elsif value = repo.circle_token
          say value
        else
          say NO_TOKEN_MESSAGE, :yellow
        end
      end

      desc 'login', 'login to Circle CI'
      def login
        say LOGIN_HELP, :yellow
        ask set_color("\nPress [enter] to open CircleCI", :blue)
        Launchy.open(CIRCLE_URL)
        value = ask set_color('Enter your token:', :blue)
        repo.circle_token = value
        say "\nYour token has been set to '#{value}'.", :green
      end

      private

      def repo
        @repo ||= Repo.new(options)
      end

      def project
        @project ||= Project.new(repo)
      end

      def latest
        project.latest
      end

      def validate_repo!
        abort! "Unsupported repo url format #{repo.uri}" unless repo.uri.github?
        abort! NO_TOKEN_MESSAGE unless repo.circle_token
      end

      def validate_latest!
        abort! 'No CircleCI builds found.' unless project.latest
      end

      def abort!(message)
        abort set_color(message, :red)
      end

      def display(description, value, color)
        status = set_color description.ljust(15), :bold
        result = set_color value.to_s, color
        say "#{status} #{result}"
      end

      def display_status
        say "#{latest[:subject]}\n\n", :cyan if latest[:subject]
        display 'Build status', latest.status, latest.color
        display 'Started at', latest.formatted_start_time, latest.color
        display 'Finished at', latest.formatted_stop_time, latest.color
        display 'Compare', latest[:compare], latest.color if latest[:compare]
        display_steps latest.steps unless latest.steps.empty?
        display_failures latest.failing_tests unless latest.failing_tests.empty?
        exit 1 if latest.failed?
        exit 0 if latest.finished?
      end

      def display_steps(steps)
        say "\nSteps:", :bold

        print_table steps.map { |step|
          [set_color(step[:name], step.color), step.duration]
        }
      end

      def display_failures(failures)
        say "\nFailing specs:", :bold

        print_table failures.map { |spec|
          [set_color(spec['file'], :red), spec['name']]
        }
      end

      def display_builds(builds)
        print_table builds.map { |build|
          branch = set_color(build[:branch], :bold)
          status = set_color(build.status, build.color)
          started = build.formatted_start_time
          [branch, status, build.subject, started]
        }
      end
    end
  end
end