require 'cl/help/table' require 'cl/help/usage' class Cl class Help class Cmd attr_reader :cmd def initialize(cmd) @cmd = cmd end def format [usage, arguments, options, common, summary, description].compact.join("\n\n") end def usage "Usage: #{Usage.new(cmd).format}" end def summary ['Summary:', indent(cmd.summary)] if cmd.summary end def description ['Description:', indent(cmd.description)] if cmd.description end def arguments ['Arguments:', table(:args)] if args.any? end def options ['Options:', requireds, table(:opts)].compact if opts.any? end def common ['Common Options:', table(:cmmn)] if common? end def table(name) table = send(name) indent(table.to_s(width - table.width + 5)) end def args @args ||= begin Table.new(cmd.args.map { |arg| [arg.name, format_obj(arg)] }) end end def opts @opts ||= begin opts = cmd.opts.to_a opts = opts - cmd.superclass.opts.to_a if common? strs = Table.new(rjust(opts.map { |opt| [*opt.strs] })) opts = opts.map { |opt| format_obj(opt) } Table.new(strs.rows.zip(opts)) end end def cmmn @cmmn ||= begin opts = cmd.superclass.opts strs = Table.new(rjust(opts.map { |opt| [*opt.strs] })) opts = opts.map { |opt| format_obj(opt) } Table.new(strs.rows.zip(opts)) end end def requireds return unless cmd.required? opts = cmd.required strs = opts.map { |alts| alts.map { |alt| Array(alt).join(' and ') }.join(', or ' ) } strs = strs.map { |str| "Either #{str} are required." }.join("\n") indent(strs) end def common? cmd.superclass < Cl::Cmd end def width [args.width, opts.width, cmmn.width].max end def format_obj(obj) opts = [] opts << "type: #{format_type(obj)}" opts << 'required: true' if obj.required? opts += format_opt(obj) if obj.is_a?(Opt) opts = opts.join(', ') opts = "(#{opts})" if obj.description && !opts.empty? opts = [obj.description, opts] opts.compact.join(' ') end def format_opt(opt) opts = [] opts << "alias: #{format_aliases(opt)}" if opt.aliases? opts << "requires: #{opt.requires.join(', ')}" if opt.requires? opts << "default: #{format_default(opt)}" if opt.default? opts << "known values: #{opt.enum.join(', ')}" if opt.enum? opts << "format: #{opt.format}" if opt.format? opts << "max: #{opt.max}" if opt.max? opts << format_deprecated(opt) if opt.deprecated? opts.compact end def format_aliases(opt) opt.aliases.map do |name| strs = [name] strs << '(deprecated)' if Array(opt.deprecated).include?(name) strs.join(' ') end.join(', ') end def format_type(obj) return obj.type unless obj.is_a?(Opt) && obj.type == :array "array (can be given multiple times)" end def format_default(opt) opt.default.is_a?(Symbol) ? opt.default.to_s.sub('_', ' ') : opt.default end def format_deprecated(opt) return 'deprecated' if opt.deprecated == [opt.name] end def rjust(objs) width = objs.max_by(&:size).size objs.map { |objs| [*Array.new(width - objs.size) { '' }, *objs] } end def indent(str) str.lines.map { |line| " #{line}" }.join end end end end