diff --git a/lib/puppet/face/module/search.rb b/lib/puppet/face/module/search.rb index a1bee8c43..cec8d9089 100644 --- a/lib/puppet/face/module/search.rb +++ b/lib/puppet/face/module/search.rb @@ -1,89 +1,55 @@ -require 'puppet/util/terminal' - Puppet::Face.define(:module, '1.0.0') do action(:search) do summary "Search a repository for a module." description <<-EOT Search a repository for modules whose names match a specific substring. EOT returns "Array of module metadata hashes" examples <<-EOT Search the default repository for a module: $ puppet module search puppetlabs NAME DESCRIPTION AUTHOR KEYWORDS bacula This is a generic Apache module @puppetlabs backups EOT arguments "" option "--module-repository=", "-r=" do default_to { Puppet.settings[:module_repository] } summary "Module repository to use." description <<-EOT Module repository to use. EOT end when_invoked do |term, options| Puppet::Module::Tool::Applications::Searcher.run(term, options) end - when_rendering :console do |results, term, options| - return '' if results.empty? - - padding = ' ' - headers = { - 'full_name' => 'NAME', - 'desc' => 'DESCRIPTION', - 'author' => 'AUTHOR', - 'tag_list' => 'KEYWORDS', - } - - min_widths = Hash[headers.map { |k,v| [k, v.length] }] - min_widths['full_name'] = min_widths['author'] = 12 + when_rendering :console do |return_value| - min_width = min_widths.inject(0) { |sum,pair| sum += pair.last } + (padding.length * (headers.length - 1)) + FORMAT = "%-10s %-32s %-14s %s\n" - terminal_width = [Puppet::Util::Terminal.width, min_width].max - - columns = results.inject(min_widths) do |hash, result| - { - 'full_name' => [ hash['full_name'], result['full_name'].length ].max, - 'desc' => [ hash['desc'], result['desc'].length ].max, - 'author' => [ hash['author'], result['author'].length ].max, - 'tag_list' => [ hash['tag_list'], result['tag_list'].join(' ').length ].max, - } + def header + FORMAT % ['NAME', 'DESCRIPTION', 'AUTHOR', 'KEYWORDS'] end - flex_width = terminal_width - columns['full_name'] - columns['author'] - (padding.length * (headers.length - 1)) - tag_lists = results.map { |r| r['tag_list'] } - - while (columns['tag_list'] > flex_width / 3) - longest_tag_list = tag_lists.max_by { |tl| tl.join(' ').length } - break if [ [], [term] ].include? longest_tag_list - longest_tag_list.delete(longest_tag_list.max_by { |t| t == term ? -1 : t.length }) - columns['tag_list'] = tag_lists.map { |tl| tl.join(' ').length }.max + def format_row(name, description, author, tag_list) + keywords = tag_list.join(' ') + FORMAT % [name[0..10], description[0..32], "@#{author[0..14]}", keywords] end - columns['tag_list'] = [ - flex_width / 3, - tag_lists.map { |tl| tl.join(' ').length }.max, - ].max - columns['desc'] = flex_width - columns['tag_list'] + output = '' + output << header unless return_value.empty? - format = %w{full_name desc author tag_list}.map do |k| - "%-#{ [ columns[k], min_widths[k] ].max }s" - end.join(padding) + "\n" + return_value.map do |match| + output << format_row(match['name'], match['desc'], match['author'], match['tag_list']) + end - format % [ headers['full_name'], headers['desc'], headers['author'], headers['tag_list'] ] + - results.map do |match| - name, desc, author, keywords = %w{full_name desc author tag_list}.map { |k| match[k] } - desc = desc[0...(columns['desc'] - 3)] + '...' if desc.length > columns['desc'] - format % [name.sub('/', '-'), desc, "@#{author}", [keywords].flatten.join(' ')] - end.join + output end end end diff --git a/lib/puppet/util/terminal.rb b/lib/puppet/util/terminal.rb deleted file mode 100644 index 8674a4956..000000000 --- a/lib/puppet/util/terminal.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Puppet::Util::Terminal - class << self - # Borrowed shamelessly from Thor, who borrowed this from Rake. - def width - if Puppet.features.posix? - result = %x{stty size 2>/dev/null}.split[1] || - %x{tput cols 2>/dev/null}.split[0] || - '80' - end - return result.to_i - rescue - return 80 - end - end -end diff --git a/spec/unit/face/module/search_spec.rb b/spec/unit/face/module/search_spec.rb index 974f49d5a..250c5e0b1 100644 --- a/spec/unit/face/module/search_spec.rb +++ b/spec/unit/face/module/search_spec.rb @@ -1,171 +1,40 @@ require 'spec_helper' require 'puppet/face' -require 'puppet/application/module' -require 'puppet/module_tool' describe "puppet module search" do subject { Puppet::Face[:module, :current] } let(:options) do {} end - describe Puppet::Application::Module do - subject do - app = Puppet::Application::Module.new - app.stubs(:action).returns(Puppet::Face.find_action(:module, :search)) - app - end - - before { subject.render_as = :console } - before { Puppet::Util::Terminal.stubs(:width).returns(100) } - - it 'should output nothing when receiving an empty dataset' do - subject.render([], ['apache', {}]).should == '' - end - - it 'should output a header when receiving a non-empty dataset' do - results = [ - {'full_name' => '', 'author' => '', 'desc' => '', 'tag_list' => [] }, - ] - - subject.render(results, ['apache', {}]).should =~ /NAME/ - subject.render(results, ['apache', {}]).should =~ /DESCRIPTION/ - subject.render(results, ['apache', {}]).should =~ /AUTHOR/ - subject.render(results, ['apache', {}]).should =~ /KEYWORDS/ - end - - it 'should output the relevant fields when receiving a non-empty dataset' do - results = [ - {'full_name' => 'Name', 'author' => 'Author', 'desc' => 'Summary', 'tag_list' => ['tag1', 'tag2'] }, - ] - - subject.render(results, ['apache', {}]).should =~ /Name/ - subject.render(results, ['apache', {}]).should =~ /Author/ - subject.render(results, ['apache', {}]).should =~ /Summary/ - subject.render(results, ['apache', {}]).should =~ /tag1/ - subject.render(results, ['apache', {}]).should =~ /tag2/ - end - - it 'should elide really long descriptions' do - results = [ - { - 'full_name' => 'Name', - 'author' => 'Author', - 'desc' => 'This description is really too long to fit in a single data table, guys -- we should probably set about truncating it', - 'tag_list' => ['tag1', 'tag2'], - }, - ] - - subject.render(results, ['apache', {}]).should =~ /\.{3} @Author/ - end - - it 'should never truncate the module name' do - results = [ - { - 'full_name' => 'This-module-has-a-really-really-long-name', - 'author' => 'Author', - 'desc' => 'Description', - 'tag_list' => ['tag1', 'tag2'], - }, - ] - - subject.render(results, ['apache', {}]).should =~ /This-module-has-a-really-really-long-name/ - end - - it 'should never truncate the author name' do - results = [ - { - 'full_name' => 'Name', - 'author' => 'This-author-has-a-really-really-long-name', - 'desc' => 'Description', - 'tag_list' => ['tag1', 'tag2'], - }, - ] - - subject.render(results, ['apache', {}]).should =~ /@This-author-has-a-really-really-long-name/ - end - - it 'should never remove tags that match the search term' do - results = [ - { - 'full_name' => 'Name', - 'author' => 'Author', - 'desc' => 'Description', - 'tag_list' => ['Supercalifragilisticexpialidocious'] + (1..100).map { |i| "tag#{i}" }, - }, - ] - - subject.render(results, ['Supercalifragilisticexpialidocious', {}]).should =~ /Supercalifragilisticexpialidocious/ - subject.render(results, ['Supercalifragilisticexpialidocious', {}]).should_not =~ /tag/ - end - - { - 100 => <<-EOT, -NAME DESCRIPTION AUTHOR KEYWORDS -Name This description is really too long to fit i... @Author tag1 tag2 taggitty3 -EOT - - 70 => <<-EOT, -NAME DESCRIPTION AUTHOR KEYWORDS -Name This description is real... @Author tag1 tag2 -EOT - - 80 => <<-EOT, -NAME DESCRIPTION AUTHOR KEYWORDS -Name This description is really too ... @Author tag1 tag2 -EOT - - 200 => <<-EOT, -NAME DESCRIPTION AUTHOR KEYWORDS -Name This description is really too long to fit in a single data table, guys -- we should probably set about truncat... @Author tag1 tag2 taggitty3 -EOT - }.each do |width, expectation| - it 'should resize the table to fit the screen, when #{width} columns' do - results = [ - { - 'full_name' => 'Name', - 'author' => 'Author', - 'desc' => 'This description is really too long to fit in a single data table, guys -- we should probably set about truncating it', - 'tag_list' => ['tag1', 'tag2', 'taggitty3'], - }, - ] - - Puppet::Util::Terminal.expects(:width).returns(width) - result = subject.render(results, ['apache', {}]) - result.lines.max_by(&:length).chomp.length.should <= width - result.should == expectation - end - end - end - describe "option validation" do context "without any options" do it "should require a search term" do pattern = /wrong number of arguments/ expect { subject.search }.to raise_error ArgumentError, pattern end end it "should accept the --module-repository option" do options[:module_repository] = "http://forge.example.com" Puppet::Module::Tool::Applications::Searcher.expects(:run).with("puppetlabs-apache", options).once subject.search("puppetlabs-apache", options) end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :search } its(:summary) { should =~ /search.*module/im } its(:description) { should =~ /search.*module/im } its(:returns) { should =~ /array/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end diff --git a/spec/unit/util/terminal_spec.rb b/spec/unit/util/terminal_spec.rb deleted file mode 100644 index d01754914..000000000 --- a/spec/unit/util/terminal_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env rspec -require 'spec_helper' -require 'puppet/util/terminal' - -describe Puppet::Util::Terminal do - describe '.width' do - it 'should invoke `stty` and return the width' do - height, width = 100, 200 - subject.expects(:`).with('stty size 2>/dev/null').returns("#{height} #{width}\n") - subject.width.should == width - end - - it 'should use `tput` if `stty` is unavailable' do - width = 200 - subject.expects(:`).with('stty size 2>/dev/null').returns("\n") - subject.expects(:`).with('tput cols 2>/dev/null').returns("#{width}\n") - subject.width.should == width - end - - it 'should default to 80 columns if `tput` and `stty` are unavailable' do - width = 80 - subject.expects(:`).with('stty size 2>/dev/null').returns("\n") - subject.expects(:`).with('tput cols 2>/dev/null').returns("\n") - subject.width.should == width - end - - it 'should default to 80 columns if `tput` or `stty` raise exceptions' do - width = 80 - subject.expects(:`).with('stty size 2>/dev/null').raises() - subject.stubs(:`).with('tput cols 2>/dev/null').returns("#{width + 1000}\n") - subject.width.should == width - end - end -end