rubikon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +25 -0
- data/README.md +129 -0
- data/Rakefile +55 -0
- data/VERSION.yml +4 -0
- data/lib/rubikon/action.rb +71 -0
- data/lib/rubikon/application.rb +331 -0
- data/lib/rubikon/exceptions.rb +28 -0
- data/lib/rubikon.rb +9 -0
- data/test/test.rb +173 -0
- data/test/testapp.rb +64 -0
- metadata +70 -0
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2009, Sebastian Staudt
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of the author nor the names of its contributors
|
13
|
+
may be used to endorse or promote products derived from this software
|
14
|
+
without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
20
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
21
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
22
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
23
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
25
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
Rubikon
|
2
|
+
=======
|
3
|
+
|
4
|
+
Rubikon is a simple to use, yet powerful Ruby framework for building
|
5
|
+
console-based applications.
|
6
|
+
|
7
|
+
### Installation
|
8
|
+
|
9
|
+
You can install Rubikon using RubyGem. This is the easiest way of installing
|
10
|
+
and recommended for most users.
|
11
|
+
|
12
|
+
$ gem install rubikon
|
13
|
+
|
14
|
+
If you want to use the development code you should clone the Git repository:
|
15
|
+
|
16
|
+
$ git clone git://github.com/koraktor/rubikon.git
|
17
|
+
$ cd rubikon
|
18
|
+
$ rake install
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Creating a Rubikon application is as simple as creating a Ruby class:
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'rubikon'
|
26
|
+
|
27
|
+
class MyApplication < Rubikon::Application
|
28
|
+
end
|
29
|
+
|
30
|
+
If you save this code in a file called `myapp.rb` you can run it using
|
31
|
+
`ruby myapp.rb`. Or you could even add a *shebang* (`#!/usr/bin/env ruby`) to
|
32
|
+
the top of the file and make it executable. You would then be able to run it
|
33
|
+
even more easy by typing `./myapp.rb`.
|
34
|
+
|
35
|
+
Now go on and define what your application should do when the user runs it.
|
36
|
+
This is done using `default`:
|
37
|
+
|
38
|
+
class MyApplication < Rubikon::Application
|
39
|
+
|
40
|
+
default do
|
41
|
+
puts 'Hello World!'
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
If you run this application it will just print `Hello World!`.
|
47
|
+
|
48
|
+
You can also add command-line options to your appication using `action`:
|
49
|
+
|
50
|
+
class MyApplication < Rubikon::Application
|
51
|
+
|
52
|
+
action 'hello' do
|
53
|
+
puts 'Hello World!'
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
This way your application would do nothing when called without options, but it
|
59
|
+
would print `Hello World!` when called using `ruby myapp.rb --hello`.
|
60
|
+
Please note that Rubikon will add dashes to options by default. If you don't
|
61
|
+
like this behaviour and want options like RubyGem's `install` or `update` just
|
62
|
+
use the following inside your application class:
|
63
|
+
|
64
|
+
set :dashed_options, false
|
65
|
+
|
66
|
+
Please see the `samples` directory for more in detail sample applications.
|
67
|
+
|
68
|
+
|
69
|
+
**Warning**:
|
70
|
+
|
71
|
+
Rubikon is still in an early development stage. If you want to use it be aware
|
72
|
+
that you will probably run into problems and or restrictions. See the
|
73
|
+
Contribute section if you want to help making Rubikon better.
|
74
|
+
|
75
|
+
## Features
|
76
|
+
|
77
|
+
* A simple to use DSL
|
78
|
+
* Automatic checks for option arguments
|
79
|
+
* User defined type safety of option arguments
|
80
|
+
* Built-in methods to capture user input and display throbbers
|
81
|
+
|
82
|
+
## Future plans
|
83
|
+
|
84
|
+
* Automatic generation of help screens
|
85
|
+
* Improved error handling
|
86
|
+
* Built-in support for configuration files
|
87
|
+
* Built-in support for colored output and progress bars
|
88
|
+
|
89
|
+
## Requirements
|
90
|
+
|
91
|
+
* Linux, MacOS X or Windows
|
92
|
+
* Ruby 1.8.6 or newer
|
93
|
+
|
94
|
+
## Contribute
|
95
|
+
|
96
|
+
There are several ways of contributing to Rubikon's development:
|
97
|
+
|
98
|
+
* Build apps using it and spread the word.<br />
|
99
|
+
* Report problems and request features using the [issue tracker][2].
|
100
|
+
* Write patches yourself to fix bugs and implement new functionality.
|
101
|
+
* Create a Rubikon fork on [GitHub][1] and start hacking.
|
102
|
+
|
103
|
+
## About the name
|
104
|
+
|
105
|
+
Rubikon is the German name of the river Rubicone in Italy. It had a historical
|
106
|
+
relevance in ancient Rome when Julius Caesar crossed that river with his army
|
107
|
+
and thereby declared war to the Roman senate. The phrase "to cross the Rubicon"
|
108
|
+
originates from this event.
|
109
|
+
|
110
|
+
You may also see Rubikon as a morphed composition of *"Ruby"* and *"console"*.
|
111
|
+
|
112
|
+
## License
|
113
|
+
|
114
|
+
This code is free software; you can redistribute it and/or modify it under the
|
115
|
+
terms of the new BSD License. A copy of this license can be found in the LICENSE
|
116
|
+
file.
|
117
|
+
|
118
|
+
## Credits
|
119
|
+
|
120
|
+
* Sebastian Staudt -- koraktor(at)gmail.com
|
121
|
+
|
122
|
+
## See Also
|
123
|
+
|
124
|
+
* [API documentation](http://www.rdoc.info/projects/koraktor/rubikon)
|
125
|
+
* [GitHub project page][1]
|
126
|
+
* [GitHub issue tracker][2]
|
127
|
+
|
128
|
+
[1]: http://github.com/koraktor/rubikon
|
129
|
+
[2]: http://github.com/koraktor/rubikon/issues
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'rake/testtask'
|
8
|
+
|
9
|
+
src_files = Dir.glob(File.join('lib', '**', '*.rb'))
|
10
|
+
test_files = Dir.glob(File.join('test', '**', '*.rb'))
|
11
|
+
|
12
|
+
task :default => :test
|
13
|
+
|
14
|
+
# Test task
|
15
|
+
Rake::TestTask.new do |t|
|
16
|
+
t.libs << 'lib'
|
17
|
+
t.test_files = test_files
|
18
|
+
t.verbose = true
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require 'jeweler'
|
23
|
+
# Gem specification
|
24
|
+
Jeweler::Tasks.new do |gem|
|
25
|
+
gem.authors = ['Sebastian Staudt']
|
26
|
+
gem.email = 'koraktor@gmail.com'
|
27
|
+
gem.description = 'A simple to use, yet powerful Ruby framework for building console-based applications.'
|
28
|
+
gem.date = Time.now
|
29
|
+
gem.homepage = 'http://koraktor.github.com/rubikon'
|
30
|
+
gem.name = gem.rubyforge_project = 'rubikon'
|
31
|
+
gem.summary = 'Rubikon - A Ruby console app framework'
|
32
|
+
|
33
|
+
gem.files = %w(README.md Rakefile LICENSE VERSION.yml) + src_files + test_files
|
34
|
+
gem.rdoc_options = ['--all', '--inline-source', '--line-numbers', '--charset=utf-8', '--webcvs=http://github.com/koraktor/rubikon/blob/master/%s']
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
puts "You need Jeweler to build the gem. Install it using `gem install jeweler`."
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a rake task +:rdoc+ to build the documentation
|
41
|
+
desc 'Building docs'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
rdoc.title = 'Rubikon - API documentation'
|
44
|
+
rdoc.rdoc_files.include ['lib/**/*.rb', 'LICENSE', 'README.md']
|
45
|
+
rdoc.main = 'README.md'
|
46
|
+
rdoc.rdoc_dir = 'doc'
|
47
|
+
rdoc.options = ['--all', '--inline-source', '--line-numbers', '--charset=utf-8', '--webcvs=http://github.com/koraktor/rubikon/blob/master/%s']
|
48
|
+
end
|
49
|
+
|
50
|
+
# Task for cleaning documentation and package directories
|
51
|
+
desc 'Clean documentation and package directories'
|
52
|
+
task :clean do
|
53
|
+
FileUtils.rm_rf 'doc'
|
54
|
+
FileUtils.rm_rf 'pkg'
|
55
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
# Instances of the Action class are used to define the real code that should
|
9
|
+
# be executed when running the application.
|
10
|
+
class Action
|
11
|
+
|
12
|
+
@@action_count = 0
|
13
|
+
|
14
|
+
attr_reader :block, :description, :name, :param_type
|
15
|
+
|
16
|
+
# Create a new Action using the given name, options and code block.
|
17
|
+
#
|
18
|
+
# +name+:: The name of this Action, used in Application options
|
19
|
+
# +options+:: A Hash of options that define more details about the Action
|
20
|
+
# +block+:: The code block which should be executed by this Action
|
21
|
+
#
|
22
|
+
# Options:
|
23
|
+
# +description+:: A description of the action. This isn't used at the
|
24
|
+
# moment.
|
25
|
+
# +param_type+:: A single Class or a Array of classes that represent the
|
26
|
+
# type(s) of argument(s) this action expects
|
27
|
+
def initialize(name, options = {}, &block)
|
28
|
+
raise BlockMissingError unless block_given?
|
29
|
+
|
30
|
+
@name = name
|
31
|
+
|
32
|
+
@description = options[:description] || ''
|
33
|
+
@param_type = options[:param_type] || Object
|
34
|
+
|
35
|
+
@block = block
|
36
|
+
end
|
37
|
+
|
38
|
+
# Run this action's code block
|
39
|
+
#
|
40
|
+
# +args+:: The argument which should be relayed to the block of this Action
|
41
|
+
def run(*args)
|
42
|
+
if (@block.arity >= 0 and args.size < @block.arity) or (@block.arity < 0 and args.size < -@block.arity - 1)
|
43
|
+
raise MissingArgumentError
|
44
|
+
end
|
45
|
+
raise Rubikon::ArgumentTypeError unless check_argument_types(args)
|
46
|
+
@block[*args]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Checks the types of the given arguments using the Class or Array of
|
52
|
+
# classes given in the +:param_type+ option of this action.
|
53
|
+
#
|
54
|
+
# +args+:: The arguments which should be checked
|
55
|
+
def check_argument_types(args)
|
56
|
+
if @param_type.is_a? Array
|
57
|
+
args.each_index do |i|
|
58
|
+
return false unless args[i].is_a? @param_type[i]
|
59
|
+
end
|
60
|
+
else
|
61
|
+
args.each do |arg|
|
62
|
+
return false unless arg.is_a? @param_type
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,331 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require 'rubikon/action'
|
10
|
+
require 'rubikon/exceptions'
|
11
|
+
|
12
|
+
module Rubikon
|
13
|
+
|
14
|
+
version = YAML.load_file(File.join(File.dirname(__FILE__), '..', '..', 'VERSION.yml'))
|
15
|
+
VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
|
16
|
+
|
17
|
+
# The main class of Rubikon. Let your own application class inherit from this
|
18
|
+
# one.
|
19
|
+
class Application
|
20
|
+
|
21
|
+
include Singleton
|
22
|
+
|
23
|
+
attr_reader :settings
|
24
|
+
|
25
|
+
# Initialize with default settings (see set for more detail)
|
26
|
+
#
|
27
|
+
# If you really need to override this in your application class, be sure to
|
28
|
+
# call +super+
|
29
|
+
def initialize
|
30
|
+
@actions = {}
|
31
|
+
@aliases = {}
|
32
|
+
@default = nil
|
33
|
+
@settings = {
|
34
|
+
:autorun => true,
|
35
|
+
:dashed_options => true,
|
36
|
+
:help_banner => "Usage: #{$0}",
|
37
|
+
:istream => $stdin,
|
38
|
+
:name => self.class.to_s,
|
39
|
+
:ostream => $stdout,
|
40
|
+
:raise_errors => false
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define an Application Action
|
45
|
+
#
|
46
|
+
# +name+:: The name of the action. Used as an option parameter.
|
47
|
+
# +options+:: A Hash of options to be used on the created Action
|
48
|
+
# (default: <tt>{}</tt>)
|
49
|
+
# +block+:: A block containing the code that should be executed when this
|
50
|
+
# Action is called, i.e. when the Application is called with
|
51
|
+
# the associated option parameter
|
52
|
+
def action(name, options = {}, &block)
|
53
|
+
raise "No block given" unless block_given?
|
54
|
+
|
55
|
+
key = name
|
56
|
+
key = "--#{key}" if @settings[:dashed_options]
|
57
|
+
|
58
|
+
@actions[key.to_sym] = Action.new(name, options, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Define an alias to an Action
|
62
|
+
#
|
63
|
+
# +name+:: The name of the alias
|
64
|
+
# +action+:: The name of the Action that should be aliased
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# action_alias :doit, :dosomething
|
69
|
+
def action_alias(name, action)
|
70
|
+
@aliases[name.to_sym] = action.to_sym
|
71
|
+
end
|
72
|
+
|
73
|
+
# Define the default Action of the Application
|
74
|
+
#
|
75
|
+
# +options+:: A Hash of options to be used on the created Action
|
76
|
+
# (default: <tt>{}</tt>)
|
77
|
+
# +block+:: A block containing the code that should be executed when this
|
78
|
+
# Action is called, i.e. when no option is given to the
|
79
|
+
# Application
|
80
|
+
def default(options = {}, &block)
|
81
|
+
@default = Action.new(:default, options, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Prompts the user for input
|
85
|
+
#
|
86
|
+
# If +prompt+ is not empty this will display a prompt using
|
87
|
+
# <tt>prompt.to_s</tt>.
|
88
|
+
#
|
89
|
+
# +prompt+:: A String or other Object responding to +to_s+ used for
|
90
|
+
# displaying a prompt to the user (default: <tt>''</tt>)
|
91
|
+
#
|
92
|
+
# Example:
|
93
|
+
#
|
94
|
+
# action 'interactive' do
|
95
|
+
# # Display a prompt "Please type something: "
|
96
|
+
# user_provided_value = input 'Please type something'
|
97
|
+
#
|
98
|
+
# # Do something with the data
|
99
|
+
# ...
|
100
|
+
# end
|
101
|
+
def input(prompt = '')
|
102
|
+
unless prompt.to_s.empty?
|
103
|
+
ostream << "#{prompt}: "
|
104
|
+
end
|
105
|
+
@settings[:istream].gets[0..-2]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convenience method for accessing the user-defined output stream
|
109
|
+
#
|
110
|
+
# Use this if you want to work directly with the output stream
|
111
|
+
#
|
112
|
+
# Example:
|
113
|
+
#
|
114
|
+
# ostream.flush
|
115
|
+
def ostream
|
116
|
+
@settings[:ostream]
|
117
|
+
end
|
118
|
+
|
119
|
+
# Output text using +IO#<<+ of the output stream
|
120
|
+
#
|
121
|
+
# +text+:: The text to write into the output stream
|
122
|
+
def put(text)
|
123
|
+
ostream << text
|
124
|
+
ostream.flush
|
125
|
+
end
|
126
|
+
|
127
|
+
# Output a character using +IO#putc+ of the output stream
|
128
|
+
#
|
129
|
+
# +char+:: The character to write into the output stream
|
130
|
+
def putc(char)
|
131
|
+
ostream.putc char
|
132
|
+
end
|
133
|
+
|
134
|
+
# Output a line of text using +IO#puts+ of the output stream
|
135
|
+
#
|
136
|
+
# +text+:: The text to write into the output stream
|
137
|
+
def puts(text)
|
138
|
+
ostream.puts text
|
139
|
+
end
|
140
|
+
|
141
|
+
# Run this application
|
142
|
+
#
|
143
|
+
# +args+:: The command line arguments that should be given to the
|
144
|
+
# application as options
|
145
|
+
#
|
146
|
+
# Calling this method explicitly is not required when you want to create a
|
147
|
+
# simple application (having one main class inheriting from
|
148
|
+
# Rubikon::Application). But it's useful for testing or if you want to have
|
149
|
+
# some sort of sub-applications.
|
150
|
+
def run(args = ARGV)
|
151
|
+
begin
|
152
|
+
assign_aliases unless @aliases.empty?
|
153
|
+
action_results = []
|
154
|
+
|
155
|
+
if !@default.nil? and args.empty?
|
156
|
+
action_results << @default.run
|
157
|
+
else
|
158
|
+
parse_options(args).each do |action, args|
|
159
|
+
action_results << @actions[action].run(*args)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
rescue
|
163
|
+
if @settings[:raise_errors]
|
164
|
+
raise $!
|
165
|
+
else
|
166
|
+
puts "Error:\n #{$!.message}"
|
167
|
+
puts " #{$!.backtrace.join("\n ")}" if $DEBUG
|
168
|
+
exit 1
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
action_results
|
173
|
+
end
|
174
|
+
|
175
|
+
# Sets an application setting
|
176
|
+
#
|
177
|
+
# +setting+:: The name of the setting to change, will be symbolized first.
|
178
|
+
# +value+:: The value the setting should be changed to
|
179
|
+
#
|
180
|
+
# Available settings
|
181
|
+
# +autorun+:: If true, let the application run as soon as its class
|
182
|
+
# is defined
|
183
|
+
# +dashed_options+:: If true, each option is prepended with a double-dash
|
184
|
+
# (<tt>-</tt><tt>-</tt>)
|
185
|
+
# +help_banner+:: Defines a banner for the help message (<em>unused</em>)
|
186
|
+
# +istream+:: Defines an input stream to use
|
187
|
+
# +name+:: Defines the name of the application
|
188
|
+
# +ostream+:: Defines an output stream to use
|
189
|
+
# +raise_errors+:: If true, raise errors, otherwise fail gracefully
|
190
|
+
#
|
191
|
+
# Example:
|
192
|
+
#
|
193
|
+
# set :name, 'My App'
|
194
|
+
# set :autorun, false
|
195
|
+
def set(setting, value)
|
196
|
+
@settings[setting.to_sym] = value
|
197
|
+
end
|
198
|
+
|
199
|
+
# Displays a throbber while the given block is executed
|
200
|
+
#
|
201
|
+
# Example:
|
202
|
+
#
|
203
|
+
# action 'slow' do
|
204
|
+
# throbber do
|
205
|
+
# # Add some long running code here
|
206
|
+
# ...
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
def throbber(&block)
|
210
|
+
spinner = '-\|/'
|
211
|
+
current_ostream = ostream
|
212
|
+
@settings[:ostream] = StringIO.new
|
213
|
+
|
214
|
+
code_thread = Thread.new { block.call }
|
215
|
+
|
216
|
+
throbber_thread = Thread.new do
|
217
|
+
i = 0
|
218
|
+
current_ostream.putc 32
|
219
|
+
while code_thread.alive?
|
220
|
+
current_ostream.putc 8
|
221
|
+
current_ostream.putc spinner[i]
|
222
|
+
current_ostream.flush
|
223
|
+
i = (i + 1) % 4
|
224
|
+
sleep 0.25
|
225
|
+
end
|
226
|
+
current_ostream.putc 8
|
227
|
+
end
|
228
|
+
|
229
|
+
code_thread.join
|
230
|
+
throbber_thread.join
|
231
|
+
|
232
|
+
current_ostream << ostream.string
|
233
|
+
@settings[:ostream] = current_ostream
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# Returns whether this application should be ran automatically
|
239
|
+
def self.autorun?
|
240
|
+
instance.settings[:autorun] || false
|
241
|
+
end
|
242
|
+
|
243
|
+
# Enables autorun functionality using <tt>Kernel#at_exit</tt>
|
244
|
+
#
|
245
|
+
# +subclass+:: The subclass inheriting from Application. This is the user's
|
246
|
+
# application.
|
247
|
+
#
|
248
|
+
# <em>This is called automatically when subclassing Application.</em>
|
249
|
+
def self.inherited(subclass)
|
250
|
+
Singleton.__init__(subclass)
|
251
|
+
at_exit { subclass.run if subclass.autorun? }
|
252
|
+
end
|
253
|
+
|
254
|
+
# This is used for convinience. Method calls on the class itself are
|
255
|
+
# relayed to the singleton instance.
|
256
|
+
#
|
257
|
+
# +method_name+:: The name of the method being called
|
258
|
+
# +args+:: Any arguments that are given to the method
|
259
|
+
# +block+:: A block that may be given to the method
|
260
|
+
#
|
261
|
+
# <em>This is called automatically when calling methods on the class.</em>
|
262
|
+
def self.method_missing(method_name, *args, &block)
|
263
|
+
instance.send(method_name, *args, &block)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Relay putc to the instance method
|
267
|
+
#
|
268
|
+
# This is used to hide <tt>Kernel#putc</tt> so that the Application's
|
269
|
+
# output IO object is used for printing text
|
270
|
+
#
|
271
|
+
# +text+:: The text to write into the output stream
|
272
|
+
def self.putc(text)
|
273
|
+
instance.putc text
|
274
|
+
end
|
275
|
+
|
276
|
+
# Relay puts to the instance method
|
277
|
+
#
|
278
|
+
# This is used to hide <tt>Kernel#puts</tt> so that the Application's
|
279
|
+
# output IO object is used for printing text
|
280
|
+
#
|
281
|
+
# +text+:: The text to write into the output stream
|
282
|
+
def self.puts(text)
|
283
|
+
instance.puts text
|
284
|
+
end
|
285
|
+
|
286
|
+
# Assigns aliases to the actions that have been defined using action_alias
|
287
|
+
#
|
288
|
+
# Clears the aliases Hash afterwards
|
289
|
+
def assign_aliases
|
290
|
+
@aliases.each do |key, action|
|
291
|
+
if @settings[:dashed_options]
|
292
|
+
action = "--#{action}".to_sym
|
293
|
+
key = "--#{key}".to_sym
|
294
|
+
end
|
295
|
+
|
296
|
+
unless @actions.key? key
|
297
|
+
@actions[key] = @actions[action]
|
298
|
+
else
|
299
|
+
warn "There's already an action called \"#{key}\"."
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
@aliases = {}
|
304
|
+
end
|
305
|
+
|
306
|
+
# Parses the options used when starting the application
|
307
|
+
#
|
308
|
+
# +options+:: An Array of Strings that should be used as application
|
309
|
+
# options. Usually +ARGV+ is used for this.
|
310
|
+
def parse_options(options)
|
311
|
+
actions_to_call = {}
|
312
|
+
last_action = nil
|
313
|
+
|
314
|
+
options.each do |option|
|
315
|
+
option_sym = option.to_sym
|
316
|
+
if @actions.keys.include? option_sym
|
317
|
+
actions_to_call[option_sym] = []
|
318
|
+
last_action = option_sym
|
319
|
+
elsif last_action.nil? || (option.is_a?(String) && @settings[:dashed_options] && option[0..1] == '--')
|
320
|
+
raise UnknownOptionError.new(option)
|
321
|
+
else
|
322
|
+
actions_to_call[last_action] << option
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
actions_to_call
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
module Rubikon
|
7
|
+
|
8
|
+
class ArgumentTypeError < ArgumentError
|
9
|
+
end
|
10
|
+
|
11
|
+
class BlockMissingError < ArgumentError
|
12
|
+
end
|
13
|
+
|
14
|
+
class MissingArgumentError < ArgumentError
|
15
|
+
end
|
16
|
+
|
17
|
+
class MissingOptionError < ArgumentError
|
18
|
+
end
|
19
|
+
|
20
|
+
class UnknownOptionError < ArgumentError
|
21
|
+
|
22
|
+
def initialize(arg)
|
23
|
+
super "Unknown argument: #{arg}"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/rubikon.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
libdir = File.dirname(__FILE__)
|
7
|
+
$:.unshift(libdir) unless $:.include?(libdir)
|
8
|
+
|
9
|
+
require 'rubikon/application'
|
data/test/test.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'shoulda'
|
8
|
+
|
9
|
+
begin require 'redgreen'; rescue LoadError; end
|
10
|
+
|
11
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
12
|
+
$: << File.dirname(__FILE__)
|
13
|
+
require 'rubikon'
|
14
|
+
require 'testapp'
|
15
|
+
|
16
|
+
class RubikonTests < Test::Unit::TestCase
|
17
|
+
|
18
|
+
context 'A Rubikon application\'s class' do
|
19
|
+
|
20
|
+
setup do
|
21
|
+
@app = RubikonTestApp.instance
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'run it\'s instance for called methods' do
|
25
|
+
assert_equal @app.run(%w{--object_id}), RubikonTestApp.run(%w{--object_id})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'A Rubikon application' do
|
30
|
+
|
31
|
+
setup do
|
32
|
+
@app = RubikonTestApp
|
33
|
+
@ostream = StringIO.new
|
34
|
+
@app.set :ostream, @ostream
|
35
|
+
end
|
36
|
+
|
37
|
+
should 'be a singleton' do
|
38
|
+
assert_raise NoMethodError do
|
39
|
+
RubikonTestApp.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'exit gracefully' do
|
44
|
+
unknown = '--unknown'
|
45
|
+
@app.set :raise_errors, false
|
46
|
+
begin
|
47
|
+
@app.run([unknown])
|
48
|
+
rescue Exception => e
|
49
|
+
end
|
50
|
+
assert_instance_of SystemExit, e
|
51
|
+
assert_equal 1, e.status
|
52
|
+
@ostream.rewind
|
53
|
+
assert_equal "Error:\n", @ostream.gets
|
54
|
+
assert_equal " Unknown argument: #{unknown}\n", @ostream.gets
|
55
|
+
@app.set :raise_errors, true
|
56
|
+
end
|
57
|
+
|
58
|
+
should 'run it\'s default action without options' do
|
59
|
+
result = @app.run
|
60
|
+
assert_equal 1, result.size
|
61
|
+
assert_equal 'default action', result.first
|
62
|
+
end
|
63
|
+
|
64
|
+
should 'run with a mandatory option' do
|
65
|
+
result = @app.run(%w{--required arg})
|
66
|
+
assert_equal 1, result.size
|
67
|
+
assert_equal 'required argument was arg', result.first
|
68
|
+
end
|
69
|
+
|
70
|
+
should 'not run without a mandatory argument' do
|
71
|
+
assert_raise Rubikon::MissingArgumentError do
|
72
|
+
@app.run(%w{--required})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
should 'require an argument type if it has been defined' do
|
77
|
+
assert_raise Rubikon::ArgumentTypeError do
|
78
|
+
@app.run(['--output', 6])
|
79
|
+
end
|
80
|
+
assert_raise Rubikon::ArgumentTypeError do
|
81
|
+
@app.run(['--number_string', 6, 7])
|
82
|
+
end
|
83
|
+
assert_raise Rubikon::ArgumentTypeError do
|
84
|
+
@app.run(['--number_string', 'test' , 6])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
should 'raise an exception when calling an action with the wrong number of
|
89
|
+
arguments' do
|
90
|
+
assert_raise Rubikon::MissingArgumentError do
|
91
|
+
@app.run(%w{--output})
|
92
|
+
end
|
93
|
+
assert_raise ArgumentError do
|
94
|
+
@app.run(%w{--output}, 'test', 3)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
should 'raise an exception when using an unknown option' do
|
99
|
+
assert_raise Rubikon::UnknownOptionError do
|
100
|
+
@app.run(%w{--unknown})
|
101
|
+
end
|
102
|
+
assert_raise Rubikon::UnknownOptionError do
|
103
|
+
@app.run(%w{--noarg --unknown})
|
104
|
+
end
|
105
|
+
assert_raise Rubikon::UnknownOptionError do
|
106
|
+
@app.run(%w{--unknown --noarg})
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
should 'be able to handle user input' do
|
111
|
+
@istream = StringIO.new
|
112
|
+
@app.set :istream, @istream
|
113
|
+
|
114
|
+
input_string = 'test'
|
115
|
+
@istream.puts input_string
|
116
|
+
@istream.rewind
|
117
|
+
assert_equal [input_string], @app.run(%w{--input})
|
118
|
+
@ostream.rewind
|
119
|
+
assert_equal 'input: ', @ostream.gets
|
120
|
+
end
|
121
|
+
|
122
|
+
should 'write output to the user given output stream' do
|
123
|
+
input_string = 'test'
|
124
|
+
@app.run(['--output', input_string])
|
125
|
+
@ostream.rewind
|
126
|
+
assert_equal "#{input_string}\n", @ostream.gets
|
127
|
+
assert_equal "#{input_string}#{input_string[0].chr}", @ostream.gets
|
128
|
+
end
|
129
|
+
|
130
|
+
should 'provide a throbber' do
|
131
|
+
@app.run(%w{--throbber})
|
132
|
+
@ostream.rewind
|
133
|
+
assert_equal " \b-\b\\\b|\b/\b", @ostream.string
|
134
|
+
@app.run(%w{--throbber true})
|
135
|
+
@ostream.rewind
|
136
|
+
assert_equal " \b-\b\\\b|\b/\bdon't\nbreak\n", @ostream.string
|
137
|
+
end
|
138
|
+
|
139
|
+
should 'have working action aliases' do
|
140
|
+
assert_equal @app.run(%w{--alias_before}), @app.run(%w{--object_id})
|
141
|
+
assert_equal @app.run(%w{--alias_after}), @app.run(%w{--object_id})
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'A Rubikon action' do
|
147
|
+
|
148
|
+
should 'throw an exception when no code block is given' do
|
149
|
+
assert_raise Rubikon::BlockMissingError do
|
150
|
+
Rubikon::Action.new 'name'
|
151
|
+
end
|
152
|
+
assert_raise Rubikon::BlockMissingError do
|
153
|
+
Rubikon::Action.new 'name', {}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
should 'not raise an exception when created without options' do
|
158
|
+
action_name = 'someaction'
|
159
|
+
action_options = {
|
160
|
+
:description => 'this is an action',
|
161
|
+
:param_type => String
|
162
|
+
}
|
163
|
+
assert_nothing_raised do
|
164
|
+
action = Rubikon::Action.new action_name, action_options do end
|
165
|
+
assert_equal action_name, action.name
|
166
|
+
assert_equal action_options[:description], action.description
|
167
|
+
assert_equal action_options[:param_type], action.param_type
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
data/test/testapp.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
class RubikonTestApp < Rubikon::Application
|
7
|
+
|
8
|
+
set :autorun, false
|
9
|
+
set :name, 'Rubikon test application'
|
10
|
+
set :raise_errors, true
|
11
|
+
|
12
|
+
default do
|
13
|
+
'default action'
|
14
|
+
end
|
15
|
+
|
16
|
+
action 'input' do
|
17
|
+
input 'input'
|
18
|
+
end
|
19
|
+
|
20
|
+
action_alias :alias_before, :object_id
|
21
|
+
|
22
|
+
action 'object_id' do
|
23
|
+
object_id
|
24
|
+
end
|
25
|
+
|
26
|
+
action_alias :alias_after, :object_id
|
27
|
+
|
28
|
+
action 'noarg' do
|
29
|
+
'noarg action'
|
30
|
+
end
|
31
|
+
|
32
|
+
action 'realnoarg' do ||
|
33
|
+
end
|
34
|
+
|
35
|
+
action 'noarg2' do
|
36
|
+
end
|
37
|
+
|
38
|
+
action 'number_string', :param_type => [Numeric, String] do |s,n|
|
39
|
+
end
|
40
|
+
|
41
|
+
action 'output', :param_type => String do |s|
|
42
|
+
puts s
|
43
|
+
put s
|
44
|
+
putc s[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
action 'required' do |what|
|
48
|
+
"required argument was #{what}"
|
49
|
+
end
|
50
|
+
|
51
|
+
action 'throbber' do |*output|
|
52
|
+
throbber do
|
53
|
+
if output[0]
|
54
|
+
sleep 0.5
|
55
|
+
puts 'don\'t'
|
56
|
+
sleep 0.5
|
57
|
+
puts 'break'
|
58
|
+
else
|
59
|
+
sleep 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubikon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sebastian Staudt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-28 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A simple to use, yet powerful Ruby framework for building console-based applications.
|
17
|
+
email: koraktor@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- VERSION.yml
|
30
|
+
- lib/rubikon.rb
|
31
|
+
- lib/rubikon/action.rb
|
32
|
+
- lib/rubikon/application.rb
|
33
|
+
- lib/rubikon/exceptions.rb
|
34
|
+
- test/test.rb
|
35
|
+
- test/testapp.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://koraktor.github.com/rubikon
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --all
|
43
|
+
- --inline-source
|
44
|
+
- --line-numbers
|
45
|
+
- --charset=utf-8
|
46
|
+
- --webcvs=http://github.com/koraktor/rubikon/blob/master/%s
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project: rubikon
|
64
|
+
rubygems_version: 1.3.5
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Rubikon - A Ruby console app framework
|
68
|
+
test_files:
|
69
|
+
- test/test.rb
|
70
|
+
- test/testapp.rb
|