Skip to content

Commit

Permalink
Adding documentation for event hooks, inline method docs, and making …
Browse files Browse the repository at this point in the history
…a few small tweaks.
  • Loading branch information
jcoglan committed Jun 17, 2009
1 parent fca9e45 commit 06b1430
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Original file line Diff line number Diff line change
@@ -1,3 +1,4 @@
README.txt README.txt
pkg pkg
doc
test/output test/output
2 changes: 2 additions & 0 deletions Manifest.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ README.txt
Rakefile Rakefile
bin/jake bin/jake
lib/jake.rb lib/jake.rb
lib/jake/helper.rb
lib/jake/observer.rb
lib/jake/build.rb lib/jake/build.rb
lib/jake/buildable.rb lib/jake/buildable.rb
lib/jake/package.rb lib/jake/package.rb
Expand Down
31 changes: 31 additions & 0 deletions README.rdoc
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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>, for the current build. If a build listed above uses <tt>packer: false</tt>,
this takes precedence over package-specific instructions. Typically used this takes precedence over package-specific instructions. Typically used
to override options for the minified build. 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: For example, here's a package listing that uses all the options:


Expand Down Expand Up @@ -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 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 == License


(The MIT License) (The MIT License)
Expand Down
48 changes: 21 additions & 27 deletions lib/jake.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,24 +1,39 @@
require 'erb'
require 'fileutils'
require 'observer'
require 'yaml'
require 'rubygems'
require 'packr'

module Jake module Jake
VERSION = '0.9.4' VERSION = '0.9.4'


CONFIG_FILE = 'jake.yml' CONFIG_FILE = 'jake.yml'
HELPER_FILE = 'Jakefile' 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 = {}) def self.build!(path, options = {})
build = Build.new(path, nil, options) build = Build.new(path, options)
build.run! build.run!
end end


# Removes all registered build event hooks.
def self.clear_hooks! def self.clear_hooks!
Build.delete_observers Build.delete_observers
end end


# Returns the contents of the given path, which may be missing a .js extension.
def self.read(path) def self.read(path)
path = File.expand_path(path) path = File.expand_path(path)
path = File.file?(path) ? path : ( File.file?("#{path}.js") ? "#{path}.js" : nil ) [path, "#{path}#{EXTENSION}"].each do |p|
path and File.read(path).strip return File.read(p).strip if File.file?(p)
end
return nil
end end


# Returns a copy of the given hash with the keys cast to symbols.
def self.symbolize_hash(hash, deep = true) def self.symbolize_hash(hash, deep = true)
hash.inject({}) do |output, (key, value)| hash.inject({}) do |output, (key, value)|
value = Jake.symbolize_hash(value) if deep and value.is_a?(Hash) value = Jake.symbolize_hash(value) if deep and value.is_a?(Hash)
Expand All @@ -27,42 +42,21 @@ def self.symbolize_hash(hash, deep = true)
end end
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 end


require 'erb' %w(helper observer build buildable package bundle).each do |file|

%w(build buildable package bundle).each do |file|
require File.dirname(__FILE__) + '/jake/' + file require File.dirname(__FILE__) + '/jake/' + file
end end


# Adds a helper method that can be called from ERB.
def jake_helper(name, &block) def jake_helper(name, &block)
Jake::Helper.class_eval do Jake::Helper.class_eval do
define_method(name, &block) define_method(name, &block)
end end
end end
alias :jake :jake_helper alias :jake :jake_helper


# Registers an event listener that will fire whenever a build is run.
def jake_hook(type, &block) def jake_hook(type, &block)
Jake::Observer.new(type, &block) Jake::Observer.new(type, &block)
end end
Expand Down
32 changes: 26 additions & 6 deletions lib/jake/build.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,10 +1,12 @@
require 'yaml'
require 'fileutils'
require 'observer'

module Jake 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 class Build
extend Observable 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) def self.notify_observers(*args)
self.changed(true) self.changed(true)
super super
Expand All @@ -15,15 +17,18 @@ def self.notify_observers(*args)
include Enumerable include Enumerable
attr_reader :helper 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) @dir = File.expand_path(dir)
@helper = Helper.new(options) @helper = Helper.new(options)
force! if options[:force] force! if options[:force]


path = "#{dir}/#{CONFIG_FILE}" path = "#{dir}/#{CONFIG_FILE}"
yaml = File.read(path) 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}" helpers = "#{dir}/#{HELPER_FILE}"
load helpers if File.file?(helpers) load helpers if File.file?(helpers)
Expand All @@ -41,22 +46,27 @@ def initialize(dir, config = nil, options = {})
end end
end end


# Yields to the block for each build in the group.
def each(&block) def each(&block)
@builds.each(&block) @builds.each(&block)
end end


# Forces the build to generate new files even if target files appear up-to-date.
def force! def force!
@forced = true @forced = true
end end


# Returns +true+ iff this is a forced build.
def forced? def forced?
defined?(@forced) defined?(@forced)
end end


# Returns the +Buildable+ with the given name.
def package(name) def package(name)
@packages[name.to_sym] || @bundles[name.to_sym] @packages[name.to_sym] || @bundles[name.to_sym]
end end


# Runs the build, generating new files in +build_directory+.
def run! def run!
FileUtils.cd(@dir) do FileUtils.cd(@dir) do
@packages.each { |name, pkg| pkg.write! } @packages.each { |name, pkg| pkg.write! }
Expand All @@ -65,34 +75,44 @@ def run!
end end
end end


# Returns the path to the build directory, where generated files appear.
def build_directory def build_directory
"#{ @dir }/#{ @config[:build_directory] || '.' }" "#{ @dir }/#{ @config[:build_directory] || '.' }"
end end
alias :build_dir :build_directory alias :build_dir :build_directory


