Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use reject instead of delete_if #592

Merged
merged 1 commit into from
May 4, 2016
Merged

Use reject instead of delete_if #592

merged 1 commit into from
May 4, 2016

Conversation

jyaworski
Copy link

Ping @hunner.

@smoeding alerted me to this problem.

@smoeding
Copy link
Contributor

I think this still includes a change that would break existing functionality.

stdlib 4.11:

delete([ 'abracadabra' ], 'bra') => [ 'abracadabra' ]
delete([ 'abracadabra' ], 'abr') => [ 'abracadabra' ]

With this proposed change:

delete([ 'abracadabra' ], 'bra') => [ 'abracadabra' ]
delete([ 'abracadabra' ], 'abr') => [ ]

Maybe a new optional parameter would be helpful, so the user could specify if the second parameter should be used literally or as a regular expression. Otherwise all special regex characters would lead to a semantic change anyway. What about this for example:

delete([ 'a', 'b', 'c', '.' ], '.')

@jyaworski
Copy link
Author

I'm OK with adding a flag to turn on the regex matching, but I'm really confused why your second case is matching. Do we need to explicitly do something like ^expr$?

Any ideas on what that flag might look like? An entirely different function even?

@smoeding
Copy link
Contributor

In your previous commit you added (coll_item =~ %r{#{item}}) == 0 as condition. The result of the match operator is the position of the match. Matching abr in abracadabra matches from position 0. So in this case every element that starts with the item is removed.

Using anchors would somehow solve this problem. But it would also force the user to provide a full matching regex. To remove all entries that contain the letter 'a' somewhere would have to be written as .*a.* while everywhere else (grep, awk, sed, perl, ruby, ...) a simple a would suffice. I think this would be confusing.

So one solution could be an additional method parameter (e.g. is_regex = false) that the user has to set to true to switch to regex matching. Without this parameter the simple string comparison just as before would be used.

@jyaworski
Copy link
Author

Ah, OK. I couldn't find docs on how regex matching return codes worked. We want to do whole-word matching. \b, possibly, from this link. http://stackoverflow.com/questions/4879395/ruby-regex-rejecting-whole-words

@jyaworski
Copy link
Author

OK @smoeding and @hunner: I think I've got this.

I did end up having to accept a third, optional argument. There's no other way (that I saw) to do this reasonably and preserve existing functionality without making a lot of assumptions (like whole word regex matching by default).

@jyaworski jyaworski changed the title Use reject instead of delete_if, and check for == 0 Use reject instead of delete_if Apr 20, 2016
@@ -4,19 +4,22 @@
it { is_expected.not_to eq(nil) }
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError) }
it { is_expected.to run.with_params([]).and_raise_error(Puppet::ParseError) }
it { is_expected.to run.with_params([], 'two', 'three').and_raise_error(Puppet::ParseError) }
it { is_expected.to run.with_params([], 'two', 'three') }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this first block of tests test that the wrong number of arguments raises an error. as you're adding an optional third param, the correct fix here is

it { is_expected.to run.with_params([], 'two', 'three', 'four').and_raise_error(Puppet::ParseError) }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@DavidS
Copy link
Contributor

DavidS commented Apr 20, 2016

Hi @jyaworski , a flag seems to be indeed the only way forward here. Good work overall, only a few minor nits around docs and tests.

@jyaworski
Copy link
Author

@DavidS fixed. Thanks.

@@ -265,7 +265,7 @@ Takes a resource reference and an optional hash of attributes. Returns 'true' if

#### `delete`

Deletes all instances of a given element from an array, substring from a string, or key from a hash. Arrays and hashes may also match on regular expressions. For example, `delete(['a','b','c','b'], 'b')` returns ['a','c']; `delete('abracadabra', 'bra')` returns 'acada'. `delete({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1}, `delete(['abf', 'ab', 'ac'], '^ab.*')` returns ['ac']. *Type*: rvalue.
Deletes all instances of a given element from an array, substring from a string, or key from a hash. Arrays and hashes may also match on regular expressions by setting an optional third parameter to a true value. For example, `delete(['a','b','c','b'], 'b')` returns ['a','c']; `delete('abracadabra', 'bra')` returns 'acada'. `delete({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1}, `delete(['abf', 'ab', 'ac'], '^ab.*', true)` returns ['ac']. `delete(['ab', 'b'], 'b')` returns ['ab']. *Type*: rvalue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is really quite long, can it be broken up?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@DavidS
Copy link
Contributor

DavidS commented Apr 21, 2016

We've been discussing this in Triage, and it turned out that this is a backwards incompatible:

it { is_expected.to run.with_params(['one', 'two', 'twentytwo', 'another two'], 'two').and_return(['one']) }

passes on the current implementation, but fails on yours, so that's not good, either.

@jyaworski
Copy link
Author

@DavidS @hunner so it looks like we need another function entirely then. Please revert #591 and I'll change this PR to add a delete_regex function.

@DavidS
Copy link
Contributor

DavidS commented Apr 21, 2016

Ohhh! so the "other" behaviour is not yet released? That then, of course, doesn't make this a breaking change. More investigation required.

@bmjen
Copy link
Contributor

bmjen commented Apr 21, 2016

I don't think you need a delete_regex function, because the regex matching will work fine with this 1-line change: collection.reject! { |coll_item| (coll_item =~ %r{#{item}}) } here https://github.com/puppetlabs/puppetlabs-stdlib/pull/592/files#diff-85b753300e047711d62ec6a8a075a7deL35.

It looks like what you want is a delete function that will match on absolute string matching... based on the diff in functionality. This can be accomplished via a new function.. or the user providing a regex that of ^string$

@jyaworski
Copy link
Author

To be clear, this is the functionality I'm trying to do:

delete(['lo', 'bond0', 'bond1', 'eth0'], '^bond.*$') => ['lo', 'eth0']

Delete all keys/entries in a hash/array matching a regular expression.

@jyaworski
Copy link
Author

Okay @bmjen and @hunner. This is no longer backwards-incompatible.

@bmjen
Copy link
Contributor

bmjen commented May 4, 2016

This looks good @jyaworski! Thanks for making the updates. Can you rebase this and we can push it through. 👍

@jyaworski
Copy link
Author

@bmjen done. Thanks.

@bmjen bmjen merged commit f46c9fd into puppetlabs:master May 4, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants