From 912a4c371c808b7ddc22c0104c4584de48900865 Mon Sep 17 00:00:00 2001 From: Fabio Pelosin Date: Sat, 10 May 2014 01:30:35 +0200 Subject: [PATCH 1/4] [Banner] Simplify header section --- lib/claide/command/banner.rb | 31 ++++++++++--------------------- spec/command/banner_spec.rb | 19 ------------------- 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/lib/claide/command/banner.rb b/lib/claide/command/banner.rb index b041ff5..17377e8 100644 --- a/lib/claide/command/banner.rb +++ b/lib/claide/command/banner.rb @@ -19,18 +19,14 @@ 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] + ] + sections.map do |(title, body)| + ["#{title}:", body] if body + end.compact.join("\n\n") end private @@ -38,16 +34,9 @@ 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 subcommands and of the options + # names. # - 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 # @return [String] The minimum between a name and its description. diff --git a/spec/command/banner_spec.rb b/spec/command/banner_spec.rb index da353d5..7dfdd4f 100644 --- a/spec/command/banner_spec.rb +++ b/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,8 +20,6 @@ module CLAide end end - #-------------------------------------------------------------------------# - describe 'banner components' do it "returns a usage description based on the command's description" do banner = Command::Banner.new(Fixture::Command::SpecFile::Create) From f15d6bcf3b33359547ee21911696835e12bc1495 Mon Sep 17 00:00:00 2001 From: Fabio Pelosin Date: Mon, 12 May 2014 10:48:40 +0200 Subject: [PATCH 2/4] [Banner] Add subcommand to signature --- lib/claide/command/banner.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/claide/command/banner.rb b/lib/claide/command/banner.rb index 17377e8..2d63976 100644 --- a/lib/claide/command/banner.rb +++ b/lib/claide/command/banner.rb @@ -119,9 +119,17 @@ 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 # @return [String] A decorated textual representation of the subcommand From f15a4a289ef7c9e1a5044a592dbdbbbf4e627273 Mon Sep 17 00:00:00 2001 From: Fabio Pelosin Date: Mon, 12 May 2014 11:52:02 +0200 Subject: [PATCH 3/4] [Banner] Wrap description to terminal width --- lib/claide/command/banner.rb | 36 ++++++++++++++++++++++++------------ lib/claide/helper.rb | 14 ++++++++++++++ spec/command/banner_spec.rb | 3 +-- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/lib/claide/command/banner.rb b/lib/claide/command/banner.rb index 2d63976..c02037a 100644 --- a/lib/claide/command/banner.rb +++ b/lib/claide/command/banner.rb @@ -34,10 +34,9 @@ def formatted_banner # @!group Banner sections #-----------------------------------------------------------------------# - # @return [String] The indentation of the subcommands and of the options - # names. + # @return [String] The indentation of the text. # - NAME_INDENTATION = 4 + TEXT_INDENT = 6 # @return [String] The minimum between a name and its description. # @@ -50,14 +49,13 @@ def formatted_banner # @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) + message = prettify_message(command, formatted_message) + result = "$ #{signature}\n\n".insert(0, ' ' * (TEXT_INDENT - 2)) + result << "#{message}" + result end end @@ -104,9 +102,9 @@ 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) @@ -132,6 +130,20 @@ def prettify_signature(command) 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.blue}`") + 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 # name. # diff --git a/lib/claide/helper.rb b/lib/claide/helper.rb index 05a41df..7bc3a27 100644 --- a/lib/claide/helper.rb +++ b/lib/claide/helper.rb @@ -15,6 +15,20 @@ def self.terminal_width @terminal_width end + def self.format_markdown(string, indent = 0) + 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) + 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. # diff --git a/spec/command/banner_spec.rb b/spec/command/banner_spec.rb index 7dfdd4f..5210d37 100644 --- a/spec/command/banner_spec.rb +++ b/spec/command/banner_spec.rb @@ -27,8 +27,7 @@ module CLAide <<-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 From 73b81bdeeb7eb511bd0df0b1fc5e3e2ed3e44c6e Mon Sep 17 00:00:00 2001 From: Fabio Pelosin Date: Mon, 12 May 2014 17:13:52 +0200 Subject: [PATCH 4/4] [Command] Wrap description text --- lib/claide/command/banner.rb | 27 +++++++---- lib/claide/helper.rb | 35 ++++++++++++--- spec/command/banner_spec.rb | 4 +- spec/helper_spec.rb | 86 +++++++++++++++++++++++++++++++++--- 4 files changed, 129 insertions(+), 23 deletions(-) diff --git a/lib/claide/command/banner.rb b/lib/claide/command/banner.rb index c02037a..de85a68 100644 --- a/lib/claide/command/banner.rb +++ b/lib/claide/command/banner.rb @@ -24,9 +24,10 @@ def formatted_banner ['Commands', formatted_subcommand_summaries], ['Options', formatted_options_description] ] - sections.map do |(title, body)| - ["#{title}:", body] if body + banner = sections.map do |(title, body)| + ["#{title.ansi.underline}:", body] if body end.compact.join("\n\n") + banner end private @@ -38,11 +39,15 @@ def formatted_banner # 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 @@ -51,9 +56,13 @@ def formatted_banner def formatted_usage_description if raw_message = command.description || command.summary signature = prettify_signature(command) - formatted_message = Helper.format_markdown(raw_message, TEXT_INDENT) + formatted_message = Helper.format_markdown(raw_message, + TEXT_INDENT, + MAX_WIDTH) message = prettify_message(command, formatted_message) - result = "$ #{signature}\n\n".insert(0, ' ' * (TEXT_INDENT - 2)) + result = "#{signature}\n\n" + result.insert(0, '$ ') + result.insert(0, ' ' * (TEXT_INDENT - '$ '.size)) result << "#{message}" result end @@ -108,7 +117,7 @@ def entry_description(name, description, name_width) 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 @@ -135,11 +144,11 @@ def prettify_message(command, message) if command.arguments command.arguments.split(' ').each do |name| name = name.sub('[', '').sub(']', '') - message.gsub!(/['`\w]#{name}['`\w]/, "`#{name.ansi.blue}`") + message.gsub!(/['`\w]#{name}['`\w]/, "`#{name}`".ansi.magenta) end end command.options.each do |(name, _description)| - message.gsub!(/['`\w]#{name}['`\w]/, "`#{name.ansi.blue}`") + message.gsub!(/['`\w]#{name}['`\w]/, "`#{name}`".ansi.blue) end message end diff --git a/lib/claide/helper.rb b/lib/claide/helper.rb index 7bc3a27..6e445b9 100644 --- a/lib/claide/helper.rb +++ b/lib/claide/helper.rb @@ -15,14 +15,29 @@ def self.terminal_width @terminal_width end - def self.format_markdown(string, indent = 0) + # @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) + result = wrap_with_indent(full_line, indent, max_width) end result.insert(0, ' ' * indent).rstrip end @@ -38,14 +53,20 @@ def self.format_markdown(string, indent = 0) # @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! diff --git a/spec/command/banner_spec.rb b/spec/command/banner_spec.rb index 5210d37..cf10cf0 100644 --- a/spec/command/banner_spec.rb +++ b/spec/command/banner_spec.rb @@ -22,12 +22,14 @@ module CLAide 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 diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index d4fb1e2..425cd46 100644 --- a/spec/helper_spec.rb +++ b/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