• Upcoming improvements for Caliper

    by Ben

    For the past several weeks, you may have noticed that not much has changed on Caliper. The reason for this is that we've been working hard to allow you to use Caliper on your private GitHub repositories (we're currently in private beta for that feature. If you are interested, you can sign up for a beta invitation).

    However, we realize that we have much to improve on Caliper. We've been talking with our users to identify the biggest problems and most-requested features. In addition, we're getting help from the folks at Viget Labs to make Caliper easier to understand, more intuitive to use, and all around better.

    In our first batch of work, we'll be focusing on three improvements.

    First, we'll be developing an improved project dashboard. Our current dashboard isn't terribly useful. We'd like to better represent the state of the project, important trends, and areas that need the most help. We're working now to come up with a concrete design that addresses these issues. In the meantime, what data would you find most helpful on the dashboard?

    We'll also be changing the way you navigate to specific tools. Users have told us that the specific tool names ('Reek', 'Flog') are not, by themselves, very helpful for navigation. As a result, we'll be increasingly highlighting their function. For example, even if you aren't familiar with Flog, we'll make it obvious that it is a tool for measuring complexity.

    We're considering eventually merging tools that have a similar function. So instead of going to two different pages for Reek and Roodi results, we might just have a 'code smells' report. Similarly, Flog and Saikuro might be presented within the same page. Would this be helpful?

    To be clear, it isn't our intention to obscure the fact that Caliper is built on great tools like metric_fu, Reek, Flog, etc. We will still make it easy to understand where the data is coming from and highlight the work being done by the dedicated open-source developers who write them. However, we think that grouping by function will make Caliper easier to use and understand.

    Did you know that you can set up Caliper to automatically generate metrics using GitHub post-receive hooks? Did you know that, by default, up to 15 of your latest commits are automatically run through Caliper when you set up a project? Or that with one click, you can generate metrics for up to 40 commits throughout the history of your project?

    Many users don't know this, because our UI for these features is currently pretty terrible. The last task in the current batch of work will be to make these features more visible and easier to use. Our goal is to make it simple to see trends in your project over time.

    (Incidentally, many users also don't know that you can view a metrics diff for two commits to see how a commit has changed the metrics, but making this more noticeable may or may not get tackled in the first set of changes).

    What's next for Caliper after we complete these changes? We're currently talking to users about what features and improvements matter most to them. One possibility is to display some or all of our metric data directly on a view of source code itself, so you don't have to open up an editor (or click through to GitHub) to see the code. How important is this feature to you?

    As always, feedback from our users is hugely helpful. If you have ideas about features that you'd like to see, please let us know in the comments or start a discussion at our support site. Thanks!

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on January 14th, 2010 by Ben in Uncategorized and tagged , .

  • A command-line prompt with timeout and countdown

    by Avdi

    Have you ever started a long operation and walked away from the computer, and come back half an hour later only to find that the process is hung up waiting for some user input? It's a sub-optimal user experience, and in many cases it can be avoided by having the program choose a default if the user doesn't respond within a certain amount of time. One example of this UI technique in the wild is powering off your computer - most modern operating systems will pop up a dialogue to confirm or cancel the shutdown, with a countdown until the shutdown proceeds automatically.

    This article is about how to achieve the same effect in command-line programs using Ruby.

    Let's start with the end result. We want to be able to call our method like this:

    puts ask_with_countdown_to_default("Do you like pie?", 30.0, false)
    

    We pass in a question, a (possibly fractional) number of seconds to wait, and a default value. The method should prompt the user with the given question and a visual countdown. If the user types 'y' or 'n', it should immediately return true or false, respectively. Otherwise when the countdown expires it should return the default value.

    Here's a high-level implementation:

    def ask_with_countdown_to_default(question, seconds, default)
      with_unbuffered_input($stdin) do
        countdown_from(seconds) do |seconds_left|
          write_then_erase_prompt(question, seconds_left) do
            wait_for_input($stdin, seconds_left % 1) do
              case char = $stdin.getc
              when ?y, ?Y then return true
              when ?n, ?N then return false
              else                  # NOOP
              end
            end
          end
        end
      end
      return default
    ensure
      $stdout.puts
    end                             # ask_with_countdown_to_default
    

    Let's take it step-by-step.

    By default, *NIX terminals operate in "canonical mode", where they buffer a line of input internally and don't send it until the user hits RETURN. This is so that the user can do simple edits like backspacing and retyping a typo. This behavior is undesirable for our purposes, however, since we want the prompt to respond as soon as the user types a key. So we need to temporarily alter the terminal configuration.

      with_unbuffered_input($stdin) do
    

    We use the POSIX Termios library, via the ruby-termios gem, to accomplish this feat.

    def with_unbuffered_input(input = $stdin)
      old_attributes = Termios.tcgetattr(input)
      new_attributes = old_attributes.dup
      new_attributes.lflag &= ~Termios::ECHO
      new_attributes.lflag &= ~Termios::ICANON
      Termios::tcsetattr(input, Termios::TCSANOW, new_attributes)
    
      yield
    ensure
      Termios::tcsetattr(input, Termios::TCSANOW, old_attributes)
    end                             # with_unbuffered_input
    

    POSIX Termios defines a set of library calls for interacting with terminals. In our case, we want to disable some of the terminal's "local" features - functionality the terminal handles internally before sending input on to the controlling program.

    We start by getting a snapshot of the terminal's current configuration. Then we make a copy for our new configuration. We are interested in two flags: "ECHO" and "ICANON". The first, ECHO, controls whether the terminal displays characters that the user has types. The second controls canonical mode, which we explained above. After turning both flags off, we set the new configuration and yield. After the block is finished, or if an exception is raised, we ensure that the original terminal configuration is reinstated.

    Now we need to arrange for a countdown timer.

        countdown_from(seconds) do |seconds_left|
    

    Here's the implementation:

    def countdown_from(seconds_left)
      start_time   = Time.now
      end_time     = start_time + seconds_left
      begin
        yield(seconds_left)
        seconds_left = end_time - Time.now
      end while seconds_left > 0.0
    end                             # countdown_from
    

    First we calculate the wallclock time at which we should stop waiting. Then we begin looping, yielding the number of seconds left, and then when the block returns recalculating the number. We keep this up until the time has expired.

    Next up is writing, and re-writing, the prompt.

          write_then_erase_prompt(question, seconds_left) do
    

    This method is implemented as follows:

    def write_then_erase_prompt(question, seconds_left)
      prompt_format = "#{question} (y/n) (%2d)"
      prompt = prompt_format % seconds_left.to_i
      prompt_length = prompt.length
      $stdout.write(prompt)
      $stdout.flush
    
      yield
    
      $stdout.write("\b" * prompt_length)
      $stdout.flush
    end                             # write_then_erase_prompt
    

    We format and print a prompt, flushing the output to insure that it is displayed immediately. The prompt includes a count of the number of seconds remaining until the query times out. In order to make it a nice visually consistent length, we use a fixed-width field for the countdown ("%2d"). Note that we don't use puts to print the prompt - we don't want it to advance to the next line, because we want to be able to dynamically rewrite the prompt as the countdown proceeds.

    After we are done yielding to the block, we erase the prompt in preparation for the next cycle. In order to erase it we create and output string of backspaces ("\b") the same length as the prompt.

    Now we need a way to wait until the user types something, while still periodically updating the prompt.

            wait_for_input($stdin, seconds_left % 1) do
    

    We pass wait_for_input an input stream and a (potentially fractional) number of seconds to wait. In this case we only want to wait until the next second-long "tick" so that we can update the countdown. So we pass in the remainder of dividing seconds_left by 1. E.g. if seconds_left was 5.3, we would set a timeout of 0.3 seconds. After 3/10 of a second of waiting for input, the wait would time out, the prompt would be erased and rewritten to show 4 seconds remaining, and then we'd start waiting for input again.

    Here's the implementation of wait_for_input:

    def wait_for_input(input, timeout)
      # Wait until input is available
      if select([input], [], [], timeout)
        yield
      end
    end                             # wait_for_input
    

    We're using Kernel#select to do the waiting. The parameters to #select are a set of arrays - one each for input, output, and errors. We only care about input, so we pass the input stream in the first array and leave the others blank. We also pass how long to wait until timing out.

    If new input is detected, select returns an array of arrays, corresponding to the three arrays we passed in. If it times out while waiting, it returns nil. We use the return value to determine whether to execute the given block or note. If there is input waiting we yield to the block; otherwise we just return.

    While it takes some getting used to, handling IO timeouts with select is safer and more reliable than using the Timeout module. And it's less messy than rescuing Timeout::Error every time a read times out.

    Finally, we need to read and interpret the character the user types, if any.

              case char = $stdin.getc
              when ?y, ?Y then return true
              when ?n, ?N then return false
              else                  # NOOP
              end
    

    If the user types 'y' or 'n' (or uppercase versions of the same), we return true or false, respectively. Otherwise, we simply ignore any characters the user types. Typing characters other than 'y' or 'n' will cause the loop to be restarted.

    Note the use of character literals like ?y to compare against the integer character code returned by IO#getc. We could alternately use Integer#chr to convert the character codes into single-character strings, if we wanted.

    Wrapping up, we make sure to return the default value should the timeout expire without any user input; and we output a newline to move the cursor past our prompt.

      return default
    

    And there you have it; a yes/no prompt with a timeout and a visual countdown. Static text doesn't really capture the effect, so rather than include sample output I'll just suggest that you try the code out for yourself (sorry, Windows users, it's *NIX-only).

    Full source for this article at: http://gist.github.com/148765

    Devver Caliper: Hosted metric_fu for your Ruby project.
    Get set up in under a minute

    Posted on July 16th, 2009 by Avdi in Ruby, Tips & Tricks and tagged , , , , .