~tyil/raku-hash-merge

09a8131c63667d9de9ee3921e7f877c09c9092c8 — Patrick Spek 6 years ago 3cf9f5e
Move Hash::Merge -> Hash::Merge::Augment, new Hash::Merge from Hash::Merge::F

Original Hash::Merge is incredibly slow due to the MONKEY. This is
someone that is very unlikely to be what people want. App::Assixt
reports parse time to go from 13s to 7s on first run, 13s to 0.3s on
subsequent runs, which is *massive*.
7 files changed, 110 insertions(+), 101 deletions(-)

M CHANGELOG.md
M META6.json
M lib/Hash/Merge.pm6
R lib/Hash/Merge/{Unit.pm6 => Augment.pm6}
M t/01-thing.t
M t/02-empty-source.t
M t/03-unit.t
M CHANGELOG.md => CHANGELOG.md +8 -0
@@ 5,6 5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic
Versioning](http://semver.org/spec/v2.0.0.html).

## [UNRELEASED]
### Added
- `:api` key in `META6.json`
- New `Hash::Merge` that exposes subs instead of augmenting `Hash`

### Changed
- Old `Hash::Merge` functionality moved to `Hash::Merge::Augment`

## [0.2.0] - 2018-03-14
### Added
- `LICENSE` file for Artistic License 2.0 ([GitHub#2](https://github.com/scriptkitties/p6-Hash-Merge/issues/2))

M META6.json => META6.json +3 -2
@@ 10,10 10,10 @@
  "description": "Module to add deep merge functionality to Hashes",
  "license": "Artistic-2.0",
  "name": "Hash::Merge",
  "perl": "6",
  "perl": "6.c",
  "provides": {
    "Hash::Merge": "lib/Hash/Merge.pm6",
    "Hash::Merge::Unit": "lib/Hash/Merge/Unit.pm6"
    "Hash::Merge::Augment": "lib/Hash/Merge/Augment.pm6"
  },
  "source-url": "https://github.com/scriptkitties/p6-Hash-Merge.git",
  "tags": [


@@ 23,5 23,6 @@
  "test-depends": [
    "Test::META"
  ],
  "api": "1",
  "version": "0.2.0"
}

M lib/Hash/Merge.pm6 => lib/Hash/Merge.pm6 +46 -49
@@ 1,63 1,60 @@
#! /usr/bin/env false

use v6.c;
use MONKEY-TYPING;

# Don't use precompilation in order to not conflict with other MONKEY-TYPING
# modules.
no precompilation;

augment class Hash
{
    #| Merges a second hash into the hash the method is called on. Hash given as
    #| the argument is not modified.
    #| Traverses the full tree, replacing items in the original hash with the
    #| hash given in the argument. Does not replace positional elements by default,
    #| and instead appends the items from the supplied hash's array to the original
    #| hash's array. The object type of positionals is not retained and instead
    #| becomes an Array type.
    #| Use :no-append-array to replace arrays and positionals instead, which will
    #| also retain the original type and not convert to an Array
    multi method merge (Hash:U: %b, Bool:D :$no-append-array = False) {
        warn "Cannot merge an undefined Hash!";
        return %b;
    }

    multi method merge (Hash:D: %b, Bool:D :$no-append-array = False)
    {
        hashmerge self, %b, :$no-append-array;
unit module Hash::Merge;

#| Merge any number of Hashes together.
sub merge-hashes(
    *@hashes, #= Hashes to merge together
    --> Hash
) is export {
    my %merge-into = @hashes.shift;

    # Nothing to do if we only got 1 argument
    return %merge-into unless @hashes.elems;

    for ^@hashes.elems {
        %merge-into = merge-hash(%merge-into, @hashes.shift);
    }

    sub hashmerge (%merge-into, %merge-source, Bool:D :$no-append-array)
    {
        for %merge-source.keys -> $key {
            if %merge-into{$key}:exists {
                given %merge-source{$key} {
                    when Hash {
                        hashmerge %merge-into{$key},
                                  %merge-source{$key},
                                  :$no-append-array;
                    }
                    when Positional {
                        %merge-into{$key} = $no-append-array
                            ?? %merge-source{$key}
                            !!
                            do {
                                my @a;
                                @a.push: $_ for %merge-into{$key}.list;
                                @a.push: $_ for %merge-source{$key}.list;
                                @a;
                            }
    %merge-into;
}

#| Merge two hashes together.
sub merge-hash(
    %merge-into,   #= The original Hash that should be merged into.
    %merge-source, #= Another Hash to merge into the original Hash.
    Bool:D :$no-append-array = False,
    --> Hash
) is export {
    for %merge-source.keys -> $key {
        if %merge-into{$key}:exists {
            given %merge-source{$key} {
                when Hash {
                    merge-hash(%merge-into{$key}, %merge-source{$key}, :$no-append-array);
                }
                when Positional {
                    %merge-into{$key} = $no-append-array
                    ?? %merge-source{$key}
                    !!
                    do {
                        my @a;
                        @a.push: $_ for %merge-into{$key}.list;
                        @a.push: $_ for %merge-source{$key}.list;
                        @a;
                    }
                    # Non-positionals, so strings or Bools or whatever
                    default { %merge-into{$key} = %merge-source{$key} }
                }
            } else {
                %merge-into{$key} = %merge-source{$key};
                default {
                    %merge-into{$key} = %merge-source{$key}
                }
            }
        } else {
            %merge-into{$key} = %merge-source{$key};
        }
        %merge-into;
    }

    %merge-into;
}

# vim: ft=perl6 ts=4 sw=4 et

R lib/Hash/Merge/Unit.pm6 => lib/Hash/Merge/Augment.pm6 +50 -47
@@ 1,60 1,63 @@
#! /usr/bin/env false

use v6;

unit module Hash::Merge::Unit;

#| Merge any number of Hashes together.
sub merge-hashes(
    *@hashes, #= Hashes to merge together
    --> Hash
) is export {
    my %merge-into = @hashes.shift;

    # Nothing to do if we only got 1 argument
    return %merge-into unless @hashes.elems;

    for ^@hashes.elems {
        %merge-into = merge-hash(%merge-into, @hashes.shift);
use v6.c;
use MONKEY-TYPING;

# Don't use precompilation in order to not conflict with other MONKEY-TYPING
# modules.
no precompilation;

augment class Hash
{
    #| Merges a second hash into the hash the method is called on. Hash given as
    #| the argument is not modified.
    #| Traverses the full tree, replacing items in the original hash with the
    #| hash given in the argument. Does not replace positional elements by default,
    #| and instead appends the items from the supplied hash's array to the original
    #| hash's array. The object type of positionals is not retained and instead
    #| becomes an Array type.
    #| Use :no-append-array to replace arrays and positionals instead, which will
    #| also retain the original type and not convert to an Array
    multi method merge (Hash:U: %b, Bool:D :$no-append-array = False) {
        warn "Cannot merge an undefined Hash!";
        return %b;
    }

    %merge-into;
}
    multi method merge (Hash:D: %b, Bool:D :$no-append-array = False)
    {
        hashmerge self, %b, :$no-append-array;
    }

#| Merge two hashes together.
sub merge-hash(
    %merge-into,   #= The original Hash that should be merged into.
    %merge-source, #= Another Hash to merge into the original Hash.
    Bool:D :$no-append-array = False,
    --> Hash
) is export {
    for %merge-source.keys -> $key {
        if %merge-into{$key}:exists {
            given %merge-source{$key} {
                when Hash {
                    merge-hash(%merge-into{$key}, %merge-source{$key}, :$no-append-array);
                }
                when Positional {
                    %merge-into{$key} = $no-append-array
                    ?? %merge-source{$key}
                    !!
                    do {
                        my @a;
                        @a.push: $_ for %merge-into{$key}.list;
                        @a.push: $_ for %merge-source{$key}.list;
                        @a;
    sub hashmerge (%merge-into, %merge-source, Bool:D :$no-append-array)
    {
        for %merge-source.keys -> $key {
            if %merge-into{$key}:exists {
                given %merge-source{$key} {
                    when Hash {
                        hashmerge %merge-into{$key},
                                  %merge-source{$key},
                                  :$no-append-array;
                    }
                    when Positional {
                        %merge-into{$key} = $no-append-array
                            ?? %merge-source{$key}
                            !!
                            do {
                                my @a;
                                @a.push: $_ for %merge-into{$key}.list;
                                @a.push: $_ for %merge-source{$key}.list;
                                @a;
                            }
                    }
                    # Non-positionals, so strings or Bools or whatever
                    default { %merge-into{$key} = %merge-source{$key} }
                }
                default {
                    %merge-into{$key} = %merge-source{$key}
                }
            } else {
                %merge-into{$key} = %merge-source{$key};
            }
        } else {
            %merge-into{$key} = %merge-source{$key};
        }
        %merge-into;
    }

    %merge-into;
}

# vim: ft=perl6 ts=4 sw=4 et

M t/01-thing.t => t/01-thing.t +1 -1
@@ 4,7 4,7 @@ use v6;
use lib 'lib';
use Test;

use Hash::Merge;
use Hash::Merge::Augment;

my %a;
my %b;

M t/02-empty-source.t => t/02-empty-source.t +1 -1
@@ 6,7 6,7 @@ use Test;

plan 3;

use Hash::Merge;
use Hash::Merge::Augment;

my Hash $hash = {
    a => "a",

M t/03-unit.t => t/03-unit.t +1 -1
@@ 2,7 2,7 @@

use v6.c;

use Hash::Merge::Unit;
use Hash::Merge;
use Test;

plan 2;