├── .autotest ├── .gitignore ├── .gitmodules ├── .travis.yml ├── Gemfile ├── History.txt ├── LICENSE ├── License.txt ├── Manifest.txt ├── README.rdoc ├── Rakefile ├── Todo.txt ├── app_generators └── ruby_app │ ├── USAGE │ ├── ruby_app_generator.rb │ └── templates │ ├── README.txt │ ├── Rakefile │ ├── configs │ └── empty_log │ ├── lib │ └── module.rb │ └── test │ └── test_helper.rb.erb ├── bin ├── install_rubigen_scripts ├── rubigen └── ruby_app ├── config └── website.yml ├── features ├── help.feature ├── rubigen_cli.feature ├── step_definitions │ └── common_steps.rb └── support │ ├── common.rb │ ├── env.rb │ └── matchers.rb ├── generators └── install_rubigen_scripts │ ├── install_rubigen_scripts_generator.rb │ └── templates │ └── script │ ├── destroy │ ├── generate │ └── win_script.cmd ├── lib ├── rubigen.rb └── rubigen │ ├── base.rb │ ├── cli.rb │ ├── commands.rb │ ├── generated_attribute.rb │ ├── helpers │ └── generator_test_helper.rb │ ├── lookup.rb │ ├── manifest.rb │ ├── options.rb │ ├── scripts.rb │ ├── scripts │ ├── destroy.rb │ ├── generate.rb │ └── update.rb │ ├── simple_logger.rb │ ├── spec.rb │ └── version.rb ├── rubigen.gemspec ├── rubygems_generators ├── application_generator │ ├── USAGE │ ├── application_generator_generator.rb │ └── templates │ │ ├── bin │ │ ├── generator.rb │ │ ├── readme │ │ ├── test.rb │ │ ├── test_generator_helper.rb │ │ └── usage └── component_generator │ ├── USAGE │ ├── component_generator_generator.rb │ └── templates │ ├── generator.rb │ ├── rails_generator.rb │ ├── readme │ ├── test.rb │ ├── test_generator_helper.rb │ └── usage ├── script ├── console ├── destroy ├── generate └── txt2html ├── test ├── test_application_generator_generator.rb ├── test_component_generator_generator.rb ├── test_generate_builtin_application.rb ├── test_generate_builtin_test_unit.rb ├── test_generator_helper.rb ├── test_helper.rb ├── test_install_rubigen_scripts_generator.rb ├── test_lookup.rb └── test_rubigen_cli.rb └── test_unit_generators └── test_unit ├── USAGE ├── templates └── test.rb └── test_unit_generator.rb /.autotest: -------------------------------------------------------------------------------- 1 | Autotest.add_hook :initialize do |at| 2 | at.clear_mappings 3 | 4 | at.add_mapping(%r%^test/test_.*.rb$%) do |filename, _| 5 | filename 6 | end 7 | at.add_mapping(/lib\/(.*)\.rb/) do |_, m| 8 | ["test/test_#{m[1]}.rb"] 9 | end 10 | # at.add_mapping(/.*_?generators\/(.*)\/(.*)_generator\.rb/) do |_, m| 11 | # ["test/test_#{m[1]}.rb"] 12 | # end 13 | end 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "website"] 2 | path = website 3 | url = git@github.com:drnic/rubigen.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.8.7 3 | - 1.9.3 4 | - rbx 5 | notifications: 6 | recipients: 7 | - drnicwilliams@gmail.com 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 1.5.8 2012-03-23 2 | * Restrict activesupport < 3.2 3 | * Change from hoe to bundler build tools 4 | 5 | == 1.5.6 2011-02-01 6 | * Allow activesupport 3.0.0+ 7 | 8 | == 1.5.5 2010-05-25 9 | 10 | * Allow activesupport 2.3.5+ 11 | 12 | == 1.5.4 2010-02-16 13 | 14 | * mocha, cucumber and shoulda are development dependencies (fixes requirement for thoughtbot-shoulda too) 15 | 16 | == 1.5.3 2010-02-11 17 | 18 | * Forced rubigen to use activesupport 2.3.5 (as the last < 3.0 version). It will upgrade to use 3.0.0 when it is released. 19 | 20 | 21 | == 1.5.2 2009-01-12 22 | 23 | * Trying to remove a circular rubigen->newgem->rubigen dependency issue 24 | 25 | == 1.5.1 2008-12-29 26 | 27 | * Return #write_manifest which was accidently removed 28 | * Moved website into gh-pages branch; thus moving homepage from http://rubigen.rubforge.org -> 29 | http://drnic.github.com/rubigen 30 | 31 | == 1.5.0 2008-12-27 32 | 33 | * Preparation for integration back into Rails 34 | * --git/-g option for generated files to be added via git commands 35 | * after_generate hook for generators (used as rails' templates mechanism) 36 | * bumped activesupport requirement to 2.2.2 37 | * upgraded internal files to support latest newgem (removed config/ folder, moved config to Rakefile) 38 | * support for RAILS_ROOT as a destination root 39 | * removed references to Merb which now has its own generator system; can access any merb 40 | generators via `rubigen` helper app 41 | * RubiGen::Base.active can be changed to a RubiGen::Base subclass that will be used 42 | for lookups (e.g. RubiGen::Base.active = Rails::Generator::Base) 43 | 44 | == 1.4.0 2008-12-11 45 | 46 | * rubigen tests now pass against ruby 1.9.0 and ruby 1.9.1(prerelease 2) 47 | * puts are sent to an explicit #stdout which can be STDOUT or a StringIO passed from tests; test output is now clean! 48 | 49 | == 1.3.4 2008-11-12 50 | 51 | * ruby_app: fixed reference to non-existent version.rb [jperkins] 52 | 53 | == 1.3.3 2008-10-21 54 | 55 | * prepend_sources correctly places arguments at start of sources list 56 | * removed lib/rubigen/version.rb; RubiGen::VERSION is in lib/rubigen.rb now; there is no RubiGen::VERSION::STRING 57 | 58 | == 1.3.2 2008-05-19 59 | 60 | * app_gen/bin - includes #!/usr/bin/env ruby 61 | * added examples of file_copy_each and template_copy_each to generators 62 | 63 | == 1.3.1 2008-04-25 64 | 65 | * Fixed the require statements in generated tests for rails/merb generators 66 | * Rails::Generator::Base is a valid generator superclass 67 | * Rails generators have alternate template (based on standard NamedBase) 68 | * test helpers define RAILS_ROOT for rails generators 69 | 70 | == 1.3.0 2008-04-25 71 | 72 | * component_generator: specific subclasses for rails + merb generators, 73 | not the generic RubiGen::Base 74 | 75 | == 1.2.3 2008-02-22 76 | 77 | * Remove -wKU flags from install_rubigen_scripts + ruby_app apps 78 | 79 | == 1.2.2 2008-02-20 80 | 81 | * Removing one File.expand_path 82 | 83 | == 1.2.1 2008-02-19 84 | 85 | * Fixed rake post_news/email output bug ("\\n\\n") 86 | * removed bad \" from generator banner strings 87 | * destination_path/root have File.expand_path applied to them to ensure 88 | base_name gives useful term 89 | * generate/destroy set APP_ROOT with expand_path too 90 | 91 | == 1.2.0 2008-02-14 92 | 93 | * application_generator's bin now looks for generator within local source, not 94 | within an install gem - so the bin/foo can be run in development without 95 | requiring installation. 96 | * New collision option *i* - ignore all collisions; works like *a* which forces 97 | all collisions to overwrite existing files; i skips all collisions. 98 | * Supported test scope for install_rubigen_scripts: javascript_test 99 | 100 | == 1.1.1 2007-11-08 101 | 102 | * active_support is now a dependency of this gem on install (it was being used 103 | but wasn't a dependency) 104 | 105 | == 1.1.0 2007-11-05 106 | 107 | * Promoted 'application_generator' and 'component_generator' to Rubigen 108 | from NewGem project (they are available even if newgem not installed) 109 | * install_rubigen_scripts default test fixed by mapping scopes to strings 110 | * version.js uploaded to website 111 | 112 | == 1.0.8 2007-11-03 113 | 114 | * install_rubigen_scripts adds 'test_unit' to scopes unless a test-related scope is added 115 | * remove examples directory because it's redundant. See rails gem. [Winston Tsang] 116 | * Add missing template empty_log. [Winston Tsang] 117 | * Moved test_unit generator to test_unit scope (instead of global scope) 118 | * generator_full_paths now returns the sources in reverse order [thx hassox] 119 | 120 | == 1.0.7 2007-11-02 121 | 122 | * Moved test_unit generator to test_unit scope (instead of global scope) 123 | 124 | == 1.0.6 2007-10-18 125 | 126 | * Templates can end with .erb (e.g. model.rb.erb) and template_copy_each will remove the .erb extension (create model.rb) 127 | 128 | == 1.0.5 2007-10-08 129 | 130 | * Added Jeremy Kemper to license/copyright/gem author list. 131 | 132 | == 1.0.4 2007-09-07 133 | 134 | * New manifest actions: 135 | * m.folder - copies over all files in folder into target path 136 | * helper run_generator returns generator instance (so you can assert instance variables/methods etc) 137 | * base_name method for generators - returns basename of destination_root, unless its trunk/tags/branches 138 | 139 | == 1.0.3 2007-08-22 140 | 141 | * /generators folder is automatically picked up by all component/app generators [bug fix] 142 | 143 | == 1.0.2 2007-08-22 144 | 145 | * Renamed rubigen_install_script -> install_rubigen_script 146 | * install_rubigen_script is now a component generator so can be reused by other generators to create script/generate 147 | * Added "Thanks to Jeremy Kemper" into website 148 | * Cleaned up USAGE for test_unit 149 | * If a generator does not have a USAGE file, then it is invisible from lists of available generators. 150 | This will be the mechanism for having hidden generators (e.g. install_rubigen_scripts) 151 | 152 | == 1.0.1 2007-08-20 153 | 154 | * Moved builtin generators into root folders, away from lib folder, to protected them 155 | from ri/rdocs processes (which didn't like ERb <%= %> inside Ruby files) 156 | * New app: rubigen_install_script 157 | * adds script/generate and script/destroy to current folder 158 | 159 | == 1.0.0 2007-08-17 160 | * 1 major enhancement: 161 | * Initial release 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dr Nic Williams 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-7 Jeremy Kemper (original code from Ruby on Rails) 2 | Copyright (c) 2007 Dr Nic Williams (extraction into RubiGen) 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | The above copyright notice and this permission notice shall be 11 | included in all copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | .autotest 2 | History.txt 3 | License.txt 4 | Gemfile 5 | Manifest.txt 6 | README.rdoc 7 | Rakefile 8 | Todo.txt 9 | app_generators/ruby_app/USAGE 10 | app_generators/ruby_app/ruby_app_generator.rb 11 | app_generators/ruby_app/templates/README.txt 12 | app_generators/ruby_app/templates/Rakefile 13 | app_generators/ruby_app/templates/lib/module.rb 14 | app_generators/ruby_app/templates/test/test_helper.rb.erb 15 | bin/install_rubigen_scripts 16 | bin/rubigen 17 | bin/ruby_app 18 | config/website.yml 19 | features/development.feature 20 | features/help.feature 21 | features/rubigen_cli.feature 22 | features/step_definitions/common_steps.rb 23 | features/support/common.rb 24 | features/support/env.rb 25 | features/support/matchers.rb 26 | generators/install_rubigen_scripts/install_rubigen_scripts_generator.rb 27 | generators/install_rubigen_scripts/templates/script/destroy 28 | generators/install_rubigen_scripts/templates/script/generate 29 | generators/install_rubigen_scripts/templates/script/win_script.cmd 30 | lib/rubigen.rb 31 | lib/rubigen/base.rb 32 | lib/rubigen/cli.rb 33 | lib/rubigen/commands.rb 34 | lib/rubigen/generated_attribute.rb 35 | lib/rubigen/helpers/generator_test_helper.rb 36 | lib/rubigen/lookup.rb 37 | lib/rubigen/manifest.rb 38 | lib/rubigen/options.rb 39 | lib/rubigen/scripts.rb 40 | lib/rubigen/scripts/destroy.rb 41 | lib/rubigen/scripts/generate.rb 42 | lib/rubigen/scripts/update.rb 43 | lib/rubigen/simple_logger.rb 44 | lib/rubigen/spec.rb 45 | rubigen.gemspec 46 | rubygems_generators/application_generator/USAGE 47 | rubygems_generators/application_generator/application_generator_generator.rb 48 | rubygems_generators/application_generator/templates/bin 49 | rubygems_generators/application_generator/templates/generator.rb 50 | rubygems_generators/application_generator/templates/readme 51 | rubygems_generators/application_generator/templates/test.rb 52 | rubygems_generators/application_generator/templates/test_generator_helper.rb 53 | rubygems_generators/application_generator/templates/usage 54 | rubygems_generators/component_generator/USAGE 55 | rubygems_generators/component_generator/component_generator_generator.rb 56 | rubygems_generators/component_generator/templates/generator.rb 57 | rubygems_generators/component_generator/templates/rails_generator.rb 58 | rubygems_generators/component_generator/templates/readme 59 | rubygems_generators/component_generator/templates/test.rb 60 | rubygems_generators/component_generator/templates/test_generator_helper.rb 61 | rubygems_generators/component_generator/templates/usage 62 | script/console 63 | script/destroy 64 | script/generate 65 | script/txt2html 66 | test/test_application_generator_generator.rb 67 | test/test_component_generator_generator.rb 68 | test/test_generate_builtin_application.rb 69 | test/test_generate_builtin_test_unit.rb 70 | test/test_generator_helper.rb 71 | test/test_helper.rb 72 | test/test_install_rubigen_scripts_generator.rb 73 | test/test_lookup.rb 74 | test/test_rubigen_cli.rb 75 | test_unit_generators/test_unit/USAGE 76 | test_unit_generators/test_unit/templates/test.rb 77 | test_unit_generators/test_unit/test_unit_generator.rb 78 | website/index.html 79 | website/index.txt 80 | website/javascripts/rounded_corners_lite.inc.js 81 | website/stylesheets/screen.css 82 | website/template.js 83 | website/template.rhtml 84 | website/version-raw.js 85 | website/version-raw.txt 86 | website/version.js 87 | website/version.txt 88 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RubiGen - Ruby Generator Framework 2 | 3 | * http://drnic.github.com/rubigen 4 | 5 | == DESCRIPTION: 6 | 7 | A framework to allow Ruby applications to generate file/folder stubs 8 | (like the `rails` command does for Ruby on Rails, and the 'script/generate' 9 | command within a Rails application during development). 10 | 11 | == Background 12 | 13 | RubiGen is originally extracted from Ruby on Rails (specifically the rails_generator 14 | from its railties gem). 15 | 16 | The rails_generator was hardcoded with Rails-specific dependencies (`RAILS_ROOT`), 17 | Rails generators ('app' = Rails application; 'model' = Rails model+tests+migration), 18 | and generally assumed it was the only generator framework within the Ruby world (it was). 19 | So, any RubyGem whose name ended with '_generator' was assumed to be a generator for 20 | a Rails application. 21 | 22 | But if you are developing an Adhearsion application, then you may want a different set of generators. 23 | If you are developing a RubyGem, then you will want a different set of generators. 24 | 25 | RubiGen exists to give different development environments their own generator framework. 26 | 27 | === Thanks go to... 28 | 29 | Jeremy Kemper wrote the original Rails Generator, which is 95% of the basis of RubiGen. He's awesome. 30 | 31 | == Installation 32 | 33 | RubiGen is only required at development time, and normally isn't required at deployment time 34 | (unless your application uses it to generate files at runtime). 35 | 36 | On your development machine: 37 | 38 | sudo gem install rubigen 39 | 40 | == Usage 41 | 42 | RubiGen comes with a stand-alone executable to allow you to invoke generators: 43 | 44 | For example, to run the rails' `model` generator: 45 | 46 | rubigen rails model Person name:string 47 | 48 | would replace the normal 49 | 50 | script/generate model Person name:string 51 | 52 | RubiGen has been traditionally integrated into another project, such as `rails`, `newgem` or `camping`, 53 | rather than be used on its own. 54 | 55 | These frameworks might use RubiGen for two reasons: 56 | 57 | 1. To generate an initial stub for developers, e.g. `rails` generated a stub to write a Rails application. 58 | `newgem` generates a stub to write a RubyGem. 59 | BTW - RubiGen has a builtin application `ruby_app` which generates a bare-bones Ruby application 60 | stub (lib, test, and script folders, plus a Rakefile, and a script/generate script) 61 | 2. To generate components within their development areas, e.g. Rails had its `script/generate` 62 | script within each Rails application, which hooked back into the rails_generator to lookup 63 | and execute generators. 64 | 65 | So, there are two steps to integrating RubiGen into your framework: 66 | 67 | 1. Use it to generate an initial stub for the developers of your framework. This would create the folders 68 | (`lib/app`, `test`, `script`, `doc`, `log`, etc) and starting files (`Rakefile`, 69 | `README.txt`, `test/test_helper.rb` etc). Importantly, it would generate a `script/generate` file. 70 | The `script/generate` file (example below) will allow developers of your framework to 71 | generate components/extensions within the framework. 72 | RubiGen allows you to restrict which generators are available. For example, within 73 | RubyGem development environment (as generated by `newgem`), the `script/generator` 74 | only shows `rubygem`-related generators. Rails could restrict `script/generator` 75 | to only show Rails related generators 76 | 2. Your framework RubyGem (e.g. `newgem` or `rails`) needs to add `rubigen` as a 77 | dependency, so that users of your RubyGem can access the generator framework. 78 | 79 | = Creating generators 80 | 81 | There are two types of generators: 82 | 83 | 1. Application Generators - used by developers of your framework to get started. 84 | Generally, you will create one Application Generator for your framework. 85 | It generates a base stub (such as the `rails` stub for new Rails applications) 86 | for your framework users. 87 | 2. Component Generators - used by developers to extend their application. 88 | You may include 1+ built-in generators with your framework. 89 | Developers can also write generators for your framework, and like Rails' generator 90 | install them in various places and have access to their via RubiGen. 91 | 92 | == Creating an Application Generator for your Framework 93 | 94 | Without RubiGen, to give your users a head start and create a stub for them, you will 95 | copiously use `mkdir_p` and `File.open`. Your script will either be primitive (only 96 | create the bare folders and very few files) or it will be very long and unreadable 97 | (ok, perhaps I'm just talking about the `newgem` script, which I am dubiously responsible 98 | for... :P). 99 | 100 | With RubiGen, you can create stubs using powerful, yet simple, syntax. Templates are 101 | extracted into a `templates` folder, and activating the generator from a script requires 102 | only a few lines of code. 103 | 104 | These are the `newgem` files related to its Application Generator. 105 | 106 | bin/ 107 | bin/newgem # Application Generator script; Usage: newgem gemname [options] 108 | app_generators/ 109 | app_generators/newgem/ 110 | app_generators/newgem/newgem_generator.rb 111 | app_generators/newgem/USAGE 112 | app_generators/newgem/templates/ 113 | app_generators/newgem/templates/app.rb 114 | app_generators/newgem/templates/History.txt 115 | app_generators/newgem/templates/... lots and lots of templates 116 | 117 | The `bin/newgem` script is very simple, and looks like: 118 | 119 | require 'rubygems' 120 | require 'rubigen' 121 | 122 | if %w(-v --version).include? ARGV.first 123 | require 'newgem/version' 124 | puts "#{File.basename($0)} #{Newgem::VERSION}" 125 | exit(0) 126 | end 127 | 128 | require 'rubigen/scripts/generate' 129 | RubiGen::Base.use_application_sources! 130 | RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'newgem') 131 | 132 | You can copy and paste this for your own generator script, and place it in your RubyGem's `bin` folder. 133 | Change `newgem` to your RubyGem's name in the script above (and in all the folders listed above too) 134 | 135 | NOTE: If you leave `newgem` there, then it will execute the `newgem_generator.rb` generator; 136 | as the generators are loaded from all RubyGem's having `/app_generators` folders. 137 | 138 | So, for your RubyGem, you need to keep the `/app_generators` folder (as you are creating an 139 | Application Generator, not a Component Generator), but change `newgem` to `your gem name` in 140 | all the subfolders and files. ESPECIALLY `newgem_generator.rb` -> `yourgem_generator.rb`, 141 | as this is how the generator is discovered (via `RubiGen::Base.use_application_sources!`). 142 | 143 | All the generator work is performed within `yourgem_generator.rb`. A stub for it will be: 144 | 145 | require 'rbconfig' 146 | 147 | class YourgemGenerator < RubiGen::Base 148 | DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], 149 | Config::CONFIG['ruby_install_name']) 150 | 151 | default_options :shebang => DEFAULT_SHEBANG, 152 | :an_option => 'some_default' 153 | 154 | attr_reader :app_name, :module_name 155 | 156 | def initialize(runtime_args, runtime_options = {}) 157 | super 158 | usage if args.empty? 159 | @destination_root = args.shift 160 | @app_name = File.basename(File.expand_path(@destination_root)) 161 | @module_name = app_name.camelize 162 | extract_options 163 | end 164 | 165 | def manifest 166 | # Use /usr/bin/env if no special shebang was specified 167 | script_options = { :chmod => 0755, :shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang] } 168 | windows = (RUBY_PLATFORM =~ /dos|win32|cygwin/i) || (RUBY_PLATFORM =~ /(:?mswin|mingw)/) 169 | 170 | record do |m| 171 | # Root directory and all subdirectories. 172 | m.directory '' 173 | BASEDIRS.each { |path| m.directory path } 174 | 175 | # Root 176 | m.template_copy_each %w( Rakefile ) 177 | m.file_copy_each %w( README.txt ) 178 | 179 | # Test helper 180 | m.template "test_helper.rb", "test/test_helper.rb" 181 | 182 | # Scripts 183 | %w( generate ).each do |file| 184 | m.template "script/#{file}", "script/#{file}", script_options 185 | m.template "script/win_script.cmd", "script/#{file}.cmd", 186 | :assigns => { :filename => file } if windows 187 | end 188 | 189 | end 190 | end 191 | 192 | protected 193 | def banner 194 | <<-EOS 195 | Create a stub for #{File.basename $0} to get started. 196 | 197 | Usage: #{File.basename $0} /path/to/your/app [options]" 198 | EOS 199 | end 200 | 201 | def add_options!(opts) 202 | opts.separator '' 203 | opts.separator "#{File.basename $0} options:" 204 | opts.on("-v", "--version", "Show the #{File.basename($0)} version number and quit.") 205 | end 206 | 207 | # Installation skeleton. Intermediate directories are automatically 208 | # created so don't sweat their absence here. 209 | BASEDIRS = %w( 210 | doc 211 | lib 212 | log 213 | script 214 | test 215 | tmp 216 | ) 217 | end 218 | 219 | Easy peasy. 220 | 221 | == Creating a Component Generator for your Framework 222 | 223 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require "rake/testtask" 4 | 5 | require './lib/rubigen' 6 | 7 | Rake::TestTask.new do |t| 8 | t.libs << "test" 9 | t.test_files = FileList['test/test*.rb'] 10 | t.verbose = true 11 | end 12 | 13 | 14 | namespace :cucumber do 15 | require 'cucumber/rake/task' 16 | Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t| 17 | t.cucumber_opts = "--tags @wip" 18 | end 19 | Cucumber::Rake::Task.new(:ok, 'Run features that should be working') do |t| 20 | t.cucumber_opts = "--tags ~@wip" 21 | end 22 | task :all => [:ok, :wip] 23 | end 24 | 25 | desc "Go to TravisCI status page" 26 | task :travis do 27 | require "launchy" 28 | Launchy.open("http://travis-ci.org/#!/drnic/rubigen") 29 | end 30 | desc 'Alias for cucumber:ok' 31 | task :cucumber => 'cucumber:ok' 32 | 33 | task :default => ["test", "cucumber"] 34 | -------------------------------------------------------------------------------- /Todo.txt: -------------------------------------------------------------------------------- 1 | = TODO 2 | 3 | * default #manifest to clone over any folders + files + apply erb to any *.erb files 4 | * rspec examples generated for application_generator and component_generator as per newgem 5 | * Labels for displays based on scope (common, rails, newgem), not rubygems, builtin, etc. Perhaps use one PathSource per scope. 6 | See RubiGen::Scripts::Base#usage_message 7 | * Local generators (~/.rubigen) can be scoped (~/.rubigen/rails_generators) 8 | -------------------------------------------------------------------------------- /app_generators/ruby_app/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | The 'ruby_app' command creates a new Ruby application with a default 3 | directory structure and configuration at the path you specify. 4 | 5 | This is based on Jay Fields' article - 6 | 7 | Example: 8 | ruby_app ~/Code/Ruby/myapp 9 | 10 | This generates a skeletal Ruby application installation in ~/Code/Ruby/myapp. 11 | -------------------------------------------------------------------------------- /app_generators/ruby_app/ruby_app_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | 3 | class RubyAppGenerator < RubiGen::Base 4 | DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], 5 | Config::CONFIG['ruby_install_name']) 6 | 7 | default_options :shebang => DEFAULT_SHEBANG 8 | 9 | attr_accessor :app_name 10 | attr_accessor :module_name 11 | 12 | def initialize(runtime_args, runtime_options = {}) 13 | super 14 | usage if args.empty? 15 | @destination_root = args.shift 16 | self.app_name = File.basename(File.expand_path(@destination_root)) 17 | self.module_name = app_name.camelize 18 | end 19 | 20 | def manifest 21 | # Use /usr/bin/env if no special shebang was specified 22 | script_options = { :chmod => 0755, :shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang] } 23 | windows = (RUBY_PLATFORM =~ /dos|win32|cygwin/i) || (RUBY_PLATFORM =~ /(:?mswin|mingw)/) 24 | 25 | record do |m| 26 | # Root directory and all subdirectories. 27 | m.directory '' 28 | BASEDIRS.each { |path| m.directory path } 29 | 30 | # Root 31 | # m.file "fresh_rakefile", "Rakefile" 32 | # m.file "README.txt", "README.txt" 33 | m.folder "" 34 | 35 | # Default module for app 36 | m.template "lib/module.rb", "lib/#{app_name}.rb" 37 | 38 | # Test helper 39 | m.template_copy_each %w(test_helper.rb.erb), "test" 40 | 41 | %w(debug).each { |file| 42 | m.file "configs/empty_log", "log/#{file}.log", :chmod => 0666 43 | } 44 | 45 | m.dependency "install_rubigen_scripts", [destination_root, "rubygems"], :shebang => options[:shebang] 46 | end 47 | end 48 | 49 | protected 50 | def banner 51 | "Usage: #{$0} /path/to/your/app [options]" 52 | end 53 | 54 | def add_options!(opts) 55 | opts.separator '' 56 | opts.separator 'Options:' 57 | opts.on("-r", "--ruby=path", String, 58 | "Path to the Ruby binary of your choice (otherwise scripts use env, dispatchers current path).", 59 | "Default: #{DEFAULT_SHEBANG}") { |v| options[:shebang] = v } 60 | end 61 | 62 | 63 | # Installation skeleton. Intermediate directories are automatically 64 | # created so don't sweat their absence here. 65 | BASEDIRS = %w( 66 | doc 67 | lib 68 | log 69 | script 70 | test 71 | tmp 72 | ) 73 | end 74 | -------------------------------------------------------------------------------- /app_generators/ruby_app/templates/README.txt: -------------------------------------------------------------------------------- 1 | README -------------------------------------------------------------------------------- /app_generators/ruby_app/templates/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | begin 3 | require 'rake' 4 | rescue LoadError 5 | puts 'This script should only be accessed via the "rake" command.' 6 | puts 'Installation: gem install rake -y' 7 | exit 8 | end 9 | require 'rake/clean' 10 | 11 | -------------------------------------------------------------------------------- /app_generators/ruby_app/templates/configs/empty_log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drnic/rubigen/5288e0014011d6f7519c4231f65c8e5d78f48afb/app_generators/ruby_app/templates/configs/empty_log -------------------------------------------------------------------------------- /app_generators/ruby_app/templates/lib/module.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) 2 | 3 | module <%= module_name %> 4 | 5 | end -------------------------------------------------------------------------------- /app_generators/ruby_app/templates/test/test_helper.rb.erb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require File.dirname(__FILE__) + '/../lib/<%= app_name %>' 3 | -------------------------------------------------------------------------------- /bin/install_rubigen_scripts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'rubigen' 5 | 6 | if %w(-v --version).include? ARGV.first 7 | require 'rubigen/version' 8 | puts "#{File.basename($0)} (via rubigen - #{RubiGen::VERSION})" 9 | exit(0) 10 | end 11 | 12 | require 'rubigen/scripts/generate' 13 | RubiGen::Base.use_component_sources! 14 | RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'install_rubigen_scripts') 15 | -------------------------------------------------------------------------------- /bin/rubigen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Created on 2008-12-27. 4 | # Copyright (c) 2008. All rights reserved. 5 | 6 | require "rubygems" 7 | gem 'activesupport', '~> 2.3.5' 8 | require File.expand_path(File.dirname(__FILE__) + "/../lib/rubigen") 9 | 10 | require "rubigen/cli" 11 | 12 | Rubigen::CLI.execute(STDOUT, ARGV, :destination => File.expand_path(".")) 13 | -------------------------------------------------------------------------------- /bin/ruby_app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if %w(--version -v).include? ARGV.first 4 | puts "Rails #{RubiGen::VERSION}" 5 | exit(0) 6 | end 7 | 8 | app_path = ARGV.first 9 | 10 | require File.dirname(__FILE__) + '/../lib/rubigen' 11 | require 'rubigen/scripts/generate' 12 | RubiGen::Base.use_application_sources! 13 | RubiGen::Scripts::Generate.new.run(ARGV, :generator => 'ruby_app') 14 | -------------------------------------------------------------------------------- /config/website.yml: -------------------------------------------------------------------------------- 1 | host: nicwilliams@rubyforge.org 2 | remote_dir: /var/www/gforge-projects/rubigen/ 3 | -------------------------------------------------------------------------------- /features/help.feature: -------------------------------------------------------------------------------- 1 | Feature: Generators offer help/usage details 2 | In order to reduce cost of learning about a new generator 3 | As a generator user 4 | I want help/usage details about generators 5 | 6 | Scenario: List of visible generators for rubygems 7 | When I run local executable "rubigen rubygems" with arguments "" 8 | Then I should see "application_generator" 9 | Then I should see "component_generator" 10 | Then I should not see "migration" # from rails scope 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/rubigen_cli.feature: -------------------------------------------------------------------------------- 1 | Feature: rubigen command-line interface to access generators anywhere 2 | In order to reduce cost of using rubigen 3 | As a Ruby developer 4 | I want to execute generators anywhere without a script/generate helper script 5 | 6 | Scenario: Run a component generator 7 | When I run local executable "rubigen" with arguments "rubygems component_generator foo bar" 8 | Then file "bar_generators/foo/foo_generator.rb" is created 9 | 10 | -------------------------------------------------------------------------------- /features/step_definitions/common_steps.rb: -------------------------------------------------------------------------------- 1 | Given /^this project is active project folder/ do 2 | @active_project_folder = File.expand_path(File.dirname(__FILE__) + "/../..") 3 | end 4 | 5 | Given /^env variable \$([\w_]+) set to "(.*)"/ do |env_var, value| 6 | ENV[env_var] = value 7 | end 8 | 9 | Given /"(.*)" folder is deleted/ do |folder| 10 | in_project_folder { FileUtils.rm_rf folder } 11 | end 12 | 13 | When /^I invoke "(.*)" generator with arguments "(.*)"$/ do |generator, arguments| 14 | @stdout = StringIO.new 15 | in_project_folder do 16 | if Object.const_defined?("APP_ROOT") 17 | APP_ROOT.replace(FileUtils.pwd) 18 | else 19 | APP_ROOT = FileUtils.pwd 20 | end 21 | run_generator(generator, arguments.split(' '), SOURCES, :stdout => @stdout) 22 | end 23 | File.open(File.join(@tmp_root, "generator.out"), "w") do |f| 24 | @stdout.rewind 25 | f << @stdout.read 26 | end 27 | end 28 | 29 | When /^I run executable "(.*)" with arguments "(.*)"/ do |executable, arguments| 30 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out")) 31 | in_project_folder do 32 | system "#{executable} #{arguments} > #{@stdout} 2> #{@stdout}" 33 | end 34 | end 35 | 36 | When /^I run project executable "(.*)" with arguments "(.*)"/ do |executable, arguments| 37 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out")) 38 | in_project_folder do 39 | system "ruby -rubygems #{executable} #{arguments} > #{@stdout} 2> #{@stdout}" 40 | end 41 | end 42 | 43 | When /^I run local executable "(.*)" with arguments "(.*)"/ do |executable, arguments| 44 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out")) 45 | executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable)) 46 | in_project_folder do 47 | system "ruby -rubygems #{executable} #{arguments} > #{@stdout} 2> #{@stdout}" 48 | end 49 | end 50 | 51 | When /^I invoke task "rake (.*)"/ do |task| 52 | @stdout = File.expand_path(File.join(@tmp_root, "tests.out")) 53 | in_project_folder do 54 | system "rake #{task} --trace > #{@stdout} 2> #{@stdout}" 55 | end 56 | end 57 | 58 | Then /^folder "(.*)" (is|is not) created/ do |folder, is| 59 | in_project_folder do 60 | File.exists?(folder).should(is == 'is' ? be_true : be_false) 61 | end 62 | end 63 | 64 | Then /^file "(.*)" (is|is not) created/ do |file, is| 65 | in_project_folder do 66 | File.exists?(file).should(is == 'is' ? be_true : be_false) 67 | end 68 | end 69 | 70 | Then /^file with name matching "(.*)" is created/ do |pattern| 71 | in_project_folder do 72 | Dir[pattern].should_not be_empty 73 | end 74 | end 75 | 76 | Then /^file "(.*)" contents (does|does not) match \/(.*)\// do |file, does, regex| 77 | in_project_folder do 78 | actual_output = File.read(file) 79 | (does == 'does') ? 80 | actual_output.should(match(/#{regex}/)) : 81 | actual_output.should_not(match(/#{regex}/)) 82 | end 83 | end 84 | 85 | Then /gem file "(.*)" and generated file "(.*)" should be the same/ do |gem_file, project_file| 86 | File.exists?(gem_file).should be_true 87 | File.exists?(project_file).should be_true 88 | gem_file_contents = File.read(File.dirname(__FILE__) + "/../../#{gem_file}") 89 | project_file_contents = File.read(File.join(@active_project_folder, project_file)) 90 | project_file_contents.should == gem_file_contents 91 | end 92 | 93 | Then /^(does|does not) invoke generator "(.*)"$/ do |does_invoke, generator| 94 | actual_output = File.read(@stdout) 95 | does_invoke == "does" ? 96 | actual_output.should(match(/dependency\s+#{generator}/)) : 97 | actual_output.should_not(match(/dependency\s+#{generator}/)) 98 | end 99 | 100 | Then /help options "(.*)" and "(.*)" are displayed/ do |opt1, opt2| 101 | actual_output = File.read(@stdout) 102 | actual_output.should match(/#{opt1}/) 103 | actual_output.should match(/#{opt2}/) 104 | end 105 | 106 | Then /^I should see "([^\"]*)"/ do |text| 107 | actual_output = File.read(@stdout) 108 | actual_output.should contain(text) 109 | end 110 | 111 | Then /^I should not see "([^\"]*)"/ do |text| 112 | actual_output = File.read(@stdout) 113 | actual_output.should_not contain(text) 114 | end 115 | 116 | Then /^I should see$/ do |text| 117 | actual_output = File.read(@stdout) 118 | actual_output.should contain(text) 119 | end 120 | 121 | Then /^I should not see$/ do |text| 122 | actual_output = File.read(@stdout) 123 | actual_output.should_not contain(text) 124 | end 125 | 126 | Then /^I should see exactly$/ do |text| 127 | actual_output = File.read(@stdout) 128 | actual_output.should == text 129 | end 130 | 131 | Then /^I should see all (\d+) tests pass/ do |expected_test_count| 132 | expected = %r{^#{expected_test_count} tests, \d+ assertions, 0 failures, 0 errors} 133 | actual_output = File.read(@stdout) 134 | actual_output.should match(expected) 135 | end 136 | 137 | Then /^I should see all (\d+) examples pass/ do |expected_test_count| 138 | expected = %r{^#{expected_test_count} examples?, 0 failures} 139 | actual_output = File.read(@stdout) 140 | actual_output.should match(expected) 141 | end 142 | 143 | Then /^yaml file "(.*)" contains (\{.*\})/ do |file, yaml| 144 | in_project_folder do 145 | yaml = eval yaml 146 | YAML.load(File.read(file)).should == yaml 147 | end 148 | end 149 | 150 | Then /^Rakefile can display tasks successfully/ do 151 | @stdout = File.expand_path(File.join(@tmp_root, "rakefile.out")) 152 | in_project_folder do 153 | system "rake -T > #{@stdout} 2> #{@stdout}" 154 | end 155 | actual_output = File.read(@stdout) 156 | actual_output.should match(/^rake\s+\w+\s+#\s.*/) 157 | end 158 | 159 | Then /^task "rake (.*)" is executed successfully/ do |task| 160 | @stdout.should_not be_nil 161 | actual_output = File.read(@stdout) 162 | actual_output.should_not match(/^Don't know how to build task '#{task}'/) 163 | actual_output.should_not match(/Error/i) 164 | end 165 | 166 | Then /^gem spec key "(.*)" contains \/(.*)\// do |key, regex| 167 | in_project_folder do 168 | gem_file = Dir["pkg/*.gem"].first 169 | gem_spec = Gem::Specification.from_yaml(`gem spec #{gem_file}`) 170 | spec_value = gem_spec.send(key.to_sym) 171 | spec_value.to_s.should match(/#{regex}/) 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /features/support/common.rb: -------------------------------------------------------------------------------- 1 | module CommonHelpers 2 | def in_tmp_folder(&block) 3 | FileUtils.chdir(@tmp_root, &block) 4 | end 5 | 6 | def in_project_folder(&block) 7 | project_folder = @active_project_folder || @tmp_root 8 | FileUtils.chdir(project_folder, &block) 9 | end 10 | 11 | def in_home_folder(&block) 12 | FileUtils.chdir(@home_path, &block) 13 | end 14 | 15 | def force_local_lib_override(project_name = @project_name) 16 | rakefile = File.read(File.join(project_name, 'Rakefile')) 17 | File.open(File.join(project_name, 'Rakefile'), "w+") do |f| 18 | f << "$:.unshift('#{@lib_path}')\n" 19 | f << rakefile 20 | end 21 | end 22 | 23 | def setup_active_project_folder project_name 24 | @active_project_folder = File.join(@tmp_root, project_name) 25 | @project_name = project_name 26 | end 27 | end 28 | 29 | World(CommonHelpers) -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | gem 'cucumber' 2 | require 'cucumber' 3 | gem 'rspec', '~> 1.3' 4 | require 'spec' 5 | 6 | require File.dirname(__FILE__) + "/../../lib/rubigen" 7 | 8 | Before do 9 | @tmp_root = File.dirname(__FILE__) + "/../../tmp" 10 | @home_path = File.expand_path(File.join(@tmp_root, "home")) 11 | FileUtils.rm_rf @tmp_root 12 | FileUtils.mkdir_p @home_path 13 | ENV['HOME'] = @home_path 14 | end 15 | -------------------------------------------------------------------------------- /features/support/matchers.rb: -------------------------------------------------------------------------------- 1 | module Matchers 2 | def contain(expected) 3 | simple_matcher("contain #{expected.inspect}") do |given, matcher| 4 | matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}" 5 | matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}" 6 | given.index expected 7 | end 8 | end 9 | end 10 | 11 | World(Matchers) 12 | -------------------------------------------------------------------------------- /generators/install_rubigen_scripts/install_rubigen_scripts_generator.rb: -------------------------------------------------------------------------------- 1 | class InstallRubigenScriptsGenerator < RubiGen::Base 2 | DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], 3 | Config::CONFIG['ruby_install_name']) 4 | 5 | default_options :shebang => DEFAULT_SHEBANG 6 | 7 | attr_reader :path, :scopes 8 | 9 | def initialize(runtime_args, runtime_options = {}) 10 | super 11 | usage if args.length < 2 # requires path and at least one scope 12 | @path = args.shift 13 | @destination_root = File.expand_path(path) 14 | @scopes = args.map { |scope| scope.to_sym } 15 | default_scopes 16 | extract_options 17 | end 18 | 19 | def manifest 20 | script_options = { :chmod => 0755, :shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang] } 21 | windows = (RUBY_PLATFORM =~ /dos|win32|cygwin/i) || (RUBY_PLATFORM =~ /(:?mswin|mingw)/) 22 | 23 | record do |m| 24 | # Ensure appropriate folder(s) exists 25 | m.directory "script" 26 | 27 | %w( generate destroy ).each do |file| 28 | m.template "script/#{file}", "script/#{file}", script_options 29 | m.template "script/win_script.cmd", "script/#{file}.cmd", 30 | :assigns => { :filename => file } if windows 31 | end 32 | end 33 | end 34 | 35 | def scopes_str 36 | scopes.inspect 37 | end 38 | 39 | protected 40 | def banner 41 | <<-EOS 42 | Installs script/generate and script/destroy scripts into current folder 43 | 44 | USAGE: #{spec.name} install_path scope[ scope] 45 | 46 | For example: #{spec.name} . rails rubygems 47 | EOS 48 | end 49 | 50 | def add_options!(opts) 51 | opts.separator '' 52 | opts.separator 'Options:' 53 | opts.on("-r", "--ruby=path", String, 54 | "Path to the Ruby binary of your choice (otherwise scripts use env, dispatchers current path).", 55 | "Default: #{DEFAULT_SHEBANG}") { |v| options[:shebang] = v } 56 | opts.on("-v", "--version", "Show the #{File.basename($0)} version number and quit.") 57 | end 58 | 59 | def extract_options 60 | # for each option, extract it into a local variable (and create an "attr_reader :author" at the top) 61 | # Templates can access these value via the attr_reader-generated methods, but not the 62 | # raw instance variable value. 63 | # @author = options[:author] 64 | end 65 | 66 | def default_scopes 67 | if (scopes.map { |s| s.to_s } & %w[test_unit rspec test_spec mini_spec javascript_test]).blank? 68 | scopes << :test_unit 69 | end 70 | end 71 | 72 | end -------------------------------------------------------------------------------- /generators/install_rubigen_scripts/templates/script/destroy: -------------------------------------------------------------------------------- 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! <%= scopes_str %> 14 | RubiGen::Scripts::Destroy.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /generators/install_rubigen_scripts/templates/script/generate: -------------------------------------------------------------------------------- 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! <%= scopes_str %> 14 | RubiGen::Scripts::Generate.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /generators/install_rubigen_scripts/templates/script/win_script.cmd: -------------------------------------------------------------------------------- 1 | @ruby <%= File.join("script", filename) %> %* -------------------------------------------------------------------------------- /lib/rubigen.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__)) unless 2 | $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) 3 | 4 | require 'active_support/all' 5 | 6 | require 'rubigen/base' 7 | require 'rubigen/lookup' 8 | require 'rubigen/commands' 9 | 10 | RubiGen::Base.send(:include, RubiGen::Lookup) 11 | RubiGen::Base.send(:include, RubiGen::Commands) 12 | 13 | # Set up a default logger for convenience. 14 | require 'rubigen/simple_logger' 15 | RubiGen::Base.logger = RubiGen::SimpleLogger.new(STDOUT) 16 | 17 | # Use self as default lookup algorithm 18 | # If your framework needs to subclass RubiGen::Base, then 19 | # assign it to #active after initialising rubigen and your code. 20 | RubiGen::Base.active = RubiGen::Base 21 | 22 | -------------------------------------------------------------------------------- /lib/rubigen/base.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/options' 2 | require File.dirname(__FILE__) + '/manifest' 3 | require File.dirname(__FILE__) + '/spec' 4 | require File.dirname(__FILE__) + '/generated_attribute' 5 | 6 | # RubiGen is a code generation platform Ruby frameworks. 7 | # Generators are easily invoked within Ruby framework instances 8 | # to add and remove components such as library and test files. 9 | # 10 | # New generators are easy to create and may be distributed within RubyGems, 11 | # user home directory, or within each Ruby framework that uses RubiGen. 12 | # 13 | # For example, newgem uses RubiGen to generate new RubyGems. Those 14 | # generated RubyGems can then use RubiGen (via a generated script/generate 15 | # application) to generate tests and executable apps, etc, for the RubyGem. 16 | # 17 | # Generators may subclass other generators to provide variations that 18 | # require little or no new logic but replace the template files. 19 | # 20 | # For a RubyGem, put your generator classes and templates within subfolders 21 | # of the +generators+ directory. 22 | # 23 | # The layout of generator files can be seen in the built-in 24 | # +test_unit+ generator: 25 | # 26 | # test_unit_generators/ 27 | # test_unit/ 28 | # test_unit_generator.rb 29 | # templates/ 30 | # test_unit.rb 31 | # 32 | # The directory name (+test_unit+) matches the name of the generator file 33 | # (test_unit_generator.rb) and class (+TestUnitGenerator+). The files 34 | # that will be copied or used as templates are stored in the +templates+ 35 | # directory. 36 | # 37 | # The filenames of the templates don't matter, but choose something that 38 | # will be self-explanatory since you will be referencing these in the 39 | # +manifest+ method inside your generator subclass. 40 | # 41 | # 42 | module RubiGen 43 | class GeneratorError < StandardError; end 44 | class UsageError < GeneratorError; end 45 | 46 | 47 | # The base code generator is bare-bones. It sets up the source and 48 | # destination paths and tells the logger whether to keep its trap shut. 49 | # 50 | # It's useful for copying files such as stylesheets, images, or 51 | # javascripts. 52 | # 53 | # For more comprehensive template-based passive code generation with 54 | # arguments, you'll want RubiGen::NamedBase. 55 | # 56 | # Generators create a manifest of the actions they perform then hand 57 | # the manifest to a command which replays the actions to do the heavy 58 | # lifting (such as checking for existing files or creating directories 59 | # if needed). Create, destroy, and list commands are included. Since a 60 | # single manifest may be used by any command, creating new generators is 61 | # as simple as writing some code templates and declaring what you'd like 62 | # to do with them. 63 | # 64 | # The manifest method must be implemented by subclasses, returning a 65 | # RubiGen::Manifest. The +record+ method is provided as a 66 | # convenience for manifest creation. Example: 67 | # 68 | # class StylesheetGenerator < RubiGen::Base 69 | # def manifest 70 | # record do |m| 71 | # m.directory('public/stylesheets') 72 | # m.file('application.css', 'public/stylesheets/application.css') 73 | # end 74 | # end 75 | # end 76 | # 77 | # See RubiGen::Commands::Create for a list of methods available 78 | # to the manifest. 79 | class Base 80 | include Options 81 | 82 | # Declare default options for the generator. These options 83 | # are inherited to subclasses. 84 | default_options :collision => :ask, :quiet => false, :stdout => STDOUT 85 | 86 | # A logger instance available everywhere in the generator. 87 | cattr_accessor :logger 88 | 89 | # Either RubiGen::Base, or a subclass (e.g. Rails::Generator::Base) 90 | # Currently used to determine the lookup paths via the overriden const_missing mechansim 91 | # in lookup.rb 92 | cattr_accessor :active 93 | 94 | # Every generator that is dynamically looked up is tagged with a 95 | # Spec describing where it was found. 96 | class_attribute :spec 97 | 98 | attr_reader :source_root, :destination_root, :args, :stdout 99 | 100 | def initialize(runtime_args, runtime_options = {}) 101 | @args = runtime_args 102 | parse!(@args, runtime_options) 103 | 104 | # Derive source and destination paths. 105 | @source_root = options[:source] || File.join(spec.path, 'templates') 106 | if options[:destination] 107 | @destination_root = options[:destination] 108 | elsif defined? ::APP_ROOT 109 | @destination_root = ::APP_ROOT 110 | elsif defined? ::RAILS_ROOT 111 | @destination_root = ::RAILS_ROOT 112 | end 113 | 114 | # Silence the logger if requested. 115 | logger.quiet = options[:quiet] 116 | 117 | @stdout = options[:stdout] 118 | 119 | # Raise usage error if help is requested. 120 | usage if options[:help] 121 | end 122 | 123 | # Generators must provide a manifest. Use the +record+ method to create 124 | # a new manifest and record your generator's actions. 125 | def manifest 126 | raise NotImplementedError, "No manifest for '#{spec.name}' generator." 127 | end 128 | 129 | # Return the full path from the source root for the given path. 130 | # Example for source_root = '/source': 131 | # source_path('some/path.rb') == '/source/some/path.rb' 132 | # 133 | # The given path may include a colon ':' character to indicate that 134 | # the file belongs to another generator. This notation allows any 135 | # generator to borrow files from another. Example: 136 | # source_path('model:fixture.yml') = '/model/source/path/fixture.yml' 137 | def source_path(relative_source) 138 | # Check whether we're referring to another generator's file. 139 | name, path = relative_source.split(':', 2) 140 | 141 | # If not, return the full path to our source file. 142 | if path.nil? 143 | File.join(source_root, name) 144 | 145 | # Otherwise, ask our referral for the file. 146 | else 147 | # FIXME: this is broken, though almost always true. Others' 148 | # source_root are not necessarily the templates dir. 149 | File.join(self.class.lookup(name).path, 'templates', path) 150 | end 151 | end 152 | 153 | # Return the full path from the destination root for the given path. 154 | # Example for destination_root = '/dest': 155 | # destination_path('some/path.rb') == '/dest/some/path.rb' 156 | def destination_path(relative_destination) 157 | File.expand_path(File.join(destination_root, relative_destination)) 158 | end 159 | 160 | # Return the basename of the destination_root, 161 | # BUT, if it is trunk, tags, or branches, it continues to the 162 | # parent path for the name 163 | def base_name 164 | name = File.basename(destination_root) 165 | root = destination_root 166 | while %w[trunk branches tags].include? name 167 | root = File.expand_path(File.join(root, "..")) 168 | name = File.basename(root) 169 | end 170 | name 171 | end 172 | 173 | def after_generate 174 | end 175 | 176 | protected 177 | # Convenience method for generator subclasses to record a manifest. 178 | def record 179 | RubiGen::Manifest.new(self) { |m| yield m } 180 | end 181 | 182 | # Override with your own usage banner. 183 | def banner 184 | "Usage: #{$0} #{spec.name} [options]" 185 | end 186 | 187 | # Read USAGE from file in generator base path. 188 | def usage_message 189 | File.read(File.join(spec.path, 'USAGE')) rescue '' 190 | end 191 | end 192 | 193 | end 194 | -------------------------------------------------------------------------------- /lib/rubigen/cli.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module Rubigen 4 | class CLI 5 | attr_reader :stdout 6 | 7 | def self.execute(stdout, arguments, runtime_arguments = {}) 8 | self.new.execute(stdout, arguments, runtime_arguments) 9 | end 10 | 11 | def execute(stdout, arguments, runtime_arguments = {}) 12 | @stdout = stdout 13 | main_usage and return unless scope = arguments.shift 14 | scopes = scope.split(",").map(&:to_sym) 15 | 16 | runtime_arguments.merge!(:stdout => stdout, :no_exit => true) 17 | RubiGen::Base.logger = RubiGen::SimpleLogger.new(stdout) 18 | 19 | require 'rubigen/scripts/generate' 20 | RubiGen::Base.use_component_sources!(scopes) 21 | RubiGen::Scripts::Generate.new.run(arguments, runtime_arguments) 22 | end 23 | 24 | def main_usage 25 | stdout.puts <<-USAGE.gsub(/^ /, '') 26 | Usage: $0 scope generator [options for generator] 27 | USAGE 28 | true 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/rubigen/commands.rb: -------------------------------------------------------------------------------- 1 | require 'delegate' 2 | require 'optparse' 3 | require 'fileutils' 4 | require 'tempfile' 5 | require 'erb' 6 | 7 | module RubiGen 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.included(base) 20 | base.send(:define_method, :command) do |command| 21 | Commands.instance(command, self) 22 | end 23 | end 24 | 25 | 26 | # Generator commands delegate RubiGen::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 RubiGen::Manifest and 33 | # RubiGen::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(RubiGen::Base) 39 | # Replay action manifest. RewindBase subclass rewinds manifest. 40 | def invoke! 41 | manifest.replay(self) 42 | after_generate 43 | end 44 | 45 | def dependency(generator_name, args, runtime_options = {}) 46 | logger.dependency(generator_name) do 47 | self.class.new(instance(generator_name, args, full_options(runtime_options))).invoke! 48 | end 49 | end 50 | 51 | # Does nothing for all commands except Create. 52 | def class_collisions(*class_names) 53 | end 54 | 55 | # Does nothing for all commands except Create. 56 | def readme(*args) 57 | end 58 | 59 | # Does nothing for all commands except Create. 60 | def write_manifest 61 | end 62 | 63 | protected 64 | def current_migration_number 65 | Dir.glob("#{RAILS_ROOT}/#{@migration_directory}/[0-9]*_*.rb").inject(0) do |max, file_path| 66 | n = File.basename(file_path).split('_', 2).first.to_i 67 | if n > max then n else max end 68 | end 69 | end 70 | 71 | def next_migration_number 72 | current_migration_number + 1 73 | end 74 | 75 | def migration_directory(relative_path) 76 | directory(@migration_directory = relative_path) 77 | end 78 | 79 | def existing_migrations(file_name) 80 | Dir.glob("#{@migration_directory}/[0-9]*_*.rb").grep(/[0-9]+_#{file_name}.rb$/) 81 | end 82 | 83 | def migration_exists?(file_name) 84 | not existing_migrations(file_name).empty? 85 | end 86 | 87 | def next_migration_string(padding = 3) 88 | if ActiveRecord::Base.timestamped_migrations 89 | Time.now.utc.strftime("%Y%m%d%H%M%S") 90 | else 91 | "%.#{padding}d" % next_migration_number 92 | end 93 | end 94 | 95 | def gsub_file(relative_destination, regexp, *args, &block) 96 | path = destination_path(relative_destination) 97 | content = File.read(path).gsub(regexp, *args, &block) 98 | File.open(path, 'wb') { |file| file.write(content) } 99 | end 100 | 101 | private 102 | # Ask the user interactively whether to force collision. 103 | def force_file_collision?(destination, src, dst, file_options = {}, &block) 104 | stdout.print "overwrite #{destination}? (enter \"h\" for help) [Ynaiqd] " 105 | stdout.flush 106 | case $stdin.gets.chomp 107 | when /\Ad\z/i 108 | Tempfile.open(File.basename(destination), File.dirname(dst)) do |temp| 109 | temp.write render_file(src, file_options, &block) 110 | temp.rewind 111 | stdout.puts `#{diff_cmd} #{dst} #{temp.path}` 112 | end 113 | stdout.puts "retrying" 114 | raise 'retry diff' 115 | when /\Aa\z/i 116 | stdout.puts "forcing #{spec.name}" 117 | options[:collision] = :force 118 | when /\Ai\z/i 119 | stdout.puts "ignoring #{spec.name}" 120 | options[:collision] = :skip 121 | when /\Aq\z/i 122 | stdout.puts "aborting #{spec.name}" 123 | raise SystemExit 124 | when /\An\z/i then :skip 125 | when /\Ay\z/i then :force 126 | else 127 | stdout.puts <<-HELP.gsub(/^ /, '') 128 | Y - yes, overwrite 129 | n - no, do not overwrite 130 | a - all, overwrite this and all others 131 | i - ignore, skip any conflicts 132 | q - quit, abort 133 | d - diff, show the differences between the old and the new 134 | h - help, show this help 135 | HELP 136 | raise 'retry' 137 | end 138 | rescue 139 | retry 140 | end 141 | 142 | def diff_cmd 143 | ENV['RAILS_DIFF'] || 'diff -u' 144 | end 145 | 146 | def render_template_part(template_options) 147 | # Getting Sandbox to evaluate part template in it 148 | part_binding = template_options[:sandbox].call.sandbox_binding 149 | part_rel_path = template_options[:insert] 150 | part_path = source_path(part_rel_path) 151 | 152 | # Render inner template within Sandbox binding 153 | rendered_part = ERB.new(File.readlines(part_path).join, nil, '-').result(part_binding) 154 | begin_mark = template_part_mark(template_options[:begin_mark], template_options[:mark_id]) 155 | end_mark = template_part_mark(template_options[:end_mark], template_options[:mark_id]) 156 | begin_mark + rendered_part + end_mark 157 | end 158 | 159 | def template_part_mark(name, id) 160 | "\n" 161 | end 162 | end 163 | 164 | # Base class for commands which handle generator actions in reverse, such as Destroy. 165 | class RewindBase < Base 166 | # Rewind action manifest. 167 | def invoke! 168 | manifest.rewind(self) 169 | end 170 | end 171 | 172 | 173 | # Create is the premier generator command. It copies files, creates 174 | # directories, renders templates, and more. 175 | class Create < Base 176 | 177 | # Check whether the given class names are already taken. 178 | # In the future, expand to check other namespaces 179 | # such as the rest of the user's app. 180 | def class_collisions(*class_names) 181 | class_names.flatten.each do |class_name| 182 | # Convert to string to allow symbol arguments. 183 | class_name = class_name.to_s 184 | 185 | # Skip empty strings. 186 | next if class_name.strip.empty? 187 | 188 | # Split the class from its module nesting. 189 | nesting = class_name.split('::') 190 | name = nesting.pop 191 | 192 | # Extract the last Module in the nesting. 193 | last = nesting.inject(Object) { |last, nest| 194 | break unless last.const_defined?(nest) 195 | last.const_get(nest) 196 | } 197 | 198 | # If the last Module exists, check whether the given 199 | # class exists and raise a collision if so. 200 | if last and last.const_defined?(name.camelize) 201 | raise_class_collision(class_name) 202 | end 203 | end 204 | end 205 | 206 | # Copy a file from source to destination with collision checking. 207 | # 208 | # The file_options hash accepts :chmod and :shebang and :collision options. 209 | # :chmod sets the permissions of the destination file: 210 | # file 'config/empty.log', 'log/test.log', :chmod => 0664 211 | # :shebang sets the #!/usr/bin/ruby line for scripts 212 | # file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby' 213 | # :collision sets the collision option only for the destination file: 214 | # file 'settings/server.yml', 'config/server.yml', :collision => :skip 215 | # 216 | # Collisions are handled by checking whether the destination file 217 | # exists and either skipping the file, forcing overwrite, or asking 218 | # the user what to do. 219 | def file(relative_source, relative_destination, file_options = {}, &block) 220 | # Determine full paths for source and destination files. 221 | source = source_path(relative_source) 222 | destination = destination_path(relative_destination) 223 | destination_exists = File.exist?(destination) 224 | 225 | # If source and destination are identical then we're done. 226 | if destination_exists and identical?(source, destination, &block) 227 | return logger.identical(relative_destination) 228 | end 229 | 230 | # Check for and resolve file collisions. 231 | if destination_exists 232 | 233 | # Make a choice whether to overwrite the file. :force and 234 | # :skip already have their mind made up, but give :ask a shot. 235 | choice = case (file_options[:collision] || options[:collision]).to_sym #|| :ask 236 | when :ask then force_file_collision?(relative_destination, source, destination, file_options, &block) 237 | when :force then :force 238 | when :skip then :skip 239 | else raise "Invalid collision option: #{options[:collision].inspect}" 240 | end 241 | 242 | # Take action based on our choice. Bail out if we chose to 243 | # skip the file; otherwise, log our transgression and continue. 244 | case choice 245 | when :force then logger.force(relative_destination) 246 | when :skip then return(logger.skip(relative_destination)) 247 | else raise "Invalid collision choice: #{choice}.inspect" 248 | end 249 | 250 | # File doesn't exist so log its unbesmirched creation. 251 | else 252 | logger.create relative_destination 253 | end 254 | 255 | # If we're pretending, back off now. 256 | return if options[:pretend] 257 | 258 | # Write destination file with optional shebang. Yield for content 259 | # if block given so templaters may render the source file. If a 260 | # shebang is requested, replace the existing shebang or insert a 261 | # new one. 262 | File.open(destination, 'wb') do |dest| 263 | dest.write render_file(source, file_options, &block) 264 | end 265 | 266 | # Optionally change permissions. 267 | if file_options[:chmod] 268 | FileUtils.chmod(file_options[:chmod], destination) 269 | end 270 | 271 | # Optionally add file to subversion or git 272 | system("svn add #{destination}") if options[:svn] 273 | end 274 | 275 | def file_copy_each(files, path=nil, options = {}) 276 | path = path ? "#{path}/" : "" 277 | files.each do |file_name| 278 | file "#{path}#{file_name}", "#{path}#{file_name}", options 279 | end 280 | end 281 | 282 | def folder(template_path, path=nil, options = {}) 283 | template_path = "/" if template_path.blank? 284 | source = source_path(template_path) 285 | files = Dir[source + '/*'].select { |file| File.file? file }.map { |file| file.sub(/^#{source}/,"") } 286 | files.each do |file_name| 287 | file "#{template_path}#{file_name}", "#{path}#{file_name}", options 288 | end 289 | system("git add -v #{relative_destination}") if options[:git] 290 | end 291 | 292 | # Checks if the source and the destination file are identical. If 293 | # passed a block then the source file is a template that needs to first 294 | # be evaluated before being compared to the destination. 295 | def identical?(source, destination, &block) 296 | return false if File.directory? destination 297 | source = block_given? ? File.open(source) {|sf| yield(sf)} : IO.read(source) 298 | destination = IO.read(destination) 299 | source == destination 300 | end 301 | 302 | # Generate a file using an ERuby template. 303 | # Looks up and evaluates a template by name and writes the result. 304 | # 305 | # The ERB template uses explicit trim mode to best control the 306 | # proliferation of whitespace in generated code. <%- trims leading 307 | # whitespace; -%> trims trailing whitespace including one newline. 308 | # 309 | # A hash of template options may be passed as the last argument. 310 | # The options accepted by the file are accepted as well as :assigns, 311 | # a hash of variable bindings. Example: 312 | # template 'foo', 'bar', :assigns => { :action => 'view' } 313 | # 314 | # Template is implemented in terms of file. It calls file with a 315 | # block which takes a file handle and returns its rendered contents. 316 | def template(relative_source, relative_destination, template_options = {}) 317 | file(relative_source, relative_destination, template_options) do |file| 318 | # Evaluate any assignments in a temporary, throwaway binding. 319 | vars = template_options[:assigns] || {} 320 | b = binding 321 | vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b } 322 | 323 | # Render the source file with the temporary binding. 324 | ERB.new(file.read, nil, '-').result(b) 325 | end 326 | end 327 | 328 | def template_copy_each(files, path = nil, options = {}) 329 | path = path ? "#{path}/" : "" 330 | files.each do |file_name| 331 | template "#{path}#{file_name}", "#{path}#{file_name.gsub(/\.erb$/,'')}", options 332 | end 333 | end 334 | 335 | def complex_template(relative_source, relative_destination, template_options = {}) 336 | options = template_options.dup 337 | options[:assigns] ||= {} 338 | options[:assigns]['template_for_inclusion'] = render_template_part(template_options) 339 | template(relative_source, relative_destination, options) 340 | end 341 | 342 | # Create a directory including any missing parent directories. 343 | # Always skips directories which exist. 344 | def directory(relative_path) 345 | path = destination_path(relative_path) 346 | if File.exist?(path) 347 | logger.exists relative_path 348 | else 349 | logger.create relative_path 350 | unless options[:pretend] 351 | FileUtils.mkdir_p(path) 352 | # git doesn't require adding the paths, adding the files later will 353 | # automatically do a path add. 354 | 355 | # Subversion doesn't do path adds, so we need to add 356 | # each directory individually. 357 | # So stack up the directory tree and add the paths to 358 | # subversion in order without recursion. 359 | if options[:svn] 360 | stack = [relative_path] 361 | until File.dirname(stack.last) == stack.last # dirname('.') == '.' 362 | stack.push File.dirname(stack.last) 363 | end 364 | stack.reverse_each do |rel_path| 365 | svn_path = destination_path(rel_path) 366 | system("svn add -N #{svn_path}") unless File.directory?(File.join(svn_path, '.svn')) 367 | end 368 | end 369 | end 370 | end 371 | end 372 | 373 | # Display a README. 374 | def readme(*relative_sources) 375 | relative_sources.flatten.each do |relative_source| 376 | logger.readme relative_source 377 | stdout.puts File.read(source_path(relative_source)) unless options[:pretend] 378 | end 379 | end 380 | 381 | def write_manifest(relative_destination) 382 | files = ([relative_destination] + Dir["#{destination_root}/**/*"]) 383 | files.reject! { |file| File.directory?(file) } 384 | files.map! { |path| path.sub("#{destination_root}/","") } 385 | files = files.uniq.sort 386 | 387 | 388 | destination = destination_path(relative_destination) 389 | destination_exists = File.exists?(destination) 390 | 391 | # Check for and resolve file collisions. 392 | if destination_exists 393 | # Always recreate the Manifest (perhaps we need to give the option... like normal files) 394 | choice = :force 395 | logger.force(relative_destination) 396 | 397 | # File doesn't exist so log its unbesmirched creation. 398 | else 399 | logger.create relative_destination 400 | end 401 | 402 | # If we're pretending, back off now. 403 | return if options[:pretend] 404 | 405 | # Write destination file with optional shebang. Yield for content 406 | # if block given so templaters may render the source file. If a 407 | # shebang is requested, replace the existing shebang or insert a 408 | # new one. 409 | File.open(destination, 'wb') do |dest| 410 | dest.write files.join("\n") 411 | dest.write "\n" 412 | end 413 | 414 | # Optionally add file to subversion 415 | system("svn add #{destination}") if options[:svn] 416 | end 417 | 418 | # When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template. 419 | def migration_template(relative_source, relative_destination, template_options = {}) 420 | migration_directory relative_destination 421 | migration_file_name = template_options[:migration_file_name] || file_name 422 | raise "Another migration is already named #{migration_file_name}: #{existing_migrations(migration_file_name).first}" if migration_exists?(migration_file_name) 423 | template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options) 424 | end 425 | 426 | def route_resources(*resources) 427 | resource_list = resources.map { |r| r.to_sym.inspect }.join(', ') 428 | sentinel = 'ActionController::Routing::Routes.draw do |map|' 429 | 430 | logger.route "map.resources #{resource_list}" 431 | unless options[:pretend] 432 | gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match| 433 | "#{match}\n map.resources #{resource_list}\n" 434 | end 435 | end 436 | end 437 | 438 | private 439 | def render_file(path, options = {}) 440 | File.open(path, 'rb') do |file| 441 | if block_given? 442 | yield file 443 | else 444 | content = '' 445 | if shebang = options[:shebang] 446 | content << "#!#{shebang}\n" 447 | if line = file.gets 448 | content << "line\n" if line !~ /^#!/ 449 | end 450 | end 451 | content << file.read 452 | end 453 | end 454 | end 455 | 456 | # Raise a usage error with an informative WordNet suggestion. 457 | # Thanks to Florian Gross (flgr). 458 | def raise_class_collision(class_name) 459 | message = <<-end_message 460 | The name '#{class_name}' is either already used in your application or reserved. 461 | Please choose an alternative and run this generator again. 462 | end_message 463 | if suggest = find_synonyms(class_name) 464 | if suggest.any? 465 | message << "\n Suggestions: \n\n" 466 | message << suggest.join("\n") 467 | end 468 | end 469 | raise UsageError, message 470 | end 471 | 472 | SYNONYM_LOOKUP_URI = "http://wordnet.princeton.edu/perl/webwn?s=%s" 473 | 474 | # Look up synonyms on WordNet. Thanks to Florian Gross (flgr). 475 | def find_synonyms(word) 476 | require 'open-uri' 477 | require 'timeout' 478 | timeout(5) do 479 | open(SYNONYM_LOOKUP_URI % word) do |stream| 480 | # Grab words linked to dictionary entries as possible synonyms 481 | data = stream.read.gsub(" ", " ").scan(/([\w ]*?)<\/a>/s).uniq 482 | end 483 | end 484 | rescue Exception 485 | return nil 486 | end 487 | end 488 | 489 | 490 | # Undo the actions performed by a generator. Rewind the action 491 | # manifest and attempt to completely erase the results of each action. 492 | class Destroy < RewindBase 493 | # Remove a file if it exists and is a file. 494 | def file(relative_source, relative_destination, file_options = {}) 495 | destination = destination_path(relative_destination) 496 | if File.exist?(destination) 497 | logger.rm relative_destination 498 | unless options[:pretend] 499 | if options[:svn] 500 | # If the file has been marked to be added 501 | # but has not yet been checked in, revert and delete 502 | if options[:svn][relative_destination] 503 | system("svn revert #{destination}") 504 | FileUtils.rm(destination) 505 | else 506 | # If the directory is not in the status list, it 507 | # has no modifications so we can simply remove it 508 | system("svn rm #{destination}") 509 | end 510 | elsif options[:git] 511 | if options[:git][:new][relative_destination] 512 | # file has been added, but not committed 513 | system("git reset HEAD #{relative_destination}") 514 | FileUtils.rm(destination) 515 | elsif options[:git][:modified][relative_destination] 516 | # file is committed and modified 517 | system("git rm -f #{relative_destination}") 518 | else 519 | # If the directory is not in the status list, it 520 | # has no modifications so we can simply remove it 521 | system("git rm #{relative_destination}") 522 | end 523 | else 524 | FileUtils.rm(destination) 525 | end 526 | end 527 | else 528 | logger.missing relative_destination 529 | return 530 | end 531 | end 532 | 533 | # Templates are deleted just like files and the actions take the 534 | # same parameters, so simply alias the file method. 535 | alias_method :template, :file 536 | 537 | # Remove each directory in the given path from right to left. 538 | # Remove each subdirectory if it exists and is a directory. 539 | def directory(relative_path) 540 | parts = relative_path.split('/') 541 | until parts.empty? 542 | partial = File.join(parts) 543 | path = destination_path(partial) 544 | if File.exist?(path) 545 | if Dir[File.join(path, '*')].empty? 546 | logger.rmdir partial 547 | unless options[:pretend] 548 | if options[:svn] 549 | # If the directory has been marked to be added 550 | # but has not yet been checked in, revert and delete 551 | if options[:svn][relative_path] 552 | system("svn revert #{path}") 553 | FileUtils.rmdir(path) 554 | else 555 | # If the directory is not in the status list, it 556 | # has no modifications so we can simply remove it 557 | system("svn rm #{path}") 558 | end 559 | # I don't think git needs to remove directories?.. 560 | # or maybe they have special consideration... 561 | else 562 | FileUtils.rmdir(path) 563 | end 564 | end 565 | else 566 | logger.notempty partial 567 | end 568 | else 569 | logger.missing partial 570 | end 571 | parts.pop 572 | end 573 | end 574 | 575 | def complex_template(*args) 576 | # nothing should be done here 577 | end 578 | 579 | # When deleting a migration, it knows to delete every file named "[0-9]*_#{file_name}". 580 | def migration_template(relative_source, relative_destination, template_options = {}) 581 | migration_directory relative_destination 582 | 583 | migration_file_name = template_options[:migration_file_name] || file_name 584 | unless migration_exists?(migration_file_name) 585 | stdout.puts "There is no migration named #{migration_file_name}" 586 | return 587 | end 588 | 589 | 590 | existing_migrations(migration_file_name).each do |file_path| 591 | file(relative_source, file_path, template_options) 592 | end 593 | end 594 | 595 | def route_resources(*resources) 596 | resource_list = resources.map { |r| r.to_sym.inspect }.join(', ') 597 | look_for = "\n map.resources #{resource_list}\n" 598 | logger.route "map.resources #{resource_list}" 599 | gsub_file 'config/routes.rb', /(#{look_for})/mi, '' 600 | end 601 | end 602 | 603 | 604 | # List a generator's action manifest. 605 | class List < Base 606 | def dependency(generator_name, args, options = {}) 607 | logger.dependency "#{generator_name}(#{args.join(', ')}, #{options.inspect})" 608 | end 609 | 610 | def class_collisions(*class_names) 611 | logger.class_collisions class_names.join(', ') 612 | end 613 | 614 | def file(relative_source, relative_destination, options = {}) 615 | logger.file relative_destination 616 | end 617 | 618 | def template(relative_source, relative_destination, options = {}) 619 | logger.template relative_destination 620 | end 621 | 622 | def complex_template(relative_source, relative_destination, options = {}) 623 | logger.template "#{options[:insert]} inside #{relative_destination}" 624 | end 625 | 626 | def directory(relative_path) 627 | logger.directory "#{destination_path(relative_path)}/" 628 | end 629 | 630 | def readme(*args) 631 | logger.readme args.join(', ') 632 | end 633 | 634 | def migration_template(relative_source, relative_destination, options = {}) 635 | migration_directory relative_destination 636 | logger.migration_template file_name 637 | end 638 | 639 | def route_resources(*resources) 640 | resource_list = resources.map { |r| r.to_sym.inspect }.join(', ') 641 | logger.route "map.resources #{resource_list}" 642 | end 643 | end 644 | 645 | # Update generator's action manifest. 646 | class Update < Create 647 | def file(relative_source, relative_destination, options = {}) 648 | # logger.file relative_destination 649 | end 650 | 651 | def template(relative_source, relative_destination, options = {}) 652 | # logger.template relative_destination 653 | end 654 | 655 | def complex_template(relative_source, relative_destination, template_options = {}) 656 | 657 | begin 658 | dest_file = destination_path(relative_destination) 659 | source_to_update = File.readlines(dest_file).join 660 | rescue Errno::ENOENT 661 | logger.missing relative_destination 662 | return 663 | end 664 | 665 | logger.refreshing "#{template_options[:insert].gsub(/\.erb/,'')} inside #{relative_destination}" 666 | 667 | begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id])) 668 | end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id])) 669 | 670 | # Refreshing inner part of the template with freshly rendered part. 671 | rendered_part = render_template_part(template_options) 672 | source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part) 673 | 674 | File.open(dest_file, 'w') { |file| file.write(source_to_update) } 675 | end 676 | 677 | def directory(relative_path) 678 | # logger.directory "#{destination_path(relative_path)}/" 679 | end 680 | end 681 | 682 | end 683 | end 684 | -------------------------------------------------------------------------------- /lib/rubigen/generated_attribute.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module RubiGen 4 | class GeneratedAttribute 5 | attr_accessor :name, :type, :column 6 | 7 | def initialize(name, type) 8 | @name, @type = name, type.to_sym 9 | @column = ActiveRecord::ConnectionAdapters::Column.new(name, nil, @type) 10 | end 11 | 12 | def field_type 13 | @field_type ||= case type 14 | when :integer, :float, :decimal then :text_field 15 | when :datetime, :timestamp, :time then :datetime_select 16 | when :date then :date_select 17 | when :string then :text_field 18 | when :text then :text_area 19 | when :boolean then :check_box 20 | else 21 | :text_field 22 | end 23 | end 24 | 25 | def default 26 | @default ||= case type 27 | when :integer then 1 28 | when :float then 1.5 29 | when :decimal then "9.99" 30 | when :datetime, :timestamp, :time then Time.now.to_s(:db) 31 | when :date then Date.today.to_s(:db) 32 | when :string then "MyString" 33 | when :text then "MyText" 34 | when :boolean then false 35 | else 36 | "" 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rubigen/helpers/generator_test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | module RubiGen 4 | module GeneratorTestHelper 5 | # Runs the create command (like the command line does) 6 | def run_generator(name, params, sources, options = {}) 7 | generator = build_generator(name, params, sources, options) 8 | silence_generator do 9 | generator.command(:create).invoke! 10 | end 11 | generator 12 | end 13 | 14 | # Instatiates the Generator 15 | def build_generator(name, params, sources, options) 16 | @stdout ||= StringIO.new 17 | options.merge!(:collision => :force) # so no questions are prompted 18 | options.merge!(:stdout => @stdout) # so stdout is piped to a StringIO 19 | if sources.is_a?(Symbol) 20 | if sources == :app 21 | RubiGen::Base.use_application_sources! 22 | else 23 | RubiGen::Base.use_component_sources! 24 | end 25 | else 26 | RubiGen::Base.reset_sources 27 | RubiGen::Base.prepend_sources(*sources) unless sources.blank? 28 | end 29 | RubiGen::Base.instance(name, params, options) 30 | end 31 | 32 | # Silences the logger temporarily and returns the output as a String 33 | def silence_generator 34 | logger_original = RubiGen::Base.logger 35 | myout = StringIO.new 36 | RubiGen::Base.logger = RubiGen::SimpleLogger.new(myout) 37 | yield if block_given? 38 | RubiGen::Base.logger = logger_original 39 | myout.string 40 | end 41 | 42 | # asserts that the given file was generated. 43 | # the contents of the file is passed to a block. 44 | def assert_generated_file(path) 45 | assert_file_exists(path) 46 | File.open("#{APP_ROOT}/#{path}") do |f| 47 | yield f.read if block_given? 48 | end 49 | end 50 | 51 | # asserts that the given file exists 52 | def assert_file_exists(path) 53 | assert File.exists?("#{APP_ROOT}/#{path}"),"The file '#{path}' should exist" 54 | end 55 | 56 | # asserts that the given directory exists 57 | def assert_directory_exists(path) 58 | assert File.directory?("#{APP_ROOT}/#{path}"),"The directory '#{path}' should exist" 59 | end 60 | 61 | # asserts that the given class source file was generated. 62 | # It takes a path without the .rb part and an optional super class. 63 | # the contents of the class source file is passed to a block. 64 | def assert_generated_class(path,parent=nil) 65 | path=~/\/?(\d+_)?(\w+)$/ 66 | class_name=$2.camelize 67 | assert_generated_file("#{path}.rb") do |body| 68 | assert body=~/class #{class_name}#{parent.nil? ? '':" < #{parent}"}/,"the file '#{path}.rb' should be a class" 69 | yield body if block_given? 70 | end 71 | end 72 | 73 | # asserts that the given module source file was generated. 74 | # It takes a path without the .rb part. 75 | # the contents of the class source file is passed to a block. 76 | def assert_generated_module(path) 77 | path=~/\/?(\w+)$/ 78 | module_name=$1.camelize 79 | assert_generated_file("#{path}.rb") do |body| 80 | assert body=~/module #{module_name}/,"the file '#{path}.rb' should be a module" 81 | yield body if block_given? 82 | end 83 | end 84 | 85 | # asserts that the given unit test was generated. 86 | # It takes a name or symbol without the test_ part and an optional super class. 87 | # the contents of the class source file is passed to a block. 88 | def assert_generated_test_for(name, parent="Test::Unit::TestCase") 89 | assert_generated_class "test/test_#{name.to_s.underscore}", parent do |body| 90 | yield body if block_given? 91 | end 92 | end 93 | 94 | # asserts that the given methods are defined in the body. 95 | # This does assume standard rails code conventions with regards to the source code. 96 | # The body of each individual method is passed to a block. 97 | def assert_has_method(body,*methods) 98 | methods.each do |name| 99 | assert body=~/^ def #{name.to_s}\n((\n| .*\n)*) end/,"should have method #{name.to_s}" 100 | yield( name, $1 ) if block_given? 101 | end 102 | end 103 | 104 | def app_root_files 105 | Dir[APP_ROOT + '/**/*'] 106 | end 107 | 108 | def rubygem_folders 109 | %w[bin examples lib test] 110 | end 111 | 112 | def rubygems_setup 113 | bare_setup 114 | rubygem_folders.each do |folder| 115 | Dir.mkdir("#{APP_ROOT}/#{folder}") unless File.exists?("#{APP_ROOT}/#{folder}") 116 | end 117 | end 118 | 119 | def rubygems_teardown 120 | bare_teardown 121 | end 122 | 123 | def bare_setup 124 | FileUtils.mkdir_p(APP_ROOT) 125 | @stdout = StringIO.new 126 | end 127 | 128 | def bare_teardown 129 | FileUtils.rm_rf TMP_ROOT || APP_ROOT 130 | end 131 | 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /lib/rubigen/lookup.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/spec' 2 | 3 | class Object 4 | class << self 5 | # Lookup missing generators using const_missing. This allows any 6 | # generator to reference another without having to know its location: 7 | # RubyGems, ~/.rubigen/generators, and APP_ROOT/generators. 8 | def lookup_missing_generator(class_id) 9 | if md = /(.+)Generator$/.match(class_id.to_s) 10 | name = md.captures.first.demodulize.underscore 11 | RubiGen::Base.active.lookup(name).klass 12 | else 13 | const_missing_before_generators(class_id) 14 | end 15 | end 16 | 17 | unless respond_to?(:const_missing_before_generators) 18 | alias_method :const_missing_before_generators, :const_missing 19 | alias_method :const_missing, :lookup_missing_generator 20 | end 21 | end 22 | end 23 | 24 | # User home directory lookup adapted from RubyGems. 25 | def Dir.user_home 26 | if ENV['HOME'] 27 | ENV['HOME'] 28 | elsif ENV['USERPROFILE'] 29 | ENV['USERPROFILE'] 30 | elsif ENV['HOMEDRIVE'] and ENV['HOMEPATH'] 31 | "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" 32 | else 33 | File.expand_path '~' 34 | end 35 | end 36 | 37 | 38 | module RubiGen 39 | 40 | # Generator lookup is managed by a list of sources which return specs 41 | # describing where to find and how to create generators. This module 42 | # provides class methods for manipulating the source list and looking up 43 | # generator specs, and an #instance wrapper for quickly instantiating 44 | # generators by name. 45 | # 46 | # A spec is not a generator: it's a description of where to find 47 | # the generator and how to create it. A source is anything that 48 | # yields generators from #each. PathSource and GemGeneratorSource are provided. 49 | module Lookup 50 | def self.included(base) 51 | base.extend(ClassMethods) 52 | # base.use_component_sources! # TODO is this required since it has no scope/source context 53 | end 54 | 55 | # Convenience method to instantiate another generator. 56 | def instance(generator_name, args, runtime_options = {}) 57 | self.class.active.instance(generator_name, args, runtime_options) 58 | end 59 | 60 | module ClassMethods 61 | # The list of sources where we look, in order, for generators. 62 | def sources 63 | if read_inheritable_attribute(:sources).blank? 64 | if superclass == RubiGen::Base 65 | superclass_sources = superclass.sources 66 | diff = superclass_sources.inject([]) do |mem, source| 67 | found = false 68 | application_sources.each { |app_source| found ||= true if app_source == source} 69 | mem << source unless found 70 | mem 71 | end 72 | write_inheritable_attribute(:sources, diff) 73 | end 74 | active.use_component_sources! if read_inheritable_attribute(:sources).blank? 75 | end 76 | read_inheritable_attribute(:sources) 77 | end 78 | 79 | # Add a source to the end of the list. 80 | def append_sources(*args) 81 | sources.concat(args.flatten) 82 | invalidate_cache! 83 | end 84 | 85 | # Add a source to the beginning of the list. 86 | def prepend_sources(*args) 87 | sources = self.sources 88 | reset_sources 89 | write_inheritable_array(:sources, args.flatten + sources) 90 | invalidate_cache! 91 | end 92 | 93 | # Reset the source list. 94 | def reset_sources 95 | write_inheritable_attribute(:sources, []) 96 | invalidate_cache! 97 | end 98 | 99 | # Use application generators (app, ?). 100 | def use_application_sources!(*filters) 101 | reset_sources 102 | write_inheritable_attribute(:sources, application_sources(filters)) 103 | end 104 | 105 | def application_sources(filters = []) 106 | filters.unshift 'app' 107 | app_sources = [] 108 | app_sources << PathSource.new(:builtin, File.join(File.dirname(__FILE__), %w[.. .. app_generators])) 109 | app_sources << filtered_sources(filters) 110 | app_sources.flatten 111 | end 112 | 113 | # Use component generators (test_unit, etc). 114 | # 1. Current application. If APP_ROOT is defined we know we're 115 | # generating in the context of this application, so search 116 | # APP_ROOT/generators. 117 | # 2. User home directory. Search ~/.rubigen/generators. 118 | # 3. RubyGems. Search for gems containing /{scope}_generators folder. 119 | # 4. Builtins. None currently. 120 | # 121 | # Search can be filtered by passing one or more prefixes. 122 | # e.g. use_component_sources!(:rubygems) means it will also search in the following 123 | # folders: 124 | # 5. User home directory. Search ~/.rubigen/rubygems_generators. 125 | # 6. RubyGems. Search for gems containing /rubygems_generators folder. 126 | def use_component_sources!(*filters) 127 | reset_sources 128 | new_sources = [] 129 | if defined? ::APP_ROOT 130 | new_sources << PathSource.new(:root, "#{::APP_ROOT}/generators") 131 | new_sources << PathSource.new(:vendor, "#{::APP_ROOT}/vendor/generators") 132 | new_sources << PathSource.new(:plugins, "#{::APP_ROOT}/vendor/plugins/*/**/generators") 133 | end 134 | new_sources << filtered_sources(filters) 135 | write_inheritable_attribute(:sources, new_sources.flatten) 136 | end 137 | 138 | def filtered_sources(filters) 139 | new_sources = [] 140 | new_sources << PathFilteredSource.new(:user, "#{Dir.user_home}/.rubigen/", *filters) 141 | if Object.const_defined?(:Gem) 142 | new_sources << GemPathSource.new(*filters) 143 | end 144 | new_sources 145 | end 146 | 147 | # Lookup knows how to find generators' Specs from a list of Sources. 148 | # Searches the sources, in order, for the first matching name. 149 | def lookup(generator_name) 150 | @found ||= {} 151 | generator_name = generator_name.to_s.downcase 152 | @found[generator_name] ||= cache.find { |spec| spec.name == generator_name } 153 | unless @found[generator_name] 154 | chars = generator_name.scan(/./).map{|c|"#{c}.*?"} 155 | rx = /^#{chars}$/ 156 | gns = cache.select {|spec| spec.name =~ rx } 157 | @found[generator_name] ||= gns.first if gns.length == 1 158 | raise GeneratorError, "Pattern '#{generator_name}' matches more than one generator: #{gns.map{|sp|sp.name}.join(', ')}" if gns.length > 1 159 | end 160 | @found[generator_name] or raise GeneratorError, "Couldn't find '#{generator_name}' generator" 161 | end 162 | 163 | # Convenience method to lookup and instantiate a generator. 164 | def instance(generator_name, args = [], runtime_options = {}) 165 | active.lookup(generator_name).klass.new(args, full_options(runtime_options)) 166 | end 167 | 168 | private 169 | # Lookup and cache every generator from the source list. 170 | def cache 171 | @cache ||= sources.inject([]) { |cache, source| cache + source.to_a } 172 | end 173 | 174 | # Clear the cache whenever the source list changes. 175 | def invalidate_cache! 176 | @cache = nil 177 | end 178 | end 179 | end 180 | 181 | # Sources enumerate (yield from #each) generator specs which describe 182 | # where to find and how to create generators. Enumerable is mixed in so, 183 | # for example, source.collect will retrieve every generator. 184 | # Sources may be assigned a label to distinguish them. 185 | class Source 186 | include Enumerable 187 | 188 | attr_reader :label 189 | def initialize(label) 190 | @label = label 191 | end 192 | 193 | # The each method must be implemented in subclasses. 194 | # The base implementation raises an error. 195 | def each 196 | raise NotImplementedError 197 | end 198 | 199 | # Return a convenient sorted list of all generator names. 200 | def names(filter = nil) 201 | inject([]) do |mem, spec| 202 | case filter 203 | when :visible 204 | mem << spec.name if spec.visible? 205 | end 206 | mem 207 | end.sort 208 | end 209 | end 210 | 211 | 212 | # PathSource looks for generators in a filesystem directory. 213 | class PathSource < Source 214 | attr_reader :path 215 | 216 | def initialize(label, path) 217 | super label 218 | @path = File.expand_path path 219 | end 220 | 221 | # Yield each eligible subdirectory. 222 | def each 223 | Dir["#{path}/[a-z]*"].each do |dir| 224 | if File.directory?(dir) 225 | yield Spec.new(File.basename(dir), dir, label) 226 | end 227 | end 228 | end 229 | 230 | def ==(source) 231 | self.class == source.class && path == source.path 232 | end 233 | end 234 | 235 | class PathFilteredSource < PathSource 236 | attr_reader :filters 237 | 238 | def initialize(label, path, *filters) 239 | super label, File.join(path, "#{filter_str(filters)}generators") 240 | end 241 | 242 | def filter_str(filters) 243 | @filters = filters.first.is_a?(Array) ? filters.first : filters 244 | return "" if @filters.blank? 245 | filter_str = @filters.map {|filter| "#{filter}_"}.join(",") 246 | filter_str += "," 247 | "{#{filter_str}}" 248 | end 249 | 250 | def ==(source) 251 | self.class == source.class && path == source.path && filters == source.filters && label == source.label 252 | end 253 | end 254 | 255 | class AbstractGemSource < Source 256 | def initialize 257 | super :RubyGems 258 | end 259 | end 260 | 261 | # GemPathSource looks for generators within any RubyGem's /{filter_}generators/**/_generator.rb file. 262 | class GemPathSource < AbstractGemSource 263 | attr_accessor :filters 264 | 265 | def initialize(*filters) 266 | super() 267 | @filters = filters 268 | end 269 | 270 | # Yield each generator within rails_generator subdirectories. 271 | def each 272 | generator_full_paths.each do |generator| 273 | yield Spec.new(File.basename(generator).sub(/_generator.rb$/, ''), File.dirname(generator), label) 274 | end 275 | end 276 | 277 | def ==(source) 278 | self.class == source.class && filters == source.filters 279 | end 280 | 281 | private 282 | def generator_full_paths 283 | @generator_full_paths ||= 284 | Gem::Specification.inject({}) do |latest, gem| 285 | hem = latest[gem.name] 286 | latest[gem.name] = gem if hem.nil? or gem.version > hem.version 287 | latest 288 | end.values.inject([]) do |mem, gem| 289 | Dir[gem.full_gem_path + "/#{filter_str}generators/**/*_generator.rb"].each do |generator| 290 | mem << generator 291 | end 292 | mem 293 | end.reverse 294 | end 295 | 296 | def filter_str 297 | @filters = filters.first.is_a?(Array) ? filters.first : filters 298 | return "" if filters.blank? 299 | filter_str = filters.map {|filter| "#{filter}_"}.join(",") 300 | filter_str += "," 301 | "{#{filter_str}}" 302 | end 303 | end 304 | end 305 | -------------------------------------------------------------------------------- /lib/rubigen/manifest.rb: -------------------------------------------------------------------------------- 1 | module RubiGen 2 | 3 | # Manifest captures the actions a generator performs. Instantiate 4 | # a manifest with an optional target object, hammer it with actions, 5 | # then replay or rewind on the object of your choice. 6 | # 7 | # Example: 8 | # manifest = Manifest.new { |m| 9 | # m.make_directory '/foo' 10 | # m.create_file '/foo/bar.txt' 11 | # } 12 | # manifest.replay(creator) 13 | # manifest.rewind(destroyer) 14 | class Manifest 15 | attr_reader :target 16 | 17 | # Take a default action target. Yield self if block given. 18 | def initialize(target = nil) 19 | @target, @actions = target, [] 20 | yield self if block_given? 21 | end 22 | 23 | # Record an action. 24 | def method_missing(action, *args, &block) 25 | @actions << [action, args, block] 26 | end 27 | 28 | # Replay recorded actions. 29 | def replay(target = nil) 30 | send_actions(target || @target, @actions) 31 | end 32 | 33 | # Rewind recorded actions. 34 | def rewind(target = nil) 35 | send_actions(target || @target, @actions.reverse) 36 | end 37 | 38 | # Erase recorded actions. 39 | def erase 40 | @actions = [] 41 | end 42 | 43 | private 44 | def send_actions(target, actions) 45 | actions.each do |method, args, block| 46 | target.send(method, *args, &block) 47 | end 48 | end 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/rubigen/options.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module RubiGen 4 | module Options 5 | def self.included(base) 6 | base.extend(ClassMethods) 7 | class << base 8 | if respond_to?(:inherited) 9 | alias_method :inherited_without_options, :inherited 10 | end 11 | alias_method :inherited, :inherited_with_options 12 | end 13 | end 14 | 15 | module ClassMethods 16 | def inherited_with_options(sub) 17 | inherited_without_options(sub) if respond_to?(:inherited_without_options) 18 | sub.extend(RubiGen::Options::ClassMethods) 19 | end 20 | 21 | def mandatory_options(options = nil) 22 | if options 23 | write_inheritable_attribute(:mandatory_options, options) 24 | else 25 | read_inheritable_attribute(:mandatory_options) or write_inheritable_attribute(:mandatory_options, {}) 26 | end 27 | end 28 | 29 | def default_options(options = nil) 30 | if options 31 | write_inheritable_attribute(:default_options, options) 32 | else 33 | read_inheritable_attribute(:default_options) or write_inheritable_attribute(:default_options, {}) 34 | end 35 | end 36 | 37 | # Merge together our class options. In increasing precedence: 38 | # default_options (class default options) 39 | # runtime_options (provided as argument) 40 | # mandatory_options (class mandatory options) 41 | def full_options(runtime_options = {}) 42 | default_options.merge(runtime_options).merge(mandatory_options) 43 | end 44 | 45 | end 46 | 47 | # Each instance has an options hash that's populated by #parse. 48 | def options 49 | @options ||= {} 50 | end 51 | attr_writer :options 52 | 53 | protected 54 | # Convenient access to class mandatory options. 55 | def mandatory_options 56 | self.class.mandatory_options 57 | end 58 | 59 | # Convenient access to class default options. 60 | def default_options 61 | self.class.default_options 62 | end 63 | 64 | # Merge together our instance options. In increasing precedence: 65 | # default_options (class default options) 66 | # options (instance options) 67 | # runtime_options (provided as argument) 68 | # mandatory_options (class mandatory options) 69 | def full_options(runtime_options = {}) 70 | self.class.full_options(options.merge(runtime_options)) 71 | end 72 | 73 | # Parse arguments into the options hash. Classes may customize 74 | # parsing behavior by overriding these methods: 75 | # #banner Usage: ./script/generate [options] 76 | # #add_options! Options: 77 | # some options.. 78 | # #add_general_options! General Options: 79 | # general options.. 80 | def parse!(args, runtime_options = {}) 81 | self.options = {} 82 | 83 | @option_parser = OptionParser.new do |opt| 84 | opt.banner = banner 85 | add_options!(opt) 86 | add_general_options!(opt) 87 | opt.parse!(args) 88 | end 89 | 90 | return args 91 | ensure 92 | self.options = full_options(runtime_options) 93 | end 94 | 95 | # Raise a usage error. Override usage_message to provide a blurb 96 | # after the option parser summary. 97 | def usage(message = usage_message) 98 | raise UsageError, "#{@option_parser}\n#{message}" 99 | end 100 | 101 | def usage_message 102 | '' 103 | end 104 | 105 | # Override with your own usage banner. 106 | def banner 107 | "Usage: #{$0} [options]" 108 | end 109 | 110 | # Override to add your options to the parser: 111 | # def add_options!(opt) 112 | # opt.on('-v', '--verbose') { |value| options[:verbose] = value } 113 | # end 114 | def add_options!(opt) 115 | end 116 | 117 | # Adds general options like -h and --quiet. Usually don't override. 118 | def add_general_options!(opt) 119 | opt.separator 'General Options:' 120 | 121 | opt.on('-h', '--help', 'Show this help message and quit.') { |v| options[:help] = v } 122 | opt.on('-p', '--pretend', 'Run but do not make any changes.') { |v| options[:pretend] = v } 123 | opt.on('-f', '--force', 'Overwrite files that already exist.') { options[:collision] = :force } 124 | opt.on('-s', '--skip', 'Skip files that already exist.') { options[:collision] = :skip } 125 | opt.on('-q', '--quiet', 'Suppress normal output.') { |v| options[:quiet] = v } 126 | opt.on('-t', '--backtrace', 'Debugging: show backtrace on errors.') { |v| options[:backtrace] = v } 127 | opt.on('-c', '--svn', 'Modify files with subversion. (Note: svn must be in path)') do 128 | options[:svn] = `svn status`.inject({}) do |opt, e| 129 | opt[e.chomp[7..-1]] = true 130 | opt 131 | end 132 | end 133 | opt.on('-g', '--git', 'Modify files with git. (Note: git must be in path)') do 134 | options[:git] = `git status`.inject({:new => {}, :modified => {}}) do |opt, e| 135 | opt[:new][e.chomp[14..-1]] = true if e =~ /new file:/ 136 | opt[:modified][e.chomp[14..-1]] = true if e =~ /modified:/ 137 | opt 138 | end 139 | end 140 | end 141 | 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/rubigen/scripts.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/options' unless Object.const_defined?("RubiGen") && RubiGen.const_defined?("Base") 2 | 3 | module RubiGen 4 | module Scripts 5 | 6 | # Generator scripts handle command-line invocation. Each script 7 | # responds to an invoke! class method which handles option parsing 8 | # and generator invocation. 9 | class Base 10 | include Options 11 | default_options :collision => :ask, :quiet => false 12 | attr_reader :stdout 13 | 14 | # Run the generator script. Takes an array of unparsed arguments 15 | # and a hash of parsed arguments, takes the generator as an option 16 | # or first remaining argument, and invokes the requested command. 17 | def run(args = [], runtime_options = {}) 18 | @stdout = runtime_options[:stdout] || $stdout 19 | begin 20 | parse!(args.dup, runtime_options) 21 | rescue OptionParser::InvalidOption => e 22 | # Don't cry, script. Generators want what you think is invalid. 23 | end 24 | 25 | # Generator name is the only required option. 26 | unless options[:generator] 27 | usage if args.empty? 28 | options[:generator] ||= args.shift 29 | end 30 | 31 | # Look up generator instance and invoke command on it. 32 | RubiGen::Base.instance(options[:generator], args, options).command(options[:command]).invoke! 33 | rescue => e 34 | stdout.puts e 35 | stdout.puts " #{e.backtrace.join("\n ")}\n" if options[:backtrace] 36 | raise SystemExit unless options[:no_exit] 37 | end 38 | 39 | protected 40 | # Override with your own script usage banner. 41 | def banner 42 | "Usage: #{$0} generator [options] [args]" 43 | end 44 | 45 | def usage_message 46 | usage = "\nInstalled Generators\n" 47 | RubiGen::Base.sources.inject([]) do |mem, source| 48 | # Using an association list instead of a hash to preserve order, 49 | # for aesthetic reasons more than anything else. 50 | label = source.label.to_s.capitalize 51 | pair = mem.assoc(label) 52 | mem << (pair = [label, []]) if pair.nil? 53 | pair[1] |= source.names(:visible) 54 | mem 55 | end.each do |label, names| 56 | usage << " #{label}: #{names.join(', ')}\n" unless names.empty? 57 | end 58 | 59 | # TODO - extensible blurbs for rails/newgem/adhearsion etc 60 | # e.g. for rails http://github.com/rails/rails/tree/daee6fd92ac16878f6806c3382a9e74592aa9656/railties/lib/rails_generator/scripts.rb#L50-74 61 | usage << <<-end_blurb 62 | 63 | More are available at http://rubigen.rubyforge.org/ 64 | end_blurb 65 | 66 | usage << <<-end_blurb 67 | Run generate with no arguments for usage information 68 | #{$0} test_unit 69 | 70 | end_blurb 71 | return usage 72 | end 73 | end # Base 74 | 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/rubigen/scripts/destroy.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../scripts' 2 | 3 | module RubiGen::Scripts 4 | class Destroy < Base 5 | mandatory_options :command => :destroy 6 | 7 | protected 8 | def usage_message 9 | usage = "\nInstalled Generators\n" 10 | RubiGen::Base.sources.each do |source| 11 | label = source.label.to_s.capitalize 12 | names = source.names 13 | usage << " #{label}: #{names.join(', ')}\n" unless names.empty? 14 | end 15 | 16 | # TODO - extensible blurbs for rails/newgem/adhearsion etc 17 | # e.g. for rails script/destroy 18 | # http://github.com/rails/rails/tree/daee6fd92ac16878f6806c3382a9e74592aa9656/railties/lib/rails_generator/scripts/destroy.rb 19 | usage << <<-end_blurb 20 | 21 | This script will destroy all files created by the corresponding 22 | script/generate command. For instance, script/destroy test_unit create_post 23 | will delete the appropriate test_create_post.rb file in /test. 24 | 25 | For instructions on finding new generators, run script/generate 26 | end_blurb 27 | return usage 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/rubigen/scripts/generate.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../scripts' 2 | 3 | module RubiGen::Scripts 4 | class Generate < Base 5 | mandatory_options :command => :create 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rubigen/scripts/update.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../scripts' 2 | 3 | module RubiGen::Scripts 4 | class Update < Base 5 | mandatory_options :command => :update 6 | 7 | protected 8 | def banner 9 | "Usage: #{$0} [options] generator" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/rubigen/simple_logger.rb: -------------------------------------------------------------------------------- 1 | module RubiGen 2 | class SimpleLogger # :nodoc: 3 | attr_reader :out 4 | attr_accessor :quiet 5 | 6 | def initialize(out = $stdout) 7 | @out = out 8 | @quiet = false 9 | @level = 0 10 | end 11 | 12 | def log(status, message, &block) 13 | @out.print("%12s %s%s\n" % [status, ' ' * @level, message]) unless quiet 14 | indent(&block) if block_given? 15 | end 16 | 17 | def indent(&block) 18 | @level += 1 19 | if block_given? 20 | begin 21 | block.call 22 | ensure 23 | outdent 24 | end 25 | end 26 | end 27 | 28 | def outdent 29 | @level -= 1 30 | if block_given? 31 | begin 32 | block.call 33 | ensure 34 | indent 35 | end 36 | end 37 | end 38 | 39 | private 40 | def method_missing(method, *args, &block) 41 | log(method.to_s, args.first, &block) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rubigen/spec.rb: -------------------------------------------------------------------------------- 1 | module RubiGen 2 | # A spec knows where a generator was found and how to instantiate it. 3 | # Metadata include the generator's name, its base path, and the source 4 | # which yielded it (PathSource, GemPathSource, etc.) 5 | class Spec 6 | attr_reader :name, :path, :source 7 | 8 | def initialize(name, path, source) 9 | @name, @path, @source, @klass = name, path, source, nil 10 | end 11 | 12 | # Look up the generator class. Require its class file, find the class 13 | # in ObjectSpace, tag it with this spec, and return. 14 | def klass 15 | unless @klass 16 | require class_file 17 | @klass = lookup_class 18 | @klass.spec = self 19 | end 20 | @klass 21 | end 22 | 23 | def class_file 24 | "#{path}/#{name}_generator.rb" 25 | end 26 | 27 | def class_name 28 | "#{name.camelize}Generator" 29 | end 30 | 31 | def usage_file 32 | "#{path}/USAGE" 33 | end 34 | 35 | def visible? 36 | File.exists? usage_file 37 | end 38 | 39 | private 40 | # Search for the first Class descending from RubiGen::Base 41 | # whose name matches the requested class name. 42 | def lookup_class 43 | ObjectSpace.each_object(Class) do |obj| 44 | return obj if valid_superclass?(obj) and 45 | obj.name.split('::').last == class_name 46 | end 47 | raise NameError, "Missing #{class_name} class in #{class_file}" 48 | end 49 | 50 | def valid_superclass?(obj) 51 | valid_generator_superclasses.each do |klass| 52 | return true if obj.ancestors.include?(klass) 53 | end 54 | false 55 | end 56 | 57 | def valid_generator_superclasses 58 | @valid_generator_superclasses ||= [ 59 | "RubiGen::Base", 60 | "Rails::Generator::Base" 61 | ].inject([]) do |list, class_name| 62 | klass = class_name.split("::").inject(Object) do |klass, name| 63 | klass.const_get(name) rescue nil 64 | end 65 | list << klass if klass 66 | list 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/rubigen/version.rb: -------------------------------------------------------------------------------- 1 | module Rubigen 2 | VERSION = "1.5.8" 3 | end -------------------------------------------------------------------------------- /rubigen.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path('../lib/rubigen/version', __FILE__) 3 | 4 | Gem::Specification.new do |gem| 5 | gem.authors = ["Dr Nic Williams", 'Jeremy Kemper', 'Ben Klang'] 6 | gem.email = ["drnicwilliams@gmail.com"] 7 | gem.description = %q{RubiGen - Ruby Generator Framework} 8 | gem.summary = <<-EOS.gsub(/^\s{2}/, '') 9 | A framework to allow Ruby applications to generate file/folder stubs 10 | (like the `rails` command does for Ruby on Rails, and the 'script/generate' 11 | command within a Rails application during development). 12 | EOS 13 | gem.homepage = "http://drnic.github.com/rubigen" 14 | 15 | gem.files = `git ls-files`.split($\) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.name = "rubigen" 19 | gem.require_paths = ["lib"] 20 | gem.version = Rubigen::VERSION 21 | 22 | gem.add_dependency 'activesupport', '>= 2.3.5', "< 3.2.0" 23 | gem.add_development_dependency 'rake' 24 | gem.add_development_dependency 'i18n' 25 | gem.add_development_dependency 'rspec','~>1.3' 26 | gem.add_development_dependency 'mocha','>= 0.9.8' 27 | gem.add_development_dependency 'cucumber','>= 0.6.2' 28 | gem.add_development_dependency 'shoulda','>= 2.10.3' 29 | gem.add_development_dependency 'launchy' 30 | end 31 | -------------------------------------------------------------------------------- /rubygems_generators/application_generator/USAGE: -------------------------------------------------------------------------------- 1 | How to create an Application Generator 2 | ====================================== 3 | 4 | 1. Create a new RubyGem (using newgem or hoe) or you can use an existing one. 5 | 2. Run the generator: script/generate application_generator foobar 6 | 3. Using the generated test class, assert what directories, files, classes etc should 7 | be generated. 8 | 4. Add these files into the app_genearators/foobar/templates folder. Your files can use 9 | ERb (that is, <%= ... %>). 10 | 5. Specify who the files in /templates are copied/templated at generation time within app_generators/foobar/foobar_generators.rb 's #manifest method. 11 | Use m.file for files to copy over. 12 | Use m.template for files containing ERb. Create attr_reader accessors for any variables your 13 | templates need access to. 14 | 6. Run unit tests. 15 | 7. If your application generator uses other generators (m.dependency "gen-name", [arg1, arg2], :option1 => 'value') 16 | then you must add this into the generated bin/foobar file. 17 | For example, if you wanted to use a rubygems and/or rails generator, then replace the use_application_sources! 18 | call in bin/foobar: 19 | 20 | RubiGen::Base.use_application_sources! :rubygems, :rails 21 | 22 | Without this, RubiGen will not be able to find your dependent generators. 23 | 8. Update your Manifest.txt with the new files (if you are using Hoe; using newgem? it uses Hoe; so you need to do this) 24 | 9. Build and install your RubyGem locally. Run: rake install_gem 25 | 10. Test your foobar application to ensure that it connects to the generator correctly and generates files. 26 | 11. Done. 27 | 28 | You will see this message again after running the generator and by using the -h/--help option. -------------------------------------------------------------------------------- /rubygems_generators/application_generator/application_generator_generator.rb: -------------------------------------------------------------------------------- 1 | class ApplicationGeneratorGenerator < RubiGen::Base 2 | 3 | default_options 4 | 5 | attr_reader :name, :class_name, :app_model_name, :generator_path, :scopes, :scope_str 6 | 7 | def initialize(runtime_args, runtime_options = {}) 8 | super 9 | usage if args.empty? 10 | @name = args.shift 11 | @scopes = args 12 | @scopes = [name] if scopes.blank? 13 | @scope_str = scopes.map { |scope| "'#{scope}'" }.join ', ' 14 | @app_model_name = name.camelize 15 | @class_name = "#{name}_generator".camelize 16 | @generator_path = "app_generators" 17 | extract_options 18 | end 19 | 20 | def manifest 21 | path = "#{generator_path}/#{name}" 22 | record do |m| 23 | # Ensure appropriate generators folder exists 24 | m.directory "#{path}/templates" 25 | m.directory "bin" 26 | m.directory "test" 27 | 28 | # Generator stub 29 | m.template "generator.rb", "#{path}/#{name}_generator.rb" 30 | m.template "test.rb", "test/test_#{name}_generator.rb" 31 | m.file "test_generator_helper.rb", "test/test_generator_helper.rb" 32 | m.file "usage", "#{path}/USAGE" 33 | m.template "bin", "bin/#{name}" 34 | m.readme 'readme' 35 | end 36 | end 37 | 38 | protected 39 | def banner 40 | <<-EOS 41 | Creates a application generator stub within your RubyGem. 42 | 43 | Application Generators are used to create new applications 44 | from scratch, and create the default scaffolding for 45 | an application (directories) plus any starter files 46 | that are useful to developers. 47 | 48 | USAGE: #{$0} #{spec.name} name 49 | EOS 50 | end 51 | 52 | def add_options!(opts) 53 | # opts.separator '' 54 | # opts.separator 'Options:' 55 | # opts.on("-a", "--author=\"Your Name\"", String, 56 | # "Generated app file will include your name.", 57 | # "Default: none") { |o| options[:author] = o } 58 | end 59 | 60 | def extract_options 61 | end 62 | 63 | end -------------------------------------------------------------------------------- /rubygems_generators/application_generator/templates/bin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'rubigen' 5 | 6 | if %w(-v --version).include? ARGV.first 7 | require '<%= name %>' 8 | puts "#{File.basename($0)} #{<%= app_model_name %>::VERSION}" 9 | exit(0) 10 | end 11 | 12 | require 'rubigen/scripts/generate' 13 | source = RubiGen::PathSource.new(:application, 14 | File.join(File.dirname(__FILE__), "../app_generators")) 15 | RubiGen::Base.reset_sources 16 | RubiGen::Base.append_sources source 17 | RubiGen::Scripts::Generate.new.run(ARGV, :generator => '<%= name %>') 18 | -------------------------------------------------------------------------------- /rubygems_generators/application_generator/templates/generator.rb: -------------------------------------------------------------------------------- 1 | class <%= class_name %> < RubiGen::Base 2 | 3 | DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], 4 | Config::CONFIG['ruby_install_name']) 5 | 6 | default_options :author => nil 7 | 8 | attr_reader :name 9 | 10 | def initialize(runtime_args, runtime_options = {}) 11 | super 12 | usage if args.empty? 13 | @destination_root = File.expand_path(args.shift) 14 | @name = base_name 15 | extract_options 16 | end 17 | 18 | def manifest 19 | record do |m| 20 | # Ensure appropriate folder(s) exists 21 | m.directory '' 22 | BASEDIRS.each { |path| m.directory path } 23 | 24 | # Create stubs 25 | # m.template "template.rb", "some_file_after_erb.rb" 26 | # m.template_copy_each ["template.rb", "template2.rb"] 27 | # m.file "file", "some_file_copied" 28 | # m.file_copy_each ["path/to/file", "path/to/file2"] 29 | 30 | m.dependency "install_rubigen_scripts", [destination_root, <%= scope_str %>], 31 | :shebang => options[:shebang], :collision => :force 32 | end 33 | end 34 | 35 | protected 36 | def banner 37 | <<-EOS 38 | Creates a ... 39 | 40 | USAGE: #{spec.name} name 41 | EOS 42 | end 43 | 44 | def add_options!(opts) 45 | opts.separator '' 46 | opts.separator 'Options:' 47 | # For each option below, place the default 48 | # at the top of the file next to "default_options" 49 | # opts.on("-a", "--author=\"Your Name\"", String, 50 | # "Some comment about this option", 51 | # "Default: none") { |o| options[:author] = o } 52 | opts.on("-v", "--version", "Show the #{File.basename($0)} version number and quit.") 53 | end 54 | 55 | def extract_options 56 | # for each option, extract it into a local variable (and create an "attr_reader :author" at the top) 57 | # Templates can access these value via the attr_reader-generated methods, but not the 58 | # raw instance variable value. 59 | # @author = options[:author] 60 | end 61 | 62 | # Installation skeleton. Intermediate directories are automatically 63 | # created so don't sweat their absence here. 64 | BASEDIRS = %w( 65 | lib 66 | log 67 | script 68 | test 69 | tmp 70 | ) 71 | end -------------------------------------------------------------------------------- /rubygems_generators/application_generator/templates/readme: -------------------------------------------------------------------------------- 1 | How to create a Generator (Application Generator) 2 | ====================================== 3 | 4 | 1. DONE - Run the generator: script/generate application_generator myapp 5 | 2. Using the generated test class, assert what directories, files, classes etc should 6 | be generated. 7 | 3. Add these files into the app_genearators/myapp/templates folder. Your files can use 8 | ERb (that is, <%= ... %>). 9 | 4. Specify who the files in /templates are copied/templated at generation time within 10 | app_genearators/myapp/myapp_generators.rb's #manifest method. 11 | Use m.file for files to copy over. 12 | Use m.template for files containing ERb. Create attr_reader accessors for any variables 13 | your templates need access to. 14 | 5. Run unit tests. 15 | 6. If your application generator uses other generators (called 'dependencies') 16 | e.g. (m.dependency "generator-from-rubygems", [arg1, arg2], options) 17 | then you must add this generators' scope into the executable bin/myapp. 18 | For example, if you wanted to use a rubygems and/or rails generator, then 19 | replace the use_application_sources! call in bin/myapp: 20 | 21 | RubiGen::Base.use_application_sources! :rubygems, :rails 22 | 23 | Without this, RubiGen will not be able to find your dependent generators. 24 | 7. Update your Manifest.txt with the new files (rake manifest:refresh) 25 | 8. Build and install your RubyGem locally. Run: rake install_gem 26 | 9. Go to a directory where you can create new test apps. 27 | 10. Run 'myapp [path]' and your application skeleton will be created. 28 | 29 | See this message again using the -h/--help option. -------------------------------------------------------------------------------- /rubygems_generators/application_generator/templates/test.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | class Test<%= class_name %> < Test::Unit::TestCase 4 | include RubiGen::GeneratorTestHelper 5 | 6 | def setup 7 | bare_setup 8 | end 9 | 10 | def teardown 11 | bare_teardown 12 | end 13 | 14 | # Some generator-related assertions: 15 | # assert_generated_file(name, &block) # block passed the file contents 16 | # assert_directory_exists(name) 17 | # assert_generated_class(name, &block) 18 | # assert_generated_module(name, &block) 19 | # assert_generated_test_for(name, &block) 20 | # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file 21 | # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet) 22 | # 23 | # Other helper methods are: 24 | # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files) 25 | # bare_setup - place this in setup method to create the APP_ROOT folder for each test 26 | # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test 27 | 28 | def test_generator_without_options 29 | run_generator('<%= name %>', [APP_ROOT], sources) 30 | assert_directory_exists "path/to/included/folder" 31 | assert_generated_file "path/to/included/folder/some_file" 32 | end 33 | 34 | private 35 | def sources 36 | [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path)) 37 | ] 38 | end 39 | 40 | def generator_path 41 | "<%= generator_path %>" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /rubygems_generators/application_generator/templates/test_generator_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require File.dirname(__FILE__) + '/test_helper' 3 | rescue LoadError 4 | require 'test/unit' 5 | end 6 | require 'fileutils' 7 | 8 | # Must set before requiring generator libs. 9 | TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT) 10 | PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME) 11 | app_root = File.join(TMP_ROOT, PROJECT_NAME) 12 | if defined?(APP_ROOT) 13 | APP_ROOT.replace(app_root) 14 | else 15 | APP_ROOT = app_root 16 | end 17 | if defined?(RAILS_ROOT) 18 | RAILS_ROOT.replace(app_root) 19 | else 20 | RAILS_ROOT = app_root 21 | end 22 | 23 | begin 24 | require 'rubigen' 25 | rescue LoadError 26 | require 'rubygems' 27 | require 'rubigen' 28 | end 29 | require 'rubigen/helpers/generator_test_helper' 30 | -------------------------------------------------------------------------------- /rubygems_generators/application_generator/templates/usage: -------------------------------------------------------------------------------- 1 | Description: 2 | 3 | 4 | Usage: 5 | 6 | -------------------------------------------------------------------------------- /rubygems_generators/component_generator/USAGE: -------------------------------------------------------------------------------- 1 | How to create a Generator (aka Component Generator) 2 | ====================================== 3 | 4 | 1. Run this generator: script/generate component_generator mygen scope 5 | Where "mygen" is the name of the generator, and scope is scope restriction. 6 | A scope of "rubygems" means this generator will only be available to RubyGems developers. 7 | A scope of "rails" means the generator is only accessible to Rails applications 8 | 2. Using the generated test class, assert what directories, files, classes etc should 9 | be generated. 10 | 3. Add these files into the scope_genearators/mygen/templates folder. Your files can use 11 | ERb (that is, <%= ... %>), and are called 'templates'. 12 | 4. Specify the files in /templates are copied/templated at generation time within rubygems_genearators/mygen/mygen_generators.rb's #manifest method. 13 | Use m.file for files to copy over. 14 | Use m.template for files containing ERb. Create attr_reader accessors for any variables your 15 | templates need access to. 16 | 5. Run unit tests. 17 | 6. Add usage information in the USAGE file. 18 | 7. Update your Manifest.txt with the new files (if you are using Hoe) 19 | 8. Build and install your RubyGem locally. Run: rake install_gem 20 | 9. Go to a work area for your scope (e.g. go to the root folder of a Rails application 21 | to use a "rails" scoped generator) 22 | 10. Run "script/generate" and your generator should appear in the list of available generators. 23 | 11. Run "script/generate mygen" to see the options and usage information for your generator. 24 | 12. Run "script/generator mygen arguments" to execute the generator. 25 | 26 | You will see this message again when you run the generator and by using the -h/--help option. -------------------------------------------------------------------------------- /rubygems_generators/component_generator/component_generator_generator.rb: -------------------------------------------------------------------------------- 1 | class ComponentGeneratorGenerator < RubiGen::Base 2 | 3 | default_options 4 | 5 | attr_reader :name, :class_name 6 | attr_reader :generator_type, :generator_path 7 | 8 | def initialize(runtime_args, runtime_options = {}) 9 | super 10 | usage if args.empty? 11 | @name = args.shift 12 | @class_name = "#{name}_generator".camelize 13 | @generator_type = args.shift # optional 14 | @generator_path = @generator_type ? "#{generator_type}_generators" : "generators" 15 | extract_options 16 | end 17 | 18 | def manifest 19 | path = "#{generator_path}/#{name}" 20 | record do |m| 21 | # Ensure appropriate generators folder exists 22 | m.directory "#{path}/templates" 23 | m.directory "test" 24 | 25 | # Generator stub 26 | m.template generator, "#{path}/#{name}_generator.rb" 27 | m.template "test.rb", "test/test_#{name}_generator.rb" 28 | m.file "test_generator_helper.rb", "test/test_generator_helper.rb" 29 | m.file "usage", "#{path}/USAGE" 30 | m.readme 'readme' 31 | end 32 | end 33 | 34 | def generator 35 | case (generator_type.to_sym rescue nil) 36 | when :rails 37 | "rails_generator.rb" 38 | else 39 | "generator.rb" 40 | end 41 | end 42 | 43 | def superclass_name 44 | case (generator_type.to_sym rescue nil) 45 | when :rails 46 | "Rails::Generator::NamedBase" 47 | else 48 | "RubiGen::Base" 49 | end 50 | end 51 | 52 | def superclass_requirement 53 | case (generator_type.to_sym rescue nil) 54 | when :rails 55 | ["rails_generator"] 56 | else 57 | [] 58 | end 59 | end 60 | 61 | protected 62 | def banner 63 | <<-EOS 64 | Creates a generator stub within your RubyGem. 65 | 66 | USAGE: #{$0} #{spec.name} name [generator_type] 67 | EOS 68 | end 69 | 70 | def add_options!(opts) 71 | # opts.separator '' 72 | # opts.separator 'Options:' 73 | # opts.on("-a", "--author=\"Your Name\"", String, 74 | # "Generated app file will include your name.", 75 | # "Default: none") { |o| options[:author] = 0 } 76 | end 77 | 78 | def extract_options 79 | end 80 | end -------------------------------------------------------------------------------- /rubygems_generators/component_generator/templates/generator.rb: -------------------------------------------------------------------------------- 1 | class <%= class_name %> < <%= superclass_name %> 2 | 3 | default_options :author => nil 4 | 5 | attr_reader :name 6 | 7 | def initialize(runtime_args, runtime_options = {}) 8 | super 9 | usage if args.empty? 10 | @name = args.shift 11 | extract_options 12 | end 13 | 14 | def manifest 15 | record do |m| 16 | # Ensure appropriate folder(s) exists 17 | m.directory 'some_folder' 18 | 19 | # Create stubs 20 | # m.template "template.rb.erb", "some_file_after_erb.rb" 21 | # m.template_copy_each ["template.rb", "template2.rb"] 22 | # m.template_copy_each ["template.rb", "template2.rb"], "some/path" 23 | # m.file "file", "some_file_copied" 24 | # m.file_copy_each ["path/to/file", "path/to/file2"] 25 | # m.file_copy_each ["path/to/file", "path/to/file2"], "some/path" 26 | end 27 | end 28 | 29 | protected 30 | def banner 31 | <<-EOS 32 | Creates a ... 33 | 34 | USAGE: #{$0} #{spec.name} name 35 | EOS 36 | end 37 | 38 | def add_options!(opts) 39 | # opts.separator '' 40 | # opts.separator 'Options:' 41 | # For each option below, place the default 42 | # at the top of the file next to "default_options" 43 | # opts.on("-a", "--author=\"Your Name\"", String, 44 | # "Some comment about this option", 45 | # "Default: none") { |o| options[:author] = o } 46 | # opts.on("-v", "--version", "Show the #{File.basename($0)} version number and quit.") 47 | end 48 | 49 | def extract_options 50 | # for each option, extract it into a local variable (and create an "attr_reader :author" at the top) 51 | # Templates can access these value via the attr_reader-generated methods, but not the 52 | # raw instance variable value. 53 | # @author = options[:author] 54 | end 55 | end -------------------------------------------------------------------------------- /rubygems_generators/component_generator/templates/rails_generator.rb: -------------------------------------------------------------------------------- 1 | class <%= class_name %> < <%= superclass_name %> 2 | def manifest 3 | record do |m| 4 | # Ensure appropriate folder(s) exists 5 | m.directory 'some_folder' 6 | 7 | # Create stubs 8 | # m.template "template.rb", "some_file_after_erb.rb" 9 | # m.file "file", "some_file_copied" 10 | end 11 | end 12 | 13 | protected 14 | def banner 15 | <<-EOS 16 | Creates a ... 17 | 18 | USAGE: #{$0} #{spec.name} name 19 | EOS 20 | end 21 | end -------------------------------------------------------------------------------- /rubygems_generators/component_generator/templates/readme: -------------------------------------------------------------------------------- 1 | How to create a Generator (Component Generator) 2 | ====================================== 3 | 4 | 1. DONE - Run the generator: script/generate component_generator mygen 5 | 2. Using the generated test class, assert what directories, files, classes etc should 6 | be generated. 7 | 3. Add these files into the *scope*_generators/mygen/templates folder. Your files can use 8 | ERb (that is, <%= ... %>), and are called 'templates'. 9 | 4. Specify who the files in /templates are copied/templated at generation time within 10 | app_generators/mygen/mygen_generators.rb 's #manifest method. 11 | * Use m.file for files to copy over. 12 | * Use m.template for files containing ERb. 13 | Create attr_reader accessors for any variables your templates need access to. 14 | 5. Run unit tests. 15 | 6. Add usage information in the USAGE file. 16 | 7. Update your Manifest.txt with the new files (if you are using Hoe) 17 | 8. Build and install your RubyGem locally. Run: rake install_gem 18 | 9. Go to a work area whose script/generate for your scope (e.g. go to the root folder 19 | of a Rails application to use a 'rails' scoped generator. 20 | 10. Run "script/generate" and your new generator should appear in the list of available generators. 21 | 11. Run "script/generate mygen" to see the options and usage information for your generator. 22 | 12. Run "script/generator mygen arguments" to execute the generator. 23 | 13. Done. 24 | 25 | See this message again using the -h/--help option. -------------------------------------------------------------------------------- /rubygems_generators/component_generator/templates/test.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | <% for requirement in superclass_requirement -%> 4 | require '<%= requirement %>' 5 | <% end -%> 6 | 7 | class Test<%= class_name %> < Test::Unit::TestCase 8 | include RubiGen::GeneratorTestHelper 9 | 10 | def setup 11 | bare_setup 12 | end 13 | 14 | def teardown 15 | bare_teardown 16 | end 17 | 18 | # Some generator-related assertions: 19 | # assert_generated_file(name, &block) # block passed the file contents 20 | # assert_directory_exists(name) 21 | # assert_generated_class(name, &block) 22 | # assert_generated_module(name, &block) 23 | # assert_generated_test_for(name, &block) 24 | # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file 25 | # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet) 26 | # 27 | # Other helper methods are: 28 | # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files) 29 | # bare_setup - place this in setup method to create the APP_ROOT folder for each test 30 | # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test 31 | 32 | def test_generator_without_options 33 | name = "myapp" 34 | run_generator('<%= name %>', [name], sources) 35 | assert_directory_exists "some/directory" 36 | assert_generated_file "some_file" 37 | end 38 | 39 | private 40 | def sources 41 | [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path)) 42 | ] 43 | end 44 | 45 | def generator_path 46 | "<%= generator_path %>" 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /rubygems_generators/component_generator/templates/test_generator_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require File.dirname(__FILE__) + '/test_helper' 3 | rescue LoadError 4 | require 'test/unit' 5 | end 6 | require 'fileutils' 7 | 8 | # Must set before requiring generator libs. 9 | TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT) 10 | PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME) 11 | app_root = File.join(TMP_ROOT, PROJECT_NAME) 12 | if defined?(APP_ROOT) 13 | APP_ROOT.replace(app_root) 14 | else 15 | APP_ROOT = app_root 16 | end 17 | if defined?(RAILS_ROOT) 18 | RAILS_ROOT.replace(app_root) 19 | else 20 | RAILS_ROOT = app_root 21 | end 22 | 23 | begin 24 | require 'rubigen' 25 | rescue LoadError 26 | require 'rubygems' 27 | require 'rubigen' 28 | end 29 | require 'rubigen/helpers/generator_test_helper' 30 | -------------------------------------------------------------------------------- /rubygems_generators/component_generator/templates/usage: -------------------------------------------------------------------------------- 1 | Description: 2 | 3 | 4 | Usage: 5 | 6 | -------------------------------------------------------------------------------- /script/console: -------------------------------------------------------------------------------- 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/rubigen.rb'}" 9 | puts "Loading rubigen gem" 10 | exec "#{irb} #{libs} --simple-prompt" -------------------------------------------------------------------------------- /script/destroy: -------------------------------------------------------------------------------- 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] 14 | RubiGen::Scripts::Destroy.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 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] 14 | RubiGen::Scripts::Generate.new.run(ARGV) 15 | -------------------------------------------------------------------------------- /script/txt2html: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.push File.join(File.dirname(__FILE__), '..') 3 | 4 | require 'rubygems' 5 | begin 6 | require 'newgem' 7 | rescue LoadError 8 | puts "\n\nGenerating the website requires the newgem RubyGem" 9 | puts "Install: gem install newgem\n\n" 10 | exit(1) 11 | end 12 | require 'redcloth' 13 | require 'syntax/convertors/html' 14 | require 'erb' 15 | require File.dirname(__FILE__) + '/../lib/rubigen' 16 | 17 | version = RubiGen::VERSION 18 | download = 'http://rubyforge.org/projects/rubigen' 19 | 20 | class Fixnum 21 | def ordinal 22 | # teens 23 | return 'th' if (10..19).include?(self % 100) 24 | # others 25 | case self % 10 26 | when 1 then return 'st' 27 | when 2 then return 'nd' 28 | when 3 then return 'rd' 29 | else return 'th' 30 | end 31 | end 32 | end 33 | 34 | class Time 35 | def pretty 36 | return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}" 37 | end 38 | end 39 | 40 | def convert_syntax(syntax, source) 41 | return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^
|
$!,'') 42 | end 43 | 44 | if ARGV.length >= 1 45 | src, template = ARGV 46 | template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml') 47 | 48 | else 49 | puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html") 50 | exit! 51 | end 52 | 53 | template = ERB.new(File.open(template).read) 54 | 55 | title = nil 56 | body = nil 57 | File.open(src) do |fsrc| 58 | title_text = fsrc.readline 59 | body_text = fsrc.read 60 | syntax_items = [] 61 | body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)!m){ 62 | ident = syntax_items.length 63 | element, syntax, source = $1, $2, $3 64 | syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}" 65 | "syntax-temp-#{ident}" 66 | } 67 | title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip 68 | body = RedCloth.new(body_text).to_html 69 | body.gsub!(%r!(?:
)?syntax-temp-(\d+)(?:
)?!){ syntax_items[$1.to_i] } 70 | end 71 | stat = File.stat(src) 72 | created = stat.ctime 73 | modified = stat.mtime 74 | 75 | $stdout << template.result(binding) 76 | -------------------------------------------------------------------------------- /test/test_application_generator_generator.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | class TestApplicationGeneratorGenerator < Test::Unit::TestCase 4 | include RubiGen::GeneratorTestHelper 5 | 6 | def setup 7 | bare_setup 8 | end 9 | 10 | def teardown 11 | bare_teardown 12 | end 13 | 14 | # Some generator-related assertions: 15 | # assert_generated_file(name, &block) # block passed the file contents 16 | # assert_directory_exists(name) 17 | # assert_generated_class(name, &block) 18 | # assert_generated_module(name, &block) 19 | # assert_generated_test_for(name, &block) 20 | # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file 21 | # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet) 22 | # 23 | # Other helper methods are: 24 | # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files) 25 | # bare_setup - place this in setup method to create the APP_ROOT folder for each test 26 | # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test 27 | 28 | def test_generator_without_options 29 | name = "genname" 30 | run_generator('application_generator', [name], sources) 31 | 32 | assert_directory_exists "app_generators" 33 | assert_directory_exists "app_generators/#{name}" 34 | assert_directory_exists "app_generators/#{name}/templates" 35 | assert_generated_file "app_generators/#{name}/USAGE" 36 | assert_generated_class "app_generators/#{name}/#{name}_generator" do |body| 37 | assert_has_method "manifest" 38 | end 39 | assert_generated_test_for("#{name}_generator") do |body| 40 | assert_has_method body, "setup" 41 | assert_has_method body, "teardown" 42 | assert_has_method body, "test_generator_without_options" 43 | assert_has_method body, "sources" 44 | assert_has_method body, "generator_path" 45 | end 46 | 47 | assert_directory_exists "bin" 48 | assert_generated_file "bin/#{name}" 49 | end 50 | 51 | private 52 | def sources 53 | [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path)) 54 | ] 55 | end 56 | 57 | def generator_path 58 | "rubygems_generators" 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/test_component_generator_generator.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | class TestGenerateComponentGenerator < Test::Unit::TestCase 4 | include RubiGen::GeneratorTestHelper 5 | 6 | def setup 7 | bare_setup 8 | end 9 | 10 | def teardown 11 | bare_teardown 12 | end 13 | 14 | def test_generator_without_options 15 | name = "genname" 16 | run_generator('component_generator', [name], sources) 17 | assert_generated_file("generators/#{name}/#{name}_generator.rb") 18 | assert_generated_file("generators/#{name}/USAGE") 19 | assert_generated_file("test/test_#{name}_generator.rb") 20 | assert_generated_file("test/test_generator_helper.rb") 21 | assert_directory_exists("generators/#{name}/templates") 22 | assert_generated_class("generators/#{name}/#{name}_generator") do |body| 23 | # assert_has_method body, "initialize" # as_has_m cannot pickup initialize(...) only initialize 24 | assert_has_method body, "manifest" 25 | 26 | gen_class, superclass = body.match(%r{class ([\w:_]+) < ([\w:_]+)})[1..2] 27 | assert_equal("GennameGenerator", gen_class) 28 | assert_equal("RubiGen::Base", superclass) 29 | end 30 | assert_generated_class("test/test_#{name}_generator") do |body| 31 | assert_has_method body, "setup" 32 | assert_has_method body, "teardown" 33 | assert_has_method body, "test_generator_without_options" 34 | assert_has_method body, "sources" 35 | assert_has_method body, "generator_path" 36 | end 37 | end 38 | 39 | def test_generator_with_generator_type 40 | name = "genname" 41 | gen_type = "fooapp" 42 | run_generator('component_generator', [name, gen_type], sources) 43 | 44 | assert_generated_file "#{gen_type}_generators/#{name}/#{name}_generator.rb" 45 | assert_generated_file "#{gen_type}_generators/#{name}/USAGE" 46 | assert_generated_file "test/test_#{name}_generator.rb" 47 | assert_generated_file "test/test_generator_helper.rb" 48 | assert_directory_exists "#{gen_type}_generators/#{name}/templates" 49 | assert_generated_class "#{gen_type}_generators/#{name}/#{name}_generator" do |body| 50 | # assert_has_method body, "initialize" # as_has_m cannot pickup initialize(...) only initialize 51 | assert_has_method body, "manifest" 52 | gen_class, superclass = body.match(%r{class ([\w:_]+) < ([\w:_]+)})[1..2] 53 | assert_equal("GennameGenerator", gen_class) 54 | assert_equal("RubiGen::Base", superclass) 55 | end 56 | assert_generated_class "test/test_#{name}_generator" do |body| 57 | assert_has_method body, "setup" 58 | assert_has_method body, "teardown" 59 | assert_has_method body, "test_generator_without_options" 60 | assert_has_method body, "sources" 61 | assert_has_method body, "generator_path" 62 | end 63 | end 64 | 65 | def test_generator_with_rails_generator_type 66 | name = "genname" 67 | gen_type = "rails" 68 | run_generator('component_generator', [name, gen_type], sources) 69 | 70 | assert_generated_file "#{gen_type}_generators/#{name}/#{name}_generator.rb" 71 | assert_generated_file "#{gen_type}_generators/#{name}/USAGE" 72 | assert_generated_file "test/test_#{name}_generator.rb" 73 | assert_generated_file "test/test_generator_helper.rb" 74 | assert_directory_exists "#{gen_type}_generators/#{name}/templates" 75 | assert_generated_class "#{gen_type}_generators/#{name}/#{name}_generator" do |body| 76 | # assert_has_method body, "initialize" # as_has_m cannot pickup initialize(...) only initialize 77 | assert_has_method body, "manifest" 78 | gen_class, superclass = body.match(%r{class ([\w:_]+) < ([\w:_]+)})[1..2] 79 | assert_equal("GennameGenerator", gen_class) 80 | assert_equal("Rails::Generator::NamedBase", superclass) 81 | end 82 | assert_generated_class "test/test_#{name}_generator" do |body| 83 | assert_has_method body, "setup" 84 | assert_has_method body, "teardown" 85 | assert_has_method body, "test_generator_without_options" 86 | assert_has_method body, "sources" 87 | assert_has_method body, "generator_path" 88 | end 89 | end 90 | 91 | private 92 | def sources 93 | [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"../#{generator_path}")) 94 | ] 95 | end 96 | 97 | def generator_path 98 | "rubygems_generators" 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /test/test_generate_builtin_application.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | class TestGenerateBuiltinApplication < Test::Unit::TestCase 4 | include RubiGen::GeneratorTestHelper 5 | 6 | def setup 7 | bare_setup 8 | end 9 | 10 | def teardown 11 | bare_teardown 12 | end 13 | 14 | def test_ruby_app 15 | run_generator('ruby_app', [APP_ROOT], sources) 16 | assert_generated_file("Rakefile") 17 | assert_generated_file("README.txt") 18 | assert_generated_file("lib/#{PROJECT_NAME}.rb") 19 | assert_generated_file("test/test_helper.rb") 20 | assert_generated_file("script/generate") 21 | assert_generated_file("script/destroy") 22 | 23 | assert_generated_module("lib/#{PROJECT_NAME}") 24 | end 25 | 26 | private 27 | def sources 28 | [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path)), 29 | RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", "generators")) 30 | ] 31 | end 32 | 33 | def generator_path 34 | "app_generators" 35 | end 36 | end -------------------------------------------------------------------------------- /test/test_generate_builtin_test_unit.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | class TestGenerateBuiltinTestUnit < Test::Unit::TestCase 4 | include RubiGen::GeneratorTestHelper 5 | 6 | def setup 7 | bare_teardown 8 | end 9 | 10 | def teardown 11 | bare_teardown 12 | end 13 | 14 | def test_with_no_options 15 | run_generator('test_unit', %w[AccountReceiver], sources) 16 | assert_generated_file("test/test_account_receiver.rb") 17 | assert_generated_class("test/test_account_receiver", "Test::Unit::TestCase") do 18 | assert_has_method("setup") 19 | assert_has_method("teardown") 20 | assert_has_method("test_truth") 21 | end 22 | end 23 | 24 | private 25 | def sources 26 | [RubiGen::PathSource.new(:testing, File.join(File.dirname(__FILE__),"..", generator_path)) 27 | ] 28 | end 29 | 30 | def generator_path 31 | "test_unit_generators" 32 | end 33 | end -------------------------------------------------------------------------------- /test/test_generator_helper.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/test_helper' 2 | require 'fileutils' 3 | 4 | # Must set before requiring generator libs. 5 | TMP_ROOT = File.expand_path(File.dirname(__FILE__) + "/../tmp") unless defined?(TMP_ROOT) 6 | PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME) 7 | app_root = File.join(TMP_ROOT, PROJECT_NAME) 8 | if defined?(APP_ROOT) 9 | APP_ROOT.replace(app_root) 10 | else 11 | APP_ROOT = app_root 12 | end 13 | 14 | FileUtils.mkdir_p(APP_ROOT) 15 | 16 | require 'rubigen/helpers/generator_test_helper' 17 | 18 | FileUtils.rm_rf(File.dirname(__FILE__) + "/tmp") # seem to be issues on runcoderun with a test/tmp/... folder still -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'shoulda' 3 | require 'test/unit' 4 | require File.dirname(__FILE__) + '/../lib/rubigen' 5 | require 'rubigen/helpers/generator_test_helper' 6 | include RubiGen 7 | require 'mocha' 8 | -------------------------------------------------------------------------------- /test/test_install_rubigen_scripts_generator.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "test_generator_helper.rb") 2 | 3 | class TestInstallRubigenScriptsGenerator < Test::Unit::TestCase 4 | include RubiGen::GeneratorTestHelper 5 | 6 | def setup 7 | bare_setup 8 | end 9 | 10 | def teardown 11 | bare_teardown 12 | end 13 | 14 | # Some generator-related assertions: 15 | # assert_generated_file(name, &block) # block passed the file contents 16 | # assert_directory_exists(name) 17 | # assert_generated_class(name, &block) 18 | # assert_generated_module(name, &block) 19 | # assert_generated_test_for(name, &block) 20 | # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file 21 | # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet) 22 | # 23 | # Other helper methods are: 24 | # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files) 25 | # bare_setup - place this in setup method to create the APP_ROOT folder for each test 26 | # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test 27 | 28 | def test_install_rubigen_scripts_should_create_script_generate 29 | run_generator('install_rubigen_scripts', [APP_ROOT] + %w(rubygems foobar), sources) 30 | assert_directory_exists "script" 31 | assert_generated_file "script/generate" 32 | assert_generated_file "script/destroy" 33 | end 34 | 35 | private 36 | def sources 37 | [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path)), 38 | RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", "generators")) 39 | ] 40 | end 41 | 42 | def generator_path 43 | "app_generators" 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/test_lookup.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/test_generator_helper" 2 | 3 | class TestLookup < Test::Unit::TestCase 4 | include RubiGen 5 | 6 | def setup 7 | RubiGen::Base.reset_sources 8 | end 9 | 10 | def test_lookup_component 11 | assert_nothing_raised(GeneratorError, "Could not find install_rubigen_scripts generator") { RubiGen::Base.lookup('install_rubigen_scripts') } 12 | end 13 | 14 | def test_lookup_unknown_component 15 | assert_raise(GeneratorError, "Should not find generator") { RubiGen::Base.lookup('dummy') } 16 | end 17 | 18 | # There are 5 sources of generators 19 | # * HOME/.rubigen/generators 20 | # * APP_ROOT/generators 21 | # * APP_ROOT/vendor/generators 22 | # * APP_ROOT/vendor/plugins/.../ 23 | # * RubyGems internal /generators folder 24 | # 25 | # Note, this differs from Rails generator: 26 | # * RubyGems whose name is suffixed with _generator are not loaded (e.g. ajax_scaffold_generator) 27 | def test_sources 28 | sources = RubiGen::Base.sources 29 | assert(sources.find do |source| 30 | source.path =~ /\.rubigen\/generators$/ if source.respond_to? :path 31 | end, "One source should be HOME/.rubigen/generators") 32 | 33 | assert(sources.find do |source| 34 | source.path =~ /#{::APP_ROOT}\/generators$/ if source.respond_to? :path 35 | end, "One source should be APP_ROOT/generators") 36 | 37 | assert(sources.find do |source| 38 | source.path =~ /#{::APP_ROOT}\/vendor\/generators$/ if source.respond_to? :path 39 | end, "One source should be APP_ROOT/vendor/generators") 40 | 41 | assert(sources.find do |source| 42 | source.path =~ /#{::APP_ROOT}\/vendor\/plugins\/.*\/generators$/ if source.respond_to? :path 43 | end, "One source should be APP_ROOT/vendor/plugins/.../generators") 44 | 45 | assert(sources.find do |source| 46 | source.is_a?(GemPathSource) 47 | end, "One source should be RubyGems containing generators") 48 | 49 | end 50 | 51 | def test_unscoped_gem_path 52 | source = GemPathSource.new 53 | assert_equal("", source.send(:filter_str)) 54 | end 55 | 56 | def test_scoped_gem_path 57 | source = GemPathSource.new("rubygems") 58 | assert_equal("{rubygems_,}", source.send(:filter_str)) 59 | end 60 | 61 | def test_alternate_scoped_gem_path 62 | source = GemPathSource.new(:rubygems, :ruby) 63 | assert_equal("{rubygems_,ruby_,}", source.send(:filter_str)) 64 | end 65 | 66 | def test_scoped_gem_path_using_array 67 | source = GemPathSource.new([:rubygems, :ruby]) 68 | assert_equal("{rubygems_,ruby_,}", source.send(:filter_str)) 69 | end 70 | 71 | def test_use_component_sources_without_scope 72 | RubiGen::Base.use_component_sources! 73 | gem_path_source = RubiGen::Base.sources.find { |source| source.is_a?(GemPathSource) } 74 | assert_not_nil(gem_path_source, "Where is the GemPathSource?") 75 | assert_equal("", gem_path_source.send(:filter_str)) 76 | end 77 | 78 | def test_use_component_sources_with_scope 79 | RubiGen::Base.use_component_sources! :rubygems, :ruby 80 | gem_path_source = RubiGen::Base.sources.find { |source| source.is_a?(GemPathSource) } 81 | assert_not_nil(gem_path_source, "Where is the GemPathSource?") 82 | assert_equal("{rubygems_,ruby_,}", gem_path_source.send(:filter_str)) 83 | user_path_source = RubiGen::Base.sources.find { |source| source.is_a?(PathFilteredSource) } 84 | assert_not_nil(user_path_source, "Where is the PathFilteredSource?") 85 | assert_match(/\.rubigen\/\{rubygems_,ruby_,\}generators/, user_path_source.path) 86 | end 87 | 88 | def test_use_application_sources 89 | RubiGen::Base.use_application_sources! 90 | expected_path = File.expand_path(File.join(File.dirname(__FILE__), %w[.. app_generators])) 91 | builtin_source = RubiGen::Base.sources.find { |s| s.path == expected_path if s.respond_to?(:path) } 92 | assert_not_nil(builtin_source, "Cannot find builtin generators") 93 | assert_nothing_raised(GeneratorError) do 94 | generator = RubiGen::Base.lookup('ruby_app') 95 | end 96 | end 97 | 98 | def test_use_application_sources_with_scope 99 | RubiGen::Base.use_application_sources! :rubygems, :newgem 100 | gem_path_source = RubiGen::Base.sources.find { |source| source.is_a?(GemPathSource) } 101 | assert_not_nil(gem_path_source, "Where is the GemPathSource?") 102 | assert_equal("{app_,rubygems_,newgem_,}", gem_path_source.send(:filter_str)) 103 | user_path_source = RubiGen::Base.sources.find { |source| source.is_a?(PathFilteredSource) } 104 | assert_not_nil(user_path_source, "Where is the PathFilteredSource?") 105 | assert_match(/\.rubigen\/\{app_,rubygems_,newgem_,\}generators/, user_path_source.path) 106 | end 107 | end -------------------------------------------------------------------------------- /test/test_rubigen_cli.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + "/test_generator_helper" 2 | require 'rubigen/cli' 3 | 4 | class TestRubigenCli < Test::Unit::TestCase 5 | include RubiGen::GeneratorTestHelper 6 | attr_reader :stdout 7 | 8 | context "run executable with scope 'rubygems'" do 9 | setup do 10 | bare_setup 11 | Rubigen::CLI.new.execute(@stdout_io = StringIO.new, 12 | %w[rubygems component_generator name scope], :backtrace => true) 13 | @stdout_io.rewind 14 | @stdout = @stdout_io.read 15 | end 16 | 17 | should "create main generator manifest" do 18 | assert_file_exists("scope_generators/name/name_generator.rb") 19 | end 20 | end 21 | 22 | context "run executable with scope 'rubygems'" do 23 | setup do 24 | Rubigen::CLI.execute(@stdout_io = StringIO.new, %w[rubygems]) 25 | @stdout_io.rewind 26 | @stdout = @stdout_io.read 27 | end 28 | 29 | should "display help" do 30 | assert_match(/General Options/, stdout) 31 | end 32 | 33 | should "display installed generators for 'rubygems'" do 34 | assert_match(/Installed Generators/, stdout) 35 | assert_match(/application_generator/, stdout) 36 | assert_match(/component_generator/, stdout) 37 | end 38 | end 39 | 40 | context "run executable with multiple scopes 'rubygems' and 'something_else'" do 41 | setup do 42 | # rubigen rubygems,something_else a_generator 43 | Rubigen::CLI.execute(@stdout_io = StringIO.new, ['rubygems,something_else']) 44 | @stdout_io.rewind 45 | @stdout = @stdout_io.read 46 | end 47 | 48 | should "display help" do 49 | assert_match(/General Options/, stdout) 50 | end 51 | 52 | should "display installed generators for 'rubygems,something_else'" do 53 | assert_match(/Installed Generators/, stdout) 54 | assert_match(/application_generator/, stdout) 55 | assert_match(/component_generator/, stdout) 56 | end 57 | end 58 | 59 | context "run executable without any arguments" do 60 | setup do 61 | Rubigen::CLI.execute(@stdout_io = StringIO.new, %w[]) 62 | @stdout_io.rewind 63 | @stdout = @stdout_io.read 64 | end 65 | 66 | should "display main usage" do 67 | assert_match(/Usage:/, stdout) 68 | end 69 | end 70 | 71 | end -------------------------------------------------------------------------------- /test_unit_generators/test_unit/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | Stubs out a new test (using Test::Unit). Pass the model name, either 3 | CamelCased or under_scored. 4 | 5 | This generates a unit test in /test. 6 | 7 | Examples: 8 | script/generate test_unit account_receiver 9 | 10 | creates an Account test: 11 | Test: test/test_account_receiver.rb 12 | 13 | The same result will occur for: 14 | script/generate test_unit AccountReceiver 15 | -------------------------------------------------------------------------------- /test_unit_generators/test_unit/templates/test.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/test_helper' 2 | 3 | class Test<%= class_name %> < Test::Unit::TestCase 4 | def setup 5 | end 6 | 7 | def teardown 8 | end 9 | 10 | # Replace this with your real tests. 11 | def test_truth 12 | assert true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test_unit_generators/test_unit/test_unit_generator.rb: -------------------------------------------------------------------------------- 1 | class TestUnitGenerator < RubiGen::Base 2 | 3 | attr_reader :name, :test_name, :class_name 4 | 5 | def initialize(runtime_args, runtime_options = {}) 6 | super 7 | usage if args.empty? 8 | @name = args.shift 9 | @test_name = "test_#{name}".underscore 10 | @class_name = name.camelize 11 | end 12 | 13 | def manifest 14 | record do |m| 15 | m.directory 'test' 16 | 17 | # Model class, unit test, and fixtures. 18 | m.template 'test.rb', "test/#{test_name}.rb" 19 | end 20 | end 21 | 22 | protected 23 | def banner 24 | "Usage: #{$0} #{spec.name} NameOfTest" 25 | end 26 | end 27 | --------------------------------------------------------------------------------