dev-ui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.gitignore +14 -0
 - data/.rubocop.yml +23 -0
 - data/.travis.yml +5 -0
 - data/Gemfile +14 -0
 - data/Gemfile.lock +47 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +20 -0
 - data/bin/console +14 -0
 - data/bin/testunit +20 -0
 - data/dev-ui.gemspec +27 -0
 - data/dev.yml +8 -0
 - data/lib/dev/ui.rb +85 -0
 - data/lib/dev/ui/ansi.rb +74 -0
 - data/lib/dev/ui/box.rb +15 -0
 - data/lib/dev/ui/color.rb +58 -0
 - data/lib/dev/ui/formatter.rb +155 -0
 - data/lib/dev/ui/frame.rb +176 -0
 - data/lib/dev/ui/glyph.rb +49 -0
 - data/lib/dev/ui/interactive_prompt.rb +114 -0
 - data/lib/dev/ui/progress.rb +65 -0
 - data/lib/dev/ui/prompt.rb +88 -0
 - data/lib/dev/ui/spinner.rb +168 -0
 - data/lib/dev/ui/stdout_router.rb +189 -0
 - data/lib/dev/ui/terminal.rb +18 -0
 - data/lib/dev/ui/version.rb +5 -0
 - metadata +111 -0
 
| 
         @@ -0,0 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dev/ui'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dev
         
     | 
| 
      
 4 
     | 
    
         
            +
              module UI
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Progress
         
     | 
| 
      
 6 
     | 
    
         
            +
                  FILLED_BAR = Dev::UI::Glyph.new("◾", 0x2588, Color::CYAN)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  UNFILLED_BAR = Dev::UI::Glyph.new("◽", 0x2588, Color::WHITE)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  # Set the percent to X
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # Dev::UI::Progress.progress do |bar|
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #   bar.tick(set_percent: percent)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # Increase the percent by 1
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # Dev::UI::Progress.progress do |bar|
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #   bar.tick
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  #
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # Increase the percent by X
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # Dev::UI::Progress.progress do |bar|
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #   bar.tick(percent: 5)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  def self.progress
         
     | 
| 
      
 24 
     | 
    
         
            +
                    bar = Progress.new
         
     | 
| 
      
 25 
     | 
    
         
            +
                    print Dev::UI::ANSI.hide_cursor
         
     | 
| 
      
 26 
     | 
    
         
            +
                    yield(bar)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 28 
     | 
    
         
            +
                    puts bar.to_s
         
     | 
| 
      
 29 
     | 
    
         
            +
                    Dev::UI.raw do
         
     | 
| 
      
 30 
     | 
    
         
            +
                      print(ANSI.show_cursor)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      puts(ANSI.previous_line + ANSI.end_of_line)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def initialize(width: Terminal.width)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    @percent_done = 0
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @max_width = width
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def tick(percent: 0.01, set_percent: nil)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    raise ArgumentError, 'percent and set_percent cannot both be specified' if percent != 0.01 && set_percent
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @percent_done += percent
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @percent_done = set_percent if set_percent
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    print to_s
         
     | 
| 
      
 47 
     | 
    
         
            +
                    print Dev::UI::ANSI.previous_line
         
     | 
| 
      
 48 
     | 
    
         
            +
                    print Dev::UI::ANSI.end_of_line + "\n"
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  def to_s
         
     | 
| 
      
 52 
     | 
    
         
            +
                    suffix = " #{(@percent_done * 100).round(2)}%"
         
     | 
| 
      
 53 
     | 
    
         
            +
                    workable_width = @max_width - Frame.prefix_width - suffix.size
         
     | 
| 
      
 54 
     | 
    
         
            +
                    filled = (@percent_done * workable_width.to_f).ceil
         
     | 
