stonepath 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +26 -0
  3. data/PostInstall.txt +2 -0
  4. data/README.rdoc +58 -0
  5. data/Rakefile +44 -0
  6. data/VERSION +1 -0
  7. data/lib/stonepath/acl/controller.rb +86 -0
  8. data/lib/stonepath/acl/role.rb +53 -0
  9. data/lib/stonepath/acl/state.rb +20 -0
  10. data/lib/stonepath/acl.rb +3 -0
  11. data/lib/stonepath/config.rb +6 -0
  12. data/lib/stonepath/controller_hooks.rb +11 -0
  13. data/lib/stonepath/extensions/activerecordbase.rb +7 -0
  14. data/lib/stonepath/group.rb +9 -0
  15. data/lib/stonepath/role.rb +9 -0
  16. data/lib/stonepath/task.rb +76 -0
  17. data/lib/stonepath/work_bench.rb +11 -0
  18. data/lib/stonepath/work_item.rb +52 -0
  19. data/lib/stonepath/work_owner.rb +11 -0
  20. data/lib/stonepath.rb +50 -0
  21. data/rails_generators/stonepath/stonepath_audit_table_generator.rb +3 -0
  22. data/rails_generators/stonepath/stonepath_task_generator.rb +3 -0
  23. data/script/console +10 -0
  24. data/script/destroy +14 -0
  25. data/script/generate +14 -0
  26. data/stonepath.gemspec +117 -0
  27. data/test/acl_test.rb +17 -0
  28. data/test/app_root/app/controllers/application_controller.rb +2 -0
  29. data/test/app_root/app/models/assignment.rb +11 -0
  30. data/test/app_root/app/models/case.rb +67 -0
  31. data/test/app_root/app/models/user.rb +10 -0
  32. data/test/app_root/config/boot.rb +114 -0
  33. data/test/app_root/config/database.yml +21 -0
  34. data/test/app_root/config/environment.rb +14 -0
  35. data/test/app_root/config/environments/in_memory.rb +0 -0
  36. data/test/app_root/config/environments/mysql.rb +0 -0
  37. data/test/app_root/config/environments/postgresql.rb +0 -0
  38. data/test/app_root/config/environments/sqlite.rb +0 -0
  39. data/test/app_root/config/environments/sqlite3.rb +0 -0
  40. data/test/app_root/config/routes.rb +4 -0
  41. data/test/app_root/db/migrate/01_create_users.rb +15 -0
  42. data/test/app_root/db/migrate/02_create_assignments.rb +13 -0
  43. data/test/app_root/db/migrate/03_create_cases.rb +15 -0
  44. data/test/app_root/lib/console_with_fixtures.rb +4 -0
  45. data/test/app_root/log/.gitignore +1 -0
  46. data/test/app_root/script/console +9 -0
  47. data/test/fixtures/users.yml +7 -0
  48. data/test/group_test.rb +20 -0
  49. data/test/role_test.rb +20 -0
  50. data/test/stonepath_test.rb +11 -0
  51. data/test/test_helper.rb +31 -0
  52. data/test/workitem_test.rb +11 -0
  53. data/test/workowner_test.rb +25 -0
  54. metadata +138 -0
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-08-09
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,26 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/stonepath.rb
7
+ lib/stonepath/acl.rb
8
+ lib/stonepath/config.rb
9
+ lib/stonepath/controller_hooks.rb
10
+ lib/stonepath/group.rb
11
+ lib/stonepath/role.rb
12
+ lib/stonepath/task.rb
13
+ lib/stonepath/work_bench.rb
14
+ lib/stonepath/work_item.rb
15
+ lib/stonepath/work_owner.rb
16
+ lib/stonepath/acl/controller.rb
17
+ lib/stonepath/acl/role.rb
18
+ lib/stonepath/acl/state.rb
19
+ lib/stonepath/extensions/activerecordbase.rb
20
+ rails_generators/stonepath/stonepath_task_generator.rb
21
+ rails_generators/stonepath/stonepath_audit_table_generator.rb
22
+ script/console
23
+ script/destroy
24
+ script/generate
25
+ test/test_helper.rb
26
+ test/test_stonepath.rb
data/PostInstall.txt ADDED
@@ -0,0 +1,2 @@
1
+ Dave Bock is Awesome.
2
+
data/README.rdoc ADDED
@@ -0,0 +1,58 @@
1
+ = stonepath
2
+
3
+ Wednesday, August 25th UPDATE:
4
+ The extraction is underway, and I have to write some generators to have some stuff make sense outside the scope of a real application.
5
+
6
+ I'm going to have a good article on the stomepath modeling methodology shortly. It is written, it just needs diagrams and an editor's touch.
7
+
8
+
9
+ Sunday, August 9th UPDATE:
10
+ Sorry for the delay. I just gave this talk again at eRubyCon.
11
+
12
+ Some of these stateful workflow ideas are being used in a project for the
13
+ DC Public School System, but the data store there is unconventional (a
14
+ QuickBase database online), so the work being done on StonePath has taken a
15
+ back seat. I expect this project to be live again shortly, starting simply
16
+ with some wiki entries explaining the same concepts that I outline in the
17
+ presentation (including WorkItem, Ownership, Assignment, Task, Permissions, and
18
+ organizational modeling with groups and roles).
19
+
20
+ ---
21
+
22
+ This is a Stateful Workflow gem that is currently being pulled out of a
23
+ real-world client project. I'm creating the repository before RailsConf 2009
24
+ so interested people that attend my talk can watch and contribute.
25
+
26
+ I'm also asking you to keep me honest - this extraction is real, but is likely
27
+ to take a back seat to real billable client time. If I know there is interest
28
+ (that is, if I see followers and if they nag me), this project is likely to get
29
+ time over others.
30
+
31
+
32
+
33
+ gem install bokmann-stonepath
34
+
35
+ == LICENSE:
36
+
37
+ (The MIT License)
38
+
39
+ Copyright (c) 2009 FIXME full name
40
+
41
+ Permission is hereby granted, free of charge, to any person obtaining
42
+ a copy of this software and associated documentation files (the
43
+ 'Software'), to deal in the Software without restriction, including
44
+ without limitation the rights to use, copy, modify, merge, publish,
45
+ distribute, sublicense, and/or sell copies of the Software, and to
46
+ permit persons to whom the Software is furnished to do so, subject to
47
+ the following conditions:
48
+
49
+ The above copyright notice and this permission notice shall be
50
+ included in all copies or substantial portions of the Software.
51
+
52
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
53
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
54
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
55
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
56
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
57
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
58
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rcov/rcovtask'
4
+ require 'shoulda/tasks'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = "stonepath"
10
+ gemspec.summary = "Stonepath: stateful workflow modeling for rails"
11
+ gemspec.description = "Stonepath: stateful workflow modeling for rails"
12
+ gemspec.email = "dbock@codesherpas.com"
13
+ gemspec.homepage = "http://github.com/bokmann/stonepath"
14
+ gemspec.description = "Stateful workflow modeling for Rails"
15
+ gemspec.authors = ["David Bock"]
16
+ gemspec.add_dependency('activerecord','>= 2.0.0')
17
+ end
18
+
19
+ Jeweler::GemcutterTasks.new
20
+
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
24
+
25
+ Rake::TestTask.new do |t|
26
+ t.libs << 'lib'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = false
29
+ end
30
+
31
+
32
+ Rcov::RcovTask.new do |t|
33
+ #t.libs << "test"
34
+ #t.test_files = FileList['./test/**/test*.rb']
35
+ #t.verbose = true
36
+
37
+ t.test_files = FileList['test/**/*_test.rb']
38
+ t.verbose = true
39
+ #t.rcov_opts << "--no-color"
40
+ #t.rcov_opts << "--save coverage.info"
41
+ t.rcov_opts << "-x ^/"
42
+ end
43
+
44
+ task :default => :rcov
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,86 @@
1
+ module StonePath
2
+ module ACL
3
+ class Controller
4
+
5
+ attr_reader :guarded_class_methods
6
+ attr_reader :guarded_instance_methods
7
+ attr_reader :states
8
+ attr_reader :guarded_class
9
+ attr_reader :method_groups
10
+
11
+
12
+
13
+ def initialize(guarded_class)
14
+ @guarded_class = guarded_class
15
+ @guarded_methods = Array.new
16
+ @states = Hash.new
17
+ @method_groups = Hash.new
18
+ @aliases = Array.new
19
+ end
20
+
21
+ def guard_method(method)
22
+ @guarded_methods << method
23
+ checked_method = checked_method_name(method)
24
+ unchecked_method = unchecked_method_name(method)
25
+
26
+ guarded_class.send(:define_method, checked_method) do |*args|
27
+ if acl.allowed?(aasm_current_state, current_user, method)
28
+ self.send(unchecked_method, *args)
29
+ elsif StonePath::Config.acl_failure_mode == :exception
30
+ raise "Access Violation"
31
+ end
32
+ end
33
+
34
+ # Save our needed aliases until later
35
+ @aliases << [unchecked_method, method]
36
+ @aliases << [method, checked_method]
37
+ end
38
+
39
+ # fix up our saved aliases
40
+ def fix_aliases
41
+ @aliases.each do |src, target|
42
+ guarded_class.send(:alias_method, src, target)
43
+ end
44
+ end
45
+
46
+
47
+ def method_group(name, methods)
48
+ @method_groups[name] = methods
49
+ end
50
+
51
+ def for_state(state_name)
52
+ @states[state_name] = State.new(state_name, self) if @states[state_name].nil?
53
+ yield @states[state_name]
54
+ end
55
+
56
+ def allowed?(state, user, method)
57
+ puts "allowed called: #{state}, #{user}, #{method}"
58
+ return true
59
+ end
60
+
61
+ private
62
+
63
+ # TODO
64
+ # these two methods are ugly - we need to dry up the repetition and make them work for ? methods.
65
+ def checked_method_name(method)
66
+ method_name = method.to_s
67
+ if method_name.include?("=")
68
+ method_name.gsub!("=","")
69
+ return "#{method_name}_with_check="
70
+ else
71
+ return "#{method_name}_with_check"
72
+ end
73
+ end
74
+
75
+ def unchecked_method_name(method)
76
+ method_name = method.to_s
77
+ if method_name.include?("=")
78
+ method_name.gsub!("=","")
79
+ return "#{method_name}_without_check="
80
+ else
81
+ return "#{method_name}_without_check"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ module StonePath
2
+ module ACL
3
+ class Role
4
+ attr_reader :name
5
+ attr_reader :parent_state
6
+ attr_reader :allowed_methods
7
+ attr_reader :denied_methods
8
+ attr_reader :checked_methods
9
+
10
+ def initialize(name, parent_state)
11
+ @name = name
12
+ @parent_state = parent_state
13
+ @allowed_methods = Array.new
14
+ @denied_methods = Array.new
15
+ @checked_methods = Hash.new
16
+ end
17
+
18
+ def parent_acl
19
+ @parent_state.parent_acl
20
+ end
21
+
22
+ def deny_method(method)
23
+ @denied_methods << method
24
+ end
25
+
26
+ def allow_method(method)
27
+ @allowed_methods << method
28
+ end
29
+
30
+ def check_method_access(method, &check)
31
+ @checked_methods[method] = check
32
+ end
33
+
34
+ def deny_method_group(group)
35
+ parent_acl.method_groups[group].each do |method|
36
+ deny_method(method)
37
+ end
38
+ end
39
+
40
+ def allow_method_group(method_group)
41
+ parent_acl.method_groups[group].each do |method|
42
+ allow_method(method)
43
+ end
44
+ end
45
+
46
+ def check_method_group_access(method_group, &check)
47
+ parent_acl.method_groups[group].each do |method|
48
+ check_method_access(check)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ module StonePath
2
+ module ACL
3
+ class State
4
+ attr_reader :name
5
+ attr_reader :roles
6
+ attr_reader :parent_acl
7
+
8
+ def initialize(name, parent_acl)
9
+ @name = name
10
+ @parent_acl = parent_acl
11
+ @roles = Hash.new
12
+ end
13
+
14
+ def access_for_role(role_name)
15
+ @roles[role_name] = Role.new(role_name, self) if @roles[role_name].nil?
16
+ yield @roles[role_name]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ require File.expand_path(File.dirname(__FILE__)) + "/acl/controller.rb"
2
+ require File.expand_path(File.dirname(__FILE__)) + "/acl/role.rb"
3
+ require File.expand_path(File.dirname(__FILE__)) + "/acl/state.rb"
@@ -0,0 +1,6 @@
1
+ module StonePath
2
+ class Config
3
+ cattr_accessor :acl_failure_mode
4
+ cattr_accessor :acl_default_access
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module StonePath
2
+ module ControllerHooks
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ before_filter do |c|
6
+ ActiveRecord::Base.current_user = current_user
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # for the ACL stuff to work, models need to know who the current user is.
2
+ # This adds a current_user to ActiveRecord
3
+ unless ActiveRecord::Base.respond_to?(:current_user)
4
+ class ActiveRecord::Base
5
+ cattr_accessor :current_user
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module StonePath
2
+ module Group
3
+ def self.included(base)
4
+ base.instance_eval do
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module StonePath
2
+ module Role
3
+ def self.included(base)
4
+ base.instance_eval do
5
+
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,76 @@
1
+ module StonePath
2
+ module SPTask
3
+ def self.included(base)
4
+ base.instance_eval do
5
+
6
+ def assigns_to(workbench)
7
+ belongs_to :assignee, :class_name => workbench.to_s.classify
8
+ end
9
+
10
+ def task_for(workitem)
11
+ #belongs_to :workitem, :polymorphic => true
12
+ belongs_to :workitem, :class_name => workitem.to_s.classify
13
+ end
14
+
15
+ def audits_transitions
16
+ puts "#{self.class} audits transitions"
17
+ end
18
+
19
+ include AASM
20
+
21
+ #we must be attached to a workitem.
22
+ validates_presence_of :workitem
23
+
24
+ # We can have unassigned tasks.
25
+ #validates_presence_of :assignee
26
+
27
+ aasm_initial_state :active
28
+
29
+
30
+ aasm_state :active, :after_enter => :notify_created
31
+ aasm_state :completed, :before_enter => :timestamp_complete, :after_enter => :notify_closed
32
+ aasm_state :expired, :after_enter => :notify_closed
33
+ aasm_state :cancelled, :after_enter => :notify_closed
34
+
35
+
36
+ aasm_event :complete do
37
+ transitions :to => :completed, :from => :active
38
+ end
39
+
40
+ aasm_event :cancel do
41
+ transitions :to => :cancelled, :from => [:active, :completed]
42
+ end
43
+
44
+ aasm_event :expire do
45
+ transitions :to => :expired, :from => :active
46
+ end
47
+
48
+ named_scope :unassigned, lambda { { :conditions => ['assignee_id IS NULL'] } }
49
+ named_scope :active, lambda { { :conditions => ['aasm_state in (?)', ['active']] } }
50
+ named_scope :overdue, lambda { { :conditions => ['aasm_state in (?) AND due_at < ?', ['active'], Time.now] } }
51
+
52
+
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def timestamp_complete
59
+ self.completed_at = Time.now
60
+ end
61
+
62
+ def notify_created
63
+ if workitem.respond_to?(:task_created)
64
+ workitem.task_created(self)
65
+ end
66
+ end
67
+
68
+ def notify_closed
69
+ if workitem.respond_to?(:task_closed)
70
+ workitem.task_closed(self)
71
+ end
72
+ end
73
+
74
+
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ module StonePath
2
+ module WorkBench
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ def workbench_for(tasks)
6
+ has_many tasks, :foreign_key => :assignee_id
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,52 @@
1
+ module StonePath
2
+ module WorkItem
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ include AASM
6
+
7
+ def owned_by(owner)
8
+ belongs_to :owner, :class_name => owner.to_s.classify
9
+ end
10
+
11
+ def subject_of(tasks)
12
+ has_many tasks
13
+ end
14
+
15
+ def stonepath_acl()
16
+ require File.expand_path(File.dirname(__FILE__)) + "/acl.rb"
17
+ #require File.expand_path(File.dirname(__FILE__)) + "/acl/acl.rb"
18
+ #require File.expand_path(File.dirname(__FILE__)) + "/acl/acl_role.rb"
19
+ #require File.expand_path(File.dirname(__FILE__)) + "/acl/acl_state.rb"
20
+ cattr_accessor :acl
21
+ self.acl = StonePath::ACL::Controller.new(self)
22
+ yield self.acl
23
+ end
24
+
25
+ def self.define_attribute_methods_with_hook
26
+ define_attribute_methods_without_hook
27
+ acl.fix_aliases
28
+ end
29
+
30
+ class << self
31
+ alias_method "define_attribute_methods_without_hook", "define_attribute_methods"
32
+ alias_method "define_attribute_methods", "define_attribute_methods_with_hook"
33
+ end
34
+ end
35
+
36
+ def allowed?(method)
37
+ acl.allowed?(aasm_current_state, current_user, method)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ # if table_exists? <workitem>_transition_log_entries
46
+ #define WorkItem::TransitionLogEntry
47
+ # then for each transition method, have an after proc that
48
+ # creates workitem_transition_log
49
+ # workitem_id
50
+ # transitioned_to
51
+ # transitioned_by
52
+ # transitioned_at
@@ -0,0 +1,11 @@
1
+ module StonePath
2
+ module WorkOwner
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ def workowner_for(work_items)
6
+ has_many work_items
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
data/lib/stonepath.rb ADDED
@@ -0,0 +1,50 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module StonePath
5
+ # main hook into the framework. From here, this should simply have methods that cause other includes to happen.
6
+ def self.included(base)
7
+
8
+ base.instance_eval {
9
+
10
+ def stonepath_workitem
11
+ require File.expand_path(File.dirname(__FILE__)) + "/stonepath/work_item.rb"
12
+ include StonePath::WorkItem
13
+ end
14
+
15
+ def stonepath_task
16
+ require File.expand_path(File.dirname(__FILE__)) + "/stonepath/task.rb"
17
+ include StonePath::SPTask
18
+ end
19
+
20
+ def stonepath_workbench
21
+ require File.expand_path(File.dirname(__FILE__)) + "/stonepath/work_bench.rb"
22
+ include StonePath::WorkBench
23
+ end
24
+
25
+ def stonepath_group
26
+ require File.expand_path(File.dirname(__FILE__)) + "/stonepath/group.rb"
27
+ include StonePath::Group
28
+ end
29
+
30
+ def stonepath_role
31
+ require File.expand_path(File.dirname(__FILE__)) + "/stonepath/role.rb"
32
+ include StonePath::Role
33
+ end
34
+
35
+ def stonepath_workowner
36
+ require File.expand_path(File.dirname(__FILE__)) + "/stonepath/work_owner.rb"
37
+ include StonePath::WorkOwner
38
+ end
39
+ }
40
+ end
41
+
42
+ end
43
+
44
+ require 'rubygems'
45
+ require 'activerecord'
46
+
47
+ load File.expand_path( File.dirname(__FILE__)) + "/stonepath/extensions/activerecordbase.rb"
48
+ require "stonepath/config"
49
+
50
+
@@ -0,0 +1,3 @@
1
+ class StonepathAuditTableGenerator < Rails::Generator::Base
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class StonepathTaskGenerator < Rails::Generator::Base
2
+
3
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/stonepath.rb'}"
9
+ puts "Loading stonepath gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)