Class: SC::Generator
- Inherits:
-
HashStruct
- Object
- Hash
- HashStruct
- SC::Generator
- Defined in:
- lib/sproutcore/models/generator.rb
Overview
A generator is a special kind of project that can process some input templates to generate some default content in a target project.
Setup Process
When a generator is created, the generator’s environment is setup accoding to the following process:
1. Process any passed :arguments hash (also saved as 'arguments')
2. Invoke any defined generator:prepare task to do further set'
3. Search the target project for a target with the target_name if set.
Standard Options
These options are automatically added to the generator if possible. Your generator code should expect to work with them. The following examples assume you pass as an argument either “AddressBook.Contact” or “address_book/contact”.
target_name::
the name of the target to use as the root. Defaults to the snake case
version of the passed namepace. This can be overridden by passing the
"target_name" option to new(). example: "address_book"
target::
If target_name is not nil and a target is found with a matching name,
then this will be set to that target. example: Target(/address_book)
build_root::
If target is not nil, set to the source_root for the target. If no
target, set to the project_root for the current target project. If no
project is defined, set to the current working directory. May be
overridden with build_root option to new(). example:
/Users/charles/projects/my_project/apps/address_book
filename::
The filename as passed in arguments. example: "contact"
namespace::
The classified version of the target name. example: "AddressBook"
css_name::
The CSS class name version of the target name. example: 'address-book'
class_name::
The classified version of the filename. example: "Contact"
method_name::
If a full three-part Namespace.ClassName.methodName is passed, this
property will be set to the method name. example: nil (no method name
included)
Constant Summary collapse
- ABBREVIATIONS =
%w(html css xml)
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#target_project ⇒ Object
readonly
the target project to build in or nil if no target provided.
Class Method Summary collapse
-
.installed_generators_for(project) ⇒ Object
Discover all installed generators for a particular project.
-
.load(generator_name, opts = {}) ⇒ Object
Creates a new generator.
Instance Method Summary collapse
-
#build! ⇒ Object
Executes the generator based on the current config options.
-
#buildfile ⇒ Object
The current buildfile for the generator.
-
#camel_case(str, capitalize = true) ⇒ Object
Converts a string to CamelCase.
-
#config ⇒ Object
The config for the current generator.
-
#copy_file(src_path, dst_path) ⇒ Object
Copies from source to destination, running the contents through ERB if the file appears to be a text file.
- #copyright_block(desc, commented = :javascript) ⇒ Object
-
#css_case(str = '') ⇒ Object
Converts a string to CSS case.
-
#debug(description) ⇒ Object
Helper method.
-
#each_template(source_dir = nil, build_dir = nil) ⇒ Object
Calls your block for each file and directory in the source template passing the expanded source path and the expanded destination directory.
-
#expand_path(path) ⇒ Object
Converts a path with optional template variables into a regular path by looking up said variables on the receiver.
-
#fatal!(description) ⇒ Object
Helper method.
-
#info(description) ⇒ Object
Helper method.
-
#initialize(generator_name, opts = {}) ⇒ Generator
constructor
A new instance of Generator.
-
#log_file(src_path, a_logger = nil) ⇒ Object
Logs the pass file to the logger after first processing it with Erubis.
-
#log_readme(a_logger = nil) ⇒ Object
Logs the README file in the source_root if found or logs a warning.
-
#log_usage(a_logger = nil) ⇒ Object
Logs the USAGE file in the source_root if found or logs a warning.
-
#namespace_class_name ⇒ Object
Returns the full namespace and class name if both are defined.
-
#namespace_instance_name ⇒ Object
Returns the full namespace and object name if both are defined.
-
#prepare! ⇒ Object
Prepares the generator state by parsing any passed arguments and then invokes the ‘generator:prepare’ task from the Buildfile, if one exists.
-
#requires!(*properties) ⇒ Object
Verifies that the passed array of keys are defined on the object.
-
#snake_case(str = '') ⇒ Object
Converts a string to snake case.
-
#warn(description) ⇒ Object
Log this when you need to issue a warning.
Methods inherited from HashStruct
#deep_clone, #has_options?, #merge, #merge!, #method_missing, #print_first_caller, #to_hash
Constructor Details
#initialize(generator_name, opts = {}) ⇒ Generator
Returns a new instance of Generator.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/sproutcore/models/generator.rb', line 125 def initialize(generator_name, opts = {}) super() @target_project = opts[:target_project] || opts['target_project'] @logger = opts[:logger] || opts['logger'] || SC.logger @buildfile = nil # delete special options %w(target_project logger).each do |key| opts.delete(key) opts.delete(key.to_sym) end # copy any remaining options onto generator opts.each { |key, value| self[key] = value } self.generator_name = generator_name end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method in the class SC::HashStruct
Instance Attribute Details
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
70 71 72 |
# File 'lib/sproutcore/models/generator.rb', line 70 def logger @logger end |
#target_project ⇒ Object (readonly)
the target project to build in or nil if no target provided
68 69 70 |
# File 'lib/sproutcore/models/generator.rb', line 68 def target_project @target_project end |
Class Method Details
.installed_generators_for(project) ⇒ Object
Discover all installed generators for a particular project.
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/sproutcore/models/generator.rb', line 77 def self.installed_generators_for(project) ret = [] while project %w(generators sc_generators gen).each do |dirname| Dir.glob(project.project_root / dirname / '**').each do |path| ret << File.basename(path) if File.directory?(path) end end project = project.parent_project end return ret.uniq.compact.sort end |
.load(generator_name, opts = {}) ⇒ Object
Creates a new generator. Expects you to pass at least a generator name and additional options including the current target project. This will search for a generator source directory in the target project and any parent projects. The source directory must live inside a folder called “gen” or “generators”. The source directory must contain a Buildfile and a templates directory to be considered a valid generator.
If no valid generator can be found matching the generator name, this method will return null
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/sproutcore/models/generator.rb', line 100 def self.load(generator_name, opts={}) # get the project to search and look for the generator target_project = project = opts[:target_project] || SC.builtin_project path = ret = nil # attempt to discover the the generator while project && path.nil? %w(generators sc_generators gen).each do |dirname| path = File.join(project.project_root, dirname, generator_name) if File.directory?(path) has_buildfile = File.exists?(path / 'Buildfile') has_templates = File.directory?(path / 'templates') break if has_buildfile && has_templates end path = nil end project = project.parent_project end # Create project if possible ret = self.new(generator_name, opts.merge(:source_root => path, :target_project => target_project)) if path return ret end |
Instance Method Details
#build! ⇒ Object
Executes the generator based on the current config options. Raises an exception if anything failed during the build. This will copy each file from the source, processing it with the rhtml template.
221 222 223 224 225 |
# File 'lib/sproutcore/models/generator.rb', line 221 def build! prepare! # if needed buildfile.invoke 'generator:build', :generator => self return self end |
#buildfile ⇒ Object
The current buildfile for the generator. The buildfile is calculated by merging the buildfile for the generator with the default generator buildfile. Buildfiles should be named “Buildfile” and should be placed in the generator root directory.
Returns
Buildfile instance
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/sproutcore/models/generator.rb', line 156 def buildfile return @buildfile unless @buildfile.nil? @buildfile = Buildfile.new # First try to load the shared buildfile path = File.join(SC.builtin_project.project_root, 'gen', 'Buildfile') if !@buildfile.load!(path).loaded_paths.include?(path) SC.logger.warn("Could not load shared generator buildfile at #{buildfile_path}") end # Then try to load the buildfile for the generator path = File.join(source_root, 'Buildfile') @buildfile.load!(path) return @buildfile end |
#camel_case(str, capitalize = true) ⇒ Object
Converts a string to CamelCase. If you pass false for the second param then the first letter will be lower case rather than upper. This will first snake_case the passed string. This version differs from the standard camel_case provided by extlib by supporting a few standard abbreviations that are always make upper case.
Examples
camel_case("foo_bar") #=> "FooBar"
camel_case("headline_cnn_news") #=> "HeadlineCnnNews"
camel_case("html_formatter") #=> "HTMLFormatter"
Params
str:: the string to camel case
capitalize:: capitalize first character if true (def: true)
471 472 473 474 475 476 |
# File 'lib/sproutcore/models/generator.rb', line 471 def camel_case(str, capitalize=true) str = snake_case(str) # normalize str.gsub(capitalize ? /(\A|_+)([^_]+)/ : /(_+)([^_]+)/) do ABBREVIATIONS.include?($2) ? $2.upcase : $2.capitalize end end |
#config ⇒ Object
The config for the current generator. The config is computed by merging the config settings for the current buildfile and the current build environment.
Returns
merged HashStruct
181 182 183 |
# File 'lib/sproutcore/models/generator.rb', line 181 def config return @config ||= buildfile.config_for(:templates, SC.build_mode).merge(SC.env) end |
#copy_file(src_path, dst_path) ⇒ Object
Copies from source to destination, running the contents through ERB if the file appears to be a text file. The destination file must not exist or else a warning will be logged.
Returns
true if copied successfully. false otherwise
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/sproutcore/models/generator.rb', line 378 def copy_file(src_path, dst_path) # interpolate dst_path to include any variables dst_path = (dst_path) src_filename = src_path.sub(self.source_root / '', '') dst_filename = dst_path.sub(self.build_root / '', '') ret = true # if the source path does not exist, just log a warning and return if !File.exist? src_path warn "Did not copy #{src_filename} because the source does not exist." ret = false # when copying a directory just make the dir if needed elsif File.directory?(src_path) logger << " ~ Created directory at #{dst_filename}\n" if !File.exist?(dst_path) FileUtils.mkdir_p(dst_path) unless self.dry_run # if destination already exists, just log warning elsif File.exist?(dst_path) && !self.force warn "Did not overwrite #{dst_filename} because it already exists." ret = false # process file through erubis and copy else require 'erubis' input = File.read(src_path) eruby = ::Erubis::Eruby.new input output = eruby.result(binding()) unless self.dry_run FileUtils.mkdir_p(File.dirname(dst_path)) file = File.new(dst_path, 'w') file.write output file.close end logger << " ~ Created file at #{dst_filename}\n" end return ret end |
#copyright_block(desc, commented = :javascript) ⇒ Object
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/sproutcore/models/generator.rb', line 296 def copyright_block(desc, commented=:javascript) company = ENV['COMPANY'] || "My Company, Inc." str = ["==========================================================================", "Project: #{desc}", "Copyright: @#{Time.now.year} #{company}", "=========================================================================="] case commented when true, :javascript, :js str.map!{|s| "// #{s}" } when :css str[0] = "/* #{str[0]}" str[1..-1].each{|s| s.replace "* #{s}" } str << "*/" when :ruby str.map!{|s| "# #{s}" } end str.join("\n") end |
#css_case(str = '') ⇒ Object
Converts a string to CSS case. This method will accept CamelCase or snake case and normalize into a format that can be converted to CSS case.
448 449 450 |
# File 'lib/sproutcore/models/generator.rb', line 448 def css_case(str='') snake_case(str).gsub(/_/, '-') end |
#debug(description) ⇒ Object
Helper method. Call this when you want to log a debug message.
242 |
# File 'lib/sproutcore/models/generator.rb', line 242 def debug(description); logger.debug(description); end |
#each_template(source_dir = nil, build_dir = nil) ⇒ Object
Calls your block for each file and directory in the source template passing the expanded source path and the expanded destination directory
Expects you to include a block with the following signature:
block |filename, src_path, dst_path|
filename:: the filename relative to the source directory
src_path:: the full path to the source
dst_path:: the full destination path
Param
source_dir:: optional source directory. Defaults to templates
build_dir:: optional build directory. Defaults to build_root
Returns
self
359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/sproutcore/models/generator.rb', line 359 def each_template(source_dir = nil, build_dir=nil) source_dir = self.source_root / 'templates' if source_dir.nil? build_dir = self.build_root if build_dir.nil? Dir.glob(source_dir / '**' / '*').each do |src_path| filename = src_path.sub(source_dir / '', '') dst_path = build_dir / filename yield(filename, src_path, dst_path) if block_given? end return self end |
#expand_path(path) ⇒ Object
Converts a path with optional template variables into a regular path by looking up said variables on the receiver. Variables in the pathname must appear inside of a pair of @@.
337 338 339 340 |
# File 'lib/sproutcore/models/generator.rb', line 337 def (path) path = path.gsub(/@(.*?)@/) { self.send($1) || $1 } File. path end |
#fatal!(description) ⇒ Object
Helper method. Call this when an acception occurs that is fatal due to a problem with the user.
233 234 235 |
# File 'lib/sproutcore/models/generator.rb', line 233 def fatal!(description) raise description end |
#info(description) ⇒ Object
Helper method. Call this when you want to log an info message. Logs to the standard logger.
239 |
# File 'lib/sproutcore/models/generator.rb', line 239 def info(description); logger.info(description); end |
#log_file(src_path, a_logger = nil) ⇒ Object
Logs the pass file to the logger after first processing it with Erubis. This is the code helper method used to log out USAGE and README files.
Params
src_path:: the file path for the logger
a_logger:: optional logger to use. defaults to builtin logger
Returns
self
258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/sproutcore/models/generator.rb', line 258 def log_file(src_path, a_logger = nil) a_logger = self.logger if a_logger.nil? if !File.exists?(src_path) warn "Could not find #{File.basename(src_path)} in generator source" else require 'erubis' a_logger << Erubis::Eruby.new(File.read(src_path)).result(binding()) a_logger << "\n" end return self end |
#log_readme(a_logger = nil) ⇒ Object
Logs the README file in the source_root if found or logs a warning.
271 272 273 274 |
# File 'lib/sproutcore/models/generator.rb', line 271 def log_readme(a_logger=nil) src_path = self.source_root / "README" log_file(src_path, a_logger) end |
#log_usage(a_logger = nil) ⇒ Object
Logs the USAGE file in the source_root if found or logs a warning.
277 278 279 280 |
# File 'lib/sproutcore/models/generator.rb', line 277 def log_usage(a_logger=nil) src_path = self.source_root / 'USAGE' log_file(src_path, a_logger) end |
#namespace_class_name ⇒ Object
Returns the full namespace and class name if both are defined.
287 288 289 |
# File 'lib/sproutcore/models/generator.rb', line 287 def namespace_class_name [self.namespace, self.class_name].compact.join '.' end |
#namespace_instance_name ⇒ Object
Returns the full namespace and object name if both are defined.
292 293 294 |
# File 'lib/sproutcore/models/generator.rb', line 292 def namespace_instance_name [self.namespace, self.instance_name].compact.join '.' end |
#prepare! ⇒ Object
Prepares the generator state by parsing any passed arguments and then invokes the ‘generator:prepare’ task from the Buildfile, if one exists. Once a generator has been prepared, you can then build it.
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/sproutcore/models/generator.rb', line 192 def prepare! return self if @is_prepared @is_prepared = true parse_arguments! has_project = target_project && target_project != SC.builtin_project if target_name && has_project && target.nil? self.target = target_project.target_for(target_name) end # Attempt to build a reasonable default build_root. Usually this should # be the target path, but if a target can't be found, use the project # path. If a project is not found or the target project is the builtin # project, then use the current working directory if target self.build_root = target.source_root else self.build_root = has_project ? target_project.project_root : Dir.pwd end # Execute prepare task - give the generator a chance to fixup defaults buildfile.invoke 'generator:prepare', :generator => self return self end |
#requires!(*properties) ⇒ Object
Verifies that the passed array of keys are defined on the object. If you pass an optional block, the block will be invoked for each key so you can validate the value as well. Otherwise, this will raise an error if any of the properties are nil.
322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/sproutcore/models/generator.rb', line 322 def requires!(*properties) properties.flatten.each do |key_name| value = self.send(key_name) is_ok = !value.nil? is_ok = yield(key_name, value) if block_given? && is_ok unless is_ok fatal!("This generator requires a #{Extlib::Inflection.humanize key_name}") end end return self end |
#snake_case(str = '') ⇒ Object
Converts a string to snake case. This method will accept any variation of camel case or snake case and normalize it into a format that can be converted back and forth to camel case.
Examples
snake_case("FooBar") #=> "foo_bar"
snake_case("HeadlineCNNNews") #=> "headline_cnn_news"
snake_case("CNN") #=> "cnn"
snake_case("innerHTML") #=> "inner_html"
snake_case("Foo_Bar") #=> "foo_bar"
snake_case("Foo-Bar") #=> "foo_bar"
Params
str:: the string to snake case
439 440 441 442 443 444 |
# File 'lib/sproutcore/models/generator.rb', line 439 def snake_case(str='') str = str.gsub(/-/, '_') str = str.gsub(/([^A-Z_])([A-Z][^A-Z]?)/,'\1_\2') # most cases str = str.gsub(/([^_])([A-Z][^A-Z_])/,'\1_\2') # HeadlineCNNNews str.downcase end |
#warn(description) ⇒ Object
Log this when you need to issue a warning.
245 |
# File 'lib/sproutcore/models/generator.rb', line 245 def warn(description); logger.warn(description); end |