| 
      
 55 
     | 
    
         
            +
                    unfilled = workable_width - filled
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    Dev::UI.resolve_text [
         
     | 
| 
      
 58 
     | 
    
         
            +
                      (FILLED_BAR.to_s * filled),
         
     | 
| 
      
 59 
     | 
    
         
            +
                      (UNFILLED_BAR.to_s * unfilled),
         
     | 
| 
      
 60 
     | 
    
         
            +
                      suffix
         
     | 
| 
      
 61 
     | 
    
         
            +
                    ].join
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dev/ui'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'readline'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Dev
         
     | 
| 
      
 5 
     | 
    
         
            +
              module UI
         
     | 
| 
      
 6 
     | 
    
         
            +
                module Prompt
         
     | 
| 
      
 7 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 8 
     | 
    
         
            +
                    def ask(question, options: nil, default: nil, is_file: nil, allow_empty: true)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      if (default && !allow_empty) || (options && (default || is_file))
         
     | 
| 
      
 10 
     | 
    
         
            +
                        raise(ArgumentError, 'conflicting arguments')
         
     | 
| 
      
 11 
     | 
    
         
            +
                      end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                      if default
         
     | 
| 
      
 14 
     | 
    
         
            +
                        puts_question("#{question} (empty = #{default})")
         
     | 
| 
      
 15 
     | 
    
         
            +
                      elsif options
         
     | 
| 
      
 16 
     | 
    
         
            +
                        puts_question("#{question} {{yellow:(choose with ↑ ↓ ⏎)}}")
         
     | 
| 
      
 17 
     | 
    
         
            +
                      else
         
     | 
| 
      
 18 
     | 
    
         
            +
                        puts_question(question)
         
     | 
| 
      
 19 
     | 
    
         
            +
                      end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      return InteractivePrompt.call(options) if options
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                      loop do
         
     | 
| 
      
 24 
     | 
    
         
            +
                        line = readline(is_file: is_file)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                        if line.empty? && default
         
     | 
| 
      
 27 
     | 
    
         
            +
                          write_default_over_empty_input(default)
         
     | 
| 
      
 28 
     | 
    
         
            +
                          return default
         
     | 
| 
      
 29 
     | 
    
         
            +
                        end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                        if !line.empty? || allow_empty
         
     | 
| 
      
 32 
     | 
    
         
            +
                          return line
         
     | 
| 
      
 33 
     | 
    
         
            +
                        end
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    def confirm(question)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      puts_question("#{question} {{yellow:(choose with ↑ ↓ ⏎)}}")
         
     | 
| 
      
 39 
     | 
    
         
            +
                      InteractivePrompt.call(%w(yes no)) == 'yes'
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    private
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    def write_default_over_empty_input(default)
         
     | 
| 
      
 45 
     | 
    
         
            +
                      Dev::UI.raw do
         
     | 
| 
      
 46 
     | 
    
         
            +
                        STDERR.puts(
         
     | 
| 
      
 47 
     | 
    
         
            +
                          Dev::UI::ANSI.cursor_up(1) +
         
     | 
| 
      
 48 
     | 
    
         
            +
                          "\r" +
         
     | 
| 
      
 49 
     | 
    
         
            +
                          Dev::UI::ANSI.cursor_forward(4) + # TODO: width
         
     | 
| 
      
 50 
     | 
    
         
            +
                          default +
         
     | 
| 
      
 51 
     | 
    
         
            +
                          Dev::UI::Color::RESET.code
         
     | 
| 
      
 52 
     | 
    
         
            +
                        )
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    def puts_question(str)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      Dev::UI.with_frame_color(:blue) do
         
     | 
| 
      
 58 
     | 
    
         
            +
                        STDOUT.puts(Dev::UI.fmt('{{?}} ' + str))
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
                    end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    def readline(is_file: false)
         
     | 
| 
      
 63 
     | 
    
         
            +
                      if is_file
         
     | 
| 
      
 64 
     | 
    
         
            +
                        Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC
         
     | 
