require "vendor/transcriptic/okjson" module Transcriptic module Helpers def home_directory running_on_windows? ? ENV['USERPROFILE'].gsub("\\","/") : ENV['HOME'] end def running_on_windows? RUBY_PLATFORM =~ /mswin32|mingw32/ end def running_on_a_mac? RUBY_PLATFORM =~ /-darwin\d/ end def display(msg="", newline=true) if newline puts(msg) else print(msg) STDOUT.flush end end def redisplay(line, line_break = false) display("\r\e[0K#{line}", line_break) end def deprecate(version) display "!!! DEPRECATION WARNING: This command will be removed in version #{version}" display end def format_with_bang(message) return '' if message.to_s.strip == "" " ! " + message.split("\n").join("\n ! ") end def output_with_bang(message="", new_line=true) return if message.to_s.strip == "" display(format_with_bang(message), new_line) end def error(msg) STDERR.puts(msg) exit 1 end def confirm_billing display display "This action will cause your account to be billed at the end of the month" display "Are you sure you want to do this? (y/n) ", false if ask.downcase == 'y' transcriptic.confirm_billing return true end end def confirm(message = "Are you sure you wish to continue? (y/n)?") display("#{message} ", false) ask.downcase == 'y' end def confirm_command(app = app) raise(Transcriptic::Command::CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app ") unless app confirmed_app = extract_option('--confirm', false) if confirmed_app unless confirmed_app == app raise(Transcriptic::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app}.") end return true else display display " ! WARNING: Potentially Destructive Action" display " ! This command will affect the app: #{app}" display " ! To proceed, type \"#{app}\" or re-run this command with --confirm #{app}" display display "> ", false if ask.downcase != app display " ! Input did not match #{app}. Aborted." false else true end end end def format_date(date) date = Time.parse(date) if date.is_a?(String) date.strftime("%Y-%m-%d %H:%M %Z") end def ask STDIN.gets.strip end def shell(cmd) FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`} end def run_command(command, args=[]) Transcriptic::Command.run(command, args) end def retry_on_exception(*exceptions) retry_count = 0 begin yield rescue *exceptions => ex raise ex if retry_count >= 3 sleep 3 retry_count += 1 retry end end def time_ago(elapsed) if elapsed < 60 "#{elapsed.floor}s ago" elsif elapsed < (60 * 60) "#{(elapsed / 60).floor}m ago" else "#{(elapsed / 60 / 60).floor}h ago" end end def truncate(text, length) if text.size > length text[0, length - 2] + '..' else text end end @@kb = 1024 @@mb = 1024 * @@kb @@gb = 1024 * @@mb def format_bytes(amount) amount = amount.to_i return '(empty)' if amount == 0 return amount if amount < @@kb return "#{(amount / @@kb).round}k" if amount < @@mb return "#{(amount / @@mb).round}M" if amount < @@gb return "#{(amount / @@gb).round}G" end def quantify(string, num) "%d %s" % [ num, num.to_i == 1 ? string : "#{string}s" ] end def longest(items) items.map { |i| i.to_s.length }.sort.last end def display_table(objects, columns, headers) lengths = [] columns.each_with_index do |column, index| header = headers[index] lengths << longest([header].concat(objects.map { |o| o[column].to_s })) end display_row headers, lengths display_row headers.map { |header| "-" * header.length }, lengths objects.each do |row| display_row columns.map { |column| row[column] }, lengths end end def display_row(row, lengths) row.zip(lengths).each do |column, length| format = column.is_a?(Fixnum) ? "%#{length}s " : "%-#{length}s " display format % column, false end display end def json_encode(object) Transcriptic::OkJson.encode(object) rescue Transcriptic::OkJson::ParserError nil end def json_decode(json) Transcriptic::OkJson.decode(json) rescue Transcriptic::OkJson::ParserError nil end def set_buffer(enable) return unless $stdin.tty? if enable `stty icanon echo` else `stty -icanon -echo` end rescue # fails on windows end def get_terminal_environment { "TERM" => ENV["TERM"], "COLUMNS" => `tput cols`, "LINES" => `tput lines` } rescue { "TERM" => ENV["TERM"] } end def fail(message) raise Transcriptic::Command::CommandFailed, message end ## DISPLAY HELPERS def arrow(message) "-----> #{message}" end def action(message) display "#{arrow(message)}... ", false Transcriptic::Helpers.enable_error_capture yield Transcriptic::Helpers.disable_error_capture display "done", false display(", #{@status}", false) if @status display end def status(message) @status = message end def output(message="") return if message.to_s.strip == "" display " " + message.split("\n").join("\n ") end def output_with_arrow(message="") return if message.to_s.strip == "" display "-----> " + message.split("\n").join("\n ") end def error_with_failure(message) display "failed" message.gsub!(/^ +! */, '') display message.split("\n").map { |line| " ! #{line}" }.join("\n") exit 1 end def self.included_into @@included_into ||= [] end def self.extended_into @@extended_into ||= [] end def self.included(base) included_into << base end def self.extended(base) extended_into << base end def self.enable_error_capture included_into.each do |base| base.send(:alias_method, :error_without_failure, :error) base.send(:alias_method, :error, :error_with_failure) end extended_into.each do |base| class << base alias_method :error_without_failure, :error alias_method :error, :error_with_failure end end end def self.disable_error_capture included_into.each do |base| base.send(:alias_method, :error, :error_without_failure) end extended_into.each do |base| class << base alias_method :error, :error_without_failure end end end def string_distance(first, last) distances = [] # 0x0s 0.upto(first.length) do |index| distances << [index] + [0] * last.length end distances[0] = 0.upto(last.length).to_a 1.upto(last.length) do |last_index| 1.upto(first.length) do |first_index| first_char = first[first_index - 1, 1] last_char = last[last_index - 1, 1] if first_char == last_char distances[first_index][last_index] = distances[first_index - 1][last_index - 1] # noop else distances[first_index][last_index] = [ distances[first_index - 1][last_index], # deletion distances[first_index][last_index - 1], # insertion distances[first_index - 1][last_index - 1] # substitution ].min + 1 # cost if first_index > 1 && last_index > 1 first_previous_char = first[first_index - 2, 1] last_previous_char = last[last_index - 2, 1] if first_char == last_previous_char && first_previous_char == last_char distances[first_index][last_index] = [ distances[first_index][last_index], distances[first_index - 2][last_index - 2] + 1 # transposition ].min end end end end end distances[first.length][last.length] end def suggestion(actual, possibilities) distances = Hash.new {|hash,key| hash[key] = []} possibilities.each do |suggestion| distances[string_distance(actual, suggestion)] << suggestion end minimum_distance = distances.keys.min if minimum_distance < 4 suggestions = distances[minimum_distance].sort if suggestions.length == 1 "Perhaps you meant `#{suggestions.first}`." else "Perhaps you meant #{suggestions[0...-1].map {|suggestion| "`#{suggestion}`"}.join(', ')} or `#{suggestions.last}`." end else nil end end end end unless String.method_defined?(:shellescape) class String def shellescape empty? ? "''" : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\\\\1').gsub(/\n/, "'\n'") end end end