shattered 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. data/bin/console +22 -0
  2. data/bin/generate +7 -0
  3. data/bin/runner +9 -0
  4. data/bin/shatter +29 -0
  5. data/lib/game_loader.rb +49 -0
  6. data/lib/rails_generator.rb +43 -0
  7. data/lib/rails_generator/base.rb +203 -0
  8. data/lib/rails_generator/commands.rb +445 -0
  9. data/lib/rails_generator/generators/applications/shattered_app/USAGE +10 -0
  10. data/lib/rails_generator/generators/applications/shattered_app/shattered_app_generator.rb +104 -0
  11. data/lib/rails_generator/generators/components/actor/actor_generator.rb +23 -0
  12. data/lib/rails_generator/generators/components/controller/USAGE +30 -0
  13. data/lib/rails_generator/generators/components/controller/controller_generator.rb +32 -0
  14. data/lib/rails_generator/generators/components/controller/templates/controller.rb +5 -0
  15. data/lib/rails_generator/generators/components/controller/templates/functional_test.rb +18 -0
  16. data/lib/rails_generator/generators/components/controller/templates/helper.rb +2 -0
  17. data/lib/rails_generator/generators/components/controller/templates/view.rhtml +2 -0
  18. data/lib/rails_generator/generators/components/model/USAGE +17 -0
  19. data/lib/rails_generator/generators/components/model/model_generator.rb +17 -0
  20. data/lib/rails_generator/generators/components/model/templates/fixtures.yml +5 -0
  21. data/lib/rails_generator/generators/components/model/templates/model.rb +2 -0
  22. data/lib/rails_generator/generators/components/model/templates/unit_test.rb +11 -0
  23. data/lib/rails_generator/generators/components/state/USAGE +30 -0
  24. data/lib/rails_generator/generators/components/state/state_generator.rb +19 -0
  25. data/lib/rails_generator/generators/components/state/templates/state.rb +4 -0
  26. data/lib/rails_generator/generators/components/view/USAGE +30 -0
  27. data/lib/rails_generator/generators/components/view/templates/material +4 -0
  28. data/lib/rails_generator/generators/components/view/templates/view.rb +3 -0
  29. data/lib/rails_generator/generators/components/view/view_generator.rb +18 -0
  30. data/lib/rails_generator/lookup.rb +206 -0
  31. data/lib/rails_generator/manifest.rb +53 -0
  32. data/lib/rails_generator/options.rb +135 -0
  33. data/lib/rails_generator/scripts.rb +83 -0
  34. data/lib/rails_generator/scripts/destroy.rb +7 -0
  35. data/lib/rails_generator/scripts/generate.rb +7 -0
  36. data/lib/rails_generator/scripts/update.rb +12 -0
  37. data/lib/rails_generator/simple_logger.rb +46 -0
  38. data/lib/rails_generator/spec.rb +44 -0
  39. data/lib/shatter.rb +11 -0
  40. data/lib/templates/MIT-LICENSE +20 -0
  41. data/lib/templates/README +35 -0
  42. data/lib/templates/Rakefile +15 -0
  43. data/lib/templates/configs/Mac/shattered.app/Contents/Info.plist +22 -0
  44. data/lib/templates/configs/Mac/shattered.app/Contents/MacOS/shattered_mac +0 -0
  45. data/lib/templates/configs/Mac/shattered.app/Contents/PkgInfo +1 -0
  46. data/lib/templates/configs/Mac/shattered.app/Contents/Resources/English.lproj/InfoPlist.strings +0 -0
  47. data/lib/templates/configs/Mac/shattered.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib +4 -0
  48. data/lib/templates/configs/Mac/shattered.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib +20 -0
  49. data/lib/templates/configs/Mac/shattered.app/Contents/Resources/English.lproj/MainMenu.nib/objects.nib +0 -0
  50. data/lib/templates/configs/Mac/shattered.app/Contents/Resources/rb_main.rb +3 -0
  51. data/lib/templates/configs/Mac/shattered.app/Contents/pbdevelopment.plist +8 -0
  52. data/lib/templates/configs/Mac/shattered.app/OgreLeaks.log +144 -0
  53. data/lib/templates/configs/Mac/shattered.app/OgreMemory.log +29 -0
  54. data/lib/templates/configs/Mac/shattered.app/config +1 -0
  55. data/lib/templates/configs/boot.rb +33 -0
  56. data/lib/templates/configs/empty.log +0 -0
  57. data/lib/templates/configs/ogre.cfg +4 -0
  58. data/lib/templates/configs/plugins.cfg +16 -0
  59. data/lib/templates/configs/plugins.darwin.cfg +13 -0
  60. data/lib/templates/configs/plugins.linux.cfg +16 -0
  61. data/lib/templates/configs/plugins.win32.cfg +9 -0
  62. data/lib/templates/configs/runner.rb +5 -0
  63. data/lib/templates/doc/README_FOR_APP +2 -0
  64. data/lib/templates/environments/environment.rb +7 -0
  65. data/lib/templates/media/basic.rmaterial +17 -0
  66. data/lib/templates/media/offset_map.rmaterial +117 -0
  67. metadata +194 -0
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/ruby18
2
+ irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
3
+
4
+ require 'optparse'
5
+ options = { :sandbox => false, :irb => irb }
6
+ OptionParser.new do |opt|
7
+ opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| }
8
+ opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |options[:irb]| }
9
+ opt.parse!(ARGV)
10
+ end
11
+
12
+ libs = " -r irb/completion"
13
+ libs << " -r #{File.dirname(__FILE__)}/../config/environment"
14
+ libs << " -r console_sandbox" if options[:sandbox]
15
+
16
+ if options[:sandbox]
17
+ puts "Loading #{ENV['RAILS_ENV']} environment in sandbox."
18
+ puts "Any modifications you make will be rolled back on exit."
19
+ else
20
+ puts "Loading #{ENV['RAILS_ENV']} environment."
21
+ end
22
+ exec "#{options[:irb]} #{libs} --prompt-mode simple"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby18
2
+ require File.dirname(__FILE__)+"/../config/boot"
3
+ require 'rails_generator'
4
+ require 'rails_generator/scripts/generate'
5
+
6
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
7
+ Rails::Generator::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/ruby18
2
+
3
+ if PLATFORM =~ /darwin/
4
+ path = File.dirname(__FILE__) + "/../config/Mac/"
5
+ puts `(cd #{path}shattered.app; ./Contents/MacOS/shattered_mac)`
6
+ else
7
+ require File.dirname(__FILE__)+'/runner'
8
+ start_game
9
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ #This file is adapted from the rails command.
3
+
4
+ begin
5
+ require 'active_support'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require_gem 'activesupport'
9
+ end
10
+
11
+ min_release = "1.8.2 (2004-12-25)"
12
+ ruby_release = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
13
+
14
+ if ruby_release < min_release
15
+ abort <<-end_message
16
+
17
+ Rails requires Ruby version #{min_release} or later.
18
+ You're running #{ruby_release}; please upgrade to continue.
19
+
20
+ end_message
21
+ end
22
+
23
+ Signal.trap("INT") { puts; exit }
24
+
25
+
26
+ require File.dirname(__FILE__) + '/../lib/rails_generator'
27
+ require 'rails_generator/scripts/generate'
28
+ Rails::Generator::Base.use_application_sources!
29
+ Rails::Generator::Scripts::Generate.new.run(ARGV, :generator => 'shattered_app')
@@ -0,0 +1,49 @@
1
+ require 'singleton'
2
+
3
+ begin
4
+ require 'shattered_controller'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'shattered_controller'
8
+ end
9
+ require 'shattered_view'
10
+ require 'shattered_model'
11
+
12
+ include ShatteredController
13
+ include ShatteredView
14
+ include ShatteredModel
15
+
16
+ module Shatter
17
+ #This class is loads the view, controller, and model. It then loads
18
+ #and starts the game based on these.
19
+ class GameLoader
20
+ include Singleton
21
+ attr_writer :environment
22
+ def require_all_files
23
+ Dir.foreach("#{SHATTERED_ROOT}/app/") do |type|
24
+ next if File.file?("#{SHATTERED_ROOT}/app/#{type}")
25
+ next if type =~ /view/ or type =~ /state/
26
+ Dir.foreach("#{SHATTERED_ROOT}/app/#{type}") do |file|
27
+ require "#{SHATTERED_ROOT}/app/#{type}/#{file}" if file =~ /\.rb$/
28
+ end
29
+ end
30
+ end
31
+ def run
32
+ view = ShatteredView::Runner.new @environment
33
+ view.add_to_environment @environment
34
+ control = ShatteredController::Runner.new @environment
35
+ ShatteredSupport::Configuration.environment=@environment
36
+
37
+ puts "require '#{SHATTERED_ROOT}/app/states/#{@environment[:start_state].to_s}_state'"
38
+ begin
39
+ eval "require '#{SHATTERED_ROOT}/app/states/#{@environment[:start_state].to_s}_state'"
40
+ rescue StandardError => output
41
+ puts output.message
42
+ end
43
+ puts "creating #{@environment[:start_state].to_s.camelize}State.new"
44
+
45
+ eval "#{@environment[:start_state].to_s.camelize}State.new"
46
+ control.start_game
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ #--
2
+ # Copyright (c) 2004 Jeremy Kemper
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ $:.unshift(File.dirname(__FILE__))
25
+ $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
26
+
27
+ begin
28
+ require 'active_support'
29
+ rescue LoadError
30
+ require 'rubygems'
31
+ require_gem 'activesupport'
32
+ end
33
+
34
+ require 'rails_generator/base'
35
+ require 'rails_generator/lookup'
36
+ require 'rails_generator/commands'
37
+
38
+ Rails::Generator::Base.send(:include, Rails::Generator::Lookup)
39
+ Rails::Generator::Base.send(:include, Rails::Generator::Commands)
40
+
41
+ # Set up a default logger for convenience.
42
+ require 'rails_generator/simple_logger'
43
+ Rails::Generator::Base.logger = Rails::Generator::SimpleLogger.new(STDOUT)
@@ -0,0 +1,203 @@
1
+ require File.dirname(__FILE__) + '/options'
2
+ require File.dirname(__FILE__) + '/manifest'
3
+ require File.dirname(__FILE__) + '/spec'
4
+
5
+ # Rails::Generator is a code generation platform tailored for the Rails
6
+ # web application framework. Generators are easily invoked within Rails
7
+ # applications to add and remove components such as models and controllers.
8
+ # New generators are easy to create and may be distributed as RubyGems or
9
+ # tarballs for inclusion system-wide, per-user, or per-application.
10
+ #
11
+ # Generators may subclass other generators to provide variations that
12
+ # require little or no new logic but replace the template files.
13
+ # The postback generator is an example: it subclasses the scaffold
14
+ # generator and just replaces the code templates with its own.
15
+ #
16
+ # Now go forth and multiply^Wgenerate.
17
+ module Rails
18
+ module Generator
19
+ class GeneratorError < StandardError; end
20
+ class UsageError < GeneratorError; end
21
+
22
+
23
+ # The base code generator is bare-bones. It sets up the source and
24
+ # destination paths and tells the logger whether to keep its trap shut.
25
+ # You're probably looking for NamedBase, a subclass meant for generating
26
+ # "named" components such as models, controllers, and mailers.
27
+ #
28
+ # Generators create a manifest of the actions they perform then hand
29
+ # the manifest to a command which replay the actions to do the heavy
30
+ # lifting. Create, destroy, and list commands are included. Since a
31
+ # single manifest may be used by any command, creating new generators is
32
+ # as simple as writing some code templates and declaring what you'd like
33
+ # to do with them.
34
+ #
35
+ # The manifest method must be implemented by subclasses, returning a
36
+ # Rails::Generator::Manifest. The record method is provided as a
37
+ # convenience for manifest creation. Example:
38
+ # class EliteGenerator < Rails::Generator::Base
39
+ # def manifest
40
+ # record do |m|
41
+ # m.do(some)
42
+ # m.things(in) { here }
43
+ # end
44
+ # end
45
+ # end
46
+ class Base
47
+ include Options
48
+
49
+ # Declare default options for the generator. These options
50
+ # are inherited to subclasses.
51
+ default_options :collision => :ask, :quiet => false
52
+
53
+ # A logger instance available everywhere in the generator.
54
+ cattr_accessor :logger
55
+
56
+ # Every generator that is dynamically looked up is tagged with a
57
+ # Spec describing where it was found.
58
+ class_inheritable_accessor :spec
59
+
60
+ attr_reader :source_root, :destination_root, :args
61
+
62
+ def initialize(runtime_args, runtime_options = {})
63
+ @args = runtime_args
64
+ parse!(@args, runtime_options)
65
+
66
+ # Derive source and destination paths.
67
+ @source_root = options[:source] || File.join(spec.path, 'templates')
68
+ if options[:destination]
69
+ @destination_root = options[:destination]
70
+ elsif defined? ::SHATTERED_ROOT
71
+ @destination_root = ::SHATTERED_ROOT
72
+ end
73
+
74
+ # Silence the logger if requested.
75
+ logger.quiet = options[:quiet]
76
+
77
+ # Raise usage error if help is requested.
78
+ usage if options[:help]
79
+ end
80
+
81
+ # Generators must provide a manifest. Use the record method to create
82
+ # a new manifest and record your generator's actions.
83
+ def manifest
84
+ raise NotImplementedError, "No manifest for '#{spec.name}' generator."
85
+ end
86
+
87
+ # Return the full path from the source root for the given path.
88
+ # Example for source_root = '/source':
89
+ # source_path('some/path.rb') == '/source/some/path.rb'
90
+ #
91
+ # The given path may include a colon ':' character to indicate that
92
+ # the file belongs to another generator. This notation allows any
93
+ # generator to borrow files from another. Example:
94
+ # source_path('model:fixture.yml') = '/model/source/path/fixture.yml'
95
+ def source_path(relative_source)
96
+ # Check whether we're referring to another generator's file.
97
+ name, path = relative_source.split(':', 2)
98
+
99
+ # If not, return the full path to our source file.
100
+ if path.nil?
101
+ File.join(source_root, name)
102
+
103
+ # Otherwise, ask our referral for the file.
104
+ else
105
+ # FIXME: this is broken, though almost always true. Others'
106
+ # source_root are not necessarily the templates dir.
107
+ File.join(self.class.lookup(name).path, 'templates', path)
108
+ end
109
+ end
110
+
111
+ # Return the full path from the destination root for the given path.
112
+ # Example for destination_root = '/dest':
113
+ # destination_path('some/path.rb') == '/dest/some/path.rb'
114
+ def destination_path(relative_destination)
115
+ File.join(destination_root, relative_destination)
116
+ end
117
+
118
+ protected
119
+ # Convenience method for generator subclasses to record a manifest.
120
+ def record
121
+ Rails::Generator::Manifest.new(self) { |m| yield m }
122
+ end
123
+
124
+ # Override with your own usage banner.
125
+ def banner
126
+ "Usage: #{$0} #{spec.name} [options]"
127
+ end
128
+
129
+ # Read USAGE from file in generator base path.
130
+ def usage_message
131
+ File.read(File.join(spec.path, 'USAGE')) rescue ''
132
+ end
133
+ end
134
+
135
+
136
+ # The base generator for named components: models, controllers, mailers,
137
+ # etc. The target name is taken as the first argument and inflected to
138
+ # singular, plural, class, file, and table forms for your convenience.
139
+ # The remaining arguments are aliased to actions for controller and
140
+ # mailer convenience.
141
+ #
142
+ # If no name is provided, the generator raises a usage error with content
143
+ # optionally read from the USAGE file in the generator's base path.
144
+ #
145
+ # See Rails::Generator::Base for a discussion of Manifests and Commands.
146
+ class NamedBase < Base
147
+ attr_reader :name, :class_name, :singular_name, :plural_name
148
+ attr_reader :class_path, :file_path, :class_nesting, :class_nesting_depth
149
+ alias_method :file_name, :singular_name
150
+ alias_method :table_name, :plural_name
151
+ alias_method :actions, :args
152
+
153
+ def initialize(runtime_args, runtime_options = {})
154
+ super
155
+
156
+ # Name argument is required.
157
+ usage if runtime_args.empty?
158
+
159
+ @args = runtime_args.dup
160
+ base_name = @args.shift
161
+ assign_names!(base_name)
162
+ end
163
+
164
+ protected
165
+ # Override with your own usage banner.
166
+ def banner
167
+ "Usage: #{$0} #{spec.name} #{spec.name.camelize}Name [options]"
168
+ end
169
+
170
+ private
171
+ def assign_names!(name)
172
+ @name = name
173
+ base_name, @class_path, @file_path, @class_nesting, @class_nesting_depth = extract_modules(@name)
174
+ @class_name_without_nesting, @singular_name, @plural_name = inflect_names(base_name)
175
+ if @class_nesting.empty?
176
+ @class_name = @class_name_without_nesting
177
+ else
178
+ @class_name = "#{@class_nesting}::#{@class_name_without_nesting}"
179
+ end
180
+ end
181
+
182
+ # Extract modules from filesystem-style or ruby-style path:
183
+ # good/fun/stuff
184
+ # Good::Fun::Stuff
185
+ # produce the same results.
186
+ def extract_modules(name)
187
+ modules = name.include?('/') ? name.split('/') : name.split('::')
188
+ name = modules.pop
189
+ path = modules.map { |m| m.underscore }
190
+ file_path = (path + [name.underscore]).join('/')
191
+ nesting = modules.map { |m| m.camelize }.join('::')
192
+ [name, path, file_path, nesting, modules.size]
193
+ end
194
+
195
+ def inflect_names(name)
196
+ camel = name.camelize
197
+ under = camel.underscore
198
+ plural = under.pluralize
199
+ [camel, under, plural]
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,445 @@
1
+ require 'delegate'
2
+ require 'optparse'
3
+ require 'fileutils'
4
+ require 'erb'
5
+
6
+ module Rails
7
+ module Generator
8
+ module Commands
9
+ # Here's a convenient way to get a handle on generator commands.
10
+ # Command.instance('destroy', my_generator) instantiates a Destroy
11
+ # delegate of my_generator ready to do your dirty work.
12
+ def self.instance(command, generator)
13
+ const_get(command.to_s.camelize).new(generator)
14
+ end
15
+
16
+ # Even more convenient access to commands. Include Commands in
17
+ # the generator Base class to get a nice #command instance method
18
+ # which returns a delegate for the requested command.
19
+ def self.append_features(base)
20
+ base.send(:define_method, :command) do |command|
21
+ Commands.instance(command, self)
22
+ end
23
+ end
24
+
25
+
26
+ # Generator commands delegate Rails::Generator::Base and implement
27
+ # a standard set of actions. Their behavior is defined by the way
28
+ # they respond to these actions: Create brings life; Destroy brings
29
+ # death; List passively observes.
30
+ #
31
+ # Commands are invoked by replaying (or rewinding) the generator's
32
+ # manifest of actions. See Rails::Generator::Manifest and
33
+ # Rails::Generator::Base#manifest method that generator subclasses
34
+ # are required to override.
35
+ #
36
+ # Commands allows generators to "plug in" invocation behavior, which
37
+ # corresponds to the GoF Strategy pattern.
38
+ class Base < DelegateClass(Rails::Generator::Base)
39
+ # Replay action manifest. RewindBase subclass rewinds manifest.
40
+ def invoke!
41
+ manifest.replay(self)
42
+ end
43
+
44
+ def dependency(generator_name, args, runtime_options = {})
45
+ logger.dependency(generator_name) do
46
+ self.class.new(instance(generator_name, args, full_options(runtime_options))).invoke!
47
+ end
48
+ end
49
+
50
+ # Does nothing for all commands except Create.
51
+ def class_collisions(*class_names)
52
+ end
53
+
54
+ # Does nothing for all commands except Create.
55
+ def readme(*args)
56
+ end
57
+
58
+ private
59
+ # Ask the user interactively whether to force collision.
60
+ def force_file_collision?(destination)
61
+ $stdout.print "overwrite #{destination}? [Ynaq] "
62
+ case $stdin.gets
63
+ when /a/i
64
+ $stdout.puts "forcing #{spec.name}"
65
+ options[:collision] = :force
66
+ when /q/i
67
+ $stdout.puts "aborting #{spec.name}"
68
+ raise SystemExit
69
+ when /n/i then :skip
70
+ else :force
71
+ end
72
+ rescue
73
+ retry
74
+ end
75
+
76
+ def render_template_part(template_options)
77
+ # Getting Sandbox to evaluate part template in it
78
+ part_binding = template_options[:sandbox].call.sandbox_binding
79
+ part_rel_path = template_options[:insert]
80
+ part_path = source_path(part_rel_path)
81
+
82
+ # Render inner template within Sandbox binding
83
+ rendered_part = ERB.new(File.readlines(part_path).join, nil, '-').result(part_binding)
84
+ begin_mark = template_part_mark(template_options[:begin_mark], template_options[:mark_id])
85
+ end_mark = template_part_mark(template_options[:end_mark], template_options[:mark_id])
86
+ begin_mark + rendered_part + end_mark
87
+ end
88
+
89
+ def template_part_mark(name, id)
90
+ "<!--[#{name}:#{id}]-->\n"
91
+ end
92
+ end
93
+
94
+ # Base class for commands which handle generator actions in reverse, such as Destroy.
95
+ class RewindBase < Base
96
+ # Rewind action manifest.
97
+ def invoke!
98
+ manifest.rewind(self)
99
+ end
100
+ end
101
+
102
+
103
+ # Create is the premier generator command. It copies files, creates
104
+ # directories, renders templates, and more.
105
+ class Create < Base
106
+
107
+ # Check whether the given class names are already taken by
108
+ # Ruby or Rails. In the future, expand to check other namespaces
109
+ # such as the rest of the user's app.
110
+ def class_collisions(*class_names)
111
+ class_names.flatten.each do |class_name|
112
+ # Convert to string to allow symbol arguments.
113
+ class_name = class_name.to_s
114
+
115
+ # Skip empty strings.
116
+ next if class_name.strip.empty?
117
+
118
+ # Split the class from its module nesting.
119
+ nesting = class_name.split('::')
120
+ name = nesting.pop
121
+
122
+ # Extract the last Module in the nesting.
123
+ last = nesting.inject(Object) { |last, nest|
124
+ break unless last.const_defined?(nest)
125
+ last.const_get(nest)
126
+ }
127
+
128
+ # If the last Module exists, check whether the given
129
+ # class exists and raise a collision if so.
130
+ if last and last.const_defined?(name.camelize)
131
+ raise_class_collision(class_name)
132
+ end
133
+ end
134
+ end
135
+
136
+ # Copy a file from source to destination with collision checking.
137
+ #
138
+ # The file_options hash accepts :chmod and :shebang options.
139
+ # :chmod sets the permissions of the destination file:
140
+ # file 'config/empty.log', 'log/test.log', :chmod => 0664
141
+ # :shebang sets the #!/usr/bin/ruby line for scripts
142
+ # file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby'
143
+ #
144
+ # Collisions are handled by checking whether the destination file
145
+ # exists and either skipping the file, forcing overwrite, or asking
146
+ # the user what to do.
147
+ def file(relative_source, relative_destination, file_options = {})
148
+ # Determine full paths for source and destination files.
149
+ source = source_path(relative_source)
150
+ destination = destination_path(relative_destination)
151
+
152
+ # Check for and resolve file collisions.
153
+ if File.exists?(destination)
154
+
155
+ # Make a choice whether to overwrite the file. :force and
156
+ # :skip already have their mind made up, but give :ask a shot.
157
+ choice = case options[:collision].to_sym #|| :ask
158
+ when :ask then force_file_collision?(relative_destination)
159
+ when :force then :force
160
+ when :skip then :skip
161
+ else raise "Invalid collision option: #{options[:collision].inspect}"
162
+ end
163
+
164
+ # Take action based on our choice. Bail out if we chose to
165
+ # skip the file; otherwise, log our transgression and continue.
166
+ case choice
167
+ when :force then logger.force(relative_destination)
168
+ when :skip then return(logger.skip(relative_destination))
169
+ else raise "Invalid collision choice: #{choice}.inspect"
170
+ end
171
+
172
+ # File doesn't exist so log its unbesmirched creation.
173
+ else
174
+ logger.create relative_destination
175
+ end
176
+
177
+ # If we're pretending, back off now.
178
+ return if options[:pretend]
179
+
180
+ # Write destination file with optional shebang. Yield for content
181
+ # if block given so templaters may render the source file. If a
182
+ # shebang is requested, replace the existing shebang or insert a
183
+ # new one.
184
+ File.open(destination, 'w') do |df|
185
+ File.open(source) do |sf|
186
+ if block_given?
187
+ df.write(yield(sf))
188
+ else
189
+ line = sf.gets
190
+ if file_options[:shebang]
191
+ df.puts("#!#{file_options[:shebang]}")
192
+ df.puts(line) if line !~ /^#!/
193
+ else
194
+ df.puts(line)
195
+ end
196
+ df.write(sf.read)
197
+ end
198
+ end
199
+ end
200
+
201
+ # Optionally change permissions.
202
+ if file_options[:chmod]
203
+ FileUtils.chmod(file_options[:chmod], destination)
204
+ end
205
+
206
+ # Optionally add file to subversion
207
+ system("svn add #{destination}") if options[:svn]
208
+ end
209
+
210
+ # Generate a file for a Rails application using an ERuby template.
211
+ # Looks up and evalutes a template by name and writes the result.
212
+ #
213
+ # The ERB template uses explicit trim mode to best control the
214
+ # proliferation of whitespace in generated code. <%- trims leading
215
+ # whitespace; -%> trims trailing whitespace including one newline.
216
+ #
217
+ # A hash of template options may be passed as the last argument.
218
+ # The options accepted by the file are accepted as well as :assigns,
219
+ # a hash of variable bindings. Example:
220
+ # template 'foo', 'bar', :assigns => { :action => 'view' }
221
+ #
222
+ # Template is implemented in terms of file. It calls file with a
223
+ # block which takes a file handle and returns its rendered contents.
224
+ def template(relative_source, relative_destination, template_options = {})
225
+ file(relative_source, relative_destination, template_options) do |file|
226
+ # Evaluate any assignments in a temporary, throwaway binding.
227
+ vars = template_options[:assigns] || {}
228
+ b = binding
229
+ vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
230
+
231
+ # Render the source file with the temporary binding.
232
+ ERB.new(file.read, nil, '-').result(b)
233
+ end
234
+ end
235
+
236
+ def complex_template(relative_source, relative_destination, template_options = {})
237
+ options = template_options.dup
238
+ options[:assigns] ||= {}
239
+ options[:assigns]['template_for_inclusion'] = render_template_part(template_options)
240
+ template(relative_source, relative_destination, options)
241
+ end
242
+
243
+ # Create a directory including any missing parent directories.
244
+ # Always directories which exist.
245
+ def directory(relative_path)
246
+ path = destination_path(relative_path)
247
+ if File.exists?(path)
248
+ logger.exists relative_path
249
+ else
250
+ logger.create relative_path
251
+ FileUtils.mkdir_p(path) unless options[:pretend]
252
+
253
+ # Optionally add file to subversion
254
+ system("svn add #{path}") if options[:svn]
255
+ end
256
+ end
257
+
258
+ # Display a README.
259
+ def readme(*relative_sources)
260
+ relative_sources.flatten.each do |relative_source|
261
+ logger.readme relative_source
262
+ puts File.read(source_path(relative_source)) unless options[:pretend]
263
+ end
264
+ end
265
+
266
+ private
267
+ # Raise a usage error with an informative WordNet suggestion.
268
+ # Thanks to Florian Gross (flgr).
269
+ def raise_class_collision(class_name)
270
+ message = <<end_message
271
+ The name '#{class_name}' is reserved by Ruby on Rails.
272
+ Please choose an alternative and run this generator again.
273
+ end_message
274
+ if suggest = find_synonyms(class_name)
275
+ message << "\n Suggestions: \n\n"
276
+ message << suggest.join("\n")
277
+ end
278
+ raise UsageError, message
279
+ end
280
+
281
+ SYNONYM_LOOKUP_URI = "http://wordnet.princeton.edu/cgi-bin/webwn2.0?stage=2&word=%s&posnumber=1&searchtypenumber=2&senses=&showglosses=1"
282
+
283
+ # Look up synonyms on WordNet. Thanks to Florian Gross (flgr).
284
+ def find_synonyms(word)
285
+ require 'open-uri'
286
+ require 'timeout'
287
+ timeout(5) do
288
+ open(SYNONYM_LOOKUP_URI % word) do |stream|
289
+ data = stream.read.gsub("&nbsp;", " ").gsub("<BR>", "")
290
+ data.scan(/^Sense \d+\n.+?\n\n/m)
291
+ end
292
+ end
293
+ rescue Exception
294
+ return nil
295
+ end
296
+ end
297
+
298
+
299
+ # Undo the actions performed by a generator. Rewind the action
300
+ # manifest and attempt to completely erase the results of each action.
301
+ class Destroy < RewindBase
302
+ # Remove a file if it exists and is a file.
303
+ def file(relative_source, relative_destination, file_options = {})
304
+ destination = destination_path(relative_destination)
305
+ if File.exists?(destination)
306
+ logger.rm relative_destination
307
+ unless options[:pretend]
308
+ if options[:svn]
309
+ # If the file has been marked to be added
310
+ # but has not yet been checked in, revert and delete
311
+ if options[:svn][relative_destination]
312
+ system("svn revert #{destination}")
313
+ FileUtils.rm(destination)
314
+ else
315
+ # If the directory is not in the status list, it
316
+ # has no modifications so we can simply remove it
317
+ system("svn rm #{destination}")
318
+ end
319
+ else
320
+ FileUtils.rm(destination)
321
+ end
322
+ end
323
+ else
324
+ logger.missing relative_destination
325
+ return
326
+ end
327
+ end
328
+
329
+ # Templates are deleted just like files and the actions take the
330
+ # same parameters, so simply alias the file method.
331
+ alias_method :template, :file
332
+
333
+ # Remove each directory in the given path from right to left.
334
+ # Remove each subdirectory if it exists and is a directory.
335
+ def directory(relative_path)
336
+ parts = relative_path.split('/')
337
+ until parts.empty?
338
+ partial = File.join(parts)
339
+ path = destination_path(partial)
340
+ if File.exists?(path)
341
+ if Dir[File.join(path, '*')].empty?
342
+ logger.rmdir partial
343
+ unless options[:pretend]
344
+ if options[:svn]
345
+ # If the directory has been marked to be added
346
+ # but has not yet been checked in, revert and delete
347
+ if options[:svn][relative_path]
348
+ system("svn revert #{path}")
349
+ FileUtils.rmdir(path)
350
+ else
351
+ # If the directory is not in the status list, it
352
+ # has no modifications so we can simply remove it
353
+ system("svn rm #{path}")
354
+ end
355
+ else
356
+ FileUtils.rmdir(path)
357
+ end
358
+ end
359
+ else
360
+ logger.notempty partial
361
+ end
362
+ else
363
+ logger.missing partial
364
+ end
365
+ parts.pop
366
+ end
367
+ end
368
+
369
+ def complex_template(*args)
370
+ # nothing should be done here
371
+ end
372
+ end
373
+
374
+
375
+ # List a generator's action manifest.
376
+ class List < Base
377
+ def dependency(generator_name, args, options = {})
378
+ logger.dependency "#{generator_name}(#{args.join(', ')}, #{options.inspect})"
379
+ end
380
+
381
+ def class_collisions(*class_names)
382
+ logger.class_collisions class_names.join(', ')
383
+ end
384
+
385
+ def file(relative_source, relative_destination, options = {})
386
+ logger.file relative_destination
387
+ end
388
+
389
+ def template(relative_source, relative_destination, options = {})
390
+ logger.template relative_destination
391
+ end
392
+
393
+ def complex_template(relative_source, relative_destination, options = {})
394
+ logger.template "#{options[:insert]} inside #{relative_destination}"
395
+ end
396
+
397
+ def directory(relative_path)
398
+ logger.directory "#{destination_path(relative_path)}/"
399
+ end
400
+
401
+ def readme(*args)
402
+ logger.readme args.join(', ')
403
+ end
404
+ end
405
+
406
+ # Update generator's action manifest.
407
+ class Update < Create
408
+ def file(relative_source, relative_destination, options = {})
409
+ # logger.file relative_destination
410
+ end
411
+
412
+ def template(relative_source, relative_destination, options = {})
413
+ # logger.template relative_destination
414
+ end
415
+
416
+ def complex_template(relative_source, relative_destination, template_options = {})
417
+
418
+ begin
419
+ dest_file = destination_path(relative_destination)
420
+ source_to_update = File.readlines(dest_file).join
421
+ rescue Errno::ENOENT
422
+ logger.missing relative_destination
423
+ return
424
+ end
425
+
426
+ logger.refreshing "#{template_options[:insert].gsub(/\.rhtml/,'')} inside #{relative_destination}"
427
+
428
+ begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id]))
429
+ end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id]))
430
+
431
+ # Refreshing inner part of the template with freshly rendered part.
432
+ rendered_part = render_template_part(template_options)
433
+ source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part)
434
+
435
+ File.open(dest_file, 'w') { |file| file.write(source_to_update) }
436
+ end
437
+
438
+ def directory(relative_path)
439
+ # logger.directory "#{destination_path(relative_path)}/"
440
+ end
441
+ end
442
+
443
+ end
444
+ end
445
+ end