| 
      
 65 
     | 
    
         
            +
                        Readline.completion_append_character = ""
         
     | 
| 
      
 66 
     | 
    
         
            +
                      else
         
     | 
| 
      
 67 
     | 
    
         
            +
                        Readline.completion_proc = proc { |*| nil }
         
     | 
| 
      
 68 
     | 
    
         
            +
                        Readline.completion_append_character = " "
         
     | 
| 
      
 69 
     | 
    
         
            +
                      end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                      # because Readline is a C library, Dev::UI's hooks into $stdout don't
         
     | 
| 
      
 72 
     | 
    
         
            +
                      # work. We could work around this by having Dev::UI use a pipe and a
         
     | 
| 
      
 73 
     | 
    
         
            +
                      # thread to manage output, but the current strategy feels like a
         
     | 
| 
      
 74 
     | 
    
         
            +
                      # better tradeoff.
         
     | 
| 
      
 75 
     | 
    
         
            +
                      prefix = Dev::UI.with_frame_color(:blue) { Dev::UI::Frame.prefix }
         
     | 
| 
      
 76 
     | 
    
         
            +
                      prompt = prefix + Dev::UI.fmt('{{blue:> }}{{yellow:')
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 79 
     | 
    
         
            +
                        Readline.readline(prompt, true).chomp
         
     | 
| 
      
 80 
     | 
    
         
            +
                      rescue Interrupt
         
     | 
| 
      
 81 
     | 
    
         
            +
                        Dev::UI.raw { STDERR.puts('^C' + Dev::UI::Color::RESET.code) }
         
     | 
| 
      
 82 
     | 
    
         
            +
                        raise
         
     | 
| 
      
 83 
     | 
    
         
            +
                      end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,168 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dev/ui'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Dev
         
     | 
| 
      
 4 
     | 
    
         
            +
              module UI
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Spinner
         
     | 
| 
      
 6 
     | 
    
         
            +
                  PERIOD = 0.1 # seconds
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 9 
     | 
    
         
            +
                    runes = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
         
     | 
| 
      
 10 
     | 
    
         
            +
                    colors = [Dev::UI::Color::CYAN.code] * 5 + [Dev::UI::Color::MAGENTA.code] * 5
         
     | 
| 
      
 11 
     | 
    
         
            +
                    raise unless runes.size == colors.size
         
     | 
| 
      
 12 
     | 
    
         
            +
                    GLYPHS = colors.zip(runes).map(&:join)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def self.spin(title, &block)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    sg = SpinGroup.new
         
     | 
| 
      
 17 
     | 
    
         
            +
                    sg.add(title, &block)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    sg.wait
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  class SpinGroup
         
     | 
| 
      
 22 
     | 
    
         
            +
                    def initialize
         
     | 
| 
      
 23 
     | 
    
         
            +
                      @m = Mutex.new
         
     | 
| 
      
 24 
     | 
    
         
            +
                      @consumed_lines = 0
         
     | 
| 
      
 25 
     | 
    
         
            +
                      @tasks = []
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    class Task
         
     | 
| 
      
 29 
     | 
    
         
            +
                      attr_reader :title, :exception, :success, :stdout, :stderr
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                      def initialize(title, &block)
         
     | 
| 
      
 32 
     | 
    
         
            +
                        @title = title
         
     | 
| 
      
 33 
     | 
    
         
            +
                        @thread = Thread.new do
         
     | 
| 
      
 34 
     | 
    
         
            +
                          cap = Dev::UI::StdoutRouter::Capture.new(with_frame_inset: false, &block)
         
     | 
| 
      
 35 
     | 
    
         
            +
                          begin
         
     | 
| 
      
 36 
     | 
    
         
            +
                            cap.run
         
     | 
| 
      
 37 
     | 
    
         
            +
                          ensure
         
     | 
| 
      
 38 
     | 
    
         
            +
                            @stdout = cap.stdout
         
     | 
