Build.PL100664001755001752 45513707566016 16052 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035# ========================================================================= # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. # DO NOT EDIT DIRECTLY. # ========================================================================= use 5.008_001; use strict; use Module::Build::Tiny 0.035; Build_PL(); Changes100664001755001752 1064413707566016 16112 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035# Revision history for Perl module autobox::Transform 1.035 2020-07-27T15:00:22Z * Fixed #7: removed dependency on "true", thanks TBSliver/Tom Bloor * Fixed #6: improved pod, thanks manwar/Mohammad S Anwar * POD polish 1.034 2019-06-19T20:22:58Z * Documentation improvements to go with new "reject", "reject_by", "reject_each" methods 1.033 2019-06-19T20:15:20Z * Doc fixes 1.032 2019-06-19T19:22:20Z * New @array method: ->reject, the opposite of Array->filter * New @array method: ->reject_by, the opposite of Array->filter_by * New @array method: ->reject_each, the opposite of Array->filter_each 1.031 2017-11-09T00:48:09Z * Improved POD documentation 1.030 2017-11-08T23:42:36Z * Document simple case for group_by $value_sub * New @array methods ->group, ->group_count, ->group_array 1.029 2016-10-24T22:09:19Z * @array->to_hash, and %hash->to_array 1.028 2016-10-23T20:18:46Z * Even more docs improvements for ->order, ->order_by. 1.027 2016-10-23T17:41:02Z * POD fixes for order, order_by * Other docs improvements 1.026 2016-10-23T16:58:04Z * New feature: sorting using @array->order and @array->order_by. 1.025 2016-10-13T12:18:59Z * Document %hash->filter_each to make it official. 1.024 2016-10-12T21:00:44Z * ->filter and ->filter_by now takes a dwim predicate which can be one of: subref (returns true), string (eq), regex (=~), hashref (key exists). Arrayref (in) and undef (is) will annoyingly have to wait until the old call style is removed in 2.000 . 1.023 2016-10-10T21:46:35Z * Officially rename all "grep"-related methods to "filter" something, to open up for a new $array->filter() method which is more capable than autobox::Core's $array->grep(). There are aliases for all existing grep-methods. All grep-related methods will continue to work: they are not deprecated and will probably never go away. * Simple $array->filter($subref) method. 1.022 2016-10-09T20:11:29Z * POD fixes and documentation improvements 1.021 2016-10-09T19:33:10Z * The new call style for accessors is now the preferred one, the old one deprecated but still supported until 2.000 . If you have code with the old call style, please pin your version to < 2.000 in your cpanfile, dist.ini or whatever you use. 1.020 2016-10-09T15:15:22Z * Support for new call style for accessor method calls with arguments e.g. ->map_by([ price_with_discount_code => $code ]) 1.019 2016-08-21T19:32:42Z * Minor POD fixes 1.018 2016-08-21T18:55:54Z * Array->to_ref, Hash->to_ref, Array->to_array, Hash->to_hash. Use these e.g. as a nicer way to enfore scalar or list context. * Use autobox::Core inside of autobox::Transform, so that e.g. String autobox methods can be used in a map_by 1.017 2016-07-25T11:53:06Z * Allow empty $args arrayref when accessing hash keys 1.016 2016-07-23T15:50:32Z * Improved docs 1.015 2016-07-23T15:27:10Z * Make $hash->map_each_value() official by documenting it. * grep_by can now take a $grep_subref to check if each item should remain (default: check for true values). 1.014 2016-07-21T17:02:10Z * map_each_value Hash method (so far undocumented) 1.013 2016-07-19T12:29:41Z * Change version required to 5.010, hopefully closing #3. Thanks to https://github.com/findmo for reporting. * Make $hash->map_each(), $hash->map_each_to_array() official by documenting them. 1.012 2016-07-07T17:10:47Z * Require perl 5.10, to avoid bogus test failures on unsupported versions. This resolves issue #2: Fails with perls < 5.10 1.011 2016-07-07T12:57:42Z * Rename Hash grep -> grep_each, grep_defined -> grep_each_defined - This is why they're undocumented * map_each, map_each_to_array Hash method (so far undocumented) 1.010 2016-06-04T06:59:49Z * grep, grep_defined Hash methods (so far undocumented) 1.009 2016-06-05T21:24:57Z * More doc improvements, examples 1.008 2016-06-04T16:51:58Z * Docs fixes and improvements 1.007 2016-06-04T16:19:00Z * Add new transform: uniq_by 1.006 2016-04-29T08:30:01Z * key_value* Hash methods (so far undocumented) 1.004 2016-03-06T12:23:17Z * Cleaner docs 1.003 2016-03-05T22:02:59Z * map_by, grep_by, group_by can look up hash keys as well as make method calls 1.002 2016-03-01T14:00:06Z * Fix #1: Add cpanfile deps 1.001 2016-02-28T22:18:35Z * Documentation for all methods 1.000 2016-02-28T21:37:22Z * Original version LICENSE100664001755001752 4404513707566017 15627 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035This software is copyright (c) 2016 by Johan Lindstrom . This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2016 by Johan Lindstrom . This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2016 by Johan Lindstrom . This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End META.json100664001755001752 462013707566017 16216 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035{ "abstract" : "Autobox methods to transform Arrays and Hashes", "author" : [ "- Johan Lindstrom, All Rights Reserved." ], "dynamic_config" : 0, "generated_by" : "Minilla/v3.1.10", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "autobox-Transform", "no_index" : { "directory" : [ "t", "xt", "inc", "share", "eg", "examples", "author", "builder" ] }, "prereqs" : { "configure" : { "requires" : { "Module::Build::Tiny" : "0.035" } }, "develop" : { "requires" : { "Test::CPAN::Meta" : "0", "Test::MinimumVersion::Fast" : "0.04", "Test::PAUSE::Permissions" : "0.07", "Test::Pod" : "1.41", "Test::Spellunker" : "v0.2.7" } }, "runtime" : { "requires" : { "Carp" : "0", "List::MoreUtils" : "0", "Sort::Maker" : "0", "autobox" : "0", "autobox::Core" : "0", "parent" : "0", "perl" : "5.010" } }, "test" : { "requires" : { "Moo" : "0", "Test::Differences" : "0", "Test::Exception" : "0", "Test::More" : "0.98" } } }, "provides" : { "autobox::Transform" : { "file" : "lib/autobox/Transform.pm", "version" : "1.035" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/jplindstrom/p5-autobox-Transform/issues" }, "homepage" : "https://github.com/jplindstrom/p5-autobox-Transform", "repository" : { "url" : "git://github.com/jplindstrom/p5-autobox-Transform.git", "web" : "https://github.com/jplindstrom/p5-autobox-Transform" } }, "version" : "1.035", "x_authority" : "cpan:JOHANL", "x_contributors" : [ "Johan Lindstrom ", "Johan Lindstrom ", "Johan Lindstrom ", "Mohammad S Anwar ", "Tom Bloor " ], "x_serialization_backend" : "JSON::PP version 2.27400_02", "x_static_install" : 1 } README.md100664001755001752 10410613707566017 16114 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035# NAME autobox::Transform - Autobox methods to transform Arrays and Hashes # CONTEXT [autobox](https://metacpan.org/pod/autobox) provides the ability to call methods on native types, e.g. strings, arrays, and hashes as if they were objects. [autobox::Core](https://metacpan.org/pod/autobox%3A%3ACore) provides the basic methods for Perl core functions like `uc`, `map`, and `grep`. This module, `autobox::Transform`, provides higher level and more specific methods to transform and manipulate arrays and hashes, in particular when the values are hashrefs or objects. # SYNOPSIS use autobox::Core; # map, uniq, sort, join, sum, etc. use autobox::Transform; ## Arrays # use autobox::Core for ->map etc. # filter (like a more versatile grep) $book_locations->filter(); # true values $books->filter(sub { $_->is_in_library($library) }); $book_names->filter( qr/lord/i ); $book_genres->filter("scifi"); $book_genres->filter({ fantasy => 1, scifi => 1 }); # hash key exists # reject: the inverse of filter $book_genres->reject("fantasy"); # order (like a more succinct sort) $book_genres->order; $book_genres->order("desc"); $book_prices->order([ "num", "desc" ]); $books->order([ sub { $_->{price} }, "desc", "num" ]); $log_lines->order([ num => qr/pid: "(\d+)"/ ]); $books->order( [ sub { $_->{price} }, "desc", "num" ] # first price sub { $_->{name} }, # then name ); # group (aggregate) array into hash $book_genres->group; # "Sci-fi" => "Sci-fi" $book_genres->group_count; # "Sci-fi" => 3 $book_genres->group_array; # "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi"] # Flatten arrayrefs-of-arrayrefs $authors->map_by("books") # ->books returns an arrayref # [ [ $book1, $book2 ], [ $book3 ] ] $authors->map_by("books")->flat; # [ $book1, $book2, $book3 ] # Return reference, even in list context, e.g. in a parameter list $book_locations->filter()->to_ref; # Return array, even in scalar context @books->to_array; # Turn paired items into a hash @titles_books->to_hash; ## Arrays where the items are hashrefs/objects # $books and $authors below are arrayrefs with either objects or # hashrefs (the call syntax is the same). These have methods/hash # keys like C<$book->genre()>, C<$book->{is_in_stock}>, # C<$book->is_in_library($library)>, etc. $books->map_by("genre"); $books->map_by([ price_with_tax => $tax_pct ]); $books->filter_by("is_in_stock"); $books->filter_by([ is_in_library => $library ]); $books->filter_by([ price_with_tax => $rate ], sub { $_ > 56.00 }); $books->filter_by("price", sub { $_ > 56.00 }); $books->filter_by("author", "James A. Corey"); $books->filter_by("author", qr/corey/i); # grep_by is an alias for filter_by $books->grep_by("is_in_stock"); # reject_by: the inverse of filter_by $books->reject_by("is_sold_out"); $books->uniq_by("id"); $books->order_by("name"); $books->order_by(name => "desc"); $books->order_by(price => "num"); $books->order_by(price => [ "num", "desc" ]); $books->order_by(name => [ sub { uc($_) }, "desc" ]); $books->order_by([ price_with_tax => $rate ] => "num"); $books->order_by( author => "str", # first by author price => [ "num", "desc" ], # then by price, most expensive first ); $books->order_by( author => [ "desc", sub { uc($_) } ], [ price_with_tax => $rate ] => [ "num", "desc" ], "name", ); $books->group_by("title"), # { # "Leviathan Wakes" => $books->[0], # "Caliban's War" => $books->[1], # "The Tree-Body Problem" => $books->[2], # "The Name of the Wind" => $books->[3], # }, $authors->group_by([ publisher_affiliation => "with" ]), # { # 'James A. Corey with Orbit' => $authors->[0], # 'Cixin Liu with Head of Zeus' => $authors->[1], # 'Patrick Rothfuss with Gollanz' => $authors->[2], # }, $books->group_by_count("genre"), # { # "Sci-fi" => 3, # "Fantasy" => 1, # }, my $genre_books = $books->group_by_array("genre"); # { # "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ], # "Fantasy" => [ $fantasy_book_1 ], # }, ## Hashes # map over each pair # e.g. Upper-case the genre name, and make the count say "n books" # (return a key => value pair) $genre_count->map_each(sub { uc( $_[0] ) => "$_ books" }); # { # "FANTASY" => "1 books", # "SCI-FI" => "3 books", # }, # map over each value # e.g. Make the count say "n books" # (return the new value) $genre_count->map_each_value(sub { "$_ books" }); # { # "Fantasy" => "1 books", # "Sci-fi" => "3 books", # }, # map each pair into an array # e.g. Transform each pair to the string "n: genre" # (return list of items) $genre_count->map_each_to_array(sub { "$_: $_[0]" }); # [ "1: Fantasy", "3: Sci-fi" ] # filter each pair # Genres with more than five books $genre_count->filter_each(sub { $_ > 5 }); # filter out each pair # Genres with more than five books $genre_count->reject_each(sub { $_ <= 5 }); # Return reference, even in list context, e.g. in a parameter list %genre_count->to_ref; # Return hash, even in scalar context $author->book_count->to_hash; # Turn key-value pairs into an array %isbn__book->to_array; ## Combined examples my $order_authors = $order->books ->filter_by("title", qr/^The/) ->uniq_by("isbn") ->map_by("author") ->uniq_by("name") ->order_by(publisher => "str", name => "str") ->map_by("name")->uniq->join(", "); my $total_order_amount = $order->books ->reject_by("is_sold_out") ->filter_by([ covered_by_vouchers => $vouchers ], sub { ! $_ }) ->map_by([ price_with_tax => $tax_pct ]) ->sum; # DESCRIPTION `autobox::Transform` provides high level autobox methods you can call on arrays, arrayrefs, hashes and hashrefs. ## Transforming lists of objects vs list of hashrefs `map_by`, `filter_by` `order_by` etc. (all methods named `*_by`) work with sets of hashrefs or objects. These methods are called the same way regardless of whether the array contains objects or hashrefs. The items in the list must be either all objects or all hashrefs. If the array contains hashrefs, the hash key is looked up on each item. If the array contains objects, a method is called on each object (possibly with the arguments provided). ### Calling accessor methods with arguments For method calls, it's possible to provide arguments to the method. Consider `map_by`: $array->map_by($accessor) If the $accessor is a string, it's a simple method call. # method call without args $books->map_by("price") # becomes $_->price() or $_->{price} If the $accessor is an arrayref, the first item is the method name, and the rest of the items are the arguments to the method. # method call with args $books->map_by([ price_with_discount => 5.0 ]) # becomes $_->price_with_discount(5.0) ## Filter predicates There are several methods that filter items, e.g. `@array->filter` (duh), `@array->filter_by`, and `%hash->filter_each`. These methods take a `$predicate` argument to determine which items to retain or filter out. The `reject` family of methods do the opposite, and _filter out_ items that match the predicate, i.e. the opposite of the filter methods. If $predicate is an _unblessed scalar_, it is compared to each value with `string eq`. $books->filter_by("author", "James A. Corey"); If $predicate is a _regex_, it is compared to each value with `=~`. $books->reject_by("author", qr/Corey/); If $predicate is a _hashref_, values in @array are retained if the $predicate hash key `exists` (the hash values are irrelevant). $books->filter_by( "author", { "James A. Corey" => undef, "Cixin Liu" => 0, "Patrick Rothfuss" => 1, }, ); If $predicate is a _subref_, the subref is called for each value to check whether this item should remain in the list. The $predicate subref should return a true value to remain. `$_` is set to the current $value. $authors->filter_by(publisher => sub { $_->name =~ /Orbit/ }); ## Sorting using order and order\_by Let's first compare how sorting is done with Perl's `sort` and autobox::Transform's `order`/`order_by`. ### Sorting with sort - provide a sub that returns the comparison outcome of two values: `$a` and `$b` - in case of a tie, provide another comparison of $a and $b # If the name is the same, compare age (oldest first) sort { uc( $a->{name} ) cmp uc( $b->{name} ) # first comparison || int( $b->{age} / 10 ) <=> int( $a->{age} / 10 ) # second comparison } @users (note the opposite order of `$a` and `$b` for the age comparison, something that's often difficult to discern at a glance) ### Sorting with order, order\_by - Provide order options for how one value should be compared with the others: - how to compare (`cmp` or `<=>`) - which direction to sort (`asc`ending or `desc`ending) - which value to compare, using a regex or subref, e.g. by `uc($_)` - In case of a tie, provide another comparison # If the name is the same, compare age (oldest first) # ->order @users->order( sub { uc( $_->{name} ) }, # first comparison [ "num", sub { int( $_->{age} / 10 ) }, "desc" ], # second comparison ) # ->order_by @users->order_by( name => sub { uc }, # first comparison age => [ num => desc => sub { int( $_ / 10 ) } ], # second comparison ) ### Comparison Options If there's only one option for a comparison (e.g. `num`), provide a single option (string/regex/subref) value. If there are many options, provide them in an arrayref in any order. ### Comparison operator - `"str"` (cmp) - default - `"num"` (<=>) ### Sort order - `"asc"` (ascending) - default - `"desc"` (descending) ### The value to compare - A subref - default is: `sub { $_ }` - The return value is used in the comparison - A regex, e.g. `qr/id: (\d+)/` - The value of `join("", @captured_groups)` are used in the comparison (`@captured_groups` are `$1`, `$2`, `$3` etc.) ### Examples of a single comparison # order: the first arg is the comparison options (one or an # arrayref with many options) ->order() # Defaults to str, asc, $_, just like sort ->order("num") ->order(sub { uc($_) }) # compare captured matches, e.g. "John" and "Doe" as "JohnDoe" ->order( qr/first_name: (\w+), last_name: (\w+)/ ) ->order([ num => qr/id: (\d+)/ ]) ->order([ sub { int($_) }, "num" ]) # order_by: the first arg is the accessor, just like with # map_by. Second arg is the comparison options (one or an arrayref # with many options) ->order_by("id") ->order_by("id", "num") ->order_by("id", [ "num", "desc" ]) ->order_by("name", sub { uc($_) }) ->order_by(log_line => qr/first_name: (\w+), last_name: (\w+)/ ) ->order_by("log_line", [ num => qr/id: (\d+)/ ]) ->order_by(age => [ sub { int($_) }, "num" ]) # compare int( $a->age_by_interval(10) ) ->order_by([ age_by_interval => 10 ] => [ sub { int($_) }, "num" ]) # compare uc( $a->name_with_title($title) ) ->order_by([ name_with_title => $title ], sub { uc($_) }) ### Examples of fallback comparisons When the first comparison is a tie, the subsequent ones are used. # order: list of comparison options (one or an arrayref with many # options, per comparison) ->order( [ sub { $_->{price} }, "num" ], # First a numeric comparison of price [ sub { $_->{name} }, "desc" ], # or if same, a reverse comparison of the name ) ->order( [ sub { uc($_) }, "desc" ], "str", ) ->order( qr/type: (\w+)/, [ num => desc => qr/duration: (\d+)/ ] [ num => sub { /id: (\d+)/ } ], "str", ) # order_by: pairs of accessor-comparison options ->order_by( price => "num", # First a numeric comparison of price name => "desc", # or if same, a reverse comparison of the name ) ->order_by( price => [ "num", "desc" ], name => "str", ) # accessor is a method call with arg: $_->price_with_discount($discount) ->order_by( [ price_with_discount => $discount ] => [ "num", "desc" ], name => [ str => sub { uc($_) } ], "id", ) ## List and Scalar Context Almost all of the methods are context sensitive, i.e. they return a list in list context and an arrayref in scalar context, just like [autobox::Core](https://metacpan.org/pod/autobox%3A%3ACore). **Beware**: _you might be in list context when you need an arrayref._ When in doubt, assume they work like `map` and `grep` (i.e. return a list), and convert the return value to references where you might have an non-obvious list context. E.g. ### Incorrect $self->my_method( # Wrong, this is list context and wouldn't return an array ref books => $books->filter_by("is_published"), ); ### Correct $self->my_method( # Correct, put the returned list in an anonymous array ref books => [ $books->filter_by("is_published") ], ); $self->my_method( # Correct, ensure scalar context to get an array ref books => scalar $books->filter_by("is_published"), ); # Probably the nicest, since ->to_ref goes at the end $self->my_method( # Correct, use ->to_ref to ensure an array ref is returned books => $books->filter_by("is_published")->to_ref, ); # METHODS ON ARRAYS ## @array->filter($predicate = \*is\_true\_subref\*) : @array | @$array Similar to Perl's `grep`, return an `@array` with values for which $predicate yields a true value. $predicate can be a subref, string, undef, regex, or hashref. See ["Filter predicates"](#filter-predicates). The default (no `$predicate`) is a subref which retains true values in the @array. ### Examples my @apples = $fruit->filter("apple"); my @any_apple = $fruit->filter( qr/apple/i ); my @publishers = $authors->filter( sub { $_->publisher->name =~ /Orbit/ }, ); ### filter and grep [autobox::Core](https://metacpan.org/pod/autobox%3A%3ACore)'s `grep` method takes a subref, just like this method. `filter` also supports the other predicate types, like string, regex, etc. ## @array->reject($predicate = \*is\_false\_subref\*) : @array | @$array Similar to the Unix command `grep -v`, return an @array with values for which `$predicate` yields a _false_ value. $predicate can be a subref, string, undef, regex, or hashref. See ["Filter predicates"](#filter-predicates). The default (no $predicate) is a subref which _filters out_ true values in the `@array`. Examples: my @apples = $fruit->reject("apple"); my @no_apples = $fruit->reject( qr/apple/i ); my @publishers = $authors->reject( sub { $_->publisher->name =~ /Orbit/ }, ); ## @array->order(@comparisons = ("str")) : @array | @$array Return `@array` ordered according to the `@comparisons`. The default comparison is the same as the default sort, e.g. a normal string comparison of the `@array` values. If the first item in `@comparison` ends in a tie, the next one is used, etc. Each _comparison_ consists of a single _option_ or an _arrayref of options_, e.g. `str`/`num`, `asc`/`desc`, or a subref/regex. See ["Sorting using order and order\_by"](#sorting-using-order-and-order_by) for details about how these work. Examples: @book_genres->order; @book_genres->order("desc"); @book_prices->order([ "num", "desc" ]); @books->order([ sub { $_->{price} }, "desc", "num" ]); @log_lines->order([ num => qr/pid: "(\d+)"/ ]); @books->order( [ sub { $_->{price} }, "desc", "num" ] # first price sub { $_->{name} }, # then name ); ## @array->group($value\_subref = item) : %key\_value | %$key\_value Group the `@array` items into a hashref with the items as keys. The default `$value_subref` puts each item in the list as the hash value. If the key is repeated, the value is overwritten with the last object. Example: my $title_book = $book_titles->group; # { # "Leviathan Wakes" => "Leviathan Wakes", # "Caliban's War" => "Caliban's War", # "The Tree-Body Problem" => "The Tree-Body Problem", # "The Name of the Wind" => "The Name of the Wind", # }, ### The $value\_subref For simple cases of just grouping a single key to a single value, the `$value_subref` is straightforward to use. The hash key is the array item. The hash value is whatever is returned from my $new_value = $value_sub->($current_value, $object, $key); - `$current` value is the current hash value for this key (or undef if the first one). - `$object` is the current item in the list. The current $\_ is also set to this. - `$key` is the array item. See also: `->group_by`. ## @array->group\_count : %key\_count | %$key\_count Just like `group`, but the hash values are the the number of instances each item occurs in the list. Example: $book_genres->group_count; # { # "Sci-fi" => 3, # "Fantasy" => 1, # }, There are three books counted for the "Sci-fi" key. ## @array->group\_array : %key\_objects | %$key\_objects Just like `group`, but the hash values are arrayrefs containing those same array items. Example: $book_genres->group_array; # { # "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi" ], # "Fantasy" => [ "Fantasy" ], # }, The three Sci-fi genres are collected under the Sci-fi key. ## @array->flat() : @array | @$array Return a (one level) flattened array, assuming the array items themselves are array refs. I.e. [ [ 1, 2, 3 ], [ "a", "b" ], [ [ 1, 2 ], { 3 => 4 } ] ]->flat returns [ 1, 2, 3, "a", "b ", [ 1, 2 ], { 3 => 4 } ] This is useful if e.g. a `->map_by("some_method")` returns arrayrefs of objects which you want to do further method calls on. Example: # ->books returns an arrayref of Book objects with a ->title $authors->map_by("books")->flat->map_by("title") Note: This is different from [autobox::Core](https://metacpan.org/pod/autobox%3A%3ACore)'s `->flatten`, which reurns a list rather than an array and therefore can't be used in this way. ## @array->to\_ref() : $arrayref Return the reference to the `@array`, regardless of context. Useful for ensuring the last array method return a reference while in scalar context. Typically: do_stuff( books => $author->map_by("books")->to_ref, ); map\_by is called in list context, so without `->to_ref` it would have return an array, not an arrayref. ## @array->to\_array() : @array Return the `@array`, regardless of context. This is mostly useful if called on a ArrayRef at the end of a chain of method calls. ## @array->to\_hash() : %hash | %$hash Return the item pairs in the `@array` as the key-value pairs of a `%hash` (context sensitive). Useful if you need to continue calling `%hash` methods on it. Die if there aren't an even number of items in `@array`. # METHODS ON ARRAYS CONTAINING OBJECTS/HASHES ## @array->map\_by($accessor) : @array | @$array `$accessor` is either a string, or an arrayref where the first item is a string. Call the `$accessor` on each object in `@array`, or get the hash key value on each hashref in `@array`. Like: map { $_->$accessor() } @array # or map { $_->{$accessor} } @array Examples: my @author_names = $authors->map_by("name"); my $author_names = @publishers->map_by("authors")->flat->map_by("name"); Or get the hash key value. Example: my @review_scores = $reviews->map_by("score"); Alternatively for when `@array` contains objects, the $accessor can be an arrayref. The first item is the method name, and the rest of the items are passed as args in the method call. This obviously won't work when the `@array` contains hashrefs. Examples: my @prices_including_tax = $books->map_by([ "price_with_tax", $tax_pct ]); my $prices_including_tax = $books->map_by([ price_with_tax => $tax_pct ]); ## @array->filter\_by($accessor, $predicate = \*is\_true\_subref\*) : @array | @$array `$accessor` is either a string, or an arrayref where the first item is a string. Call the `$accessor` on each object in the list, or get the hash key value on each hashref in the list. Example: my @prolific_authors = $authors->filter_by("is_prolific"); Alternatively the `$accessor` is an arrayref. The first item is the accessor name, and the rest of the items are passed as args the method call. This only works when working with objects, not with hashrefs. Example: my @books_to_charge_for = $books->filter_by([ price_with_tax => $tax_pct ]); Use the `$predicate` to determine whether the value should remain. `$predicate` can be a subref, string, undef, regex, or hashref. See ["Filter predicates"](#filter-predicates). The default (no `$predicate`) is a subref which retains true values in the result `@array`. Examples: # Custom predicate subref my @authors = $authors->filter_by( "publisher", sub { $_->name =~ /Orbit/ }, ); # Call method with args and match a regex my @authors = $authors->filter_by( [ publisher_affiliation => "with" ], qr/Orbit/ }, ); Note: if you do something complicated with a $predicate subref, it might be easier and more readable to simply use `$array-$. ### Alias `grep_by` is an alias for `filter_by`. Unlike `grep` vs `filter`, this one works exaclty the same way. ## @array->reject\_by($accessor, $predicate = \*is\_false\_subref\*) : @array | @$array `reject_by` is the same as [`filter_by`](https://metacpan.org/pod/filter_by), except it _filters out_ items that matches the $predicate. Example: my @unproductive_authors = $authors->reject_by("is_prolific"); The default (no $predicate) is a subref which _filters out_ true values in the result `@array`. ## @array->uniq\_by($accessor) : @array | @$array `$accessor` is either a string, or an arrayref where the first item is a string. Call the $`accessor` on each object in the list, or get the hash key value on each hashref in the list. Return list of items which have a unique set of return values. The order is preserved. On duplicates, keep the first occurrence. Examples: # You have gathered multiple Author objects with duplicate ids my @authors = $authors->uniq_by("author_id"); Alternatively the `$accessor` is an arrayref. The first item is the accessor name, and the rest of the items are passed as args the method call. This only works when working with objects, not with hashrefs. Examples: my @example_book_at_price_point = $books->uniq_by( [ price_with_tax => $tax_pct ], ); ## @array->order\_by(@accessor\_comparison\_pairs) : @array | @$array Return `@array` ordered according to the `@accessor_comparison_pairs`. The comparison value comes from an initial `@array-`map\_by($accessor)> for each accessor-comparison pair. It is important that the $accessor call returns exactly a single scalar that can be compared with the other values. It then works just like with `->order`. $books->order_by("name"); # default order, i.e. "str" $books->order_by(price => "num"); $books->order_by(price => [ "num", "desc" ]); As with `map_by`, if the $accessor is used on an object, the method call can include arguments. $books->order_by([ price_wih_tax => $tax_rate ] => "num"); Just like with `order`, the value returned by the accessor can be transformed using a sub, or be matched against a regex. $books->order_by(price => [ num => sub { int($_) } ]); # Ignore leading "The" in book titles by optionally matching it # with a non-capturing group and the rest with a capturing group # paren $books->order_by( title => qr/^ (?: The \s+ )? (.+) /x ); If a comparison is missing for the last pair, the default is a normal `str` comparison. $books->order_by("name"); # default "str" If the first comparison ends in a tie, the next pair is used, etc. Note that in order to provide accessor-comparison pairs, it's often necessary to provide a default "str" comparison just to make it a pair. $books->order_by( author => "str", price => [ "num", "desc" ], ); ## @array->group\_by($accessor, $value\_subref = object) : %key\_value | %$key\_value `$accessor` is either a string, or an arrayref where the first item is a string. Call `->$accessor` on each object in the array, or get the hash key for each hashref in the array (just like `->map_by`) and group the values as keys in a hashref. The default `$value_subref` puts each object in the list as the hash value. If the key is repeated, the value is overwritten with the last object. Example: my $title_book = $books->group_by("title"); # { # "Leviathan Wakes" => $books->[0], # "Caliban's War" => $books->[1], # "The Tree-Body Problem" => $books->[2], # "The Name of the Wind" => $books->[3], # }, ### The $value\_subref For simple cases of just grouping a single key to a single value, the `$value_subref` is straightforward to use. The hash key is whatever is returned from `$object->$accessor`. The hash value is whatever is returned from my $new_value = $value_sub->($current_value, $object, $key); - `$current` value is the current hash value for this key (or undef if the first one). - `$object` is the current item in the list. The current $\_ is also set to this. - `$key` is the key returned by $object->$accessor(@$args) A simple example would be to group by the accessor, but instead of the object used as the value you want to look up an attribute on each object: my $book_id__author = $books->group_by("id", sub { $_->author }); # keys: book id; values: author If you want to create an aggregate value the `$value_subref` can be a bit tricky to use, so the most common thing would probably be to use one of the more specific group\_by-methods (see below). It should be capable enough to achieve what you need though. ## @array->group\_by\_count($accessor) : %key\_count | %$key\_count `$accessor` is either a string, or an arrayref where the first item is a string. Just like `group_by`, but the hash values are the the number of instances each $accessor value occurs in the list. Example: $books->group_by_count("genre"), # { # "Sci-fi" => 3, # "Fantasy" => 1, # }, `$book->genre()` returns the genre string. There are three books counted for the "Sci-fi" key. ## @array->group\_by\_array($accessor) : %key\_objects | %$key\_objects `$accessor` is either a string, or an arrayref where the first item is a string. Just like `group_by`, but the hash values are arrayrefs containing the objects which has each $accessor value. Example: my $genre_books = $books->group_by_array("genre"); # { # "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ], # "Fantasy" => [ $fantasy_book_1 ], # }, $book->genre() returns the genre string. The three Sci-fi book objects are collected under the Sci-fi key. # METHODS ON HASHES ## %hash->map\_each($key\_value\_subref) : %new\_hash | %$new\_hash Map each key-value pair in the hash using the `$key_value_subref`. Similar to how to how map transforms a list into another list, map\_each transforms a hash into another hash. `$key_value_subref->($key, $value)` is called for each pair (with $\_ set to the value). The subref should return an even-numbered list with zero or more key-value pairs which will make up the `%new_hash`. Typically two items are returned in the list (the key and the value). ### Example { a => 1, b => 2 }->map_each(sub { "$_[0]$_[0]" => $_ * 2 }); # Returns { aa => 2, bb => 4 } ## %hash->map\_each\_value($value\_subref) : %new\_hash | %$new\_hash Map each value in the hash using the `$value_subref`, but keep the keys the same. `$value_subref->($key, $value)` is called for each pair (with `$_` set to the value). The subref should return a single value for each key which will make up the `%new_hash` (with the same keys but with new mapped values). ### Example { a => 1, b => 2 }->map_each_value(sub { $_ * 2 }); # Returns { a => 2, b => 4 } ## %hash->map\_each\_to\_array($item\_subref) : @new\_array | @$new\_array Map each key-value pair in the hash into a list using the `$item_subref`. `$item_subref->($key, $value)` is called for each pair (with `$_` set to the value) in key order. The subref should return zero or more list items which will make up the `@new_array`. Typically one item is returned. ### Example { a => 1, b => 2 }->map_each_to_array(sub { "$_[0]-$_" }); # Returns [ "a-1", "b-2" ] ## %hash->filter\_each($predicate = \*is\_true\_subref\*) : @hash | @$hash Return a `%hash` with values for which `$predicate` yields a true value. `$predicate` can be a subref, string, undef, regex, or hashref. See ["Filter predicates"](#filter-predicates). The default (no $predicate) is a subref which retains true values in the `%hash`. If the $predicate is a subref, `$predicate->($key, $value)` is called for each pair (with `$_` set to the value). The subref should return a true value to retain the key-value pair in the result `%hash`. ### Examples { a => 1, b => 2 }->filter_each(sub { $_ == 2 }); # Returns { b => 2 } $book_author->filter_each(sub { $_->name =~ /Corey/ }); ## %hash->reject\_each($predicate = \*is\_false\_subref\*) : @hash | @$hash `reject_each` is the same as [`filter_each`](https://metacpan.org/pod/filter_each), except it _filters out_ items that matches the $predicate. Examples: { a => 1, b => 2 }->reject_each(sub { $_ == 2 }); # Returns { a => 1 } The default (no $predicate) is a subref which _filters out_ true values in the `%hash`. ## %hash->to\_ref() : $hashref Return the reference to the `%hash`, regardless of context. Useful for ensuring the last hash method return a reference while in scalar context. Typically: do_stuff( genre_count => $books->group_by_count("genre")->to_ref, ); ## %hash->to\_hash() : %hash Return the `%hash`, regardless of context. This is mostly useful if called on a HashRef at the end of a chain of method calls. ## %hash->to\_array() : @array | @$array Return the key-value pairs of the `%hash` as an `@array`, ordered by the keys. Useful if you need to continue calling `@array` methods on it. # AUTOBOX AND VANILLA PERL ## Raison d'etre [autobox::Core](https://metacpan.org/pod/autobox%3A%3ACore) is awesome, for a variety of reasons. - It cuts down on dereferencing punctuation clutter, both by using methods on references and by using ->elements to deref arrayrefs. - It makes map and grep transforms read in the same direction it's executed. - It makes it easier to write those things in a natural order. No need to move the cursor around a lot just to fix dereferencing, order of operations etc. On top of this, [autobox::Transform](https://metacpan.org/pod/autobox%3A%3ATransform) provides a few higher level methods for mapping, filtering and sorting common cases which are easier to read and write. Since they are at a slightly higher semantic level, once you know them they also provide a more specific meaning than just `map` or `grep`. (Compare the difference between seeing a `map` and seeing a `foreach` loop. Just seeing the word `map` hints at what type of thing is going on here: transforming a list into another list). The methods of `autobox::Transform` are not suitable for all cases, but when used appropriately they will lead to much more clear, succinct and direct code, especially in conjunction with `autobox::Core`. ## Code Comparison These examples are only for when there's a straightforward and simple Perl equivalent. ### map_by - method call: $books are Book objects my @genres = map { $_->genre() } @$books; my @genres = $books->map_by("genre"); my $genres = [ map { $_->genre() } @$books ]; my $genres = $books->map_by("genre"); # With sum from autobox::Core / List::AllUtils my $book_order_total = sum( map { $_->price_with_tax($tax_pct) } @{$order->books} ); my $book_order_total = $order->books ->map_by([ price_with_tax => $tax_pct ])->sum; ### map_by - hash key: $books are book hashrefs my @genres = map { $_->{genre} } @$books; my @genres = $books->map_by("genre"); ### filter_by - method call: $books are Book objects my $sold_out_books = [ grep { $_->is_in_stock } @$books ]; my $sold_out_books = $books->filter_by("is_in_stock"); my $sold_out_books = $books->grep_by("is_in_stock"); my $books_in_library = [ grep { $_->is_in_library($library) } @$books ]; my $books_in_library = $books->filter_by([ is_in_library => $library ]); ### reject_by - hash key: $books are book hashrefs my $sold_out_books = [ grep { ! $_->{is_in_stock} } @$books ]; my $sold_out_books = $books->reject_by("is_in_stock"); ### uniq_by - method call: $books are Book objects my %seen; my $distinct_books = [ grep { ! %seen{ $_->id // "" }++ } @$books ]; my $distinct_books = $books->uniq_by("id"); ### uniq_by - hash key: $books are book hashrefs my %seen; my $distinct_books = [ grep { ! %seen{ $_->{id} // "" }++ } @$books ]; my $distinct_books = $books->uniq_by("id"); #### flat - $author->books returns an arrayref of Books my $author_books = [ map { @{$_->books} } @$authors ]; my $author_books = $authors->map_by("books")->flat; # DEVELOPMENT ## Author Johan Lindstrom, `` ## Source code [https://github.com/jplindstrom/p5-autobox-Transform](https://github.com/jplindstrom/p5-autobox-Transform) ## Bug reports Please report any bugs or feature requests on GitHub: [https://github.com/jplindstrom/p5-autobox-Transform/issues](https://github.com/jplindstrom/p5-autobox-Transform/issues). # COPYRIGHT & LICENSE Copyright 2016- Johan Lindstrom, All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. cpanfile100664001755001752 46413707566017 16263 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035requires 'perl', '5.008001'; requires 'autobox'; requires 'autobox::Core'; requires 'Carp'; requires 'parent'; requires 'Sort::Maker'; requires 'List::MoreUtils'; on 'test' => sub { requires 'Test::More', '0.98'; requires 'Test::Differences'; requires 'Test::Exception'; requires 'Moo'; }; Transform.pm100664001755001752 15606713707566017 21412 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/lib/autoboxpackage autobox::Transform; use strict; use warnings; use 5.010; use parent qw/autobox/; our $VERSION = "1.035"; =head1 NAME autobox::Transform - Autobox methods to transform Arrays and Hashes =head1 CONTEXT L provides the ability to call methods on native types, e.g. strings, arrays, and hashes as if they were objects. L provides the basic methods for Perl core functions like C, C, and C. This module, C, provides higher level and more specific methods to transform and manipulate arrays and hashes, in particular when the values are hashrefs or objects. =head1 SYNOPSIS use autobox::Core; # map, uniq, sort, join, sum, etc. use autobox::Transform; =head2 Arrays # use autobox::Core for ->map etc. # filter (like a more versatile grep) $book_locations->filter(); # true values $books->filter(sub { $_->is_in_library($library) }); $book_names->filter( qr/lord/i ); $book_genres->filter("scifi"); $book_genres->filter({ fantasy => 1, scifi => 1 }); # hash key exists # reject: the inverse of filter $book_genres->reject("fantasy"); # order (like a more succinct sort) $book_genres->order; $book_genres->order("desc"); $book_prices->order([ "num", "desc" ]); $books->order([ sub { $_->{price} }, "desc", "num" ]); $log_lines->order([ num => qr/pid: "(\d+)"/ ]); $books->order( [ sub { $_->{price} }, "desc", "num" ] # first price sub { $_->{name} }, # then name ); # group (aggregate) array into hash $book_genres->group; # "Sci-fi" => "Sci-fi" $book_genres->group_count; # "Sci-fi" => 3 $book_genres->group_array; # "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi"] # Flatten arrayrefs-of-arrayrefs $authors->map_by("books") # ->books returns an arrayref # [ [ $book1, $book2 ], [ $book3 ] ] $authors->map_by("books")->flat; # [ $book1, $book2, $book3 ] # Return reference, even in list context, e.g. in a parameter list $book_locations->filter()->to_ref; # Return array, even in scalar context @books->to_array; # Turn paired items into a hash @titles_books->to_hash; =head2 Arrays where the items are hashrefs/objects # $books and $authors below are arrayrefs with either objects or # hashrefs (the call syntax is the same). These have methods/hash # keys like C<$book->genre()>, C<$book->{is_in_stock}>, # C<$book->is_in_library($library)>, etc. $books->map_by("genre"); $books->map_by([ price_with_tax => $tax_pct ]); $books->filter_by("is_in_stock"); $books->filter_by([ is_in_library => $library ]); $books->filter_by([ price_with_tax => $rate ], sub { $_ > 56.00 }); $books->filter_by("price", sub { $_ > 56.00 }); $books->filter_by("author", "James A. Corey"); $books->filter_by("author", qr/corey/i); # grep_by is an alias for filter_by $books->grep_by("is_in_stock"); # reject_by: the inverse of filter_by $books->reject_by("is_sold_out"); $books->uniq_by("id"); $books->order_by("name"); $books->order_by(name => "desc"); $books->order_by(price => "num"); $books->order_by(price => [ "num", "desc" ]); $books->order_by(name => [ sub { uc($_) }, "desc" ]); $books->order_by([ price_with_tax => $rate ] => "num"); $books->order_by( author => "str", # first by author price => [ "num", "desc" ], # then by price, most expensive first ); $books->order_by( author => [ "desc", sub { uc($_) } ], [ price_with_tax => $rate ] => [ "num", "desc" ], "name", ); $books->group_by("title"), # { # "Leviathan Wakes" => $books->[0], # "Caliban's War" => $books->[1], # "The Tree-Body Problem" => $books->[2], # "The Name of the Wind" => $books->[3], # }, $authors->group_by([ publisher_affiliation => "with" ]), # { # 'James A. Corey with Orbit' => $authors->[0], # 'Cixin Liu with Head of Zeus' => $authors->[1], # 'Patrick Rothfuss with Gollanz' => $authors->[2], # }, $books->group_by_count("genre"), # { # "Sci-fi" => 3, # "Fantasy" => 1, # }, my $genre_books = $books->group_by_array("genre"); # { # "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ], # "Fantasy" => [ $fantasy_book_1 ], # }, =head2 Hashes # map over each pair # e.g. Upper-case the genre name, and make the count say "n books" # (return a key => value pair) $genre_count->map_each(sub { uc( $_[0] ) => "$_ books" }); # { # "FANTASY" => "1 books", # "SCI-FI" => "3 books", # }, # map over each value # e.g. Make the count say "n books" # (return the new value) $genre_count->map_each_value(sub { "$_ books" }); # { # "Fantasy" => "1 books", # "Sci-fi" => "3 books", # }, # map each pair into an array # e.g. Transform each pair to the string "n: genre" # (return list of items) $genre_count->map_each_to_array(sub { "$_: $_[0]" }); # [ "1: Fantasy", "3: Sci-fi" ] # filter each pair # Genres with more than five books $genre_count->filter_each(sub { $_ > 5 }); # filter out each pair # Genres with more than five books $genre_count->reject_each(sub { $_ <= 5 }); # Return reference, even in list context, e.g. in a parameter list %genre_count->to_ref; # Return hash, even in scalar context $author->book_count->to_hash; # Turn key-value pairs into an array %isbn__book->to_array; =head2 Combined examples my $order_authors = $order->books ->filter_by("title", qr/^The/) ->uniq_by("isbn") ->map_by("author") ->uniq_by("name") ->order_by(publisher => "str", name => "str") ->map_by("name")->uniq->join(", "); my $total_order_amount = $order->books ->reject_by("is_sold_out") ->filter_by([ covered_by_vouchers => $vouchers ], sub { ! $_ }) ->map_by([ price_with_tax => $tax_pct ]) ->sum; =cut use Carp; sub import { my $self = shift; $self->SUPER::import( ARRAY => "autobox::Transform::Array" ); $self->SUPER::import( HASH => "autobox::Transform::Hash" ); } sub throw { my ($error) = @_; ###JPL: remove lib $error =~ s/ at [\\\/\w ]*?\bautobox.Transform\.pm line \d+\.\n?$//; local $Carp::CarpLevel = 1; croak($error); } # Normalize the two method calling styles for accessor + args: # $acessor, $args_arrayref # or # $acessor_and_args_arrayref sub _normalized_accessor_args_subref { my ($accessor, $args, $subref) = @_; # Note: unfortunately, this won't allow the $subref (modifier) to # become an arrayref later on when we do many types of modifiers # (string eq, qr regex match, sub call, arrayref in) for # filtering. # # That has to happen after the deprecation has expired and the old # syntax is removed. if(ref($args) eq "CODE") { $subref = $args; # Move down one step $args = undef; } if(ref($accessor) eq "ARRAY") { ($accessor, my @args) = @$accessor; $args = \@args; } return ($accessor, $args, $subref); } ###JPL: rename subref to predicate # Normalize the two method calling styles for accessor + args: # $acessor, $args_arrayref, $modifier # or # $acessor_and_args_arrayref, $modifier sub _normalized_accessor_args_predicate { my ($accessor, $args, $subref) = @_; # Note: unfortunately, this won't allow the $subref (modifier) to # be an arrayref, or undef for many types of modifiers (string eq, # qr regex match, sub call, arrayref in) for filtering. # # That has to happen after the deprecation has expired and the old # syntax is removed. if(defined($args) && ref($args) ne "ARRAY") { $subref = $args; # Move down one step $args = undef; } if(ref($accessor) eq "ARRAY") { ($accessor, my @args) = @$accessor; $args = \@args; } return ($accessor, $args, $subref); } sub _predicate { my ($name, $predicate, $default_predicate) = @_; # No predicate, use default is_true defined($predicate) or return $default_predicate; # scalar, do string eq my $type = ref($predicate) or return sub { $predicate eq $_ }; $type eq "CODE" and return $predicate; $type eq "Regexp" and return sub { $_ =~ $predicate }; $type eq "HASH" and return sub { exists $predicate->{ $_ } }; # Invalid predicate Carp::croak("->$name() \$predicate: ($predicate) is not one of: subref, string, regex"); } =head1 DESCRIPTION C provides high level autobox methods you can call on arrays, arrayrefs, hashes and hashrefs. =head2 Transforming lists of objects vs list of hashrefs C, C C etc. (all methods named C<*_by>) work with sets of hashrefs or objects. These methods are called the same way regardless of whether the array contains objects or hashrefs. The items in the list must be either all objects or all hashrefs. If the array contains hashrefs, the hash key is looked up on each item. If the array contains objects, a method is called on each object (possibly with the arguments provided). =head3 Calling accessor methods with arguments For method calls, it's possible to provide arguments to the method. Consider C: $array->map_by($accessor) If the $accessor is a string, it's a simple method call. # method call without args $books->map_by("price") # becomes $_->price() or $_->{price} If the $accessor is an arrayref, the first item is the method name, and the rest of the items are the arguments to the method. # method call with args $books->map_by([ price_with_discount => 5.0 ]) # becomes $_->price_with_discount(5.0) =head2 Filter predicates There are several methods that filter items, e.g. C<@array-Efilter> (duh), C<@array-Efilter_by>, and C<%hash-Efilter_each>. These methods take a C<$predicate> argument to determine which items to retain or filter out. The C family of methods do the opposite, and I items that match the predicate, i.e. the opposite of the filter methods. If $predicate is an I, it is compared to each value with C. $books->filter_by("author", "James A. Corey"); If $predicate is a I, it is compared to each value with C<=~>. $books->reject_by("author", qr/Corey/); If $predicate is a I, values in @array are retained if the $predicate hash key C (the hash values are irrelevant). $books->filter_by( "author", { "James A. Corey" => undef, "Cixin Liu" => 0, "Patrick Rothfuss" => 1, }, ); If $predicate is a I, the subref is called for each value to check whether this item should remain in the list. The $predicate subref should return a true value to remain. C<$_> is set to the current $value. $authors->filter_by(publisher => sub { $_->name =~ /Orbit/ }); =head2 Sorting using order and order_by Let's first compare how sorting is done with Perl's C and autobox::Transform's C/C. =head3 Sorting with sort =over 4 =item * provide a sub that returns the comparison outcome of two values: C<$a> and C<$b> =item * in case of a tie, provide another comparison of $a and $b =back # If the name is the same, compare age (oldest first) sort { uc( $a->{name} ) cmp uc( $b->{name} ) # first comparison || int( $b->{age} / 10 ) <=> int( $a->{age} / 10 ) # second comparison } @users (note the opposite order of C<$a> and C<$b> for the age comparison, something that's often difficult to discern at a glance) =head3 Sorting with order, order_by =over 4 =item * Provide order options for how one value should be compared with the others: =over 8 =item * how to compare (C or C<<=E>) =item * which direction to sort (Cending or Cending) =item * which value to compare, using a regex or subref, e.g. by C =back =item * In case of a tie, provide another comparison =back # If the name is the same, compare age (oldest first) # ->order @users->order( sub { uc( $_->{name} ) }, # first comparison [ "num", sub { int( $_->{age} / 10 ) }, "desc" ], # second comparison ) # ->order_by @users->order_by( name => sub { uc }, # first comparison age => [ num => desc => sub { int( $_ / 10 ) } ], # second comparison ) =head3 Comparison Options If there's only one option for a comparison (e.g. C), provide a single option (string/regex/subref) value. If there are many options, provide them in an arrayref in any order. =head3 Comparison operator =over 4 =item * C<"str"> (cmp) - default =item * C<"num"> (<=>) =back =head3 Sort order =over 4 =item * C<"asc"> (ascending) - default =item * C<"desc"> (descending) =back =head3 The value to compare =over 4 =item * A subref - default is: C =over 8 =item * The return value is used in the comparison =back =item * A regex, e.g. C =over 8 =item * The value of C are used in the comparison (C<@captured_groups> are C<$1>, C<$2>, C<$3> etc.) =back =back =head3 Examples of a single comparison # order: the first arg is the comparison options (one or an # arrayref with many options) ->order() # Defaults to str, asc, $_, just like sort ->order("num") ->order(sub { uc($_) }) # compare captured matches, e.g. "John" and "Doe" as "JohnDoe" ->order( qr/first_name: (\w+), last_name: (\w+)/ ) ->order([ num => qr/id: (\d+)/ ]) ->order([ sub { int($_) }, "num" ]) # order_by: the first arg is the accessor, just like with # map_by. Second arg is the comparison options (one or an arrayref # with many options) ->order_by("id") ->order_by("id", "num") ->order_by("id", [ "num", "desc" ]) ->order_by("name", sub { uc($_) }) ->order_by(log_line => qr/first_name: (\w+), last_name: (\w+)/ ) ->order_by("log_line", [ num => qr/id: (\d+)/ ]) ->order_by(age => [ sub { int($_) }, "num" ]) # compare int( $a->age_by_interval(10) ) ->order_by([ age_by_interval => 10 ] => [ sub { int($_) }, "num" ]) # compare uc( $a->name_with_title($title) ) ->order_by([ name_with_title => $title ], sub { uc($_) }) =head3 Examples of fallback comparisons When the first comparison is a tie, the subsequent ones are used. # order: list of comparison options (one or an arrayref with many # options, per comparison) ->order( [ sub { $_->{price} }, "num" ], # First a numeric comparison of price [ sub { $_->{name} }, "desc" ], # or if same, a reverse comparison of the name ) ->order( [ sub { uc($_) }, "desc" ], "str", ) ->order( qr/type: (\w+)/, [ num => desc => qr/duration: (\d+)/ ] [ num => sub { /id: (\d+)/ } ], "str", ) # order_by: pairs of accessor-comparison options ->order_by( price => "num", # First a numeric comparison of price name => "desc", # or if same, a reverse comparison of the name ) ->order_by( price => [ "num", "desc" ], name => "str", ) # accessor is a method call with arg: $_->price_with_discount($discount) ->order_by( [ price_with_discount => $discount ] => [ "num", "desc" ], name => [ str => sub { uc($_) } ], "id", ) =head2 List and Scalar Context Almost all of the methods are context sensitive, i.e. they return a list in list context and an arrayref in scalar context, just like L. B: I When in doubt, assume they work like C and C (i.e. return a list), and convert the return value to references where you might have an non-obvious list context. E.g. =head3 Incorrect $self->my_method( # Wrong, this is list context and wouldn't return an array ref books => $books->filter_by("is_published"), ); =head3 Correct $self->my_method( # Correct, put the returned list in an anonymous array ref books => [ $books->filter_by("is_published") ], ); $self->my_method( # Correct, ensure scalar context to get an array ref books => scalar $books->filter_by("is_published"), ); # Probably the nicest, since ->to_ref goes at the end $self->my_method( # Correct, use ->to_ref to ensure an array ref is returned books => $books->filter_by("is_published")->to_ref, ); =head1 METHODS ON ARRAYS =cut package # hide from PAUSE autobox::Transform::Array; use autobox::Core; use Sort::Maker (); use List::MoreUtils (); =head2 @array->filter($predicate = *is_true_subref*) : @array | @$array Similar to Perl's C, return an C<@array> with values for which $predicate yields a true value. $predicate can be a subref, string, undef, regex, or hashref. See L. The default (no C<$predicate>) is a subref which retains true values in the @array. =head3 Examples my @apples = $fruit->filter("apple"); my @any_apple = $fruit->filter( qr/apple/i ); my @publishers = $authors->filter( sub { $_->publisher->name =~ /Orbit/ }, ); =head3 filter and grep L's C method takes a subref, just like this method. C also supports the other predicate types, like string, regex, etc. =cut sub filter { my $array = shift; my ($predicate) = @_; my $subref = autobox::Transform::_predicate( "filter", $predicate, sub { !! $_ }, ); my $result = eval { [ CORE::grep { $subref->( $_ ) } @$array ] } or autobox::Transform::throw($@); return wantarray ? @$result : $result; } =head2 @array->reject($predicate = *is_false_subref*) : @array | @$array Similar to the Unix command C, return an @array with values for which C<$predicate> yields a I value. $predicate can be a subref, string, undef, regex, or hashref. See L. The default (no $predicate) is a subref which I true values in the C<@array>. Examples: my @apples = $fruit->reject("apple"); my @no_apples = $fruit->reject( qr/apple/i ); my @publishers = $authors->reject( sub { $_->publisher->name =~ /Orbit/ }, ); =cut sub reject { my $array = shift; my ($predicate) = @_; my $subref = autobox::Transform::_predicate( "reject", $predicate, sub { !! $_ }, ); my $result = eval { [ CORE::grep { ! $subref->( $_ ) } @$array ] } or autobox::Transform::throw($@); return wantarray ? @$result : $result; } my $option__group = { str => "operator", num => "operator", asc => "direction", desc => "direction", }; sub _group__value_from_order_options { my ($method_name, $options) = @_; my $group__value = {}; for my $option (grep { $_ } @$options) { my $group; my $ref_option = ref($option); ( $ref_option eq "CODE" ) and $group = "extract"; if ( $ref_option eq "Regexp" ) { my $regex = $option; $option = sub { join("", m/$regex/) }; $group = "extract"; } $group ||= $option__group->{ $option } or Carp::croak("->$method_name(): Invalid comparison option ($option), did you mean ->order_by('$option')?"); exists $group__value->{ $group } and Carp::croak("->$method_name(): Conflicting comparison options: ($group__value->{ $group }) and ($option)"); $group__value->{ $group } = $option; } return $group__value; } my $transform__sorter = { str => "string", num => "number", asc => "ascending", desc => "descending", }; sub _sorter_from_comparisons { my ($method_name, $comparisons) = @_; my @sorter_keys; my @extracts; for my $options (@$comparisons) { ref($options) eq "ARRAY" or $options = [ $options ]; # Check one comparison my $group__value = _group__value_from_order_options( $method_name, $options, ); my $operator = $group__value->{operator} // "str"; my $direction = $group__value->{direction} // "asc"; my $extract = $group__value->{extract} // sub { $_ }; my $sorter_operator = $transform__sorter->{$operator}; my $sorter_direction = $transform__sorter->{$direction}; push(@extracts, $extract); my $extract_index = @extracts; push( @sorter_keys, $sorter_operator => [ $sorter_direction, # Sort this one by the extracted value code => "\$_->[ $extract_index ]", ], ); } my $sorter = Sort::Maker::make_sorter( "plain", "ref_in", "ref_out", @sorter_keys, ) or Carp::croak(__PACKAGE__ . " internal error: $@"); return ($sorter, \@extracts); } sub _item_values_array_from_array_item_extracts { my ($array, $extracts) = @_; # Custom Schwartzian Transform where each array item is arrayref of: # 0: $array item; rest 1..n : comparison values # The sorter keys are simply indexed into the nth value return [ map { ## no critic my $item = $_; [ $item, # array item to compare map { my $extract = $_; local $_ = $item; $extract->(); } @$extracts, # comparison values for array item ]; } @$array ]; } sub _item_values_array_from_map_by_extracts { my ($array, $accessors, $extracts) = @_; # Custom Schwartzian Transform where each array item is arrayref of: # 0: $array item; rest 1..n : comparison values # The sorter keys are simply indexed into the nth value my $accessor_values = $accessors->map( sub { [ map_by($array, $_) ] } ); return [ map { ## no critic my $item = $_; my $accessor_index = 0; [ $item, # array item to compare map { my $extract = $_; my $value = shift @{$accessor_values->[ $accessor_index++ ]}; local $_ = $value; $extract->(); } @$extracts, # comparison values for array item ]; } @$array ]; } =head2 @array->order(@comparisons = ("str")) : @array | @$array Return C<@array> ordered according to the C<@comparisons>. The default comparison is the same as the default sort, e.g. a normal string comparison of the C<@array> values. If the first item in C<@comparison> ends in a tie, the next one is used, etc. Each I consists of a single I *** TODO Fresh **** order ***** description ****** for each key there's an option or arrayref of options ****** If there are multiple keys to compare ******* there are multiple option(s) ***** examples **** order_by ***** description ****** for each key, there's pair of accessor-option(s) ******* the first item is always the accessor ******* the second item is always an option or an arrayref of options ****** if there are multiple keys to compare ******* there are multiple pairs of accessor-option(s) ***** param check ****** when all items are consumed, ******* if there's an expected option ******** the default is an empty option list ***** examples ->order_by("name") ->order_by(name => sub { int($_) }) ->order_by(name => [ "desc", sub { int($_) } ]) ->order_by([ price => $discount ] => sub { int($_) }) ->order_by([ price => $discount ] => [ sub { int($_) }, "num" ]) ->order_by( [ price => $discount ] => [ sub { int($_) }, "num" ], name => [ sub { uc($_) }, "desc" ], time => [ ], [ duration => $now ] => "natural", ) *** CPAN **** https://v1.metacpan.org/pod/Sort::Maker **** https://v1.metacpan.org/pod/Sort::Key **** https://metacpan.org/pod/Sort::Key::Multi **** https://metacpan.org/pod/Sort::Key::Maker **** https://metacpan.org/pod/Sort::Key::Natural *** Old attempts **** multi key using hashrefs ***** description ***** examples ->order("num") ->order(sub { int($_) }, "num") ->order( { num => 1, value => sub { uc($_) } }, { str => 1, value => sub { int($_) } }, ) ->order({ num => sub { $_->[1] } }) ->order_by( { num => [ "price", 0.22 ] }, { str => "symbol", value => sub { uc($_) } }, { num => "id" }, ) ->order_by("symbol") ->order_by([ price => 0.22 ]) ->order_by({ num => "id" }) ->order_by({ num => [ "price" => 0.22 ] }) **** multi key using arrayrefs ***** description ****** for _by, first item is always the accessor ****** the rest are named options ******* e.g. option strings, or a subref to transform it ***** examples ->order("num") ->order(sub { int($_) }, "num") ->order( [ num => sub { uc($_) } ], [ "str", sub { int($_) } ], ) ->order(num => sub { $_->[1] }) ->order_by( # PROBLEM: how is this distinguished from an arrayref accessor? [ [ "price", 0.22 ], "num" ], [ "symbol", str => sub { uc($_) } ], [ id => "num" ], ) ->order_by("symbol") ->order_by([ price => 0.22 ]) ->order_by(id => "num", "reverse") ->order_by([ "price" => 0.22 ], "num") ** set_each set_each([$accessor, @args]) This uses the same format for getting at a accessor and args as everything else, i.e. an arrayref. For hashrefs, set the key $accessor to $args[0] (because a hashref value can't be a list) For objects, call $_->$accessor(@args) to set the value. ** array includes, includes_by / contains, contains_by *** http://emberjs.com/api/classes/Ember.Enumerable.html *** with predicate, same as always: string eq, regex, hashref keys, (array in (later)) *** hash contains_value_by ** hash contains_key, contains_value -- checks the predicate for keys/values ** Ember Array, Enumerable *** http://emberjs.com/api/classes/Ember.Array.html ** Fix croak, use throw * Hash ** DONE key_value($key, $new_key_name=$key) : ($new_key_name => $value) | { $new_key_name => $value } *** exists *** exists, undef *** doesn't exist, undef ** DONE key_value_if_exists($key, $new_key_name=$key) : ($new_key_name => $value) | { $new_key_name => $value } ** DONE key_value_if_true($key, $new_key_name=$key) : ($new_key_name => $value) | { $new_key_name => $value } ** DONE key_value_if_defined($key, $new_key_name=$key) : ($new_key_name => $value) | { $new_key_name => $value } ** keys_value* to support multiple pairs *** if there are more than one arg, assume they are pairs ** map family *** map hash -> array **** [#A] map_each_to_array($subref) ***** ($key, $value), $_ is value ****** return value (or die) to be list item ***** return array with return values *** map hash -> hash **** map_each($subref) ***** ($key, $value), $_ is value ****** return new key, new value (or die) ***** return hash with return values **** map_each_value($subref, $new_key = $key) ***** ($key, $value), $_ is value ****** return new value ***** return hash with same key, return value **** map_each_value_by($accessor, $new_key = $accessor) maps values from one thing to another ***** (key, $value, $accessor_value), $_ is accessor_value ****** return new value ***** return hash with same keys + $new_key = return value **** map_by ? ** grep family *** filter -> hash **** [#A] filter_each($subref = true) ***** ($key, $value), $_ is value **** [#B] filter_each_defined ***** value is defined **** filter_each_by($accessor, $args?, $subref = true) ***** ($key, $value, $accessor_value), $_ is accessor value **** filter_each_by_defined ***** accessor value is defined * DOCS ** Operate on an array of scalars *** TODO grep_true *** TODO grep_defined *** flat *** TODO group *** TODO group_count *** TODO group_array ** Operate on a hash *** map_each *** map_each_value *** map_each_to_array *** grep_each *** TODO grep_each_true *** grep_each_defined *** key_value *** key_value_if_true *** key_value_if_defined *** key_value_if_exists ** Operate on an array of hashrefs/objects *** map_by *** grep_by *** TODO grep_by_true *** TODO grep_by_defined *** uniq_by *** group_by *** group_by_array *** group_by_count ** Operate on a hash with hashref/object values *** TODO map_each_value_by *** TODO grep_each_value_by *** TODO grep_each_value_by_true *** TODO grep_each_value_by_defined * Cookbook ** Document the gems from core *** elements **** avoid ugly deref punctuation *** length *** strip **** better chomp ** map_by("strip") ** map_each_value_by("group_count") ** map_by("flat") ** map_by([ split => "/" ]) ** DBIC ->all doesn't work so well, since it returns a list Base ResultSet sub all_ref { [ shift->all ] } ** etc 00_compile.t100664001755001752 13613707566017 17132 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use Test::More 0.98; use_ok $_ for qw( autobox::Transform ); done_testing; filter.t100664001755001752 514713707566017 16517 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; subtest filter_default_true => sub { note "Default is checking for true"; my $array = [ 0, 1, 2, 3, "", 4, undef, 5 ]; eq_or_diff( $array->filter->to_ref, [ 1, 2, 3, 4, 5 ], "Only true values remain", ); }; subtest filter_invalid_predicate => sub { my $strings = [ "abc", "def", "abc" ]; throws_ok( sub { $strings->filter(\"abc")->to_ref }, qr/->filter .+? \$predicate: .+?\Q is not one of: subref, string, regex/x, "Invalid predicate type", ); }; subtest filter_subref => sub { note "ArrayRef call, list context result, subref predicate"; eq_or_diff( [ map { $_->name } $authors->filter(sub { $_->is_prolific }) ], [ "James A. Corey", ], "filter simple method call works", ); note "list call, list context result"; my @authors = @$authors; my $prolific_authors = @authors->filter(sub { $_->is_prolific }); eq_or_diff( [ map { $_->name } @$prolific_authors ], [ "James A. Corey", ], "filter simple method call works", ); }; subtest filter_string => sub { my $strings = [ "abc", "def", "abc" ]; eq_or_diff( $strings->filter("abc")->to_ref, [ "abc", "abc" ], "filter scalar string", ); # TODO: deal with undef comparisons }; # TODO: Can't work until the old call style is removed. # subtest filter_undef => sub { # my $strings = [ "abc", undef, "abc" ]; # eq_or_diff( # $strings->filter(undef)->to_ref, # [ undef ], # "filter undef", # ); # }; subtest filter_regex => sub { my $strings = [ "abc", "def", "abc" ]; eq_or_diff( $strings->filter(qr/a/)->to_ref, [ "abc", "abc" ], "filter regex", ); eq_or_diff( $strings->filter(qr/A/)->to_ref, [ ], "filter regex miss", ); eq_or_diff( $strings->filter(qr/A/i)->to_ref, [ "abc", "abc" ], "filter regex with flags", ); # TODO: deal with undef comparisons }; subtest filter_hashref_keys => sub { my $strings = [ "abc", "def", "ghi" ]; eq_or_diff( $strings->filter({ abc => undef, def => 1 })->to_ref, [ "abc", "def" ], "filter hashref keys (exists, not true hash value)", ); # TODO: deal with undef comparisons }; done_testing(); filter_by.t100664001755001752 743213707566017 17210 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; subtest filter_by => sub { note "ArrayRef call, list context result, default predicate (true)"; eq_or_diff( [ map { $_->name } $authors->filter_by("is_prolific") ], [ "James A. Corey", ], "filter_by simple method call works", ); note "list call, list context result"; my @authors = @$authors; my $prolific_authors = @authors->filter_by("is_prolific"); eq_or_diff( [ map { $_->name } @$prolific_authors ], [ "James A. Corey", ], "filter_by simple method call works", ); }; subtest grep_by => sub { eq_or_diff( [ map { $_->name } $authors->grep_by("is_prolific") ], [ "James A. Corey", ], "grep_by alias works", ); }; subtest filter_by_with_arrayref_accessor => sub { note "Call with arrayref accessor without args"; eq_or_diff( [ map { $_->name } $authors->filter_by([ "is_prolific" ]) ], [ "James A. Corey", ], "filter_by simple method call works", ); }; subtest filter_by_with_predicate_subref => sub { eq_or_diff( [ map { $_->name } $authors->filter_by("name", undef, sub { /Corey/ }) ], [ "James A. Corey", ], "filter_by without predicate argument and with predicate sub call works", ); eq_or_diff( [ map { $_->name } $authors->filter_by( "publisher_affiliation", [ "with" ], sub { /Corey/ }, ), ], [ "James A. Corey", ], "filter_by with method argument and predicate sub call works", ); note "Arrayref accessor"; eq_or_diff( [ map { $_->name } $authors->filter_by("name", sub { /Corey/ }) ], [ "James A. Corey", ], "filter_by without predicate argument and with predicate sub call works", ); eq_or_diff( [ map { $_->name } $authors->filter_by( [ publisher_affiliation => "with" ], sub { /Corey/ }, ), ], [ "James A. Corey", ], "filter_by with method argument and predicate sub call works", ); }; subtest filter_by_with_predicate_string => sub { eq_or_diff( [ map { $_->name } $authors->filter_by("name", "James A. Corey") ], [ "James A. Corey" ], "filter_by predicate string works", ); eq_or_diff( [ map { $_->name } $authors->filter_by("name", undef, "James A. Corey") ], [ "James A. Corey" ], "filter_by predicate string works, with old call style", ); }; # TODO: undef can't work until old call style is removed subtest filter_by_with_predicate_regex => sub { eq_or_diff( [ map { $_->name } $authors->filter_by("name", qr/corey/i) ], [ "James A. Corey" ], "filter_by predicate regex works", ); }; subtest filter_by_with_predicate_hashref => sub { eq_or_diff( [ map { $_->name } $authors->filter_by("name", { "James A. Corey" => 1 }) ], [ "James A. Corey" ], "filter_by predicate hashref works", ); }; subtest examples => sub { my $prolific_author_book_titles = $authors->filter_by("is_prolific") ->map_by("books")->flat ->map_by("title")->sort; eq_or_diff( $prolific_author_book_titles, [ "Caliban's War", "Leviathan Wakes" ], "prolific_author_book_titles", ); }; done_testing(); group.t100664001755001752 371613707566017 16366 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $books = $literature->{books}; my $authors = $literature->{authors}; my $reviews = $literature->{reviews}; my $titles = $books->map_by("title")->order; subtest group => sub { note "Basic group"; my $book_title__title = { "Leviathan Wakes" => "Leviathan Wakes", "Caliban's War" => "Caliban's War", "The Tree-Body Problem" => "The Tree-Body Problem", "The Name of the Wind" => "The Name of the Wind", }; eq_or_diff( { $titles->group }, $book_title__title, "List context, basic group works", ); note "list call, list context result"; my @titles = @$titles; my $title_exists = @titles->group; eq_or_diff( $title_exists, $book_title__title, "Group by simple method call works", ); }; subtest group_count => sub { my $book_title__count = { "Leviathan Wakes" => 1, "Caliban's War" => 1, "The Tree-Body Problem" => 1, "The Name of the Wind" => 2, }; eq_or_diff( { [ @$titles, "The Name of the Wind" ]->group_count }, $book_title__count, "Group count works", ); }; subtest group__sub_ref => sub { eq_or_diff( { $books->map_by("genre")->group(sub { 1 }) }, { "Sci-fi" => 1, "Fantasy" => 1, }, "group with sub_ref works", ); }; subtest group__array => sub { my $genres = $books->map_by("genre"); eq_or_diff( { $genres->group_array }, { "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi" ], "Fantasy" => [ "Fantasy" ], }, "group_array works", ); }; subtest examples => sub { ok(1); }; done_testing(); group_by.t100664001755001752 1224713707566017 17077 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $books = $literature->{books}; my $authors = $literature->{authors}; my $reviews = $literature->{reviews}; subtest group_by__method => sub { note "ArrayRef call, list context result"; my $book_object = { "Leviathan Wakes" => $books->[0], "Caliban's War" => $books->[1], "The Tree-Body Problem" => $books->[2], "The Name of the Wind" => $books->[3], }; eq_or_diff( { $books->group_by("title") }, $book_object, "Group by simple method call works", ); note "list call, list context result"; my @books = @$books; my $genre_exists = @books->group_by("title"); eq_or_diff( $genre_exists, $book_object, "Group by simple method call works", ); note "Call with arrayref accessor"; eq_or_diff( { $books->group_by([ "title" ]) }, $book_object, "Group by simple method call works", ); }; subtest group_by__key => sub { note "ArrayRef key lookup"; eq_or_diff( { $reviews->group_by("id") }, { 1 => { id => 1, score => 7 }, 2 => { id => 2, score => 6 }, 3 => { id => 3, score => 9 }, }, "Group by simple hash key lookup works", ); }; subtest group_by__key__with_args => sub { note "ArrayRef key lookup"; throws_ok( sub { $reviews->group_by("id" => 32) }, qr{ group_by .+? 'id' .+? \@args .+? \(32\) .+? only[ ]supported[ ]for .+? t.group_by.t}x, "Group by simple hash key lookup with args dies as expected", ); }; subtest group_by_count__method => sub { note "ArrayRef call, list context result"; my $genre_count = { "Sci-fi" => 3, "Fantasy" => 1, }; eq_or_diff( { $books->group_by_count("genre") }, $genre_count, "Group by simple method call works", ); note "list call, list context result"; my @books = @$books; my $genre_exists = @books->group_by_count("genre"); eq_or_diff( $genre_exists, $genre_count, "Group by simple method call works", ); }; subtest group_by__missing_method => sub { throws_ok( sub { $books->group_by() }, qr{^->group_by\(\)[ ]missing[ ]argument:[ ]\$accessor \s at .+? t.group_by.t }x, "Missing arg croaks from the caller, not from the lib" ); }; subtest group_by__not_a_method => sub { # Invalid arg, not a method throws_ok( sub { $books->group_by("not_a_method") }, qr{ not_a_method .+? Book .+? t.group_by.t }x, "Missing method croaks from the caller, not from the lib", ); }; subtest group_by__method__args => sub { eq_or_diff( { $authors->group_by_count(publisher_affiliation => ["with"]) }, { 'James A. Corey with Orbit' => 1, 'Cixin Liu with Head of Zeus' => 1, 'Patrick Rothfuss with Gollanz' => 1, }, "group_by with argument list", ); eq_or_diff( { $authors->group_by_count([publisher_affiliation => "with"]) }, { 'James A. Corey with Orbit' => 1, 'Cixin Liu with Head of Zeus' => 1, 'Patrick Rothfuss with Gollanz' => 1, }, "group_by with argument list", ); }; subtest group_by__method__args__invalid_type => sub { throws_ok( sub { $authors->group_by(publisher_affiliation => 342) }, qr{ group_by .+? 'publisher_affiliation' .+? \@args .+? \(342\) .+? list .+? t.group_by.t}x, "group_by with argument which isn't an array ref", ); }; subtest group_by_count__method__args__invalid_type => sub { throws_ok( sub { $authors->group_by_count(publisher_affiliation => 342) }, qr{ group_by .+? 'publisher_affiliation' .+? \@args .+? \(342\) .+? list .+? t.group_by.t}x, "group_by with argument which isn't an array ref", ); }; subtest group_by__sub_ref => sub { eq_or_diff( { $books->group_by("genre", [], sub { 1 }) }, { "Sci-fi" => 1, "Fantasy" => 1, }, "group_by with sub_ref works", ); eq_or_diff( { $books->group_by([ "genre" ], sub { 1 }) }, { "Sci-fi" => 1, "Fantasy" => 1, }, "group_by with sub_ref works", ); }; subtest group_by__method__array => sub { my $genre_books = $books->group_by_array("genre"); my $genre_books2 = $books->group_by_array([ "genre" ]); eq_or_diff($genre_books, $genre_books2, "Same output"); my $genre_book_titles = { map { $_ => $genre_books->{$_}->map_by("title")->sort->join(", "); } $genre_books->keys }; eq_or_diff( $genre_book_titles, { "Sci-fi" => "Caliban's War, Leviathan Wakes, The Tree-Body Problem", "Fantasy" => "The Name of the Wind", }, "group_by_array work", ); }; subtest examples => sub { ok(1); }; done_testing(); hash-filter_each.t100664001755001752 460513707566017 20416 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; subtest filter_each_subref_basic => sub { my $hash = { one => 1, zero => 0, two => 2, undefined => undef }; eq_or_diff( scalar $hash->filter_each(), { one => 1, two => 2 }, "filter_each with default 'true'", ); eq_or_diff( scalar $hash->filter_each(sub { !! $_ }), { one => 1, two => 2 }, "filter_each with subref 'true'", ); eq_or_diff( scalar $hash->filter_each(sub { ($_ || 0) > 1 }), { two => 2 }, "filter_each with subref using _", ); eq_or_diff( scalar $hash->filter_each(sub { !! $_[1] }), { one => 1, two => 2 }, "filter_each with value 'true'", ); eq_or_diff( scalar $hash->filter_each(sub { $_[0] eq "one" }), { one => 1 }, "filter_each with key eq", ); }; subtest filter_each_string => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->filter_each(0), { zero => 0 }, "filter_each with string number 0", ); eq_or_diff( { one => "one", zero => 0, two => 2 }->filter_each("one")->to_ref, { one => "one" }, "filter_each with string", ); }; # TODO: undef when the old call style is gone subtest filter_each_regex => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->filter_each(qr/2/), { two => 2 }, "filter_each with regex", ); }; subtest filter_each_hashref_keys => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->filter_each({ 2 => 1 }), { two => 2 }, "filter_each with regex", ); }; subtest filter_each_defined_basic => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->filter_each_defined(), { one => 1, two => 2, zero => 0 }, "filter_each_defined", ); }; subtest grep => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->grep_each(), { one => 1, two => 2 }, "grep_each with default 'true'", ); eq_or_diff( scalar $hash->grep_each_defined(), { one => 1, two => 2, zero => 0 }, "grep_each_defined", ); }; done_testing(); hash-map_each.t100664001755001752 453113707566017 17704 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $books = $literature->{books}; subtest map_each_missing_subref => sub { throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each() }, qr/map_each\(\$key_value_subref\): \$key_value_subref \(\) is not a sub ref at/, "map_each dies without subref", ); throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each("abc") }, qr/map_each\(\$key_value_subref\): \$key_value_subref \(abc\) is not a sub ref at/, "map_each dies without subref", ); }; subtest map_each_subref_returns_wrong_number_of_items => sub { throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each(sub { return (1) }) }, qr|returned odd number of keys/values at|, "map_each, 1 return value", ); throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each(sub { return (1, 2, 3) }) }, qr|returned odd number of keys/values at|, "map_each, 3 return values", ); }; subtest map_each_basic => sub { eq_or_diff( scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each( sub { $_[0] . ( $_[1] // "undef" ), ( $_ // "UNDEF" ) }, ), { zero0 => 0, one1 => 1, two2 => 2, undefinedundef => "UNDEF" }, "map_each with key, value, topic variable", ); eq_or_diff( scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each( sub { ($_[0] => $_, ($_ // "") => $_[0] ) }, ), { one => 1, zero => 0, two => 2, undefined => undef, 1 => "one", 0 => "zero", 2 => "two", "" => "undefined", }, "map_each with multiple return pairs", ); }; subtest examples => sub { # Upper-case the genre name, and make the count say "n books" eq_or_diff( { $books->group_by_count("genre")->map_each(sub { uc($_[0]) => "$_ books" }) }, { "FANTASY" => "1 books", "SCI-FI" => "3 books", }, "Book count", ); ok(1); }; done_testing(); hash-map_each_to_array.t100664001755001752 356513707566017 21612 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $books = $literature->{books}; subtest map_each_to_array_missing_subref => sub { throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_to_array() }, qr/map_each_to_array\(\$array_item_subref\): \$array_item_subref \(\) is not a sub ref at/, "map_each_to_array dies without subref", ); throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_to_array("abc") }, qr/map_each_to_array\(\$array_item_subref\): \$array_item_subref \(abc\) is not a sub ref at/, "map_each_to_array dies without subref", ); }; subtest map_each_to_array_basic => sub { eq_or_diff( scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_to_array( sub { $_[0] . ( $_[1] // "undef" ) . ( $_ // "UNDEF" ) }, ), [ "one11", "two22", "undefinedundefUNDEF", "zero00" ], "map_each_to_array with key, value, topic variable", ); }; subtest map_each_to_array_return_many_items => sub { eq_or_diff( scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_to_array( sub { $_[0] . ( $_[1] // "undef" ), ( $_ // "UNDEF" ) }, ), [ "one1", "1", "two2", "2", "undefinedundef", "UNDEF", "zero0", "0" ], "map_each_to_array with key, value, topic variable", ); }; subtest examples => sub { my $genre_count = $books->group_by_count("genre"); # Summarize each genre eq_or_diff( [ $genre_count->map_each_to_array(sub { "$_: $_[0]" }) ], [ "1: Fantasy", "3: Sci-fi" ], "Book count", ); ok(1); }; done_testing(); hash-map_each_value.t100664001755001752 367213707566017 21105 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $books = $literature->{books}; subtest map_each_value_missing_subref => sub { throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_value() }, qr/map_each_value\(\$value_subref\): \$value_subref \(\) is not a sub ref at/, "map_each_value dies without subref", ); throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_value("abc") }, qr/map_each_value\(\$value_subref\): \$value_subref \(abc\) is not a sub ref at/, "map_each_value dies without subref", ); }; subtest map_each_value_subref_returns_wrong_number_of_items => sub { throws_ok( sub { scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_value(sub { return (1, 2) }) }, qr/map_each_value \$value_subref returned multiple values.+?(one|zero|two|undefined)/, "map_each_value, multiple return values", ); }; subtest map_each_value_basic => sub { eq_or_diff( scalar { one => 1, zero => 0, two => 2, undefined => undef }->map_each_value( sub { $_[0] . ( $_ // "undef" ) }, ), { zero => "zero0", one => "one1", two => "two2", undefined => "undefinedundef", }, "map_each_value with key, value, topic variable", ); }; subtest examples => sub { # Upper-case the genre name, and make the count say "n books" eq_or_diff( { $books->group_by_count("genre")->map_each_value(sub { "$_ books" }) }, { "Fantasy" => "1 books", "Sci-fi" => "3 books", }, "Book count", ); ok(1); }; done_testing(); hash-reject_each.t100664001755001752 422413707566017 20402 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; subtest reject_each_subref_basic => sub { my $hash = { one => 1, zero => 0, two => 2, undefined => undef }; eq_or_diff( scalar $hash->reject_each(), { zero => 0, undefined => undef }, "reject_each with default 'false'", ); eq_or_diff( scalar $hash->reject_each(sub { !! $_ }), { zero => 0, undefined => undef }, "reject_each with subref 'false'", ); eq_or_diff( scalar $hash->reject_each(sub { ($_ || 0) > 1 }), { one => 1, zero => 0, undefined => undef }, "reject_each with subref using _", ); eq_or_diff( scalar $hash->reject_each(sub { !! $_[1] }), { zero => 0, undefined => undef }, "reject_each with value 'true'", ); eq_or_diff( scalar $hash->reject_each(sub { $_[0] eq "one" }), { zero => 0, two => 2, undefined => undef }, "reject_each with key eq", ); }; subtest reject_each_string => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->reject_each(0), { one => 1, two => 2 }, "reject_each with string number 0", ); eq_or_diff( { one => "one", zero => 0, two => 2 }->reject_each("one")->to_ref, { zero => 0, two => 2 }, "reject_each with string", ); }; # TODO: undef when the old call style is gone subtest reject_each_regex => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->reject_each(qr/2/), { one => 1, zero => 0 }, "reject_each with regex", ); }; subtest reject_each_hashref_keys => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->reject_each({ 2 => 1 }), { one => 1, zero => 0 }, "reject_each with regex", ); }; subtest reject_each_defined_basic => sub { my $hash = { one => 1, zero => 0, two => 2 }; eq_or_diff( scalar $hash->reject_each_defined(), { }, "reject_each_defined", ); }; done_testing(); key_value.t100664001755001752 672613707566017 17222 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; subtest key_value => sub { eq_or_diff( [ { present => "value", other => "other value" }->key_value("present") ], [ present => "value", ], "Present key gives only that key", ); eq_or_diff( [ scalar { present => "value", other => "other value" }->key_value("present") ], [ { present => "value" }, ], "Scalar context returns hashref", ); eq_or_diff( [ {}->key_value("missing") ], [ missing => undef, ], "Missing key gives original key with undef", ); eq_or_diff( [ {}->key_value("missing", "new_name") ], [ new_name => undef, ], "Missing key with new_name gives new_name key with undef", ); }; subtest key_value_exists => sub { eq_or_diff( [ { present => "value", other => undef }->key_value_if_exists("missing" ) ], [ ], "Doesn't exist, not returned", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_exists("other") ], [ other => undef, ], "present and undefined value found", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_exists("present") ], [ present => "value", ], "present and defined value found", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_exists("present", "alias") ], [ alias => "value", ], "returned with new_key", ); }; subtest key_value_defined => sub { eq_or_diff( [ { present => "value", other => undef }->key_value_if_defined("missing" ) ], [ ], "Doesn't exist, not returned", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_defined("other") ], [ ], "present and undefined value not returned", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_defined("present") ], [ present => "value", ], "present and defined value found", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_defined("present", "alias") ], [ alias => "value", ], "returned with new_key", ); }; subtest key_value_true => sub { eq_or_diff( [ { present => "value", other => undef }->key_value_if_true("missing" ) ], [ ], "Doesn't exist, not returned", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_true("other") ], [ ], "present and undefined value not returned", ); eq_or_diff( [ { present => "value", other => undef, is_false => 0 }->key_value_if_true("is_false") ], [ ], "present and false value not returned", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_true("present") ], [ present => "value", ], "present and defined value found", ); eq_or_diff( [ { present => "value", other => undef }->key_value_if_true("present", "alias") ], [ alias => "value", ], "returned with new_key", ); }; done_testing(); Literature.pm100664001755001752 603713707566017 20270 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/t/libuse strict; use warnings; package # hide from PAUSE Author; use Moo; use autobox::Core; has name => ( is => "lazy" ); has publisher => ( is => "lazy" ); has books => ( is => "lazy" ); sub _build_books { [] } sub publisher_affiliation { my $self = shift; my ($of) = @_; return $self->name . " $of " . $self->publisher->name; } sub is_prolific { my $self = shift; return $self->books->length > 1; } package # hide from PAUSE Book; use Moo; has title => ( is => "lazy" ); has genre => ( is => "lazy" ); has page_count => ( is => "lazy" ); has price => ( is => "lazy" ); sub _build_price { 10 } has author => ( is => "rw", weak_ref => 1 ); sub price_with_tax { my $self = shift; my ($tax_percent) = @_; return $self->price + ( $self->price * $tax_percent ); } sub title_uc { my $self = shift; return $self->title->uc; } package # hide from PAUSE Publisher; use Moo; has name => ( is => "lazy" ); sub _build_authors { [] } package # hide from PAUSE Literature; use autobox::Core; sub literature { my $p_orbit = Publisher->new({ name => "Orbit" }); my $p_zeus = Publisher->new({ name => "Head of Zeus" }); my $p_gollancz = Publisher->new({ name => "Gollanz" }); # Corey my $b_leviathan = Book->new({ title => "Leviathan Wakes", genre => "Sci-fi", page_count => 342, price => 6, }); my $b_caliban = Book->new({ title => "Caliban's War", genre => "Sci-fi", page_count => 430, price => 6, }); # Liu my $b_three = Book->new({ title => "The Tree-Body Problem", genre => "Sci-fi", page_count => 400, price => 5, }); # Rothfuss my $b_wind = Book->new({ title => "The Name of the Wind", genre => "Fantasy", page_count => 676, price => 11, }); my $a_corey = Author->new({ name => "James A. Corey", publisher => $p_orbit, books => [ $b_leviathan, $b_caliban ], }); my $a_liu = Author->new({ name => "Cixin Liu", publisher => $p_zeus, books => [ $b_three ], }); my $a_rothfuss = Author->new({ name => "Patrick Rothfuss", publisher => $p_gollancz, books => [ $b_wind ], }); my $reviews = [ { id => 1, score => 7, }, { id => 2, score => 6, }, { id => 3, score => 9, }, ]; my $literature = { books => ( my $books = [ $b_leviathan, $b_caliban, $b_three, $b_wind ] ), authors => ( my $authors = [ $a_corey, $a_liu, $a_rothfuss ] ), publishers => ( my $publishers = [ $p_orbit, $p_zeus, $p_gollancz ] ), reviews => $reviews, }; for my $author (@$authors) { $_->author( $author ) for ( $author->books->elements ); } return $literature; } 1; map_by.t100664001755001752 1131713707566017 16515 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $books = $literature->{books}; my $authors = $literature->{authors}; my $reviews = $literature->{reviews}; subtest map_by__empty => sub { note "Empty arrayref with method args"; eq_or_diff( [ []->map_by(genre => [ 34 ]) ], [ ], "Empty list gives empty list", ); eq_or_diff( [ []->map_by([ genre => 34 ]) ], [ ], "Empty list gives empty list", ); }; ### method subtest map_by__method => sub { note "ArrayRef call, list context result"; eq_or_diff( [ $books->map_by("genre") ], [ "Sci-fi", "Sci-fi", "Sci-fi", "Fantasy", ], "Map by simple method call works", ); note "list call, list context result"; my @books = @$books; my $genres = @books->map_by("genre"); eq_or_diff( $genres, [ "Sci-fi", "Sci-fi", "Sci-fi", "Fantasy", ], "Map by simple method call works", ); note "ArrayRef call, arrayref accessor"; eq_or_diff( [ $books->map_by([ "genre" ]) ], [ "Sci-fi", "Sci-fi", "Sci-fi", "Fantasy", ], "Map by simple method call works", ); }; subtest map_by__missing_method => sub { throws_ok( sub { $books->map_by() }, qr{^->map_by\(\)[ ]missing[ ]argument:[ ]\$accessor \s at .+? t.map_by.t }x, "Missing arg croaks from the caller, not from the lib" ) }; subtest map_by__method__not_a_method => sub { # Invalid arg, not a method throws_ok( sub { $books->map_by("not_a_method") }, qr{ not_a_method .+? Book .+? t.map_by.t }x, "Missing method croaks from the caller, not from the lib", ) }; subtest map_by__method__args => sub { eq_or_diff( [ $authors->map_by(publisher_affiliation => ["with"]) ], [ 'James A. Corey with Orbit', 'Cixin Liu with Head of Zeus', 'Patrick Rothfuss with Gollanz', ], "map_by with argument list", ); eq_or_diff( [ $authors->map_by([ publisher_affiliation => "with" ]) ], [ 'James A. Corey with Orbit', 'Cixin Liu with Head of Zeus', 'Patrick Rothfuss with Gollanz', ], "map_by with argument list", ); }; subtest map_by__method__args__invalid_type => sub { throws_ok( sub { $authors->map_by(publisher_affiliation => 342) }, qr{ map_by .+? 'publisher_affiliation' .+? \@args .+? \(342\) .+? list .+? t.map_by.t}x, "map_by with argument which isn't an array ref", ); }; ### hash key subtest map_by__key => sub { note "ArrayRef key, list context result"; eq_or_diff( [ $reviews->map_by("score") ], [ 7, 6, 9 ], "Map by key call works", ); }; subtest map_by__key__with_args => sub { note "ArrayRef key, list context result"; throws_ok( sub { $reviews->map_by([ "score" => "abc" ]) }, qr{ map_by .+? 'score' .+? \@args .+? only[ ]supported[ ]for[ ]method[ ]calls.+? t.map_by.t}x, "Arrayref with items, not allowed" ); lives_ok( sub { $reviews->map_by("score" => [ ]) }, "Empty arrayref is allowed", ); throws_ok( sub { $reviews->map_by([ "score" => "abc" ]) }, qr{ map_by .+? 'score' .+? \@args .+? only[ ]supported[ ]for[ ]method[ ]calls.+? t.map_by.t}x, "Arrayref with items, not allowed" ); lives_ok( sub { $reviews->map_by([ "score" ]) }, "No args is allowed", ); }; subtest examples => sub { my $tax_pct = 0.15; my $total_order_amount = $books ->map_by(price_with_tax => [ $tax_pct ]) ->sum; my $total_order_amount2 = $books ->map_by([ price_with_tax => $tax_pct ]) ->sum; is($total_order_amount, 32.2, "total_order_amount"); my $order_authors = $books ->map_by("author") ->map_by("name") ->uniq->sort->join(", "); is( $order_authors, "Cixin Liu, James A. Corey, Patrick Rothfuss", "order_authors ok", ) }; subtest "map_by can use autobox::Core methods" => sub { my $messages = [ "Hello world", " Hello space ", ]; eq_or_diff( scalar $messages->map_by("strip"), [ "Hello world", "Hello space", ], "Called autobox::Core String->strip", ) }; done_testing(); order.t100664001755001752 607513707566017 16346 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; my $books = $literature->{books}; my $expected_titles_str = [ "Caliban's War", "Leviathan Wakes", "The Name of the Wind", "The Tree-Body Problem", ]; # order by last char my $expected_titles_regex = [ "The Name of the Wind", "The Tree-Body Problem", "Caliban's War", "Leviathan Wakes", ]; my $expected_prices_asc = [ 5, 6, 6, 11 ]; subtest order_simple => sub { eq_or_diff( $books->map_by("title")->order->to_ref, $expected_titles_str, "order default everything, scalar context", ); eq_or_diff( [ $books->map_by("title")->order ], $expected_titles_str, "order default everything, list context", ); }; subtest order_num_str => sub { eq_or_diff( $books->map_by("price")->order("num")->to_ref, $expected_prices_asc, "order num", ); eq_or_diff( $books->map_by("title")->order("str")->to_ref, $expected_titles_str, "order str", ); }; subtest order_asc_desc => sub { eq_or_diff( $books->map_by("title")->order("asc")->to_ref, $expected_titles_str, "order str asc", ); eq_or_diff( $books->map_by("title")->order("desc")->to_ref, $expected_titles_str->reverse->to_ref, "order str desc", ); }; subtest order_sub => sub { eq_or_diff( $books->map_by("price")->order([ "num", sub { 0 - $_ } ])->to_ref, $expected_prices_asc->reverse->to_ref, "order num, subref", ); }; subtest order_regex => sub { eq_or_diff( $books->map_by("title")->order(qr/(.)$/)->to_ref, $expected_titles_regex->to_ref, "order regex", ); }; subtest order_multiple_options__num_desc => sub { eq_or_diff( $books->map_by("price")->order([ "num", "desc" ])->to_ref, $expected_prices_asc->reverse->to_ref, "order num, desc", ); }; subtest comparison_args_validation => sub { throws_ok( sub { [1]->order("blah")->to_ref }, qr/\Q->order(): Invalid comparison option (blah)/, "Invalid arg dies ok", ); throws_ok( sub { [1]->order([ "asc", "desc" ])->to_ref }, qr/\Q->order(): Conflicting comparison options: (asc) and (desc)/, "Invalid arg dies ok", ); # Ignore ugly subref vs regex for now }; subtest order_multiple_comparisons => sub { my $words = [ "abc", "def", "ABC", "DEF", "Abc", ]; # ASCII order is UPPER before lower my $expected_words = [ "DEF", "def", "ABC", "Abc", "abc", ]; eq_or_diff( $words->order( [ desc => sub { uc($_) } ], qr/(.+)/, )->to_ref, $expected_words, "First reverse uc, then whole match cmp", ); }; done_testing(); order_by.t100664001755001752 1077113707566017 17056 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; my $books = $literature->{books}; my $expected_titles_str = [ "Caliban's War", "Leviathan Wakes", "The Name of the Wind", "The Tree-Body Problem", ]; # order by last char my $expected_titles_regex = [ "The Name of the Wind", "The Tree-Body Problem", "Caliban's War", "Leviathan Wakes", ]; my $expected_prices_asc = [ 5, 6, 6, 11 ]; subtest order_by_simple => sub { eq_or_diff( $books->order_by("title")->map_by("title")->to_ref, $expected_titles_str, "order_by default everything, scalar context", ); eq_or_diff( [ $books->order_by("title")->map_by("title") ], $expected_titles_str, "order_by default everything, list context", ); }; subtest order_by_num_str => sub { eq_or_diff( $books->order_by(price => "num")->map_by("price")->to_ref, $expected_prices_asc, "order_by num", ); eq_or_diff( $books->order_by(title => "str")->map_by("title")->to_ref, $expected_titles_str, "order_by str", ); }; subtest order_by_asc_desc => sub { eq_or_diff( $books->order_by(title => "asc")->map_by("title")->to_ref, $expected_titles_str, "order_by str asc", ); eq_or_diff( $books->order_by(title => "desc")->map_by("title")->to_ref, $expected_titles_str->reverse->to_ref, "order_by str desc", ); }; subtest order_by_method_accessor => sub { # Call method with dummy arg eq_or_diff( $books->order_by([ title_uc => "dummy" ])->map_by("title")->to_ref, $expected_titles_str, "order_by method call without comparison", ); eq_or_diff( $books->order_by([ title_uc => "dummy" ] => "desc")->map_by("title")->to_ref, $expected_titles_str->reverse->to_ref, "order_by method call with comparison", ); }; subtest order_by_sub => sub { eq_or_diff( $books->order_by(price => [ "num", sub { 0 - $_ } ])->map_by("price")->to_ref, $expected_prices_asc->reverse->to_ref, "order_by num, subref", ); }; subtest order_by_regex => sub { eq_or_diff( $books->order_by(title => qr/(.)$/)->map_by("title")->to_ref, $expected_titles_regex, "order_by regex", ); }; subtest order_by_multiple_options__num_desc => sub { eq_or_diff( $books->order_by(price => [ "num", "desc" ])->map_by("price")->to_ref, $expected_prices_asc->reverse->to_ref, "order_by num, desc", ); }; subtest accessor_missing => sub { throws_ok( sub { $books->order_by() }, qr/\Q->order_by() missing argument:\E \$accessor/, "Missing arg dies ok", ); }; subtest comparison_args_validation => sub { throws_ok( sub { $books->order_by(title => "blah")->to_ref }, qr/\Q->order_by(): Invalid comparison option (blah)/, "Invalid arg dies ok", ); throws_ok( sub { [1]->order_by(title => [ "asc", "desc" ])->to_ref }, qr/\Q->order_by(): Conflicting comparison options: (asc) and (desc)/, "Invalid arg dies ok", ); # Ignore ugly subref vs regex for now }; subtest order_by_multiple_comparisons => sub { my $words = [ "abc", "def", "ABC", "DEF", "Abc", ]->map(sub { { word => $_ }}); # ASCII order is UPPER before lower my $expected_words = [ "DEF", "def", "ABC", "Abc", "abc", ]; eq_or_diff( $words->order_by( word => [ desc => sub { uc($_) } ], word => qr/(.+)/, )->map_by("word")->to_ref, $expected_words, "First reverse uc, then whole match cmp", ); }; subtest examples => sub { my $titles = [ "Leviathan Wakes", "The Name of the Wind", "The Tree-Body Problem", "Caliban's War", "The Butcher of Anderson Station", ]; my $expected_titles = [ "The Butcher of Anderson Station", "Caliban's War", "Leviathan Wakes", "The Name of the Wind", "The Tree-Body Problem", ]; eq_or_diff( $titles->order( qr/^ (?: The \s+ )? (.+) /x )->to_ref, $expected_titles, "regex to remove leading article", ); }; done_testing(); reject.t100664001755001752 523313707566017 16502 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; subtest reject_default_true => sub { note "Default is checking for true"; my $array = [ 0, 1, 2, 3, "", 4, undef, 5 ]; eq_or_diff( $array->reject->to_ref, [ 0, "", undef ], "Only false values remain", ); }; subtest reject_invalid_predicate => sub { my $strings = [ "abc", "def", "abc" ]; throws_ok( sub { $strings->reject(\"abc")->to_ref }, qr/->reject .+? \$predicate: .+?\Q is not one of: subref, string, regex/x, "Invalid predicate type", ); }; subtest reject_subref => sub { note "ArrayRef call, list context result, subref predicate"; eq_or_diff( [ map { $_->name } $authors->reject(sub { $_->is_prolific }) ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject simple method call works", ); note "list call, list context result"; my @authors = @$authors; my $prolific_authors = @authors->reject(sub { $_->is_prolific }); eq_or_diff( [ map { $_->name } @$prolific_authors ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject simple method call works", ); }; subtest reject_string => sub { my $strings = [ "abc", "def", "abc" ]; eq_or_diff( $strings->reject("abc")->to_ref, [ "def" ], "reject scalar string", ); # TODO: deal with undef comparisons }; # TODO: Can't work until the old call style is removed. # subtest reject_undef => sub { # my $strings = [ "abc", undef, "abc" ]; # eq_or_diff( # $strings->reject(undef)->to_ref, # [ "abc", "abc" ], # "reject undef", # ); # }; subtest reject_regex => sub { my $strings = [ "abc", "def", "abc" ]; eq_or_diff( $strings->reject(qr/a/)->to_ref, [ "def" ], "reject regex", ); eq_or_diff( $strings->reject(qr/A/)->to_ref, [ "abc", "def", "abc"], "reject regex miss", ); eq_or_diff( $strings->reject(qr/A/i)->to_ref, [ "def" ], "reject regex with flags", ); # TODO: deal with undef comparisons }; subtest reject_hashref_keys => sub { my $strings = [ "abc", "def", "ghi" ]; eq_or_diff( $strings->reject({ abc => undef, def => 1 })->to_ref, [ "ghi" ], "reject hashref keys (exists, not true hash value)", ); # TODO: deal with undef comparisons }; done_testing(); reject_by.t100664001755001752 774413707566017 17205 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; subtest reject_by => sub { note "ArrayRef call, list context result, default predicate (true)"; eq_or_diff( [ map { $_->name } $authors->reject_by("is_prolific") ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by simple method call works", ); note "list call, list context result"; my @authors = @$authors; my $prolific_authors = @authors->reject_by("is_prolific"); eq_or_diff( [ map { $_->name } @$prolific_authors ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by simple method call works", ); }; subtest reject_by_with_arrayref_accessor => sub { note "Call with arrayref accessor without args"; eq_or_diff( [ map { $_->name } $authors->reject_by([ "is_prolific" ]) ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by simple method call works", ); }; subtest reject_by_with_predicate_subref => sub { eq_or_diff( [ map { $_->name } $authors->reject_by("name", undef, sub { /Corey/ }) ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by without predicate argument and with predicate sub call works", ); eq_or_diff( [ map { $_->name } $authors->reject_by( "publisher_affiliation", [ "with" ], sub { /Corey/ }, ), ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by with method argument and predicate sub call works", ); note "Arrayref accessor"; eq_or_diff( [ map { $_->name } $authors->reject_by("name", sub { /Corey/ }) ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by without predicate argument and with predicate sub call works", ); eq_or_diff( [ map { $_->name } $authors->reject_by( [ publisher_affiliation => "with" ], sub { /Corey/ }, ), ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by with method argument and predicate sub call works", ); }; subtest reject_by_with_predicate_string => sub { eq_or_diff( [ map { $_->name } $authors->reject_by("name", "James A. Corey") ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by predicate string works", ); eq_or_diff( [ map { $_->name } $authors->reject_by("name", undef, "James A. Corey") ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by predicate string works, with old call style", ); }; # TODO: undef can't work until old call style is removed subtest reject_by_with_predicate_regex => sub { eq_or_diff( [ map { $_->name } $authors->reject_by("name", qr/corey/i) ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by predicate regex works", ); }; subtest reject_by_with_predicate_hashref => sub { eq_or_diff( [ map { $_->name } $authors->reject_by("name", { "James A. Corey" => 1 }) ], [ "Cixin Liu", "Patrick Rothfuss", ], "reject_by predicate hashref works", ); }; subtest examples => sub { my $prolific_author_book_titles = $authors->reject_by("is_prolific") ->map_by("books")->flat ->map_by("title")->sort; eq_or_diff( $prolific_author_book_titles, [ "The Name of the Wind", "The Tree-Body Problem", ], "non-prolific_author_book_titles", ); }; done_testing(); to_x.t100664001755001752 404013707566017 16172 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; subtest "array_to_ref" => sub { my $array = [ 1, 2 ]; my @array = @$array; eq_or_diff( [ @array->to_ref ], [ $array ], "Array to_ref in list context works", ); eq_or_diff( [ $array->to_ref ], [ $array ], "ArrayRef to_ref in list context works", ); }; subtest "hash_to_ref" => sub { my $hash = { 1 => 2, 2 => 3 }; my %hash = %$hash; eq_or_diff( [ %hash->to_ref ], [ $hash ], "Hash to_ref in list context works", ); eq_or_diff( [ $hash->to_ref ], [ $hash ], "HashRef to_ref in list context works", ); }; subtest "array_to_array" => sub { my $array = [ 1, 2 ]; my @array = @$array; eq_or_diff( [ @array->to_array ], [ @array ], "Array to_array in list context works", ); eq_or_diff( [ $array->to_array ], [ @array ], "ArrayRef to_array in list context works", ); }; subtest "array_to_hash" => sub { my $array = [ 1, 2 ]; my @array = @$array; eq_or_diff( @array->to_hash->to_ref, { 1 => 2 }, "Array to_hash works", ); throws_ok( sub { [ 1, 2, "c" ]->to_hash }, qr/\@array->to_hash on an array with an odd number of elements \(3\) at/, "Array to_hash with an odd number of items goes *boom*", ); }; subtest "hash_to_hash" => sub { my $hash = { a => 1, b => 2 }; my %hash = %$hash; eq_or_diff( { %hash->to_hash }, { %hash }, "Hash to_hash in list context works", ); eq_or_diff( { $hash->to_hash }, { %hash }, "HashRef to_hash in list context works", ); }; subtest "hash_to_array" => sub { my $hash = { z => 1, a => 1, b => 2 }; eq_or_diff( [ $hash->to_array ], [ a => 1, b => 2, z => 1 ], "Hash to_array works", ); }; done_testing(); uniq_by.t100664001755001752 146213707566017 16674 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035/tuse strict; use warnings; use Test::More; use Test::Differences; use Test::Exception; use autobox::Core; use lib "lib"; use autobox::Transform; use lib "t/lib"; use Literature; my $literature = Literature::literature(); my $authors = $literature->{authors}; subtest uniq_by => sub { note "ArrayRef call, list context result"; eq_or_diff( [ map { $_->name } $authors->uniq_by("is_prolific") ], [ "James A. Corey", # true "Cixin Liu", # false ], "uniq_by simple method call works", ); eq_or_diff( [ map { $_->name } $authors->uniq_by([ "is_prolific" ]) ], [ "James A. Corey", # true "Cixin Liu", # false ], "uniq_by simple method call works", ); }; done_testing(); META.yml100664001755001752 257413707566017 16054 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035--- abstract: 'Autobox methods to transform Arrays and Hashes' author: - '- Johan Lindstrom, All Rights Reserved.' build_requires: Moo: '0' Test::Differences: '0' Test::Exception: '0' Test::More: '0.98' configure_requires: Module::Build::Tiny: '0.035' dynamic_config: 0 generated_by: 'Minilla/v3.1.10, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: autobox-Transform no_index: directory: - t - xt - inc - share - eg - examples - author - builder provides: autobox::Transform: file: lib/autobox/Transform.pm version: '1.035' requires: Carp: '0' List::MoreUtils: '0' Sort::Maker: '0' autobox: '0' autobox::Core: '0' parent: '0' perl: '5.010' resources: bugtracker: https://github.com/jplindstrom/p5-autobox-Transform/issues homepage: https://github.com/jplindstrom/p5-autobox-Transform repository: git://github.com/jplindstrom/p5-autobox-Transform.git version: '1.035' x_authority: cpan:JOHANL x_contributors: - 'Johan Lindstrom ' - 'Johan Lindstrom ' - 'Johan Lindstrom ' - 'Mohammad S Anwar ' - 'Tom Bloor ' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' x_static_install: 1 MANIFEST100664001755001752 63313707566017 15706 0ustar00bbcpebbcpe000000000000autobox-Transform-1.035Build.PL Changes LICENSE META.json README.md cpanfile lib/autobox/Transform.pm minil.toml notes/notes.org t/00_compile.t t/filter.t t/filter_by.t t/group.t t/group_by.t t/hash-filter_each.t t/hash-map_each.t t/hash-map_each_to_array.t t/hash-map_each_value.t t/hash-reject_each.t t/key_value.t t/lib/Literature.pm t/map_by.t t/order.t t/order_by.t t/reject.t t/reject_by.t t/to_x.t t/uniq_by.t META.yml MANIFEST