Search code examples
ruby-on-railsrubycommand-linerakerake-task

Rake removes trailing whitespaces from parameters. How to prevent?


Having a simple rake task:

task :ws, [:str] => :environment do |t, args|
  puts args.str.inspect
end

I get following results when run this task in command line:

$ rake ws[' ']
nil

$ rake ws['    ']
nil

$ rake ws['    1']
"    1"

$ rake ws['    1    ']
"    1"

$ rake 'ws[ ]'
nil

$ rake 'ws[    ]'
nil

$ rake 'ws[    1]'
"    1"

$ rake 'ws[    1    ]'
"    1"

While when the task is being invoked in rails console everything works as expected:

2.3.3 :258 > Rake::Task['ws'].invoke(' ')
" "
2.3.3 :259 > Rake::Task['ws'].reenable
2.3.3 :260 > Rake::Task['ws'].invoke('    ')
"    "
2.3.3 :261 > Rake::Task['ws'].reenable
2.3.3 :262 > Rake::Task['ws'].invoke('    1')
"    1"
2.3.3 :263 > Rake::Task['ws'].reenable
2.3.3 :264 > Rake::Task['ws'].invoke('    1    ')
"    1    "

Again, it is definitely not the OS who is responsible for that trimming, since it shouldn't trim anything between quote marks. And besides it can be easily tested this way:

task :ws, [:str] => :environment do |t, args|
  puts args.str.inspect
  puts ARGV[1].inspect
end

then in command line:

$ rake 'ws[  1  ]' '  2  '
"  1"
"  2  "

Can trailing whitespaces be somehow preserved when task is being run in command line ? Without using command line parameters (' 2 ' in the example above) since Rake will try to execute a task for each such parameter and will raise an error if a task is not found, or even worse if it finds a task then it will execute it.

Rake version 12.0.0


Solution

  • You can escape a single space character at the end of your whitespace, like so:

    rake 'ws[   2  \ ]'
    "   2   "
    

    This seems to work how you would want.

    The backslash is passed along as part of the argument and the backslash-space pair is getting interpreted as a space (by design or accident?) in another layer.

    Take a look at Rake::Application#parse_task_string. You can experiment yourself in irb, e.g.

    Rake::Application.new.parse_task_string('ws[   1   ]')
    => ["ws", ["   1"]]
    
    Rake::Application.new.parse_task_string('ws[   1  \ ]')
    => ["ws", ["   1   "]] 
    

    I actually want to step through this to see what's going on.

    First it splits your task name and arguments like so:

    /^([^\[]+)(?:\[(.*)\])$/ =~ 'ws[    1    ]'
    

    Now we have "ws" in $1 and " 1 " in $2. All good so far. $2 becomes remaining_args. remaining_args then gets chopped up by repeated passes by another regex:

    /((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args # Starts as "    1    "
    

    And now $1 is " 1"! That \s* is eating any continuous whitespace at the end of any single argument.

    So why does the backspace preserve those spaces?

    1. A single space character is permitted to survive after the backspace (captured by the \\. in the capture group). Also, everything up to the backspace is captured.

    2. The argument string is then passed to .gsub(/\\(.)/, '\1'). This replaces backspace-anything with anything. So the backspace-space pair is replaced with space. I wonder why they don't just .gsub(/\\/, '')? It must be to allow escaped backslashes.

    The current implementation will additionally preserve a backspace that happens to be the last char at the end of an argument, for whatever reason. As a side effect it seems that ending a single argument with a backslash (even an escaped backslash) will turn the whole argument into an empty string, e.g.

    Rake::Application.new.parse_task_string('task[tears in rain\]')
    =>["task", [""]]
    

    So that's fun (if you need to pass along a Windows directory for example).

    Rake::Application.new.parse_task_string('task[C:\\Johnny\\Likes\\Windows\\]')
    =>["task", [""]]
    

    I guess the author's intention is to allow you to escape the comma to avoid splitting the argument string. It has the side effect of allowing you to pass whitespace. I wouldn't rely on it though...