| 
      
 39 
     | 
    
         
            +
                            @stderr = cap.stderr
         
     | 
| 
      
 40 
     | 
    
         
            +
                          end
         
     | 
| 
      
 41 
     | 
    
         
            +
                        end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                        @done      = false
         
     | 
| 
      
 44 
     | 
    
         
            +
                        @exception = nil
         
     | 
| 
      
 45 
     | 
    
         
            +
                        @success   = false
         
     | 
| 
      
 46 
     | 
    
         
            +
                      end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                      def check
         
     | 
| 
      
 49 
     | 
    
         
            +
                        return true if @done
         
     | 
| 
      
 50 
     | 
    
         
            +
                        return false if @thread.alive?
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                        @done = true
         
     | 
| 
      
 53 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 54 
     | 
    
         
            +
                          status = @thread.join.status
         
     | 
| 
      
 55 
     | 
    
         
            +
                          @success = (status == false)
         
     | 
| 
      
 56 
     | 
    
         
            +
                        rescue => exc
         
     | 
| 
      
 57 
     | 
    
         
            +
                          @exception = exc
         
     | 
| 
      
 58 
     | 
    
         
            +
                          @success = false
         
     | 
| 
      
 59 
     | 
    
         
            +
                        end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                        @done
         
     | 
| 
      
 62 
     | 
    
         
            +
                      end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                      def render(index, force = true)
         
     | 
| 
      
 65 
     | 
    
         
            +
                        return full_render(index) if force
         
     | 
| 
      
 66 
     | 
    
         
            +
                        partial_render(index)
         
     | 
| 
      
 67 
     | 
    
         
            +
                      end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                      private
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                      def full_render(index)
         
     | 
| 
      
 72 
     | 
    
         
            +
                        inset + glyph(index) + Dev::UI::Color::RESET.code + ' ' + Dev::UI.resolve_text(title)
         
     | 
| 
      
 73 
     | 
    
         
            +
                      end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                      def partial_render(index)
         
     | 
| 
      
 76 
     | 
    
         
            +
                        Dev::UI::ANSI.cursor_forward(inset_width) + glyph(index) + Dev::UI::Color::RESET.code
         
     | 
| 
      
 77 
     | 
    
         
            +
                      end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                      def glyph(index)
         
     | 
| 
      
 80 
     | 
    
         
            +
                        if @done
         
     | 
| 
      
 81 
     | 
    
         
            +
                          @success ? Dev::UI::Glyph::CHECK.to_s : Dev::UI::Glyph::X.to_s
         
     | 
| 
      
 82 
     | 
    
         
            +
                        else
         
     | 
| 
      
 83 
     | 
    
         
            +
                          GLYPHS[index]
         
     | 
| 
      
 84 
     | 
    
         
            +
                        end
         
     | 
| 
      
 85 
     | 
    
         
            +
                      end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                      def inset
         
     | 
| 
      
 88 
     | 
    
         
            +
                        @inset ||= Dev::UI::Frame.prefix
         
     | 
| 
      
 89 
     | 
    
         
            +
                      end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                      def inset_width
         
     | 
| 
      
 92 
     | 
    
         
            +
                        @inset_width ||= Dev::UI::ANSI.printing_width(inset)
         
     | 
| 
      
 93 
     | 
    
         
            +
                      end
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    def add(title, &block)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      @m.synchronize do
         
     | 
| 
      
 98 
     | 
    
         
            +
                        @tasks << Task.new(title, &block)
         
     | 
| 
      
 99 
     | 
    
         
            +
                      end
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                    def wait
         
     | 
| 
      
 103 
     | 
    
         
            +
                      idx = 0
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                      loop do
         
     | 
| 
      
 106 
     | 
    
         
            +
                        all_done = true
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                        @m.synchronize do
         
     | 
| 
      
 109 
     | 
    
         
            +
                          Dev::UI.raw do
         
     | 
