-
Notifications
You must be signed in to change notification settings - Fork 1
RubyTapas
You can call a Rake application from an external ruby application.
require 'rake'
Rake.application.init
Rake.application.load_rakefile # looks in cwd
Rake::Task["hello"].invoke # will not run again if ctime hasn't changed.
# To run a task a 2nd time
Rake::Task["hello"].invoke
Rake::Task["hello"].reenable
Rake::Task["hello"].invoke
# To load a Rakefile from somewhere else
Rake.load_rakefile("./Rakefile")
Rake::Task["ns:hello"].invoke
Rake has a simple mechanism for running items in parallel that is dead simple. Change task
to multitask
. rake -j
allows you to set the number of threads you want to use. rake -m
allows you to run items as multitask without changing the tasks.
require 'rake/clean'
task :default => :highlights
LISTINGS = FileList["listings/*"]
HIGHLIGHTS = LISTINGS.ext(".html")
CLEAN.include(HIGHLIGHTS)
multitask :highlights => HIGHLIGHTS
rule ".html" => ->(f){ FileList[f.ext(".*")].first } do |t|
sh "pygmentize -o ${t.name} #{t.source}"
end
For example I think you could do this to start up processes.
Rake has a library to specifically clean generated files and directories.
require 'rake/clean'
CLEAN # FileList of generated files
CLEAN.include(SOURCE_FILES.ext(".html"))
CLOBBER # FileList of final product files
# CLOBBER also runs CLEAN
CLOBBER.include("book.mobi")
directory
task in rake will ensure a directory exists before running a task. Add the directory task as a dependency for another task. You can also ensure subdirectories are created by using mkdir_p
.
directory "output"
rule ".html" => [".md", "output"] do |t|
mkdir_p t.name.pathmap("%d")
"md_to_html #{t.name} #{t.source}"
end
Cleaning up after a run is also important. This task will remove the directory before creating it on the next run.
task :clean do
rm_rf "output"
end
Using pathmap, we can modify lists of paths to new paths. For example, we could change the load path for ruby.
load_paths = FileList["mylibs", "yourlibs", "ourlibs"]
ruby_args = load_paths.pathmap("-I%p")
# => ["-Imylibs", "-Iyourlibs", "-Iourlibs"]
Assume all source files are under a src
directory and we want to generate the files to the output
directory.
source_files = Rake::fileList.new("sources/**/*.md")
output_files = source_files.pathmap("%{^src/,output/}X.html")
# ensure all the directories are created
sh "mkdir -p #{f.pathmap('%d')}"
The Rake rule for building files is that if the target file is newer than all it's dependencies, it is not built.
Invoke rake -P
will cause Rake to spit out it's list of prerequisites. You can get further diagnostic information by turning on Rake's trace-rules
option. You can also set it via rake --rules
.
Rake.application.options.trace_rules = true
In order to prevent ourselves from having to redefine rules for each type of mapping, we can make a general rule.
SOURCE_FILES = Rake::FileList.new("**/*.md", "**/.markdown") do |files|
files.exclude("~*")
files.exclude(/^tmp\//)
files.exclude do |f|
`git ls-files #{f}`.empty?
end
end
rule ".html" => ->(f) { source_for_html(f) } do |t|
sh "md_to_html #{t.name} #{t.source}"
end
def source_for_html(html_file)
SOURCE_FILES.detect { |f| f.ext == html_file.ext }
end
Jim Weirich mentioned in the comments that he would normally just loop over the rule generation, which is something I think I prefer.
%w(.md .markdown).each do |ext|
rule ".html" => ext do |t|
"md_to_html #{t.name} #{t.source}"
end
end
File Lists are flexible because you can pass patterns of files to include and files to exclude.
markdown_files = Rake::FileList.new("**/*.md", "**/.markdown") do |files|
files.exclude("~*")
files.exclude(/^tmp\//)
files.exclude do |f|
`git ls-files #{f}`.empty?
end
end
This can also be invoke using the shorthand method Rake::FileList["*.md"]
. If you want to change the extension of the file list, use the #ext
method.
markdown_files.ext(".html")
To add this to the previous example:
source_files = Rake::FileList.new("**/*.md", "**/*.markdown") do |files|
files.exclude("~*")
files.exclude(/^tmp\//)
files.exclude do |f|
`git ls-files #{f}`.empty
end
end
task :html => source_files.ext(".html")
# Also duplicate the "html" => "md" rule for "html" => "markdown"
Rake is super useful for declaring file dependencies. In the example below, the file
method declares that the html_file
is dependent upon the md_file
. Provide a block that acts as "make" instructions.
%w(file1.md file2.md).each do |md_file|
html_file = File.basename(md_file, ".md") + ".html"
file html_file => md_file do
sh "md_to_html #{md_file} #{html_file}
end
end
This can now be invoked via rake file1.html
. If the modification time hasn't changed, no action is taken. We can take this a step further by making a default task.
task :default => :html
task :html => %w(file1.html file2.html)
We can remove a lot of duplication by teaching rake how to generate the HTML file.
rule ".html" => ".md" do |t|
sh "md_to_html #{t.name} #{t.source}"
end
There is some filename matching logic that makes Rake intelligent enough to know if it's given an HTML file it should look for the corresponding Markdown file.
There are some special matchers for yielding blocks in RSpec. The most simple:
class Operation
def perform(device, &block)
yield
end
end
it "yields control to a block" do
expect { |probe|
operation.perform(device, &probe)
}.to yield_control
end
You can specify what is yielded by using #yield_with_args
. Additionally, if you want to ensure that multiple items are yielded you can use #yield_successive_args
.
Given the implementation for Episode 38.
it "yields an error" do
yielded = :unyielded
do_request(method, uri, nil) do |error|
yielded = error
end
expect(yielded).to be_a(CustomError)
end
This also works if it yields multiple items
let(:my_hash) { { foo: :bar } }
it "yields the key and value" do
yielded = []
my_hash.each do |key, value|
yielded << [key, value]
end
expect(yielded).to include([:foo, :bar])
end
class Operation
class << self
attr_accessor :logger
end
end
Operation.logger = Logger.new(STDOUT)
or
class Operation
def self.logger
@logger
end
def self.logger=(logger)
@logger = logger
end
end
Operation.logger = Logger.new(STDOUT)
A technique for allowing the caller to specify behavior within a method.
DEFAULT_STRATEGY = ->(error) { raise }
def do_request(method, uri, data = nil, &strategy)
strategy ||= DEFAULT_STRATEGY
response = connection.send(method, uri, data)
rescue => error
strategy.call(error)
end
do_request(:get, uri, nil) { "N/A" }
do_request(:get, uri, nil) do |error|
raise CustomError, error.message
end
This implementation can be found in #fetch
.
A technique for composing
class Operation
extend Forwardable
def_delegators :runner, :run
end
The target for delegation can be anything that could be eval'd such as '@runner.connection'
.
class Operation
extend Forwardable
# The singular version of #def_delegator sets up an alias for the method.
def_delegator :runner, :configuration, :config
end
Concepts
Elements
Guidelines
Miscellaneous
Techniques