# Returns the path to the source directory, where source code is read from.
def source_directory def source_directory
"#{ @dir }/#{ @config[:source_directory] || '.' }" "#{ @dir }/#{ @config[:source_directory] || '.' }"
end end
alias :source_dir :source_directory 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 def header
@config[:header] ? @config[:header] ?
Jake.read("#{ source_directory }/#{ @config[:header] }") : Jake.read("#{ source_directory }/#{ @config[:header] }") :
"" ""
end 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) def packer_settings(build_name)
build = @builds[build_name.to_sym] build = @builds[build_name.to_sym]
return false unless build return false unless build
build[:packer].nil? ? build : build[:packer] build[:packer].nil? ? build : build[:packer]
end 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) def use_suffix?(build_name)
build = @builds[build_name.to_sym] build = @builds[build_name.to_sym]
return true unless build return true unless build
build[:suffix] != false build[:suffix] != false
end end


# Returns the name of the layout being used, either +together+ or +apart+.
def layout def layout
@config[:layout] || DEFAULT_LAYOUT @config[:layout] || DEFAULT_LAYOUT
end end
Expand Down
23 changes: 20 additions & 3 deletions lib/jake/buildable.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,11 +1,15 @@
require 'fileutils'
require 'packr'

module Jake 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 class Buildable


attr_reader :name 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) def initialize(build, name, config)
@build, @name = build, name @build, @name = build, name
@config = case config @config = case config
Expand All @@ -16,24 +20,30 @@ def initialize(build, name, config)
@code = {} @code = {}
end end


# Returns the parent +Buildable+ set using the +extends+ option, or +nil+ if
# there is no parent.
def parent def parent
return nil unless @config[:extends] return nil unless @config[:extends]
@parent ||= @build.package(@config[:extends]) @parent ||= @build.package(@config[:extends])
end end


# Returns the source directory for this package.
def directory def directory
dir = @config[:directory] dir = @config[:directory]
return parent.directory if parent && !dir return parent.directory if parent && !dir
"#{ @build.source_directory }/#{ @config[:directory] }" "#{ @build.source_directory }/#{ @config[:directory] }"
end end


# Returns the path to the output file from this package for the given build name.
def build_path(build_name) def build_path(build_name)
suffix = @build.use_suffix?(build_name) ? "-#{ build_name }" : "" suffix = @build.use_suffix?(build_name) ? "-#{ build_name }" : ""
@build.layout == 'together' ? @build.layout == 'together' ?
"#{ @build.build_directory }/#{ @name }#{ suffix }.js" : "#{ @build.build_directory }/#{ @name }#{ suffix }.js" :
"#{ @build.build_directory }/#{ build_name }/#{ @name }.js" "#{ @build.build_directory }/#{ build_name }/#{ @name }.js"
end end


# Returns +true+ if the build file for the given build name is out of date and
# should be regenerated.
def build_needed?(name) def build_needed?(name)
return true if @build.forced? return true if @build.forced?
path = build_path(name) path = build_path(name)
Expand All @@ -42,13 +52,15 @@ def build_needed?(name)
files.any? { |path| File.mtime(path) > build_time } files.any? { |path| File.mtime(path) > build_time }
end end


# Returns the header string being used for this package.
def header def header
content = @config[:header] ? content = @config[:header] ?
Jake.read("#{ directory }/#{ @config[:header] }") : Jake.read("#{ directory }/#{ @config[:header] }") :
(parent ? parent.header : @build.header) (parent ? parent.header : @build.header)
ERB.new(content).result(@build.helper.scope).strip ERB.new(content).result(@build.helper.scope).strip
end end


# Returns the PackR settings to use for this package during the given build.
def packer_settings(build_name) def packer_settings(build_name)
global = @build.packer_settings(build_name) global = @build.packer_settings(build_name)
local = @config[:packer] local = @config[:packer]
Expand All @@ -57,10 +69,15 @@ def packer_settings(build_name)
{}.merge(global || {}).merge(local || {}) {}.merge(global || {}).merge(local || {})
end end


# Returns a hash containing any metadata attached to the package in the config.
def meta def meta
@config[:meta] || {} @config[:meta] || {}
end 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! def write!
puts "Package #{@name}..." puts "Package #{@name}..."


Expand Down
7 changes: 7 additions & 0 deletions lib/jake/bundle.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,15 +1,22 @@
module Jake module Jake
class Bundle < Buildable class Bundle < Buildable


# Returns a list of paths to all the files used to build this package.
def files def files
base = parent ? parent.files : [] base = parent ? parent.files : []
base + @config[:files].map { |pkg| @build.package(pkg).files }.flatten base + @config[:files].map { |pkg| @build.package(pkg).files }.flatten
end 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 def source
@source ||= @config[:files].map { |pkg| @build.package(pkg).source }.join("\n\n\n") @source ||= @config[:files].map { |pkg| @build.package(pkg).source }.join("\n\n\n")
end end


# Returns the result of building the source template and minifying
# the output using the given named set of PackR settings.
def code(name) def code(name)
joiner = (packer_settings(name) == false) ? "\n\n\n" : "\n" joiner = (packer_settings(name) == false) ? "\n\n\n" : "\n"
@code[name] ||= @config[:files].map { |pkg| @build.package(pkg).code(name) }.join(joiner) @code[name] ||= @config[:files].map { |pkg| @build.package(pkg).code(name) }.join(joiner)
Expand Down
15 changes: 15 additions & 0 deletions lib/jake/helper.rb
Original file line number Original file line Diff line number Diff line change
@@ -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

16 changes: 16 additions & 0 deletions lib/jake/observer.rb
Original file line number Original file line Diff line number Diff line change
@@ -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

Loading

0 comments on commit 06b1430

Please sign in to comment.