| 
      
 110 
     | 
    
         
            +
                            @tasks.each.with_index do |task, int_index|
         
     | 
| 
      
 111 
     | 
    
         
            +
                              nat_index = int_index + 1
         
     | 
| 
      
 112 
     | 
    
         
            +
                              task_done = task.check
         
     | 
| 
      
 113 
     | 
    
         
            +
                              all_done = false unless task_done
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                              if nat_index > @consumed_lines
         
     | 
| 
      
 116 
     | 
    
         
            +
                                print(task.render(idx, true) + "\n")
         
     | 
| 
      
 117 
     | 
    
         
            +
                                @consumed_lines += 1
         
     | 
| 
      
 118 
     | 
    
         
            +
                              else
         
     | 
| 
      
 119 
     | 
    
         
            +
                                offset = @consumed_lines - int_index
         
     | 
| 
      
 120 
     | 
    
         
            +
                                move_to   = Dev::UI::ANSI.cursor_up(offset) + "\r"
         
     | 
| 
      
 121 
     | 
    
         
            +
                                move_from = "\r" + Dev::UI::ANSI.cursor_down(offset)
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                                print(move_to + task.render(idx, idx.zero?) + move_from)
         
     | 
| 
      
 124 
     | 
    
         
            +
                              end
         
     | 
| 
      
 125 
     | 
    
         
            +
                            end
         
     | 
| 
      
 126 
     | 
    
         
            +
                          end
         
     | 
| 
      
 127 
     | 
    
         
            +
                        end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                        break if all_done
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                        idx = (idx + 1) % GLYPHS.size
         
     | 
| 
      
 132 
     | 
    
         
            +
                        sleep(PERIOD)
         
     | 
| 
      
 133 
     | 
    
         
            +
                      end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                      debrief
         
     | 
| 
      
 136 
     | 
    
         
            +
                    end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                    def debrief
         
     | 
| 
      
 139 
     | 
    
         
            +
                      @m.synchronize do
         
     | 
| 
      
 140 
     | 
    
         
            +
                        @tasks.each do |task|
         
     | 
| 
      
 141 
     | 
    
         
            +
                          next if task.success
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                          e = task.exception
         
     | 
| 
      
 144 
     | 
    
         
            +
                          out = task.stdout
         
     | 
| 
      
 145 
     | 
    
         
            +
                          err = task.stderr
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                          Dev::UI::Frame.open('Task Failed: ' + task.title, color: :red) do
         
     | 
| 
      
 148 
     | 
    
         
            +
                            if e
         
     | 
| 
      
 149 
     | 
    
         
            +
                              puts"#{e.class}: #{e.message}"
         
     | 
| 
      
 150 
     | 
    
         
            +
                              puts "\tfrom #{e.backtrace.join("\n\tfrom ")}"
         
     | 
| 
      
 151 
     | 
    
         
            +
                            end
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                            Dev::UI::Frame.divider('STDOUT')
         
     | 
| 
      
 154 
     | 
    
         
            +
                            out = "(empty)" if out.strip.empty?
         
     | 
| 
      
 155 
     | 
    
         
            +
                            puts out
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                            Dev::UI::Frame.divider('STDERR')
         
     | 
| 
      
 158 
     | 
    
         
            +
                            err = "(empty)" if err.strip.empty?
         
     | 
| 
      
 159 
     | 
    
         
            +
                            puts err
         
     | 
| 
      
 160 
     | 
    
         
            +
                          end
         
     | 
| 
      
 161 
     | 
    
         
            +
                        end
         
     | 
| 
      
 162 
     | 
    
         
            +
                        @tasks.all?(&:success)
         
     | 
| 
      
 163 
     | 
    
         
            +
                      end
         
     | 
| 
      
 164 
     | 
    
         
            +
                    end
         
     | 
| 
      
 165 
     | 
    
         
            +
                  end
         
     | 
