diff --git a/devops-client/lib/devops-client/options/common_options.rb b/devops-client/lib/devops-client/options/common_options.rb index 35f6cbf..7a5c1fb 100644 --- a/devops-client/lib/devops-client/options/common_options.rb +++ b/devops-client/lib/devops-client/options/common_options.rb @@ -1,5 +1,5 @@ -require "optparse" require "devops-client/version" +require "devops-client/options/helpers/devops_options_parser" class CommonOptions @@ -82,78 +82,15 @@ class CommonOptions end - def options - o = {} - optparse = OptionParser.new do |opts| - - opts.banner = "\n" + I18n.t("options.usage", :cmd => $0) + "\n\n" + I18n.t("options.commands") + ":\n" - - if block_given? - opts.separator(I18n.t("options.options") + ":\n") - yield opts, o - end - - opts.separator("\n" + I18n.t("options.common_options") + ":\n") - opts.on("-h", "--help", I18n.t("options.common.help")) do - opts.banner << "\n" - puts opts - exit - end - - o[:no_ask] = false - opts.on("-y", "--assumeyes", I18n.t("options.common.confirmation")) do - o[:no_ask] = true; - end - - #Not used, just for banner purposes. This should be fixed when we find how to deal with options separetely - opts.on("-c", "--config CONFIG", I18n.t("options.common.config", :file => DevopsClient.config_file)) do - puts "Not implemented yet" - exit - end - - opts.on("-v", "--version", I18n.t("options.common.version")) do - puts I18n.t("options.common.version") + ": #{DevopsClient::VERSION}" - exit - end - - opts.on("--host HOST", I18n.t("options.common.host", :host => default_options[:host])) do |h| - o[:host] = h - end - - o[:api] = default_options[:api] - opts.on("--api VER", I18n.t("options.common.api", :api => o[:api])) do |a| - o[:api] = a - end - - o[:prefix] = default_options[:prefix] - opts.on("--prefix PREFIX", I18n.t("options.common.prefix", :prefix => o[:prefix])) do |p| - o[:prefix] = p - end - - o[:username] = default_options[:username] - opts.on("--user USERNAME", I18n.t("options.common.username", :username => o[:username])) do |u| - o[:username] = u.strip - print I18n.t("handler.user.password_for", :user => o[:username]) - begin - system("stty -echo") - o[:password] = STDIN.gets.strip - ensure - system("stty echo") - end - puts - end - - o[:format] = TABLE_FORMAT - opts.on("--format FORMAT", I18n.t("options.common.format", :formats => OUTPUT_FROMATS.join("', '"), :format => TABLE_FORMAT)) do |f| - o[:format] = f if OUTPUT_FROMATS.include?(f) - end - - # should be handled in lib/devops-client.rb - opts.on("", "--completion", I18n.t("options.common.completion")) - - end - optparse.parse!(self.args) - o + # You could use block to extend default functionality: + # self.options do |parser, options| + # parser.banner << 'banner' + # options[:any_option] = {a: 123} + # end + def options(&block) + parser = Options::Helpers::DevopsOptionsParser.new(default_options, &block) + parser.parse!(self.args) + parser.parsed_options end def invalid_command diff --git a/devops-client/lib/devops-client/options/helpers/devops_options_parser.rb b/devops-client/lib/devops-client/options/helpers/devops_options_parser.rb new file mode 100644 index 0000000..c3fc63e --- /dev/null +++ b/devops-client/lib/devops-client/options/helpers/devops_options_parser.rb @@ -0,0 +1,189 @@ +require "optparse" +require 'forwardable' +require_relative 'option_value_recognizer' + +module Options + module Helpers + class DevopsOptionsParser + extend Forwardable + attr_reader :parsed_options + + # leave this duplication for a while + TABLE_FORMAT = "table" + JSON_FORMAT = "json" + CSV_FORMAT = "csv" + OUTPUT_FROMATS = [TABLE_FORMAT, JSON_FORMAT, CSV_FORMAT] + + def_delegators :@parser, :banner, :separator, :on, :parse! + + def initialize(default_options, &block) + @parser = OptionParser.new + @default_options = default_options + @parsed_options = {} + + banner_usage + if block + @parser.separator(I18n.t("options.options") + ":\n") + block.call(self, @parsed_options) + end + + common_options_separator + recognize_help + recognize_assume_yes + recognize_config + recognize_version + recognize_host + recognize_api + recognize_prefix + recognize_username + recognize_format + recognize_completion + + @parser + end + + + # it is used to set options values without later quiz. + # Arguments description: + # option_name - name of option; + # resource_name - used for description lookup. Lookup path is "options.descriptions.#{resource_name}.#{option_name}". + # attrs - hash with following options: + # :type - could be one of following values: + # :required (default) + # :optional + # :switch + # :default - default value. nil by default + # :switch_value - set this value, if type is :switch and option've been recognized. + # :option_key - key in result_options hash. Default - option_name.to_sym + # :variable - default - option_name.upcase + # :description - default - I18n.t("options.descriptions.#{resource_name}.#{option_name}") + # :i18n_scope - if present, change I18n lookup path to "options.descriptions.#{resource_name}.#{i18n_scope}.#{option_name}" + # :short - short option name + # + # EXAMPLES: + # 1) + # parser.recognize_option_value(:provider, 'stack') + # is equal to + # opts.on("--provider provider", I18n.t("options.descriptions.stack.provider)) do |provider| + # options[:provider] = provider + # end + # + # 2) + # parser.recognize_option_value(:provider, 'stack', type: :optional, default: 'openstack') + # is equal to + # options[:provider] = 'openstack' + # opts.on("--provider [provider]", I18n.t("options.descriptions.stack.provider)) do |provider| + # options[:provider] = provider + # end + # + # 3) + # parser.recognize_option_value(:no_template, 'image', type: :switch, default: false, switch_value: true) + # is equal to + # options[:no_template] = false + # opts.on("--no_template", I18n.t("options.descriptions.image.no_template)) do + # options[:no_template] = true + # end + # + # 4) + # parser.recognize_option_value(:parameters, 'stack') do |parameters| + # options[:parameters] = JSON.parse(parameters) + # end + # is equal to + # opts.on("--parameters parameters", I18n.t("options.descriptions.stack.parameters)) do |parameters| + # options[:parameters] = JSON.parse(parameters) + # end + def recognize_option_value(option_name, resource_name, attrs={}, &block) + recognizer = OptionValueRecognizer.new(option_name, resource_name, attrs) + recognizer.recognize(@parser, @parsed_options, &block) + end + + private + + def banner_usage + @parser.banner = "\n" + I18n.t("options.usage", :cmd => $0) + "\n\n" + I18n.t("options.commands") + ":\n" + end + + def common_options_separator + @parser.separator("\n" + I18n.t("options.common_options") + ":\n") + end + + def recognize_help + @parser.on("-h", "--help", I18n.t("options.common.help")) do + @parser.banner << "\n" + puts @parser + exit + end + end + + def recognize_assume_yes + @parsed_options[:no_ask] = false + @parser.on("-y", "--assumeyes", I18n.t("options.common.confirmation")) do + @parsed_options[:no_ask] = true; + end + end + + def recognize_config + #Not used, just for banner purposes. This should be fixed when we find how to deal with options separetely + @parser.on("-c", "--config CONFIG", I18n.t("options.common.config", :file => DevopsClient.config_file)) do + puts "Not implemented yet" + exit + end + end + + def recognize_version + @parser.on("-v", "--version", I18n.t("options.common.version")) do + puts I18n.t("options.common.version") + ": #{DevopsClient::VERSION}" + exit + end + end + + def recognize_host + @parser.on("--host HOST", I18n.t("options.common.host", :host => @default_options[:host])) do |h| + @parsed_options[:host] = h + end + end + + def recognize_api + @parsed_options[:api] = @default_options[:api] + @parser.on("--api VER", I18n.t("options.common.api", :api => @parsed_options[:api])) do |a| + @parsed_options[:api] = a + end + end + + def recognize_prefix + @parsed_options[:prefix] = @default_options[:prefix] + @parser.on("--prefix PREFIX", I18n.t("options.common.prefix", :prefix => @parsed_options[:prefix])) do |p| + @parsed_options[:prefix] = p + end + end + + def recognize_username + @parsed_options[:username] = @default_options[:username] + @parser.on("--user USERNAME", I18n.t("options.common.username", :username => @parsed_options[:username])) do |u| + @parsed_options[:username] = u.strip + print I18n.t("handler.user.password_for", :user => @parsed_options[:username]) + begin + system("stty -echo") + @parsed_options[:password] = STDIN.gets.strip + ensure + system("stty echo") + end + puts + end + end + + def recognize_format + @parsed_options[:format] = TABLE_FORMAT + @parser.on("--format FORMAT", I18n.t("options.common.format", :formats => OUTPUT_FROMATS.join("', '"), :format => TABLE_FORMAT)) do |f| + @parsed_options[:format] = f if OUTPUT_FROMATS.include?(f) + end + end + + def recognize_completion + # should be handled in lib/devops-client.rb + @parser.on("", "--completion", I18n.t("options.common.completion")) + end + + end + end +end \ No newline at end of file diff --git a/devops-client/lib/devops-client/options/helpers/option_value_recognizer.rb b/devops-client/lib/devops-client/options/helpers/option_value_recognizer.rb new file mode 100644 index 0000000..6c1735a --- /dev/null +++ b/devops-client/lib/devops-client/options/helpers/option_value_recognizer.rb @@ -0,0 +1,73 @@ +# This class is used only in devops_option_parser. +# It was extracted because #recognize_option_value method became too large. +# Description and examples of usage are in devops_option_parser.rb. +class OptionValueRecognizer + + def initialize(option_name, resource_name, attrs={}) + @option_name, @attrs = option_name, attrs + + set_type(option_name, resource_name) + set_option_key(option_name, resource_name) + set_description(option_name, resource_name) + set_variable(option_name, resource_name) + set_options_to_recognize(option_name, resource_name) + end + + def recognize(parser, parsed_options, &block) + parsed_options[@option_key] = @attrs[:default] if @attrs.keys.include?(:default) + + parser.on(*@options_to_recognize, @description) do |value| + value = @attrs[:switch_value] if @type == :switch + + if block + block.call(value) + else + parsed_options[@option_key] = value + end + end + end + + private + + def set_type(option_name, resource_name) + @type = @attrs[:type] || :required + raise "Illegal optional type: '#{@type}'" unless [:required, :optional, :switch].include?(@type) + if @type == :switch && !@attrs.keys.include?(:switch_value) + raise 'Missing switch value' + end + end + + def set_option_key(option_name, resource_name) + @option_key = @attrs[:option_key] || option_name.to_sym + end + + def set_description(option_name, resource_name) + if @attrs[:description] + @description = @attrs[:description] + else + lookup_path = [:options, :descriptions, resource_name] + lookup_path << @attrs[:i18n_scope] if @attrs[:i18n_scope] + lookup_path << option_name + @description = I18n.t(lookup_path.join('.')) + end + end + + def set_variable(option_name, resource_name) + variable = @attrs[:variable] || option_name.upcase + + @variable = case @type + when :optional + " [#{variable}]" + when :switch + '' + else # required by default + " #{variable}" + end + end + + def set_options_to_recognize(option_name, resource_name) + full = "--#{@option_name}#{@variable}" + @options_to_recognize = [full] + @options_to_recognize.unshift(@attrs[:short]) if @attrs[:short] + end +end \ No newline at end of file