#-- # Author:: Daniel DeLeo () # Copyright:: Copyright 2011-2016, Chef Software, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/knife/core/text_formatter" class Chef class Knife module Core # Allows includer knife commands to return multiple attributes # @brief knife node show NAME -a ATTR1 -a ATTR2 module MultiAttributeReturnOption # :nodoc: def self.included(includer) includer.class_eval do @attrs_to_show = [] option :attribute, :short => "-a ATTR1 [-a ATTR2]", :long => "--attribute ATTR1 [--attribute ATTR2] ", :proc => lambda {|val| @attrs_to_show << val}, :description => "Show one or more attributes" end end end #==Chef::Knife::Core::GenericPresenter # The base presenter class for displaying structured data in knife commands. # This is not an abstract base class, and it is suitable for displaying # most kinds of objects that knife needs to display. class GenericPresenter attr_reader :ui attr_reader :config # Instaniates a new GenericPresenter. This is generally handled by the # Chef::Knife::UI object, though you need to match the signature of this # method if you intend to use your own presenter instead. def initialize(ui, config) @ui, @config = ui, config end # Is the selected output format a data interchange format? # Returns true if the selected output format is json or yaml, false # otherwise. Knife search uses this to adjust its data output so as not # to produce invalid JSON output. def interchange? case parse_format_option when :json, :yaml true else false end end # Returns a String representation of +data+ that is suitable for output # to a terminal or perhaps for data interchange with another program. # The representation of the +data+ depends on the value of the # `config[:format]` setting. def format(data) case parse_format_option when :summary summarize(data) when :text text_format(data) when :json Chef::JSONCompat.to_json_pretty(data) when :yaml require "yaml" YAML::dump(data) when :pp require "stringio" # If you were looking for some attribute and there is only one match # just dump the attribute value if config[:attribute] and data.length == 1 data.values[0] else out = StringIO.new PP.pp(data, out) out.string end end end # Converts the user-supplied value of `config[:format]` to a Symbol # representing the desired output format. # ===Returns # returns one of :summary, :text, :json, :yaml, or :pp # ===Raises # Raises an ArgumentError if the desired output format could not be # determined from the value of `config[:format]` def parse_format_option case config[:format] when "summary", /^s/, nil :summary when "text", /^t/ :text when "json", /^j/ :json when "yaml", /^y/ :yaml when "pp", /^p/ :pp else raise ArgumentError, "Unknown output format #{config[:format]}" end end # Summarize the data. Defaults to text format output, # which may not be very summary-like def summarize(data) text_format(data) end # Converts the +data+ to a String in the text format. Uses # Chef::Knife::Core::TextFormatter def text_format(data) TextFormatter.new(data, ui).formatted_data end def format_list_for_display(list) config[:with_uri] ? list : list.keys.sort { |a,b| a <=> b } end def format_for_display(data) if formatting_subset_of_data? format_data_subset_for_display(data) elsif config[:id_only] name_or_id_for(data) elsif config[:environment] && data.respond_to?(:chef_environment) {"chef_environment" => data.chef_environment} else data end end def format_data_subset_for_display(data) subset = if config[:attribute] result = {} Array(config[:attribute]).each do |nested_value_spec| nested_value = extract_nested_value(data, nested_value_spec) result[nested_value_spec] = nested_value end result elsif config[:run_list] run_list = data.run_list.run_list { "run_list" => run_list } else raise ArgumentError, "format_data_subset_for_display requires attribute, run_list, or id_only config option to be set" end {name_or_id_for(data) => subset } end def name_or_id_for(data) data.respond_to?(:name) ? data.name : data["id"] end def formatting_subset_of_data? config[:attribute] || config[:run_list] end def extract_nested_value(data, nested_value_spec) nested_value_spec.split(".").each do |attr| if data.nil? nil # don't get no method error on nil # Must check :[] before attr because spec can include # `keys` - want the key named `keys`, not a list of # available keys. elsif data.respond_to?(:[]) && data.has_key?(attr) data = data[attr] elsif data.respond_to?(attr.to_sym) data = data.send(attr.to_sym) else data = begin data.send(attr.to_sym) rescue NoMethodError nil end end end ( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data end def format_cookbook_list_for_display(item) if config[:with_uri] item.inject({}) do |collected, (cookbook, versions)| collected[cookbook] = Hash.new versions["versions"].each do |ver| collected[cookbook][ver["version"]] = ver["url"] end collected end else versions_by_cookbook = item.inject({}) do |collected, ( cookbook, versions )| collected[cookbook] = versions["versions"].map {|v| v["version"]} collected end key_length = versions_by_cookbook.empty? ? 0 : versions_by_cookbook.keys.map {|name| name.size }.max + 2 versions_by_cookbook.sort.map do |cookbook, versions| "#{cookbook.ljust(key_length)} #{versions.join(' ')}" end end end end end end end