lib/rubikon/application/dsl_methods.rb in rubikon-0.5.3 vs lib/rubikon/application/dsl_methods.rb in rubikon-0.6.0

- old
+ new

@@ -1,10 +1,12 @@ # This code is free software; you can redistribute it and/or modify it under # the terms of the new BSD License. # -# Copyright (c) 2010, Sebastian Staudt +# Copyright (c) 2010-2011, Sebastian Staudt +require 'rubikon/argument_vector' + module Rubikon module Application # This module contains all DSL-related instance methods of @@ -51,35 +53,44 @@ # Call another named command with the given arguments # # @param [Symbol] command_name The name of the command to call # @param [Array<String>] args The arguments to pass to the called command + # @see ArgumentVector#params! # @see Command#run def call(command_name, *args) - command = @current_command + args.extend ArgumentVector + current_command = @current_command + current_param = @current_param + @current_command = @commands[command_name] - @current_command.send(:run, *args) - @current_command = command + args.params!(@current_command.params).each do |param| + @current_param = param + param.send :active! + @current_param = nil + end + @current_command.send :run + @current_command = current_command + @current_param = current_param end # Define a new application Command or an alias to an existing one # # @param [String, Hash] name The name of the Command as used in # application parameters. This might also be a Hash where every # key will be an alias to the corresponding value, e.g. <tt>{ # :alias => :command }</tt>. - # @param [String] description A description for this Command for use in - # the application's help screen + # @param options (see HasArguments#initialize) # @param [Proc] block A block that contains the code that should be # executed when this Command is called, i.e. when the application # is called with the associated parameter # # @return [Command] # @see default # @see Command # @since 0.2.0 - def command(name, arg_count = nil, description = nil, &block) + def command(name, *options, &block) command = nil if name.is_a? Hash name.each do |alias_name, command_name| command = @commands[command_name] @@ -89,12 +100,11 @@ command.aliases << alias_name @commands[alias_name] = command end end else - command = Command.new(self, name, arg_count, &block) - command.description = description unless description.nil? + command = Command.new(self, name, *options, &block) @commands.each do |command_alias, command_name| if command_name == command.name @commands[command_alias] = command command.aliases << command_alias end @@ -121,28 +131,69 @@ end # Define the default Command of the application, i.e. the Command that is # called if no matching Command parameter can be found # - # @param [String] description A description for this Command for use in - # the application's help screen + # @param options (see HasArguments#initialize) # @param [Proc] block A block that contains the code that should be # executed when this Command is called, i.e. when no command # parameter is given to the application # # @return [Command] The default Command object # @see Command # @see command # @since 0.2.0 - def default(arg_count = nil, description = nil, &block) - if arg_count.is_a? Symbol - command({ :__default => arg_count }) + # + # @example Define a default command with an argument + # default 'This is the default', :arg do + # ... + # end + # + # @example Use another command as default + # default :other_command + def default(*options, &block) + if options.size == 1 && options.first.is_a?(Symbol) && !block_given? + command :__default => options.first else - command(:__default, arg_count, description, &block) + command :__default, *options, &block end end + # Set the default configuration for this application + # + # @param [Hash] config The default configuration to use + # @see #config + # @since 0.6.0 + def default_config=(config) + unless config.is_a? Hash + raise ArgumentError.new('Configuration has to be a Hash') + end + + @default_config = config + end + + # Output a line of text using +IO#puts+ of the error output stream + # + # @param [String] text The text to write into the error output stream + # @since 0.6.0 + def error(text = nil) + estream.puts text + end + + # Convenience method for accessing the user-defined error output stream + # + # Use this if you want to work directly with the error output stream + # + # @return [IO] The error output stream object - usually +$stderr+ + # @since 0.6.0 + # + # @example + # estream.flush + def estream + @settings[:estream] + end + # Create a new Flag with the given name for the next Command # # @param [Symbol, #to_sym] name The name of the flag (without dashes). # Dashes will be automatically added (<tt>-</tt> for # single-character flags, <tt>--</tt> for other flags). This might @@ -156,15 +207,17 @@ # flag :status # flag :st => :status # command :something do # ... # end - def flag(name, &block) + def flag(name, description = nil, &block) if name.is_a? Hash @parameters << name else - @parameters << Flag.new(self, name, &block) + flag = Flag.new(self, name, &block) + flag.description = description unless description.nil? + @parameters << flag end end # Create a new flag with the given name to be used globally # @@ -182,11 +235,11 @@ # global_flag :quiet do # @quiet = true # end # @example Define an alias to a global flag # global_flag :q => :quiet - def global_flag(name, &block) + def global_flag(name, description = nil, &block) if name.is_a? Hash name.each do |alias_name, flag_name| flag = @global_parameters[flag_name] if flag.nil? @global_parameters[alias_name] = flag_name @@ -195,10 +248,11 @@ @global_parameters[alias_name] = flag end end else flag = Flag.new(self, name, &block) + flag.description = description unless description.nil? @global_parameters.each do |flag_alias, flag_name| if flag_name == flag.name @global_parameters[flag_alias] = flag flag.aliases << flag_alias end @@ -215,19 +269,19 @@ # @param (see #option) # @see #option # @see Option # @since 0.2.0 # - # @example Define a global option - # global_option :user, 1 - # @example Define a global option with a block to execute - # global_option :user, 1 do - # @user = args[0] + # @example Define a global option with an optional argument + # global_option :user, :login => :optional + # @example Define a global option with an argument and a block to execute + # global_option :user, :login do + # @user = login # end # @example Define an alias to a global option # global_option :u => :user - def global_option(name, arg_count = 0, &block) + def global_option(name, *options, &block) if name.is_a? Hash name.each do |alias_name, option_name| option = @global_parameters[option_name] if option.nil? @global_parameters[alias_name] = option_name @@ -235,11 +289,11 @@ option.aliases << alias_name @global_parameters[alias_name] = option end end else - option = Option.new(self, name, arg_count, &block) + option = Option.new(self, name, *options, &block) @global_parameters.each do |option_alias, option_name| if option_name == option.name @global_parameters[option_alias] = option option.aliases << option_alias end @@ -250,52 +304,63 @@ # Prompts the user for input # # @param [String, #to_s] prompt A String or other Object responding to # +to_s+ used for displaying a prompt to the user + # @param [Array<String>] expected A list of strings that are accepted as + # valid input. If not empty, input will be checked and the prompt + # will be repeated if required. # @since 0.2.0 # # @example Display a prompt "Please type something: " # command 'interactive' do # user_provided_value = input 'Please type something' # # # Do something with the data # ... # end - def input(prompt = '') - unless prompt.to_s.empty? - ostream << "#{prompt}: " + # + # @example Display a question with validated input + # command :question do + # good = input 'Do you feel good', 'y', 'n' + # ... + # end + def input(prompt = '', *expected) + prompt << " [#{expected.join '/'}]" unless expected.empty? + ostream << "#{prompt}: " unless prompt.to_s.empty? + input = @settings[:istream].gets[0..-2] + unless expected.empty? || expected.include?(input) + input = input 'Please provide valid input', *expected end - @settings[:istream].gets[0..-2] + input end # Create a new Option with the given name for the next Command # # @param [Symbol, #to_sym] name The name of the Option (without dashes). # Dashes will be automatically added (+-+ for single-character # options, +--+ for other options). This might also be a Hash # where every key will be an alias to the corresponding value, # e.g. <tt>{ :alias => :option }</tt>. - # @param [Numeric] arg_count The number of arguments this option takes. - # Use +0+ for no required arguments or a negative value for an - # arbitrary number of arguments + # @param options (see HasArguments#initialize) # @param [Proc] block An optional code block that should be executed if # this option is used # @see Option # @since 0.2.0 # - # @example - # option :message,1 + # @example Define an option (and its alias) to a command + # option :message, 'A message', :text # option :m => :message # command :something do - # ... + # puts message.text # end - def option(name, arg_count = 0, &block) + def option(name, *options, &block) if name.is_a? Hash @parameters << name else - @parameters << Option.new(self, name.to_s, arg_count, &block) + option = Option.new(self, name.to_s, *options, &block) + @parameters << option end end # Convenience method for accessing the user-defined output stream # @@ -421,10 +486,11 @@ # class is defined. This is generally useful for simple # "code and run" applications. # +colors+:: If +true+, enables colored output using ColoredIO # +config_file+:: The name of the config file to search # +config_paths+:: The paths to search for config files + # +estream+:: Defines an error output stream to use # +help_banner+:: Defines a banner for the help message # +istream+:: Defines an input stream to use # +name+:: Defines the name of the application # +ostream+:: Defines an output stream to use # +raise_errors+:: If +true+, raise errors, otherwise fail gracefully @@ -432,14 +498,47 @@ # @example # set :name, 'My App' # set :autorun, false def set(setting, value) setting = setting.to_sym - unless setting == :ostream - @settings[setting.to_sym] = value - else + if setting == :estream + self.estream = value + elsif setting == :ostream self.ostream = value + else + @settings[setting.to_sym] = value end + end + + # Saves the current configuration into a configuration file + # + # The file name and format are specified in the application settings. + # + # @param [:global, :local, :user, String] Either one of the default + # scopes or a specific path. The scopes map to a special path. On + # UNIX systems this is +/etc+ for global configurations, the + # user's home directory (+~+) for user configurations and the + # current working directory (+.+) for local configurations. + # @see #default_config= + # @since 0.6.0 + def save_config(scope = :user) + case scope + when :global + if RUBY_PLATFORM.downcase =~ /mswin(?!ce)|mingw|bccwin/ + path = ENV['ALLUSERSPROFILE'] + else + path = '/etc' + end + when :local + path = File.expand_path '.' + when :user + path = File.expand_path '~' + else + path = scope + end + + @config_factory.save_config @config, + File.join(path, @settings[:config_file]) end # Displays a throbber while the given block is executed # # @param [Proc] block The block to execute while the throbber is