Skip to content

Instantly share code, notes, and snippets.

@thinkerbot
Created June 15, 2011 16:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thinkerbot/1027463 to your computer and use it in GitHub Desktop.
Save thinkerbot/1027463 to your computer and use it in GitHub Desktop.
Illustration of rake/pull/24
export RUBYOPT='-rpatch'
echo '------------ part 1 ------------'
rake standard_echo[a,b]
# got: a b
rake standard_echo['a,b',c]
# got: a b
rake -- cmd_echo a b
# got: a b
rake -- cmd_echo 'a,b' c
# got: a b
echo '------------ part 2 ------------'
rake standard_one MESSAGE=hello standard_two MESSAGE=goodbye
# goodbye
# goodbye
rake -- cmd_one -m hello -- cmd_two -m goodbye
# hello
# goodbye
require 'rubygems'
require 'rake'
require 'optparse'
module Rake
class CommandTask < Task
def parser
@parser ||= OptionParser.new do |opts|
opts.on_tail("-h", "--help", "Display this help message.") do
puts opts
exit
end
end
end
def invoke(*args)
parser.parse!(args)
super(*args)
end
def invoke_with_call_chain(*args)
super
reenable
end
def reenable
@config = nil
super
end
def config
@configs ||= default_config.dup
end
def default_config
@default_config ||= {}
end
def [](key)
config[key.to_sym]
end
def method_missing(sym, *args, &block)
sym = sym.to_sym
config.has_key?(sym) ? config[sym] : super
end
def set_arg_names(args)
while options = parse_options(args.last)
set_options(options)
args.pop
end
@arg_names = args.map { |a| a.to_sym }
parser.banner = "Usage: rake -- #{name} [options] #{@arg_names.join(' ')}"
@arg_names
end
def parse_options(obj)
case obj
when Array then [obj]
when String then parse_options_string(obj)
else nil
end
end
def parse_options_string(string)
string = string.strip
return nil unless string[0] == ?-
string.split(/\s*\n\s*/).collect do |str|
flags, desc = str.split(':', 2)
flags = flags.split(',').collect! {|arg| arg.strip }
key = guess_key(flags)
default = flags.last =~ /\s+\[(.*)\]/ ? guess_default($1) : guess_bool_default(flags)
[key, default] + flags + [desc.to_s.strip]
end
end
def guess_key(flags)
keys = flags.collect do |flag|
case flag.split(' ').first
when /\A-([^-])\z/ then $1
when /\A--\[no-\](.*)\z/ then $1
when /\A--(.*)\z/ then $1
else nil
end
end
keys.compact.sort_by {|key| key.length }.last
end
def guess_default(str)
case str
when /\A(\d+)\z/ then str.to_i
when /\A(\d+\.\d+)\z/ then str.to_f
else str
end
end
def guess_bool_default(flags)
flags.any? {|flag| flag =~ /\A--\[no-\]/ ? true : false }
end
def set_options(options)
options.each do |(key, default, *option)|
default = false if default.nil?
option = guess_option(key, default) if option.empty?
default_config[key.to_sym] = default
parser.on(*option) do |value|
config[key.to_sym] = parse_config_value(default, value)
end
end
end
def guess_option(key, default)
n = key.to_s.length
case default
when false
n == 1 ? ["-#{key}"] : ["--#{key}"]
when true
["--[no-]#{key}"]
else
n == 1 ? ["-#{key} [#{key.to_s.upcase}]"] : ["--#{key} [#{key.to_s.upcase}]"]
end
end
def parse_config_value(default, value)
case default
when String then value.to_s
when Integer then value.to_i
when Float then value.to_f
else value
end
end
end
end
module Rake
module DSL
def cmd(*args, &block)
# Removed to skip depracation warning...
# unless args.last.kind_of?(Hash)
# args << {:needs => []}
# end
Rake::CommandTask.define_task(*args, &block)
end
end
end
module Rake
class Application
def format_task_string(args)
"#{args.shift}[#{args.join(',')}]"
end
# Read and handle the command line options.
def handle_options
options.rakelib = ['rakelib']
OptionParser.new do |opts|
opts.banner = "rake [-f rakefile] {options} targets..."
opts.separator ""
opts.separator "Options are ..."
opts.on_tail("-h", "--help", "-H", "Display this help message.") do
puts opts
exit
end
standard_rake_options.each { |args| opts.on(*args) }
opts.on_tail("--", "Turn on arg syntax ('-- a b c' => 'a[b,c]')") do
# restores option break to ARGV if found, allowing arg syntax
throw :terminate, "--"
end
opts.environment('RAKEOPT')
end.parse!
# If class namespaces are requested, set the global options
# according to the values in the options structure.
if options.classic_namespace
$show_tasks = options.show_tasks
$show_prereqs = options.show_prereqs
$trace = options.trace
$dryrun = options.dryrun
$silent = options.silent
end
end
# Collect the list of tasks on the command line. If no tasks are
# given, return a list containing only the default task.
# Environmental assignments are processed at this time as well.
def collect_tasks
@top_level_tasks = []
current = nil
ARGV.each do |arg|
case arg
when '--'
if current && !current.empty?
@top_level_tasks << format_task_string(current)
end
current = []
when /^(\w+)=(.*)$/
ENV[$1] = $2
else
if current
current << arg
else
@top_level_tasks << arg unless arg =~ /^-/
end
end
end
if current && !current.empty?
@top_level_tasks << format_task_string(current)
end
@top_level_tasks.push("default") if @top_level_tasks.size == 0
end
end
end
# To run, export RUBYOPT so that it applies the patch before invoking rake:
#
# export RUBYOPT='-rpatch'
# rake -h
# rake -T
# rake -- cmd_echo a b
#
#
# Part 1
#
task :standard_echo, :a, :b do |t, args|
message = ENV['MESSAGE'] || 'got'
puts "#{message}: #{args.a} #{args.b}"
end
cmd :cmd_echo, :a, :b, %{
-m,--message [got] : A message
} do |config, args|
puts "#{config.message}: #{args.a} #{args.b}"
end
#
# Part 2
#
task :standard_one do
puts(ENV['MESSAGE'] || 'got')
end
task :standard_two do
puts(ENV['MESSAGE'] || 'got')
end
cmd :cmd_one, %{
-m,--message [got] : A message
} do |config, args|
puts config.message
end
cmd :cmd_two, %{
-m,--message [got] : A message
} do |config, args|
puts config.message
end
@richmeyers for the most part I agree with your opinion but I should clarify that the '--' syntax does nothing to fix the escape issues. Commas cannot be specified using either syntax. Here are some examples - see this [gist](https://gist.github.com/1027463) to run these yourself:
```bash
# [Rakefile]
# task :standard_echo, :a, :b do |t, args|
# message = ENV['MESSAGE'] || 'got'
# puts "#{message}: #{args.a} #{args.b}"
# end
#
# cmd :cmd_echo, :a, :b, %{
# -m,--message [got] : A message
# } do |config, args|
# puts "#{config.message}: #{args.a} #{args.b}"
# end
rake standard_echo[a,b]
# got: a b
rake standard_echo['a,b',c]
# got: a b
rake -- cmd_echo a b
# got: a b
rake -- cmd_echo 'a,b' c
# got: a b
```
To answer your question, one of the benefits of per-task options is that you can specify different options for different tasks. This is not possible with the ENV syntax, or at least opens you up to collisions because ENV is global. Sometimes you do want global options; this proposal provides per-task options when you don't.
```bash
# [Rakefile (continued)]
# task :standard_one do
# puts(ENV['MESSAGE'] || 'got')
# end
# task :standard_two do
# puts(ENV['MESSAGE'] || 'got')
# end
#
# cmd :cmd_one, %{
# -m,--message [got] : A message
# } do |config, args|
# puts config.message
# end
# cmd :cmd_two, %{
# -m,--message [got] : A message
# } do |config, args|
# puts config.message
# end
rake standard_one MESSAGE=hello standard_two MESSAGE=goodbye
# goodbye
# goodbye
rake -- cmd_one -m hello -- cmd_two -m goodbye
# hello
# goodbye
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment