Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Banner] Simplify header section #25

Merged
merged 4 commits into from
May 12, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 55 additions & 37 deletions lib/claide/command/banner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,56 +19,52 @@ def initialize(command)
# @return [String] The banner for the command.
#
def formatted_banner
banner = []
banner << banner_head

if commands = formatted_subcommand_summaries
banner << "Commands:\n\n#{commands}"
end

if options = formatted_options_description
banner << "Options:\n\n#{options}"
end

banner.compact.join("\n\n")
sections = [
['Usage', formatted_usage_description],
['Commands', formatted_subcommand_summaries],
['Options', formatted_options_description]
]
banner = sections.map do |(title, body)|
["#{title.ansi.underline}:", body] if body
end.compact.join("\n\n")
banner
end

private

# @!group Banner sections
#-----------------------------------------------------------------------#

# @return [String] The head section describing the usage or
# the description for abstract commands.
# @return [String] The indentation of the text.
#
def banner_head
if command.abstract_command?
command.description if command.description
elsif usage = formatted_usage_description
"Usage:\n\n#{usage}"
end
end
NAME_INDENTATION = 4
TEXT_INDENT = 6

# @return [String] The minimum between a name and its description.
# @return [Fixnum] The maximum width of the text.
#
MAX_WIDTH = TEXT_INDENT + 80

# @return [Fixnum] The minimum between a name and its description.
#
DESCRIPTION_SPACES = 3

# @return [String] The minimum between a name and its description.
# @return [Fixnum] The minimum between a name and its description.
#
SUBCOMMAND_BULLET_SIZE = 2

# @return [String] The section describing the usage of the command.
#
def formatted_usage_description
if message = command.description || command.summary
message_lines = Helper.strip_heredoc(message).split("\n")
message_lines = message_lines.map { |l| l.insert(0, ' ' * 6) }
formatted_message = message_lines.join("\n")

if raw_message = command.description || command.summary
signature = prettify_signature(command)
result = "$ #{signature}\n\n#{formatted_message}"
result.insert(0, ' ' * NAME_INDENTATION)
formatted_message = Helper.format_markdown(raw_message,
TEXT_INDENT,
MAX_WIDTH)
message = prettify_message(command, formatted_message)
result = "#{signature}\n\n"
result.insert(0, '$ ')
result.insert(0, ' ' * (TEXT_INDENT - '$ '.size))
result << "#{message}"
result
end
end

Expand Down Expand Up @@ -115,13 +111,13 @@ def formatted_options_description
# option).
#
def entry_description(name, description, name_width)
desc_start = max_name_width + NAME_INDENTATION + DESCRIPTION_SPACES
desc_start = max_name_width + (TEXT_INDENT - 2) + DESCRIPTION_SPACES
result = ''
result << ' ' * NAME_INDENTATION
result << ' ' * (TEXT_INDENT - 2)
result << name
result << ' ' * DESCRIPTION_SPACES
result << ' ' * (max_name_width - name_width)
result << Helper.wrap_with_indent(description, desc_start)
result << Helper.wrap_with_indent(description, desc_start, MAX_WIDTH)
end

# @!group Subclasses overrides
Expand All @@ -130,9 +126,31 @@ def entry_description(name, description, name_width)
# @return [String] A decorated textual representation of the command.
#
def prettify_signature(command)
result = "#{command.full_command.ansi.green}"
result << " #{command.arguments.ansi.magenta}" if command.arguments
result
components = []
components << command.full_command.ansi.green
if command.subcommands.any?
if command.default_subcommand
components << '[COMMAND]'.ansi.green
else
components << 'COMMAND'.ansi.green
end
end
components << command.arguments.ansi.magenta if command.arguments
components.join(' ')
end

