Skip to content

Commit

Permalink
Separate HighLine extensions into their own class, use extension rath…
Browse files Browse the repository at this point in the history
…er than injection. Fix unit tests to properly isolate $terminal.

Implement a cleaner table, heading, and word break algorithm that uses scanning for ANSI sequences as well as automatic width adjustment.
  • Loading branch information
smarterclayton committed Apr 8, 2013
1 parent 7c6da94 commit c911db3
Show file tree
Hide file tree
Showing 16 changed files with 867 additions and 589 deletions.
5 changes: 5 additions & 0 deletions lib/rhc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ module RHC

require 'rhc/exceptions'

require 'commander'
require 'commander/delegates'
require 'highline/system_extensions'

require 'rhc/highline_extensions'
2 changes: 1 addition & 1 deletion lib/rhc/cartridge_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def list_cartridges(cartridges)
carts = cartridges.map{ |c| [c.name, c.display_name || ''] }.sort{ |a,b| a[1].downcase <=> b[1].downcase }
carts.unshift ['==========', '=========']
carts.unshift ['Short Name', 'Full name']
say table(carts).join("\n")
say table(carts)
end
end
end
6 changes: 0 additions & 6 deletions lib/rhc/cli.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
require 'rhc'
require 'commander'
require 'commander/runner'
require 'commander/delegates'
require 'rhc/commands'

include Commander::UI
include Commander::UI::AskForClass

module RHC
#
# Run and execute a command line session with the RHC tools.
Expand Down
4 changes: 1 addition & 3 deletions lib/rhc/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,7 @@ def self.needs_configuration!(cmd, options, config)
config.has_local_config? or
config.has_opts_config?)

RHC::Helpers.warn(
"You have not yet configured the OpenShift client tools. Please run 'rhc setup'.",
:stderr => true)
$stderr.puts RHC::Helpers.color("You have not yet configured the OpenShift client tools. Please run 'rhc setup'.", :yellow)
end
end

Expand Down
12 changes: 7 additions & 5 deletions lib/rhc/commands/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ class Account < Base
def run
user = rest_client.user

say_table nil, get_properties(user, :login, :plan_id, :consumed_gears, :max_gears) + get_properties(user.capabilities, :gear_sizes).unshift(['Server:', openshift_server]) << ['SSL Certificates Supported:', user.capabilities.private_ssl_certificates ? 'yes' : 'no'], :delete => true

if openshift_online_server?
else
end
say format_table \
nil,
get_properties(user, :login, :plan_id, :consumed_gears, :max_gears).
concat(get_properties(user.capabilities, :gear_sizes)).
unshift(['Server:', openshift_server]).
push(['SSL Certificates Supported:', user.capabilities.private_ssl_certificates ? 'yes' : 'no']),
:delete => true

0
end
Expand Down
12 changes: 6 additions & 6 deletions lib/rhc/commands/alias.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Alias < Base
def add(app, app_alias)
rest_app = rest_client.find_application(options.namespace, app)
rest_app.add_alias(app_alias)
results { say "Alias '#{app_alias}' has been added." }
success "Alias '#{app_alias}' has been added."
0
end

Expand All @@ -29,7 +29,7 @@ def add(app, app_alias)
def remove(app, app_alias)
rest_app = rest_client.find_application(options.namespace, app)
rest_app.remove_alias(app_alias)
results { say "Alias '#{app_alias}' has been removed." }
success "Alias '#{app_alias}' has been removed."
0
end

Expand Down Expand Up @@ -70,7 +70,7 @@ def update_cert(app, app_alias)
rest_alias = rest_app.find_alias(app_alias)
if rest_client.api_version_negotiated >= 1.4
rest_alias.add_certificate(certificate_content, private_key_content, options.passphrase)
results { say "SSL certificate successfully added." }
success "SSL certificate successfully added."
0
else
raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases."
Expand All @@ -89,7 +89,7 @@ def delete_cert(app, app_alias)
if rest_client.api_version_negotiated >= 1.4
confirm_action "#{color("This is a non-reversible action! Your SSL certificate will be permanently deleted from application '#{app}'.", :yellow)}\n\nAre you sure you want to delete the SSL certificate?"
rest_alias.delete_certificate
results { say "SSL certificate successfully deleted." }
success "SSL certificate successfully deleted."
0
else
raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases."
Expand All @@ -108,9 +108,9 @@ def list(app)
[a.id, a.has_private_ssl_certificate? ? 'yes' : 'no', a.has_private_ssl_certificate? ? Date.parse(a.certificate_added_at) : '-']
end
if items.empty?
results { say "No aliases associated with the application #{app}." }
info "No aliases associated with the application #{app}."
else
table(items, :header => ["Alias", "Has Certificate?", "Certificate Added"]).each { |s| say s }
say table(items, :header => ["Alias", "Has Certificate?", "Certificate Added"])
end
0
end
Expand Down
26 changes: 13 additions & 13 deletions lib/rhc/commands/cartridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@ class Cartridge < Base
summary "List available cartridges"
option ["-v", "--verbose"], "Display more details about each cartridge"
alias_action :"app cartridge list", :root_command => true, :deprecated => true
alias_action :"cartridges", :root_command => true
def list
carts = rest_client.cartridges.sort_by{ |c| "#{c.type == 'standalone' && 1}_#{c.tags.include?('experimental') ? 1 : 0}_#{(c.display_name || c.name).downcase}" }

