Permalink
Browse files

Adding documentation for event hooks, inline method docs, and making …

…a few small tweaks.
  • Loading branch information...
1 parent fca9e45 commit 06b143089d8b6607240cd4e3797ee153115eebf7 @jcoglan committed Jun 17, 2009
Showing with 146 additions and 36 deletions.
  1. +1 −0 .gitignore
  2. +2 −0 Manifest.txt
  3. +31 −0 README.rdoc
  4. +21 −27 lib/jake.rb
  5. +26 −6 lib/jake/build.rb
  6. +20 −3 lib/jake/buildable.rb
  7. +7 −0 lib/jake/bundle.rb
  8. +15 −0 lib/jake/helper.rb
  9. +16 −0 lib/jake/observer.rb
  10. +7 −0 lib/jake/package.rb
View
@@ -1,3 +1,4 @@
README.txt
pkg
+doc
test/output
View
@@ -4,6 +4,8 @@ README.txt
Rakefile
bin/jake
lib/jake.rb
+lib/jake/helper.rb
+lib/jake/observer.rb
lib/jake/build.rb
lib/jake/buildable.rb
lib/jake/package.rb
View
@@ -137,6 +137,8 @@ The full list of package options is as follows:
for the current build. If a build listed above uses <tt>packer: false</tt>,
this takes precedence over package-specific instructions. Typically used
to override options for the minified build.
+* +meta+ - should be a YAML dictionary containing arbitrary data useful to
+ user-defined build events. May be omitted. See 'Event hooks' below.
For example, here's a package listing that uses all the options:
@@ -211,6 +213,35 @@ name. When built, the output would contain the following:
MyJavaScriptLib.VERSION = "1.0-src"; // or "1.0-min" for the 'min' build
+=== Event hooks
+
+The +Jakefile+ may also define event hooks that are fired during a build when
+interesting things happen. This allows you to extend your build process using
+configuration data from Jake. We currently have two events:
+
++file_created+ is fired whenever a new build file is created. The callback is
+passed the +Buildable+ package object, the current build name (+src+ or +min+
+using the above examples), and the full path to the newly created file.
+The package object may contain metadata (set using the +meta+ option, see
+above) which you can use for further code generation.
+
++build_complete+ is fired after a build has finished running, that is after
+all sets of minification options have been run. At this point you can use any
+metadata you've gathered to generate more code, copy files to your distribution
+directory, etc.
+
+ $register = {}
+
+ jake_hook :file_created do |pkg, build_name, path|
+ $register[path] = pkg.meta
+ end
+
+ jake_hook :build_complete do |build|
+ FileUtils.cp 'README', build.build_directory + '/README'
+ # generate code from $register
+ end
+
+
== License
(The MIT License)
View
@@ -1,24 +1,39 @@
+require 'erb'
+require 'fileutils'
+require 'observer'
+require 'yaml'
+require 'rubygems'
+require 'packr'
+
module Jake
VERSION = '0.9.4'
CONFIG_FILE = 'jake.yml'
HELPER_FILE = 'Jakefile'
+ EXTENSION = '.js'
+ # Runs a build in the given directory. The directory must contain a jake.yml
+ # file, and may contain a Jakefile. See README for example YAML configurations.
def self.build!(path, options = {})
- build = Build.new(path, nil, options)
+ build = Build.new(path, options)
build.run!
end
+ # Removes all registered build event hooks.
def self.clear_hooks!
Build.delete_observers
end
+ # Returns the contents of the given path, which may be missing a .js extension.
def self.read(path)
path = File.expand_path(path)
- path = File.file?(path) ? path : ( File.file?("#{path}.js") ? "#{path}.js" : nil )
- path and File.read(path).strip
+ [path, "#{path}#{EXTENSION}"].each do |p|
+ return File.read(p).strip if File.file?(p)
+ end
+ return nil
end
+ # Returns a copy of the given hash with the keys cast to symbols.
def self.symbolize_hash(hash, deep = true)
hash.inject({}) do |output, (key, value)|
value = Jake.symbolize_hash(value) if deep and value.is_a?(Hash)
@@ -27,42 +42,21 @@ def self.symbolize_hash(hash, deep = true)
end
end
- class Helper
- attr_accessor :build
- attr_reader :options
-
- def initialize(options = {})
- @options = options
- end
-
- def scope; binding; end
- end
-
- class Observer
- def initialize(type, &block)
- @type, @block = type, block
- Build.add_observer(self)
- end
-
- def update(*args)
- @block[*args[1..-1]] if args.first == @type
- end
- end
end
-require 'erb'
-
-%w(build buildable package bundle).each do |file|
+%w(helper observer build buildable package bundle).each do |file|
require File.dirname(__FILE__) + '/jake/' + file
end
+# Adds a helper method that can be called from ERB.
def jake_helper(name, &block)
Jake::Helper.class_eval do
define_method(name, &block)
end
end
alias :jake :jake_helper
+# Registers an event listener that will fire whenever a build is run.
def jake_hook(type, &block)
Jake::Observer.new(type, &block)
end
View
@@ -1,10 +1,12 @@
-require 'yaml'
-require 'fileutils'
-require 'observer'
-
module Jake
+ # A +Build+ encapsulates a single instance of running <tt>Jake.build!</tt>. It
+ # is responsible for running the build and provides access to any configuration
+ # data used to set up the build.
class Build
extend Observable
+
+ # Calls all registered +Observer+ instances with any arguments passed in. Note
+ # that it is the +Build+ class that is +Observable+, not its instances.
def self.notify_observers(*args)
self.changed(true)
super
@@ -15,15 +17,18 @@ def self.notify_observers(*args)
include Enumerable
attr_reader :helper
- def initialize(dir, config = nil, options = {})
+ # Builds are initialized using a directory in which to run the build, and an
+ # options hash. Options are passed through to helper methods in the +options+
+ # object.
+ def initialize(dir, options = {})
@dir = File.expand_path(dir)
@helper = Helper.new(options)
force! if options[:force]
path = "#{dir}/#{CONFIG_FILE}"
yaml = File.read(path)
- @config = Jake.symbolize_hash( config || YAML.load(ERB.new(yaml).result(@helper.scope)) )
+ @config = Jake.symbolize_hash( YAML.load(ERB.new(yaml).result(@helper.scope)) )
helpers = "#{dir}/#{HELPER_FILE}"
load helpers if File.file?(helpers)
@@ -41,22 +46,27 @@ def initialize(dir, config = nil, options = {})
end
end
+ # Yields to the block for each build in the group.
def each(&block)
@builds.each(&block)
end
+ # Forces the build to generate new files even if target files appear up-to-date.
def force!
@forced = true
end
+ # Returns +true+ iff this is a forced build.
def forced?
defined?(@forced)
end
+ # Returns the +Buildable+ with the given name.
def package(name)
@packages[name.to_sym] || @bundles[name.to_sym]
end
+ # Runs the build, generating new files in +build_directory+.
def run!
FileUtils.cd(@dir) do
@packages.each { |name, pkg| pkg.write! }
@@ -65,34 +75,44 @@ def run!
end
end
+ # Returns the path to the build directory, where generated files appear.
def build_directory
"#{ @dir }/#{ @config[:build_directory] || '.' }"
end
alias :build_dir :build_directory
+ # Returns the path to the source directory, where source code is read from.
def source_directory
"#{ @dir }/#{ @config[:source_directory] || '.' }"
end
alias :source_dir :source_directory
+ # Returns the header string used for the build, or an empty string if no
+ # header file has been set.
def header
@config[:header] ?
Jake.read("#{ source_directory }/#{ @config[:header] }") :
""
end
+ # Returns the minification settings for PackR for the given build name. Each
+ # +Build+ object can build all its packages multiple times with different
+ # minification settings in each run.
def packer_settings(build_name)
build = @builds[build_name.to_sym]
return false unless build
build[:packer].nil? ? build : build[:packer]
end
+ # Returns +true+ iff filename suffixes based on build name should be added
+ # to generated files for the given build name.
def use_suffix?(build_name)
build = @builds[build_name.to_sym]
return true unless build
build[:suffix] != false
end
+ # Returns the name of the layout being used, either +together+ or +apart+.
def layout
@config[:layout] || DEFAULT_LAYOUT
end
View
@@ -1,11 +1,15 @@
-require 'fileutils'
-require 'packr'
-
module Jake
+ # A +Buildable+ represents a group of files that may be merged to form a single
+ # build file. There are two subtypes of +Buildable+; a +Package+ takes several
+ # source files and produces a build file, and a +Bundle+ takes several +Package+
+ # build files to produce another build file. This class should be considered
+ # abstract as some of its method must be filled in by subtypes.
class Buildable
attr_reader :name
+ # Create a new +Buildable+ using a +Build+ container, a name and a configuration
+ # hash, typically a subsection of jake.yml.
def initialize(build, name, config)
@build, @name = build, name
@config = case config
@@ -16,24 +20,30 @@ def initialize(build, name, config)
@code = {}
end
+ # Returns the parent +Buildable+ set using the +extends+ option, or +nil+ if
+ # there is no parent.
def parent
return nil unless @config[:extends]
@parent ||= @build.package(@config[:extends])
end
+ # Returns the source directory for this package.
def directory
dir = @config[:directory]
return parent.directory if parent && !dir
"#{ @build.source_directory }/#{ @config[:directory] }"
end
+ # Returns the path to the output file from this package for the given build name.
def build_path(build_name)
suffix = @build.use_suffix?(build_name) ? "-#{ build_name }" : ""
@build.layout == 'together' ?
"#{ @build.build_directory }/#{ @name }#{ suffix }.js" :
"#{ @build.build_directory }/#{ build_name }/#{ @name }.js"
end
+ # Returns +true+ if the build file for the given build name is out of date and
+ # should be regenerated.
def build_needed?(name)
return true if @build.forced?
path = build_path(name)
@@ -42,13 +52,15 @@ def build_needed?(name)
files.any? { |path| File.mtime(path) > build_time }
end
+ # Returns the header string being used for this package.
def header
content = @config[:header] ?
Jake.read("#{ directory }/#{ @config[:header] }") :
(parent ? parent.header : @build.header)
ERB.new(content).result(@build.helper.scope).strip
end
+ # Returns the PackR settings to use for this package during the given build.
def packer_settings(build_name)
global = @build.packer_settings(build_name)
local = @config[:packer]
@@ -57,10 +69,15 @@ def packer_settings(build_name)
{}.merge(global || {}).merge(local || {})
end
+ # Returns a hash containing any metadata attached to the package in the config.
def meta
@config[:meta] || {}
end
+ # Generates all the build files for this package by looping over all the
+ # required builds and compressing the source code using each set of minification
+ # options. Files are only generated if they are out of date or the build has
+ # been forced.
def write!
puts "Package #{@name}..."
View
@@ -1,15 +1,22 @@
module Jake
class Bundle < Buildable
+ # Returns a list of paths to all the files used to build this package.
def files
base = parent ? parent.files : []
base + @config[:files].map { |pkg| @build.package(pkg).files }.flatten
end
+ # Returns the full uncompressed source code of this package, before
+ # ERB processing. ERB output will be build-dependent; this method
+ # simply builds the raw template for further processing by other
+ # methods.
def source
@source ||= @config[:files].map { |pkg| @build.package(pkg).source }.join("\n\n\n")
end
+ # Returns the result of building the source template and minifying
+ # the output using the given named set of PackR settings.
def code(name)
joiner = (packer_settings(name) == false) ? "\n\n\n" : "\n"
@code[name] ||= @config[:files].map { |pkg| @build.package(pkg).code(name) }.join(joiner)
View
@@ -0,0 +1,15 @@
+module Jake
+ # The +Helper+ class stores helper methods that can be called from ERB
+ # when generating source code. Use +jake_helper+ to define new helpers.
+ class Helper
+ attr_accessor :build
+ attr_reader :options
+
+ def initialize(options = {})
+ @options = options
+ end
+
+ def scope; binding; end
+ end
+end
+
View
@@ -0,0 +1,16 @@
+module Jake
+ # The +Observer+ class is used in conjunction with Ruby's +Observable+
+ # module to provide typed event listeners. Use +jake_hook+ to register
+ # new listeners in Jakefiles.
+ class Observer
+ def initialize(type, &block)
+ @type, @block = type, block
+ Build.add_observer(self)
+ end
+
+ def update(*args)
+ @block[*args[1..-1]] if args.first == @type
+ end
+ end
+end
+
Oops, something went wrong.

0 comments on commit 06b1430

Please sign in to comment.