| 
      
 166 
     | 
    
         
            +
                end
         
     | 
| 
      
 167 
     | 
    
         
            +
              end
         
     | 
| 
      
 168 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,189 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'dev/ui'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'stringio'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Dev
         
     | 
| 
      
 5 
     | 
    
         
            +
              module UI
         
     | 
| 
      
 6 
     | 
    
         
            +
                module StdoutRouter
         
     | 
| 
      
 7 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 8 
     | 
    
         
            +
                    attr_accessor :duplicate_output_to
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  class Writer
         
     | 
| 
      
 12 
     | 
    
         
            +
                    def initialize(stream, name)
         
     | 
| 
      
 13 
     | 
    
         
            +
                      @stream = stream
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @name = name
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    def write(*args)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      if auto_frame_inset?
         
     | 
| 
      
 19 
     | 
    
         
            +
                        str = args[0].dup # unfreeze
         
     | 
| 
      
 20 
     | 
    
         
            +
                        str = str.force_encoding(Encoding::UTF_8)
         
     | 
| 
      
 21 
     | 
    
         
            +
                        str = apply_line_prefix(str, Dev::UI::Frame.prefix)
         
     | 
| 
      
 22 
     | 
    
         
            +
                        args[0] = str
         
     | 
| 
      
 23 
     | 
    
         
            +
                      else
         
     | 
| 
      
 24 
     | 
    
         
            +
                        @pending_newline = false
         
     | 
| 
      
 25 
     | 
    
         
            +
                      end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                      hook = Thread.current[:devui_output_hook]
         
     | 
| 
      
 28 
     | 
    
         
            +
                      # hook return of false suppresses output.
         
     | 
| 
      
 29 
     | 
    
         
            +
                      if !hook || hook.call(args.first, @name) != false
         
     | 
| 
      
 30 
     | 
    
         
            +
                        args.first
         
     | 
| 
      
 31 
     | 
    
         
            +
                        @stream.write_without_dev_ui(*args)
         
     | 
| 
      
 32 
     | 
    
         
            +
                        if dup = StdoutRouter.duplicate_output_to
         
     | 
| 
      
 33 
     | 
    
         
            +
                          dup.write(*args)
         
     | 
| 
      
 34 
     | 
    
         
            +
                        end
         
     | 
| 
      
 35 
     | 
    
         
            +
                      end
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                    private
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                    def auto_frame_inset?
         
     | 
| 
      
 41 
     | 
    
         
            +
                      !Thread.current[:no_devui_frame_inset]
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    def apply_line_prefix(str, prefix)
         
     | 
| 
      
 45 
     | 
    
         
            +
                      return '' if str.empty?
         
     | 
| 
      
 46 
     | 
    
         
            +
                      prefixed = String.new
         
     | 
| 
      
 47 
     | 
    
         
            +
                      str.force_encoding(Encoding::UTF_8).lines.each do |line|
         
     | 
| 
      
 48 
     | 
    
         
            +
                        if @pending_newline
         
     | 
| 
      
 49 
     | 
    
         
            +
                          prefixed << line
         
     | 
| 
      
 50 
     | 
    
         
            +
                          @pending_newline = false
         
     | 
| 
      
 51 
     | 
    
         
            +
                        else
         
     | 
| 
      
 52 
     | 
    
         
            +
                          prefixed << prefix << line
         
     | 
| 
      
 53 
     | 
    
         
            +
                        end
         
     | 
| 
      
 54 
     | 
    
         
            +
                      end
         
     | 
| 
      
 55 
     | 
    
         
            +
                      @pending_newline = !str.end_with?("\n")
         
     | 
| 
      
 56 
     | 
    
         
            +
                      prefixed
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  class Capture
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @m = Mutex.new
         
     | 
| 
      
 62 
     | 
    
         
            +
                    @active_captures = 0
         
     | 
