rubikon 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2010, Sebastian Staudt
5
+
6
+ module Rubikon
7
+
8
+ module Application
9
+
10
+ # The application sandbox is a wrapper used to secure internal Rubikon
11
+ # logic from access by user generated application code.
12
+ #
13
+ # This is mostly to prevent accidental execution or change of Rubikon's
14
+ # internal code. But it also helps to prevent possible security problems
15
+ # depending on the code used inside the application logic.
16
+ #
17
+ # @see Application::InstanceMethods
18
+ # @since 0.4.0
19
+ class Sandbox
20
+
21
+ # Create a new application sandbox
22
+ #
23
+ # @param [Application::Base] app The application to be sandboxed
24
+ def initialize(app)
25
+ raise ArgumentError unless app.is_a? Application::Base
26
+ @__app__ = app
27
+ end
28
+
29
+ # Method calls on the sandbox wrapper will be relayed to the singleton
30
+ # instance. Methods defined in InstanceMethods are protected and will
31
+ # raise a NoMethodError.
32
+ #
33
+ # @param (see ClassMethods#method_missing)
34
+ # @raise [NoMethodError] if a method is called that is defined inside
35
+ # InstanceMethods and should therefore be protected
36
+ # @see InstanceMethods
37
+ def method_missing(name, *args, &block)
38
+ if InstanceMethods.method_defined?(name) ||
39
+ InstanceMethods.private_method_defined?(name)
40
+ raise NoMethodError.new("Method `#{name}' is protected by the application sandbox", name)
41
+ end
42
+ @__app__.send(name, *args, &block)
43
+ end
44
+
45
+ # Relay putc to the instance method
46
+ #
47
+ # This is used to hide <tt>Kernel#putc</tt> so that the application's
48
+ # output IO object is used for printing characters
49
+ #
50
+ # @param [String, Numeric] char The character to write into the output
51
+ # stream
52
+ def putc(text)
53
+ @__app__.send(:putc, text)
54
+ end
55
+
56
+ # Relay puts to the instance method
57
+ #
58
+ # This is used to hide <tt>Kernel#puts</tt> so that the application's
59
+ # output IO object is used for printing text
60
+ #
61
+ # @param [String] text The text to write into the output stream
62
+ def puts(text)
63
+ @__app__.send(:puts, text)
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -5,6 +5,7 @@
5
5
 
6
6
  require 'rubikon/application/base'
7
7
  require 'rubikon/exceptions'
8
+ require 'rubikon/has_arguments'
8
9
  require 'rubikon/parameter'
9
10
 
10
11
  module Rubikon
@@ -16,31 +17,33 @@ module Rubikon
16
17
  # @since 0.3.0
17
18
  class Command
18
19
 
19
- include Parameter
20
+ include HasArguments
20
21
 
22
+ # @return [String] The description of this command
21
23
  attr_accessor :description
22
- attr_reader :args, :params
23
- alias_method :arguments, :args
24
+
25
+ # @return [Array<Parameter>] The parameters of this command
26
+ attr_reader :params
24
27
  alias_method :parameters, :params
25
28
 
26
29
  # Create a new application command with the given name with a reference to
27
30
  # the app it belongs to
28
31
  #
29
- # @param [Application::Base] app A reference to the application
30
- # instance this command belongs to
31
- # @param [#to_sym] name The name of this command, used in application
32
+ # @param [Application::Base] app The application this command belongs to
33
+ # @param [Symbol, #to_sym] name The name of this command, used in application
32
34
  # arguments
35
+ # @param [Range, Array, Numeric] arg_count The number of arguments this
36
+ # command takes.
33
37
  # @param [Proc] block The code block which should be executed by this
34
38
  # command
35
39
  # @raise [ArgumentError] if the given application object isn't a Rubikon
36
40
  # application
37
41
  # @raise [BlockMissingError] if no command code block is given and a
38
42
  # command file does not exist
39
- def initialize(app, name, &block)
40
- raise ArgumentError unless app.is_a? Application::Base
41
- super(name, nil)
43
+ # @see HasArguments#arg_count=
44
+ def initialize(app, name, arg_count = nil, &block)
45
+ super
42
46
 
43
- @app = app
44
47
  @params = {}
45
48
 
46
49
  if block_given?
@@ -53,14 +56,14 @@ module Rubikon
53
56
  end
54
57
  end
55
58
 
56
- # Add a new Parameter for this command
59
+ # Add a new parameter for this command
57
60
  #
58
- # @param [Parameter, Hash] parameter The parameter to add to this
61
+ # @param [Parameter, Hash] parameter The parameter to add to this
59
62
  # command. This might also be a Hash where every key will be an
60
63
  # alias to the corresponding value, e.g. <tt>{ :alias => :parameter
61
64
  # }</tt>.
62
65
  # @see Parameter
63
- def <<(parameter)
66
+ def add_param(parameter)
64
67
  if parameter.is_a? Hash
65
68
  parameter.each do |alias_name, name|
66
69
  alias_name = alias_name.to_sym
@@ -78,12 +81,33 @@ module Rubikon
78
81
  @params.each do |name, param|
79
82
  if param == parameter.name
80
83
  parameter.aliases << name
84
+ @params[name] = parameter
81
85
  end
82
86
  end
83
87
  @params[parameter.name] = parameter
84
88
  end
85
89
  end
86
90
 
91
+ # If a parameter with the specified method name exists, a call to that
92
+ # method will return the value of the parameter.
93
+ #
94
+ # @param (see ClassMethods#method_missing)
95
+ # @see DSLMethods#params
96
+ #
97
+ # @example
98
+ # option :user, [:who]
99
+ # command :hello, [:mood] do
100
+ # puts "Hello #{user.who}"
101
+ # puts "I feel #{mood}"
102
+ # end
103
+ def method_missing(name, *args, &block)
104
+ if args.empty? && !block_given? && @params.key?(name)
105
+ @params[name]
106
+ else
107
+ super
108
+ end
109
+ end
110
+
87
111
  # Parses the arguments of this command and sets each Parameter as active
88
112
  # if it has been supplied by the user on the command-line. Additional
89
113
  # arguments are passed to the individual parameters.
@@ -96,7 +120,6 @@ module Rubikon
96
120
  # @see Option
97
121
  def parse_arguments(args)
98
122
  @args = []
99
- parameter = nil
100
123
  args.each do |arg|
101
124
  if arg.start_with?('-')
102
125
  parameter_name = arg.start_with?('--') ? arg[2..-1] : arg[1..-1]
@@ -104,21 +127,41 @@ module Rubikon
104
127
  raise UnknownParameterError.new(arg) if parameter.nil?
105
128
  end
106
129
 
107
- unless parameter.nil? || parameter.active?
108
- parameter.active!
130
+ unless parameter.nil?
131
+ @app.current_param.active! unless @app.current_param.nil?
132
+ @app.current_param = parameter
109
133
  next
110
134
  end
111
135
 
112
- if parameter.nil? || !parameter.more_args?
113
- @args << arg
136
+ if @app.current_param.nil? || !@app.current_param.more_args?
137
+ self << arg
114
138
  else
115
- parameter << arg
139
+ @app.current_param << arg
116
140
  end
117
141
  end
118
142
 
119
- @params.values.each do |param|
120
- param.check_args if param.is_a?(Option) && param.active?
121
- end
143
+ @app.current_param.active! unless @app.current_param.nil?
144
+ @app.current_param = nil
145
+ end
146
+
147
+ # Resets this command to its initial state
148
+ #
149
+ # @see HasArguments#reset
150
+ # @since 0.4.0
151
+ def reset
152
+ super
153
+ @params.values.uniq.each { |param| param.reset if param.is_a? Parameter }
154
+ end
155
+
156
+ # Checks whether a parameter with the given name exists for this command
157
+ #
158
+ # This is used to determine if a method call would successfully return the
159
+ # value of a parameter.
160
+ #
161
+ # @return +true+ if named parameter with the specified name exists
162
+ # @see #method_missing
163
+ def respond_to_missing?(name, include_private = false)
164
+ @params.key?(name) || super
122
165
  end
123
166
 
124
167
  # Run this command's code block
@@ -127,7 +170,8 @@ module Rubikon
127
170
  # command
128
171
  def run(*args)
129
172
  parse_arguments(args)
130
- @app.instance_eval(&@block)
173
+ check_args
174
+ @app.sandbox.instance_eval(&@block)
131
175
  end
132
176
 
133
177
  end
data/lib/rubikon/flag.rb CHANGED
@@ -18,14 +18,6 @@ module Rubikon
18
18
 
19
19
  include Parameter
20
20
 
21
- # Creates a new flag with the given name and an optional code block
22
- #
23
- # @param name (see Parameter#initialize)
24
- # @param block (see Parameter#initialize)
25
- def initialize(name, &block)
26
- super(name, 0, &block)
27
- end
28
-
29
21
  # Adds an argument to this flag
30
22
  #
31
23
  # @param arg (see Parameter#<<)
@@ -0,0 +1,182 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2010, Sebastian Staudt
5
+
6
+ require 'rubikon/parameter'
7
+
8
+ module Rubikon
9
+
10
+ # This module is included in all classes used for parsing command-line
11
+ # arguments
12
+ #
13
+ # @author Sebastian Staudt
14
+ # @see Application::InstanceMethods
15
+ # @see Command
16
+ # @see Option
17
+ # @since 0.4.0
18
+ module HasArguments
19
+
20
+ include Parameter
21
+
22
+ # @return [Array<String>] The arguments given to this parameter
23
+ attr_reader :args
24
+ alias_method :arguments, :args
25
+
26
+ # Creates a new parameter with arguments with the given name and an
27
+ # optional code block
28
+ #
29
+ # @param [Application::Base] app The application this parameter belongs to
30
+ # @param [Symbol, #to_sym] name The name of the option
31
+ # @param [Fixnum, Range, Array] arg_count A range or array allows any
32
+ # number of arguments inside the limits between the first and the
33
+ # last element of the range or array (-1 stands for an arbitrary
34
+ # number of arguments). A positive number indicates the exact amount
35
+ # of required arguments while a negative argument count indicates
36
+ # the amount of required arguments, but allows additional, optional
37
+ # arguments. A argument count of 0 means there are no required
38
+ # arguments, but it allows optional arguments.
39
+ # Finally an array of symbols enables named arguments where the
40
+ # argument count is the size of the array and each argument is named
41
+ # after the corresponding symbol.
42
+ # @param [Proc] block An optional code block to be executed if this
43
+ # option is used
44
+ def initialize(app, name, arg_count = 0, &block)
45
+ super(app, name, &block)
46
+
47
+ @args = []
48
+ @arg_names = nil
49
+ if arg_count.is_a? Fixnum
50
+ if arg_count > 0
51
+ @min_arg_count = arg_count
52
+ @max_arg_count = arg_count
53
+ elsif arg_count <= 0
54
+ @min_arg_count = -arg_count
55
+ @max_arg_count = -1
56
+ end
57
+ elsif arg_count.is_a?(Array) && arg_count.all? { |a| a.is_a? Symbol }
58
+ @max_arg_count = @min_arg_count = arg_count.size
59
+ @arg_names = arg_count
60
+ elsif arg_count.is_a?(Range) || arg_count.is_a?(Array)
61
+ @min_arg_count = arg_count.first
62
+ @max_arg_count = arg_count.last
63
+ else
64
+ @min_arg_count = 0
65
+ @max_arg_count = 0
66
+ end
67
+ end
68
+
69
+ # Access the arguments of this parameter using a numeric or symbolic index
70
+ #
71
+ # @param [Numeric, Symbol] The index of the argument to return. Numeric
72
+ # indices can be used always while symbolic arguments are only
73
+ # available for named arguments.
74
+ # @return The argument with the specified index
75
+ # @since 0.4.0
76
+ def [](arg)
77
+ arg = @arg_names.index(arg) if arg.is_a? Symbol
78
+ @args[arg]
79
+ end
80
+
81
+ # Adds an argument to this parameter. Arguments can be accessed inside the
82
+ # application code using the args method.
83
+ #
84
+ # @param [String] arg The argument to add to the supplied arguments of this
85
+ # parameter
86
+ # @raise [ExtraArgumentError] if the parameter has all required arguments
87
+ # supplied and does not take optional arguments
88
+ # @return [Array] The supplied arguments of this parameter
89
+ # @see #[]
90
+ # @see #args
91
+ # @since 0.3.0
92
+ def <<(arg)
93
+ if args_full? && @args.size == @max_arg_count
94
+ raise ExtraArgumentError.new(@name)
95
+ end
96
+ @args << arg
97
+ end
98
+
99
+ # Marks this parameter as active when it has been supplied by the user on
100
+ # the command-line. This also checks the arguments given to this parameter.
101
+ #
102
+ # @see #check_args
103
+ # @see Paramter#active!
104
+ def active!
105
+ check_args
106
+ super
107
+ end
108
+
109
+ # Return the allowed range of argument counts this parameter takes
110
+ #
111
+ # @return [Range] The allowed range of argument counts this parameter takes
112
+ def arg_count
113
+ @min_arg_count..@max_arg_count
114
+ end
115
+
116
+ # Checks whether this parameter has all required arguments supplied
117
+ #
118
+ # @return +true+ if all required parameter arguments have been supplied
119
+ # @since 0.3.0
120
+ def args_full?
121
+ @args.size >= @min_arg_count
122
+ end
123
+
124
+ # Checks the arguments for this parameter
125
+ #
126
+ # @raise [MissingArgumentError] if there are not enough arguments for
127
+ # this parameter
128
+ # @since 0.3.0
129
+ def check_args
130
+ raise MissingArgumentError.new(@name) unless args_full?
131
+ end
132
+
133
+ # If a named argument with the specified method name exists, a call to that
134
+ # method will return the value of the argument.
135
+ #
136
+ # @param (see ClassMethods#method_missing)
137
+ # @see #args
138
+ # @see #[]
139
+ #
140
+ # @example
141
+ # option :user, [:name] do
142
+ # @user = name
143
+ # end
144
+ def method_missing(name, *args, &block)
145
+ if args.empty? && !block_given? && !@arg_names.nil? && @arg_names.include?(name)
146
+ @args[@arg_names.index(name)]
147
+ else
148
+ super
149
+ end
150
+ end
151
+
152
+ # Checks whether this parameter can take more arguments
153
+ #
154
+ # @return +true+ if this parameter can take more arguments
155
+ # @since 0.3.0
156
+ def more_args?
157
+ @max_arg_count == -1 || @args.size < @max_arg_count
158
+ end
159
+
160
+ # Resets this parameter to its initial state
161
+ #
162
+ # @see Parameter#reset
163
+ # @since 0.4.0
164
+ def reset
165
+ super
166
+ @args.clear
167
+ end
168
+
169
+ # Checks whether an argument with the given name exists for this parameter
170
+ #
171
+ # This is used to determine if a method call would successfully return the
172
+ # value of an argument.
173
+ #
174
+ # @return +true+ if named argument with the specified name exists
175
+ # @see #method_missing
176
+ def respond_to_missing?(name, include_private = false)
177
+ !@arg_names.nil? && @arg_names.include?(name)
178
+ end
179
+
180
+ end
181
+
182
+ end
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # Copyright (c) 2010, Sebastian Staudt
5
5
 
6
- require 'rubikon/parameter'
6
+ require 'rubikon/has_arguments'
7
7
 
8
8
  module Rubikon
9
9
 
@@ -16,14 +16,7 @@ module Rubikon
16
16
  # @since 0.3.0
17
17
  class Option
18
18
 
19
- # @return [Numeric] The number of arguments this parameter takes
20
- attr_reader :arg_count
21
-
22
- # @return [Array<String>] The arguments given to this parameter
23
- attr_reader :args
24
- alias_method :arguments, :args
25
-
26
- include Parameter
19
+ include HasArguments
27
20
 
28
21
  end
29
22
 
@@ -7,9 +7,10 @@ module Rubikon
7
7
 
8
8
  # A parameter is any command-line argument given to the application that is
9
9
  # not prefixed with one or two dashes. Once a parameter is supplied by the
10
- # user, it is relayed to the Command it belongs to.
10
+ # user, it is relayed to the command it belongs to.
11
11
  #
12
12
  # @author Sebastian Staudt
13
+ # @see Command
13
14
  # @since 0.3.0
14
15
  module Parameter
15
16
 
@@ -21,38 +22,18 @@ module Rubikon
21
22
 
22
23
  # Creates a new parameter with the given name
23
24
  #
25
+ # @param [Application::Base] app The application this parameter belongs to
24
26
  # @param [Symbol, #to_sym] name The name of the parameter
25
- # @param [Numeric] arg_count The number of arguments this parameter takes
26
- # if any
27
27
  # @param [Proc] block An optional code block to be executed if this
28
28
  # parameter is used
29
- #
30
- # A positive argument count indicates the exact amount of required
31
- # arguments, while a negative argument count indicates the amount of
32
- # required arguments, but allows additional, optional arguments. A argument
33
- # count of 0 means there are no required arguments, but it allows optional
34
- # arguments. If you need a parameter that does not allow arguments at all
35
- # you should use a flag instead.
36
- def initialize(name, arg_count = 0, &block)
37
- @active = false
38
- @aliases = []
39
- @arg_count = arg_count
40
- @args = []
41
- @block = block
42
- @name = name.to_sym
43
- end
29
+ def initialize(app, name, &block)
30
+ raise ArgumentError unless app.is_a? Application::Base
44
31
 
45
- # Adds an argument to this parameter. Parameter arguments can be accessed
46
- # inside the Application code using the parameter's args method.
47
- #
48
- # @param [String] arg The argument to add to the supplied arguments of this
49
- # parameter
50
- # @raise [ExtraArgumentError] if the parameter has all required arguments
51
- # supplied and does not take optional arguments
52
- # @return [Array] The supplied arguments of this parameter
53
- def <<(arg)
54
- raise ExtraArgumentError.new(@name) if args_full? && @arg_count > 0
55
- @args << arg
32
+ @active = false
33
+ @aliases = []
34
+ @app = app
35
+ @block = block
36
+ @name = name.to_sym
56
37
  end
57
38
 
58
39
  # Marks this parameter as active when it has been supplied by the user on
@@ -60,7 +41,7 @@ module Rubikon
60
41
  # exists
61
42
  def active!
62
43
  @active = true
63
- @block.call unless @block.nil?
44
+ @app.sandbox.instance_eval(&@block) unless @block.nil?
64
45
  end
65
46
 
66
47
  # Returns whether this parameter has is active, i.e. it has been supplied
@@ -70,30 +51,13 @@ module Rubikon
70
51
  def active?
71
52
  @active
72
53
  end
54
+ alias_method :given?, :active?
73
55
 
74
- # Checks whether this parameter has all required arguments supplied
75
- #
76
- # @return +true+ if all required parameter arguments have been supplied
77
- def args_full?
78
- arg_count = @arg_count
79
- arg_count = -arg_count if arg_count < 0
80
-
81
- arg_count == 0 || @args.size >= arg_count
82
- end
83
-
84
- # Checks the arguments for this parameter
85
- #
86
- # @raise [MissingArgumentError] if there are not enough arguments for
87
- # this parameter
88
- def check_args
89
- raise MissingArgumentError.new(@name) unless args_full?
90
- end
91
-
92
- # Checks whether this parameter can take more arguments
56
+ # Resets this parameter to its initial state
93
57
  #
94
- # @return +true+ if this parameter can take more arguments
95
- def more_args?
96
- arg_count <= 0 || @args.size < arg_count
58
+ # @since 0.4.0
59
+ def reset
60
+ @active = false
97
61
  end
98
62
 
99
63
  end
@@ -72,7 +72,7 @@ module Rubikon
72
72
  if add_progress > 0
73
73
  @ostream << @progress_char * add_progress
74
74
  @ostream.flush
75
- @ostream.puts '' if @progress == @size
75
+ @ostream.putc 10 if @progress == @size
76
76
  end
77
77
 
78
78
  self
data/lib/rubikon.rb CHANGED
@@ -6,15 +6,24 @@
6
6
  libdir = File.dirname(__FILE__)
7
7
  $:.unshift(libdir) unless $:.include?(libdir)
8
8
 
9
+ require 'core_ext/object'
9
10
  require 'core_ext/string'
10
11
  require 'rubikon/application/base'
11
12
 
12
- # A namespace module for all Rubikon related code
13
+ # Rubikon is a simple to use, yet powerful Ruby framework for building
14
+ # console-based applications. Rubikon aims to provide an easy to write and easy
15
+ # to read domain-specific language (DSL) to speed up development of
16
+ # command-line applications. With Rubikon it's a breeze to implement
17
+ # applications with only few options as well as more complex programs like
18
+ # RubyGems, Homebrew or even Git.
19
+ #
20
+ # This is the namespace module for all Rubikon related code.
13
21
  #
14
22
  # @author Sebastian Staudt
15
23
  # @since 0.1.0
16
24
  module Rubikon
17
25
 
18
- VERSION = '0.3.0'
26
+ # This is the current version of the Rubikon gem
27
+ VERSION = '0.4.0'
19
28
 
20
29
  end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This code is free software; you can redistribute it and/or modify it under
4
+ # the terms of the new BSD License.
5
+ #
6
+ # Copyright (c) 2010, Sebastian Staudt
7
+
8
+ if ENV['RUBIKON_DEV']
9
+ require File.join(File.expand_path(File.dirname(__FILE__)), '..', '..', 'lib', 'rubikon')
10
+ else
11
+ require 'rubygems'
12
+ require 'rubikon'
13
+ end
14
+
15
+ # A relatively simple Hello World application using Rubikon
16
+ class HelloWorld < Rubikon::Application::Base
17
+
18
+ # Greet the whole world per default
19
+ flag :more
20
+ option :name, [:who]
21
+ option :names, -1
22
+ default 'Simple hello world' do
23
+ debug 'Starting to greet the world...'
24
+ if given? :name
25
+ greet parameters[:name].who
26
+ elsif given? :names
27
+ names.args.each do |name|
28
+ greet name
29
+ end
30
+ else
31
+ greet 'World'
32
+ end
33
+ puts 'Nice to see you.' if given? :more
34
+ end
35
+
36
+ # Interactive mode
37
+ #
38
+ # Ask the user for his name and greet him
39
+ command :interactive, 'Greet interactively' do
40
+ name = input 'Please enter your name'
41
+ greet name
42
+ end
43
+
44
+ # Show a progress bar while iterating through a loop
45
+ command :progress, 'Display a progress bar' do
46
+ put 'Watch my progress while I greet the world: '
47
+ x = 1000000
48
+ progress_bar(:char => '+', :maximum => x, :size => 30) do |progress|
49
+ x.times do
50
+ progress.+
51
+ end
52
+ end
53
+ end
54
+
55
+ # Sleep for 5 seconds while displaying a throbber
56
+ command :throbber, 'Display a throbber' do
57
+ put 'Greeting the whole world takes some time... '
58
+ throbber do
59
+ sleep 5
60
+ end
61
+ puts 'done.'
62
+ end
63
+
64
+ # A standard Ruby class method for greeting
65
+ def greet(someone)
66
+ puts "Hello #{someone}!"
67
+ end
68
+
69
+ end