Expose the functions of ruby's built-in Shellwords module (#580)

* Add shell_escape function, shell_join function & shell_split function
This commit is contained in:
Joris 2016-04-28 19:44:30 +02:00 committed by TP Honey
parent 24797bb5ad
commit 9e1f74f3fc
7 changed files with 190 additions and 0 deletions

View file

@ -821,6 +821,40 @@ Strips spaces to the right of the string. *Type*: rvalue.
Takes an integer max value and a string seed value and returns a repeatable random integer smaller than max. Like `fqdn_rand`, but does not add node specific data to the seed. *Type*: rvalue.
#### `shell_escape`
Escapes a string so that it can be safely used in a Bourne shell command line. Note that the resulting string should be used unquoted and is not intended for use in double quotes nor in single quotes. This function behaves the same as ruby's `Shellwords.shellescape()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellescape).
*Example:*
~~~
shell_escape('foo b"ar') => 'foo\ b\"ar'
~~~
*Type*: rvalue.
#### `shell_join`
Builds a command line string from the given array of strings. Each array item is escaped for Bourne shell. All items are
then joined together, with a single space in between. This function behaves the same as ruby's `Shellwords.shelljoin()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shelljoin).
*Example:*
~~~
shell_join(['foo bar', 'ba"z']) => 'foo\ bar ba\"z'
~~~
*Type*: rvalue.
#### `shell_split`
Splits a string into an array of tokens in the same way the Bourne shell does. This function behaves the same as ruby's `Shellwords.shellsplit()` function, also see the [ruby documentation](http://ruby-doc.org/stdlib-2.3.0/libdoc/shellwords/rdoc/Shellwords.html#method-c-shellsplit).
*Example:*
~~~
shell_split('foo\ bar ba\"z') => ['foo bar', 'ba"z']
~~~
*Type*: rvalue.
#### `shuffle`
Randomizes the order of a string or array elements. *Type*: rvalue.

View file

@ -0,0 +1,30 @@
#
# shell_escape.rb
#
require 'shellwords'
module Puppet::Parser::Functions
newfunction(:shell_escape, :type => :rvalue, :doc => <<-EOS
Escapes a string so that it can be safely used in a Bourne shell command line.
Note that the resulting string should be used unquoted and is not intended for use in double quotes nor in single
quotes.
This function behaves the same as ruby's Shellwords.shellescape() function.
EOS
) do |arguments|
raise(Puppet::ParseError, "shell_escape(): Wrong number of arguments " +
"given (#{arguments.size} for 1)") if arguments.size != 1
# explicit conversion to string is required for ruby 1.9
string = arguments[0].to_s
result = Shellwords.shellescape(string)
return result
end
end
# vim: set ts=2 sw=2 et :

View file

@ -0,0 +1,31 @@
#
# shell_join.rb
#
require 'shellwords'
module Puppet::Parser::Functions
newfunction(:shell_join, :type => :rvalue, :doc => <<-EOS
Builds a command line string from the given array of strings. Each array item is escaped for Bourne shell. All items are
then joined together, with a single space in between.
This function behaves the same as ruby's Shellwords.shelljoin() function
EOS
) do |arguments|
raise(Puppet::ParseError, "shell_join(): Wrong number of arguments " +
"given (#{arguments.size} for 1)") if arguments.size != 1
array = arguments[0]
raise Puppet::ParseError, ("First argument is not an Array: #{array.inspect}") unless array.is_a?(Array)
# explicit conversion to string is required for ruby 1.9
array = array.map { |item| item.to_s }
result = Shellwords.shelljoin(array)
return result
end
end
# vim: set ts=2 sw=2 et :

View file

@ -0,0 +1,26 @@
#
# shell_split.rb
#
require 'shellwords'
module Puppet::Parser::Functions
newfunction(:shell_split, :type => :rvalue, :doc => <<-EOS
Splits a string into an array of tokens in the same way the Bourne shell does.
This function behaves the same as ruby's Shellwords.shellsplit() function
EOS
) do |arguments|
raise(Puppet::ParseError, "shell_split(): Wrong number of arguments " +
"given (#{arguments.size} for 1)") if arguments.size != 1
string = arguments[0].to_s
result = Shellwords.shellsplit(string)
return result
end
end
# vim: set ts=2 sw=2 et :

View file

@ -0,0 +1,22 @@
require 'spec_helper'
describe 'shell_escape' do
it { is_expected.not_to eq(nil) }
describe 'signature validation' do
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
it { is_expected.to run.with_params('foo', 'bar').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
end
describe 'stringification' do
it { is_expected.to run.with_params(10).and_return('10') }
it { is_expected.to run.with_params(false).and_return('false') }
end
describe 'escaping' do
it { is_expected.to run.with_params('foo').and_return('foo') }
it { is_expected.to run.with_params('foo bar').and_return('foo\ bar') }
it { is_expected.to run.with_params('~`!@#$%^&*()_+-=[]\{}|;\':",./<>?')
.and_return('\~\`\!@\#\$\%\^\&\*\(\)_\+-\=\[\]\\\\\{\}\|\;\\\':\",./\<\>\?') }
end
end

View file

@ -0,0 +1,23 @@
require 'spec_helper'
describe 'shell_join' do
it { is_expected.not_to eq(nil) }
describe 'signature validation' do
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
it { is_expected.to run.with_params(['foo'], ['bar']).and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
it { is_expected.to run.with_params('foo').and_raise_error(Puppet::ParseError, /is not an Array/i) }
end
describe 'shell argument joining' do
it { is_expected.to run.with_params(['foo']).and_return('foo') }
it { is_expected.to run.with_params(['foo', 'bar']).and_return('foo bar') }
it { is_expected.to run.with_params(['foo', 'bar baz']).and_return('foo bar\ baz') }
it { is_expected.to run.with_params(['~`!@#$', '%^&*()_+-=', '[]\{}|;\':"', ',./<>?'])
.and_return('\~\`\!@\#\$ \%\^\&\*\(\)_\+-\= \[\]\\\\\{\}\|\;\\\':\" ,./\<\>\?') }
end
describe 'stringification' do
it { is_expected.to run.with_params([10, false, 'foo']).and_return('10 false foo') }
end
end

View file

@ -0,0 +1,24 @@
require 'spec_helper'
describe 'shell_split' do
it { is_expected.not_to eq(nil) }
describe 'signature validation' do
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
it { is_expected.to run.with_params('foo', 'bar').and_raise_error(Puppet::ParseError, /wrong number of arguments/i) }
end
describe 'stringification' do
it { is_expected.to run.with_params(10).and_return(['10']) }
it { is_expected.to run.with_params(false).and_return(['false']) }
end
describe 'shell line spliting' do
it { is_expected.to run.with_params('foo').and_return(['foo']) }
it { is_expected.to run.with_params('foo bar').and_return(['foo', 'bar']) }
it { is_expected.to run.with_params('\~\`\!@\#\$\%\^\&\*\(\)_\+-\=\[\]\\\\\{\}\|\;\\\':\",./\<\>\?')
.and_return(['~`!@#$%^&*()_+-=[]\{}|;\':",./<>?']) }
it { is_expected.to run.with_params('\~\`\!@\#\$ \%\^\&\*\(\)_\+-\= \[\]\\\\\{\}\|\;\\\':\" ,./\<\>\?')
.and_return(['~`!@#$', '%^&*()_+-=', '[]\{}|;\':"', ',./<>?']) }
end
end