| 
      
 63 
     | 
    
         
            +
                    @saved_stdin = nil
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    def self.with_stdin_masked
         
     | 
| 
      
 66 
     | 
    
         
            +
                      @m.synchronize do
         
     | 
| 
      
 67 
     | 
    
         
            +
                        if @active_captures.zero?
         
     | 
| 
      
 68 
     | 
    
         
            +
                          @saved_stdin = $stdin
         
     | 
| 
      
 69 
     | 
    
         
            +
                          $stdin, w = IO.pipe
         
     | 
| 
      
 70 
     | 
    
         
            +
                          $stdin.close
         
     | 
| 
      
 71 
     | 
    
         
            +
                          w.close
         
     | 
| 
      
 72 
     | 
    
         
            +
                        end
         
     | 
| 
      
 73 
     | 
    
         
            +
                        @active_captures += 1
         
     | 
| 
      
 74 
     | 
    
         
            +
                      end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                      yield
         
     | 
| 
      
 77 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 78 
     | 
    
         
            +
                      @m.synchronize do
         
     | 
| 
      
 79 
     | 
    
         
            +
                        @active_captures -= 1
         
     | 
| 
      
 80 
     | 
    
         
            +
                        if @active_captures.zero?
         
     | 
| 
      
 81 
     | 
    
         
            +
                          $stdin = @saved_stdin
         
     | 
| 
      
 82 
     | 
    
         
            +
                        end
         
     | 
| 
      
 83 
     | 
    
         
            +
                      end
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                    def initialize(with_frame_inset: true, &block)
         
     | 
| 
      
 87 
     | 
    
         
            +
                      @with_frame_inset = with_frame_inset
         
     | 
| 
      
 88 
     | 
    
         
            +
                      @block = block
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    attr_reader :stdout, :stderr
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                    def run
         
     | 
| 
      
 94 
     | 
    
         
            +
                      require 'stringio'
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                      StdoutRouter.assert_enabled!
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                      out = StringIO.new
         
     | 
| 
      
 99 
     | 
    
         
            +
                      err = StringIO.new
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                      prev_frame_inset = Thread.current[:no_devui_frame_inset]
         
     | 
| 
      
 102 
     | 
    
         
            +
                      prev_hook = Thread.current[:devui_output_hook]
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                      self.class.with_stdin_masked do
         
     | 
| 
      
 105 
     | 
    
         
            +
                        Thread.current[:no_devui_frame_inset] = !@with_frame_inset
         
     | 
| 
      
 106 
     | 
    
         
            +
                        Thread.current[:devui_output_hook] = ->(data, stream) do
         
     | 
| 
      
 107 
     | 
    
         
            +
                          case stream
         
     | 
| 
      
 108 
     | 
    
         
            +
                          when :stdout then out.write(data)
         
     | 
| 
      
 109 
     | 
    
         
            +
                          when :stderr then err.write(data)
         
     | 
| 
      
 110 
     | 
    
         
            +
                          else raise
         
     | 
| 
      
 111 
     | 
    
         
            +
                          end
         
     | 
| 
      
 112 
     | 
    
         
            +
                          false # suppress writing to terminal
         
     | 
| 
      
 113 
     | 
    
         
            +
                        end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 116 
     | 
    
         
            +
                          @block.call
         
     | 
| 
      
 117 
     | 
    
         
            +
                        ensure
         
     | 
| 
      
 118 
     | 
    
         
            +
                          @stdout = out.string
         
     | 
| 
      
 119 
     | 
    
         
            +
                          @stderr = err.string
         
     | 
| 
      
 120 
     | 
    
         
            +
                        end
         
     | 
| 
      
 121 
     | 
    
         
            +
                      end
         
     | 
| 
      
 122 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 123 
     | 
    
         
            +
                      Thread.current[:devui_output_hook] = prev_hook
         
     | 
| 
      
 124 
     | 
    
         
            +
                      Thread.current[:no_devui_frame_inset] = prev_frame_inset
         
     | 
