diff --git a/lib/puppet/functions/scanf.rb b/lib/puppet/functions/scanf.rb new file mode 100644 index 000000000..f107c68a8 --- /dev/null +++ b/lib/puppet/functions/scanf.rb @@ -0,0 +1,46 @@ +# Scans a string and returns an array of one or more converted values as directed by a given format string.args +# See the documenation of Ruby's String::scanf method for details about the supported formats (which +# are similar but not identical to the formats used in Puppet's `sprintf` function. +# +# This function takes two mandatory arguments: the first is the String to convert, and the second +# the format String. A parameterized block may optionally be given, which is called with the result +# that is produced by scanf if no block is present, the result of the block is then returned by +# the function. +# +# The result of the scan is an Array, with each sucessfully scanned and transformed value.args The scanning +# stops if a scan is unsuccesful and the scanned result up to that point is returned. If there was no +# succesful scan at all, the result is an empty Array. The optional code block is typically used to +# assert that the scan was succesful, and either produce the same input, or perform unwrapping of +# the result +# +# @example scanning an integer in string form (result is an array with +# integer, or empty if unsuccessful) +# "42".scanf("%i") +# +# @example scanning and processing resulting array to assert result and unwrap +# +# "42".scanf("%i") |$x| { +# unless $x[0] =~ Integer { +# fail "Expected a well formed integer value, got '$x[0]'" +# } +# $x[0] +# } +# +# @since 3.7.4 +Puppet::Functions.create_function(:scanf) do + require 'scanf' + + dispatch :scanf do + param 'String', 'data' + param 'String', 'format' + optional_block_param + end + + def scanf(data, format, block=nil) + result = data.scanf(format) + if !block.nil? + result = block.call({}, result) + end + result + end +end diff --git a/lib/puppet/parser/functions/scanf.rb b/lib/puppet/parser/functions/scanf.rb new file mode 100644 index 000000000..c9677c86b --- /dev/null +++ b/lib/puppet/parser/functions/scanf.rb @@ -0,0 +1,35 @@ +Puppet::Parser::Functions::newfunction( + :scanf, + :type => :rvalue, + :arity => 2, + :doc => <<-DOC +Scans a string and returns an array of one or more converted values as directed by a given format string.args +See the documenation of Ruby's String::scanf method for details about the supported formats (which +are similar but not identical to the formats used in Puppet's `sprintf` function. + +This function takes two mandatory arguments: the first is the String to convert, and the second +the format String. A parameterized block may optionally be given, which is called with the result +that is produced by scanf if no block is present, the result of the block is then returned by +the function. + +The result of the scan is an Array, with each sucessfully scanned and transformed value.args The scanning +stops if a scan is unsuccesful and the scanned result up to that point is returned. If there was no +succesful scan at all, the result is an empty Array. The optional code block is typically used to +assert that the scan was succesful, and either produce the same input, or perform unwrapping of +the result + + + "42".scanf("%i") + "42".scanf("%i") |$x| { + unless $x[0] =~ Integer { + fail "Expected a well formed integer value, got '$x[0]'" + } + $x[0] + } + +- since 3.7.4 +- note requires `parser = future` +DOC +) do |args| + function_fail(["scanf() is only available when parser/evaluator future is in effect"]) +end diff --git a/spec/unit/functions/scanf_spec.rb b/spec/unit/functions/scanf_spec.rb new file mode 100644 index 000000000..99b4d1cb3 --- /dev/null +++ b/spec/unit/functions/scanf_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe 'the scanf function' do + include PuppetSpec::Compiler + include Matchers::Resource + + before :each do + Puppet[:parser] = 'future' + end + + it 'scans a value and returns an array' do + expect(compile_to_catalog("$x = '42'.scanf('%i')[0] + 1; notify { \"test$x\": }")).to have_resource('Notify[test43]') + end + + it 'scans a value and returns result of a code block' do + expect(compile_to_catalog("$x = '42'.scanf('%i')|$x|{$x[0]} + 1; notify { \"test$x\": }")).to have_resource('Notify[test43]') + end + + it 'returns empty array if nothing was scanned' do + expect(compile_to_catalog("$x = 'no'.scanf('%i')[0]; notify { \"test${x}test\": }")).to have_resource('Notify[testtest]') + end + + it 'produces result up to first unsuccessful scan' do + expect(compile_to_catalog("$x = '42 no'.scanf('%i'); notify { \"test${x[0]}${x[1]}test\": }")).to have_resource('Notify[test42test]') + end + + + it 'errors when not given enough arguments' do + expect do + compile_to_catalog("'42'.scanf()") + end.to raise_error(/.*expected.*scanf\(String data, String format, Callable block \{0,1\}\)/m) + end +end