def prettify_message(command, message)
message = message.dup
if command.arguments
command.arguments.split(' ').each do |name|
name = name.sub('[', '').sub(']', '')
message.gsub!(/['`\w]#{name}['`\w]/, "`#{name}`".ansi.magenta)
end
end
command.options.each do |(name, _description)|
message.gsub!(/['`\w]#{name}['`\w]/, "`#{name}`".ansi.blue)
end
message
end

# @return [String] A decorated textual representation of the subcommand
Expand Down
45 changes: 40 additions & 5 deletions lib/claide/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,35 @@ def self.terminal_width
@terminal_width
end

# @return [String] Formats a markdown string by stripping heredoc
# indentation and wrapping by word to the terminal width taking
# into account a maximum one, and indenting the string. Code lines
# (i.e. indented by four spaces) are not word wrapped.
#
# @param [String] string
# The string to format.
#
# @param [Fixnum] indent
# The number of spaces to insert before the string.
#
# @param [Fixnum] max_width
# The maximum width to use to format the string if the terminal is
# too wide.
#
def self.format_markdown(string, indent = 0, max_width = 80)
paragraphs = Helper.strip_heredoc(string).split("\n\n")
paragraphs = paragraphs.map do |paragraph|
if paragraph.start_with?(' ' * 4)
result = paragraph
else
full_line = paragraph.gsub("\n", ' ')
result = wrap_with_indent(full_line, indent, max_width)
end
result.insert(0, ' ' * indent).rstrip
end
paragraphs.join("\n\n")
end

# @return [String] Wraps a string to the terminal width taking into
# account the given indentation.
#
Expand All @@ -24,14 +53,20 @@ def self.terminal_width
# @param [Fixnum] indent
# The number of spaces to insert before the string.
#
def self.wrap_with_indent(string, indent = 0)
# @param [Fixnum] max_width
# The maximum width to use to format the string if the terminal is
# too wide.
#
def self.wrap_with_indent(string, indent = 0, max_width = 80)
if terminal_width == 0
string
width = max_width
else
width = terminal_width - indent
space = ' ' * indent
word_wrap(string, width).split("\n").join("\n#{space}")
width = [terminal_width, max_width].min
end

available_width = width - indent
space = ' ' * indent
word_wrap(string, available_width).split("\n").join("\n#{space}")
end

# @return [String] Lifted straight from ActionView. Thanks guys!
Expand Down
24 changes: 3 additions & 21 deletions spec/command/banner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,6 @@
module CLAide
describe Command::Banner do
describe 'in general' do

it 'does not include a usage banner for an abstract command' do
banner = Command::Banner.new(Fixture::Command::SpecFile)
banner.formatted_banner.should ==
<<-BANNER.strip_margin('|').rstrip
|Manage spec files.
|
|Commands:
|
|#{banner.send(:formatted_subcommand_summaries)}
|
|Options:
|
|#{banner.send(:formatted_options_description)}
BANNER
end

it 'combines the summary/description, commands, and options' do
banner = Command::Banner.new(Fixture::Command::SpecFile::Create)
banner.formatted_banner.should ==
Expand All @@ -37,17 +20,16 @@ module CLAide
end
end

#-------------------------------------------------------------------------#

describe 'banner components' do
it "returns a usage description based on the command's description" do
Helper.stubs(:terminal_width).returns(52)
banner = Command::Banner.new(Fixture::Command::SpecFile::Create)
banner.send(:formatted_usage_description).should ==
<<-USAGE.strip_margin('|').rstrip
| $ bin spec-file create [NAME]
|
| Creates a spec file called NAME
| and populates it with defaults.
| Creates a spec file called NAME and populates
| it with defaults.
USAGE
end

Expand Down
86 changes: 80 additions & 6 deletions spec/helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,99 @@ module CLAide
end
end

describe '::format_markdown' do
it 'wraps a string by paragraph' do
@subject.stubs(:terminal_width).returns(20)
string = <<-DOC.strip_margin('|').rstrip
|Downloads all dependencies defined
|in `Podfile` and creates an Xcode Pods
|library project in `./Pods`.
|
|The Xcode project file should be specified
|in your `Podfile` like this:
DOC
result = <<-DOC.strip_margin('|').rstrip
|Downloads all
|dependencies defined
|in `Podfile` and
|creates an Xcode
|Pods library project
|in `./Pods`.
|
|The Xcode project
|file should be
|specified in your
|`Podfile` like this:
DOC
@subject.format_markdown(string).should == result
end

it 'supports an optional indentation' do
@subject.stubs(:terminal_width).returns(20)
string = <<-DOC.strip_margin('|').rstrip
|Downloads all dependencies defined
|in `Podfile` and creates an Xcode Pods
|library project in `./Pods`.
DOC
result = <<-DOC.strip_margin('|').rstrip
| Downloads all
| dependencies
| defined in
| `Podfile` and
| creates an Xcode
| Pods library
| project in
| `./Pods`.
DOC
@subject.format_markdown(string, 2).should == result
end

it 'supports an optional indentation' do
@subject.stubs(:terminal_width).returns(80)
string = <<-DOC.strip_margin('|').rstrip
|Downloads all dependencies defined
|in `Podfile` and creates an Xcode Pods
|library project in `./Pods`.
DOC
result = <<-DOC.strip_margin('|').rstrip
|Downloads all
|dependencies defined
|in `Podfile` and
|creates an Xcode
|Pods library project
|in `./Pods`.
DOC
@subject.format_markdown(string, 0, 20).should == result
end
end

describe '::wrap_with_indent' do
it 'wraps a string according to the terminal width' do
@subject.stubs(:terminal_width).returns(10)
string = '1234567890 1234567890'
@subject.wrap_with_indent(string).should == "1234567890\n1234567890"
end

it 'does not wrap the string if the terminal width is not available' do
@subject.stubs(:terminal_width).returns(0)
string = '1234567890 1234567890'
@subject.wrap_with_indent(string).should == '1234567890 1234567890'
end

it 'indents the lines except the first' do
@subject.stubs(:terminal_width).returns(10)
string = '1234567890 1234567890'
@subject.wrap_with_indent(string, 2).should ==
"1234567890\n 1234567890"
end

it 'supports a maximum width' do
@subject.stubs(:terminal_width).returns(20)
string = '1234567890 1234567890'
@subject.wrap_with_indent(string, 0, 10).should ==
"1234567890\n1234567890"
end

it 'wraps to the maximum width if the terminal one is not available' do
@subject.stubs(:terminal_width).returns(0)
string = '1234567890 1234567890'
@subject.wrap_with_indent(string, 0, 10).should ==
"1234567890\n1234567890"
end
end

describe '::word_wrap' do
Expand Down