| 
      
 125 
     | 
    
         
            +
                    end
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 129 
     | 
    
         
            +
                    WRITE_WITHOUT_DEV_UI = :write_without_dev_ui
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                    NotEnabled = Class.new(StandardError)
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                    def assert_enabled!
         
     | 
| 
      
 134 
     | 
    
         
            +
                      raise NotEnabled unless enabled?
         
     | 
| 
      
 135 
     | 
    
         
            +
                    end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                    def with_enabled
         
     | 
| 
      
 138 
     | 
    
         
            +
                      enable
         
     | 
| 
      
 139 
     | 
    
         
            +
                      yield
         
     | 
| 
      
 140 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 141 
     | 
    
         
            +
                      disable
         
     | 
| 
      
 142 
     | 
    
         
            +
                    end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                    # TODO: remove this
         
     | 
| 
      
 145 
     | 
    
         
            +
                    def ensure_activated
         
     | 
| 
      
 146 
     | 
    
         
            +
                      enable unless enabled?
         
     | 
| 
      
 147 
     | 
    
         
            +
                    end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                    def enable
         
     | 
| 
      
 150 
     | 
    
         
            +
                      return false if enabled?($stdout) || enabled?($stderr)
         
     | 
| 
      
 151 
     | 
    
         
            +
                      activate($stdout, :stdout)
         
     | 
| 
      
 152 
     | 
    
         
            +
                      activate($stderr, :stderr)
         
     | 
| 
      
 153 
     | 
    
         
            +
                      true
         
     | 
| 
      
 154 
     | 
    
         
            +
                    end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                    def enabled?(stream = $stdout)
         
     | 
| 
      
 157 
     | 
    
         
            +
                      stream.respond_to?(WRITE_WITHOUT_DEV_UI)
         
     | 
| 
      
 158 
     | 
    
         
            +
                    end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                    def disable
         
     | 
| 
      
 161 
     | 
    
         
            +
                      return false unless enabled?($stdout) && enabled?($stderr)
         
     | 
| 
      
 162 
     | 
    
         
            +
                      deactivate($stdout)
         
     | 
| 
      
 163 
     | 
    
         
            +
                      deactivate($stderr)
         
     | 
| 
      
 164 
     | 
    
         
            +
                      true
         
     | 
| 
      
 165 
     | 
    
         
            +
                    end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                    private
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                    def deactivate(stream)
         
     | 
| 
      
 170 
     | 
    
         
            +
                      sc = stream.singleton_class
         
     | 
| 
      
 171 
     | 
    
         
            +
                      sc.send(:remove_method, :write)
         
     | 
| 
      
 172 
     | 
    
         
            +
                      sc.send(:alias_method, :write, WRITE_WITHOUT_DEV_UI)
         
     | 
| 
      
 173 
     | 
    
         
            +
                    end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                    def activate(stream, streamname)
         
     | 
| 
      
 176 
     | 
    
         
            +
                      writer = StdoutRouter::Writer.new(stream, streamname)
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
                      raise if stream.respond_to?(WRITE_WITHOUT_DEV_UI)
         
     | 
| 
      
 179 
     | 
    
         
            +
                      stream.singleton_class.send(:alias_method, WRITE_WITHOUT_DEV_UI, :write)
         
     | 
| 
      
 180 
     | 
    
         
            +
                      stream.define_singleton_method(:write) do |*args|
         
     | 
| 
      
 181 
     | 
    
         
            +
                        writer.write(*args)
         
     | 
| 
      
 182 
     | 
    
         
            +
                      end
         
     | 
| 
      
 183 
     | 
    
         
            +
                    end
         
     | 
| 
      
 184 
     | 
    
         
            +
                  end
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
              end
         
     | 
| 
      
 187 
     | 
    
         
            +
            end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
            Dev::UI::StdoutRouter.enable
         
     |