list = if options.verbose
carts.map do |c|
name = c.display_name != c.name && "#{color(c.display_name, :cyan)} [#{c.name}]" || c.name
tags = c.tags - RHC::Rest::Cartridge::HIDDEN_TAGS
[
underline("#{name} (#{c.only_in_new? ? 'web' : 'addon'})"),
c.description,
tags.present? ? "\nTagged with: #{tags.sort.join(', ')}" : nil,
c.usage_rate? ? "\n#{format_usage_message(c)}" : nil,
].compact << "\n"
end.flatten
if options.verbose
carts.each do |c|
paragraph do
name = c.display_name != c.name && "#{color(c.display_name, :cyan)} [#{c.name}]" || c.name
tags = c.tags - RHC::Rest::Cartridge::HIDDEN_TAGS
say header([name, "(#{c.only_in_new? ? 'web' : 'addon'})"])
say c.description
paragraph{ say "Tagged with: #{tags.sort.join(', ')}" } if tags.present?
paragraph{ say format_usage_message(c) } if c.usage_rate?
end
end
else
table(carts.collect do |c|
say table(carts.collect do |c|
[c.usage_rate? ? "#{c.name} (*)" : c.name,
c.display_name,
c.only_in_new? ? 'web' : 'addon']
end)
end

say list.join("\n")
paragraph{ say "Note: Web cartridges can only be added to new applications." }
paragraph{ say "(*) denotes a cartridge with additional usage costs." } if carts.any? { |c| c.usage_rate? }

Expand Down
141 changes: 65 additions & 76 deletions lib/rhc/core_ext.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# From Rails core_ext/object.rb
require 'rhc/json'
require 'open-uri'
require 'highline'
require 'httpclient'

class Object
Expand Down Expand Up @@ -45,6 +44,71 @@ def strip_heredoc
gsub(/\n+\Z/, '').
gsub(/\n{3,}/, "\n\n")
end

ANSI_ESCAPE_SEQUENCE = /\e\[(\d{1,2}(?:;\d{1,2})*[@-~])/
ANSI_ESCAPE_MATCH = '\e\[\d+(?:;\d+)*[@-~]'
CHAR_SKIP_ANSI = "(?:(?:#{ANSI_ESCAPE_MATCH})+.?|.(?:#{ANSI_ESCAPE_MATCH})*)"

#
# Split the given string at limit, treating ANSI escape sequences as
# zero characters in length. Will insert an ANSI reset code (\e[0m)
# at the end of each line containing an ANSI code, assuming that a
# reset was not in the wrapped segment.
#
# All newlines are preserved.
#
# Lines longer than limit without natural breaks will be forcibly
# split at the exact limit boundary.
#
# Returns an Array
#
def textwrap_ansi(limit, breakword=true)
re = breakword ? /
(
# Match substrings that end in whitespace shorter than limit
#{CHAR_SKIP_ANSI}{1,#{limit}} # up to limit
(?:\s+|$) # require the limit to end on whitespace
|
# Match substrings equal to the limit
#{CHAR_SKIP_ANSI}{1,#{limit}}
)
/x :
/
(
# Match substrings that end in whitespace shorter than limit
#{CHAR_SKIP_ANSI}{1,#{limit}}
(?:\s|$) # require the limit to end on whitespace
|
# Match all continguous whitespace strings
#{CHAR_SKIP_ANSI}+?
(?:\s|$)
(?:\s+|$)?
)
/x

split("\n",-1).inject([]) do |a, line|
if line.length < limit
a << line
else
line.scan(re) do |segment, other|
# short escape sequence matches have whitespace from regex
a << segment.rstrip
# find any escape sequences after the last 0m reset, in order
escapes = segment.scan(ANSI_ESCAPE_SEQUENCE).map{ |e| e.first }.reverse.take_while{ |e| e != '0m' }.uniq.reverse
if escapes.present?
a[-1] << "\e[0m"
# TODO: Apply the unclosed sequences to the beginning of the
# next string
end
end
end
a
end
end

def strip_ansi
gsub(ANSI_ESCAPE_SEQUENCE, '')
end
end

unless HTTP::Message.method_defined? :ok?
Expand Down Expand Up @@ -107,78 +171,3 @@ def reverse_merge!(other_hash)
merge!( other_hash ){|key,left,right| left }
end
end

# Some versions of highline get in an infinite loop when trying to wrap.
# Fixes BZ 866530.
class HighLine

def wrap_line(line)
wrapped_line = []
i = chars_in_line = 0
word = []

while i < line.length
# we have to give a length to the index because ruby 1.8 returns the
# byte code when using a single fixednum index
c = line[i, 1]
color_code = nil
# escape character probably means color code, let's check
if c == "\e"
color_code = line[i..i+6].match(/\e\[\d{1,2}m/)
if color_code
# first the existing word buffer then the color code
wrapped_line << word.join.wrap(@wrap_at) << color_code[0]
word.clear

i += color_code[0].length
end
end

# visible character
if !color_code
chars_in_line += 1
word << c

# time to wrap the line?
if chars_in_line == @wrap_at
if c == ' ' or line[i+1, 1] == ' ' or word.length == @wrap_at
wrapped_line << word.join
word.clear
end

wrapped_line[-1].rstrip!
wrapped_line << "\n"

# consume any spaces at the begining of the next line
word = word.join.lstrip.split(//)
chars_in_line = word.length

if line[i+1, 1] == ' '
i += 1 while line[i+1, 1] == ' '
end

else
if c == ' '
wrapped_line << word.join
word.clear
end
end

i += 1
end
end

wrapped_line << word.join
wrapped_line.join
end

def wrap(text)
wrapped_text = []
lines = text.split(/\r?\n/)
lines.each_with_index do |line, i|
wrapped_text << wrap_line(i == lines.length - 1 ? line : line.rstrip)
end

return wrapped_text.join("\n")
end
end
Loading

0 comments on commit c911db3

Please sign in to comment.