Data-Hash-Patch-Smart

 view release on metacpan or  search on metacpan

lib/Data/Hash/Patch/Smart/Engine.pm  view on Meta::CPAN

sub patch {
	my ($data, $changes, %opts) = @_;

	my $copy = dclone($data);

	for my $c (@$changes) {
		_apply_change($copy, $c, \%opts);
	}

	return $copy;
}

sub _apply_change {
	my ($root, $c, $opts) = @_;

	my $op = $c->{op} or die 'change missing op';
	my $path = $c->{path} or die 'change missing path';

	# Split path into segments like ('items', '0') or ('items', '*')
	my @parts = _split_path($path);

	# Leaf is the last segment; parent is everything before it
	my $leaf = pop @parts;

	# Structural wildcard (in parent path)
	if (grep { $_ eq '*' } @parts) {
		return _apply_structural_wildcard($root, \@parts, $leaf, $c, $opts);
	}

	# Walk down to the parent container (hash or array)
	my $parent = _walk_to_parent($root, \@parts, $leaf, $opts);

	# Unordered array semantics: leaf is '*'
	if ($leaf eq '*') {
		if ($op eq 'add') {
			_add_unordered($parent, $c->{value}, $opts);
		} elsif ($op eq 'remove') {
			_remove_unordered($parent, $c->{from}, $opts);
		} else {
			die "Unsupported op '$op' for unordered path '$path'";
		}
		return;
	}

	# Normal index/hash semantics
	if ($op eq 'change') {
		_set_value($parent, $leaf, $c->{to}, $opts);
	} elsif ($op eq 'add') {
		_add_value($parent, $leaf, $c->{value}, $opts);
	} elsif ($op eq 'remove') {
		_remove_value($parent, $leaf, $opts);
	} else {
		die "Unsupported op: $op";
	}
}

sub _split_path {
	my $path = $_[0];

	return () if !defined $path || $path eq '';
	my @parts = grep { length $_ } split m{/}, $path;
	return @parts;
}

# Walk down the structure following the given path segments,
# stopping at the parent of the leaf. In strict mode, we die on
# invalid paths. With create_missing => 1, we auto-create
# intermediate hashes/arrays as needed.
sub _walk_to_parent {
	my ($cur, $parts, $leaf, $opts) = @_;

	# Walk all segments that lead to the parent of $leaf
	for (my $i = 0; $i < @$parts; $i++) {
		my $p	= $parts->[$i];
		my $is_last = ($i == $#$parts);

		# For container creation, "next" is either the next part,
		# or, if we're at the last part, the leaf segment.
		my $next = $is_last ? $leaf : $parts->[$i + 1];

		# -----------------------------
		# HASH navigation
		# -----------------------------
		if (ref($cur) eq 'HASH') {

			# Missing key
			if (!exists $cur->{$p}) {
				if ($opts->{create_missing}) {
					# Decide container type based on what comes after
					if (defined $next && $next =~ /^\d+$/) {
						$cur->{$p} = [];
					} else {
						$cur->{$p} = {};
					}
				}
				elsif ($opts->{strict}) {
					die "Invalid path: missing hash key '$p'";
				}
				else {
					return undef;
				}
			}

			$cur = $cur->{$p};
			next;
		}

		# -----------------------------
		# ARRAY navigation
		# -----------------------------
		if (ref($cur) eq 'ARRAY') {

			# Index must be numeric
			if ($p !~ /^\d+$/) {
				die "Invalid path: non-numeric array index '$p'"
					if $opts->{strict};
				return undef;
			}

			# Out of bounds
			if ($p > $#$cur) {



( run in 1.928 second using v1.01-cache-2.11-cpan-71847e10f99 )