Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

[Banner] Simplify header section #25

Merged
merged 4 commits into from

3 participants

@fabiopelosin

This is about always having the usage section.

[Edit] Removed old screenshoots.

@orta
Owner

This feels awesome.

@fabiopelosin

This is a pull request because it changes the banner to always have an usage section. /c @alloy. The usage section now indicates wether a subcommand is required, optional (square brackets) or not available.

It also prettifies the description by colouring arguments and option flags, and wrapping the text. If the terminal is too wide a maximum width keeps the text readable.

Before

screen shot 2014-05-12 at 17 17 53
screen shot 2014-05-12 at 17 18 54

After

screen shot 2014-05-12 at 17 15 56
screen shot 2014-05-12 at 17 15 48
screen shot 2014-05-12 at 17 15 22
screen shot 2014-05-12 at 17 16 12

@alloy
Owner

Looks amaze craze! :+1:

@fabiopelosin

Ace!

@fabiopelosin fabiopelosin merged commit a1e3bcb into master

1 check passed

Details continuous-integration/travis-ci The Travis CI build passed
@fabiopelosin fabiopelosin deleted the experimental-usage branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
92 lib/claide/command/banner.rb
@@ -19,18 +19,15 @@ 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
@@ -38,37 +35,36 @@ def formatted_banner
# @!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
@@ -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
@@ -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
View
45 lib/claide/helper.rb
@@ -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.
#
@@ -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!
View
24 spec/command/banner_spec.rb
@@ -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 ==
@@ -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
View
86 spec/helper_spec.rb
@@ -35,6 +35,72 @@ 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)
@@ -42,18 +108,26 @@ module CLAide
@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
Something went wrong with that request. Please try again.