File Coverage

blib/lib/App/HL7/Compare.pm
Criterion Covered Total %
statement 139 139 100.0
branch 18 20 90.0
condition 9 9 100.0
subroutine 18 18 100.0
pod 2 2 100.0
total 186 188 98.9


line stmt bran cond sub pod time code
1             package App::HL7::Compare;
2             $App::HL7::Compare::VERSION = '0.002';
3 3     3   140333 use v5.10;
  3         30  
4 3     3   19 use strict;
  3         5  
  3         85  
5 3     3   17 use warnings;
  3         5  
  3         90  
6              
7 3     3   1907 use Moo;
  3         35797  
  3         13  
8 3     3   6172 use Mooish::AttributeBuilder -standard;
  3         5506  
  3         31  
9 3     3   1701 use App::HL7::Compare::Parser;
  3         9  
  3         140  
10 3     3   20 use Types::Standard qw(Tuple Str ScalarRef InstanceOf Bool);
  3         6  
  3         21  
11 3     3   9395 use List::Util qw(max);
  3         8  
  3         4385  
12              
13             has param 'files' => (
14             isa => Tuple [Str | ScalarRef, Str | ScalarRef],
15             );
16              
17             has param 'exclude_matching' => (
18             isa => Bool,
19             default => sub { 1 },
20             );
21              
22             has field 'parser' => (
23             isa => InstanceOf ['App::HL7::Compare::Parser'],
24             default => sub { App::HL7::Compare::Parser->new },
25             );
26              
27             sub _compare_line
28             {
29 38     38   82 my ($self, $segment, $field, $component, $subcomponent, $message_num, $comps) = @_;
30              
31 38         149 my $name = sprintf "%s.%s", $segment->name, $segment->number;
32 38   100     116 my $order = $comps->{order}{$name} //= @{$comps->{segments}};
  8         26  
33 38         656 $comps->{segments}[$order]
34             [$field->number][$component->number][$subcomponent->number]
35             [$message_num] = $subcomponent->value;
36             }
37              
38             sub _gather_recursive
39             {
40 96     96   181 my ($self, $item, $levels_down) = @_;
41 96         137 $levels_down -= 1;
42              
43 96         135 my @results;
44 96         134 foreach my $subitem (@{$item->parts}) {
  96         1557  
45 126 100       25352 if ($levels_down == 0) {
46 38         113 push @results, [$subitem];
47             }
48             else {
49             push @results, map {
50 114         149 [$subitem, @{$_}]
  114         293  
51 88         139 } @{$self->_gather_recursive($subitem, $levels_down)};
  88         204  
52             }
53             }
54              
55 96         286 return \@results;
56             }
57              
58             sub _build_comparison_recursive
59             {
60 74     74   108 my ($self, $parts, $levels_down) = @_;
61 74         95 $levels_down -= 1;
62              
63 74 100       121 if ($levels_down == 0) {
64             return [
65             {
66             path => [],
67 25         41 value => [@{$parts}[0, 1]],
  25         104  
68             }
69             ];
70             }
71              
72 49         60 my @results;
73 49         68 foreach my $part_num (0 .. $#{$parts}) {
  49         81  
74 116         162 my $part = $parts->[$part_num];
75 116 100       196 next unless defined $part;
76              
77 66         132 my $deep_results = $self->_build_comparison_recursive($part, $levels_down);
78 66 100 100     85 if (@{$deep_results} == 1 && defined $deep_results->[0]{path}[0]) {
  66         222  
79             $deep_results->[0]{path}[0] = $part_num
80 35 50       78 if $deep_results->[0]{path}[0] == 1;
81 35         67 push @results, $deep_results->[0];
82             }
83             else {
84             push @results, map {
85 40         55 unshift @{$_->{path}}, $part_num;
  40         71  
86 40         94 $_
87 31         60 } @{$deep_results};
  31         48  
88             }
89             }
90              
91 49         111 return \@results;
92             }
93              
94             sub _build_comparison
95             {
96 4     4   11 my ($self, $comps) = @_;
97              
98 4         6 my %reverse_order = map { $comps->{order}{$_} => $_ } keys %{$comps->{order}};
  8         31  
  4         29  
99 4         11 my @results;
100              
101 4         6 foreach my $segment_num (0 .. $#{$comps->{segments}}) {
  4         16  
102 8         18 my $segment = $comps->{segments}[$segment_num];
103             push @results, {
104 8         26 segment => $reverse_order{$segment_num},
105             compared => $self->_build_comparison_recursive($segment, 4)
106             };
107             }
108              
109 4         16 return \@results;
110             }
111              
112             sub _compare_messages
113             {
114 4     4   95 my ($self, $message1, $message2) = @_;
115              
116 4         18 my %comps = (
117             order => {},
118             segments => [],
119             );
120              
121 4         10 my $message_num = 0;
122 4         24 foreach my $message ($message1, $message2) {
123 8         23 my $parts = $self->_gather_recursive($message, 4);
124 8         16 foreach my $part (@{$parts}) {
  8         17  
125 38         760 $self->_compare_line(@{$part}, $message_num, \%comps);
  38         98  
126             }
127              
128 8         198 $message_num += 1;
129             }
130              
131 4         17 return $self->_build_comparison(\%comps);
132             }
133              
134             sub _get_files
135             {
136 4     4   11 my ($self) = @_;
137              
138             my $slurp = sub {
139 4     4   8 my ($file) = @_;
140              
141 4 50       168 open my $fh, '<', $file
142             or die "couldn't open file $file: $!";
143              
144 4         24 local $/;
145 4         186 return readline $fh;
146 4         21 };
147              
148 4         10 my @files = @{$self->files};
  4         20  
149 4         11 foreach my $file (@files) {
150 8 100       29 if (ref $file eq 'SCALAR') {
151 4         8 $file = ${$file};
  4         11  
152             }
153             else {
154 4         10 $file = $slurp->($file);
155             }
156             }
157              
158 4         31 return @files;
159             }
160              
161             sub _remove_matching
162             {
163 4     4   10 my ($self, $compared) = @_;
164              
165 4 100       20 return unless $self->exclude_matching;
166              
167 3         14 foreach my $segment (@{$compared}) {
  3         9  
168 5         8 my @to_delete;
169              
170 5         9 foreach my $comp_num (0 .. $#{$segment->{compared}}) {
  5         12  
171 17         30 my $comp = $segment->{compared}[$comp_num];
172              
173 17         32 my @values = @{$comp->{value}};
  17         35  
174 17 100 100     32 if ((grep { defined } @values) == 2 && $values[0] eq $values[1]) {
  34         95  
175 5         14 push @to_delete, $comp_num;
176             }
177             }
178              
179 5         13 foreach my $comp_num (reverse @to_delete) {
180 5         11 splice @{$segment->{compared}}, $comp_num, 1;
  5         17  
181             }
182             }
183             }
184              
185             sub compare
186             {
187 4     4 1 99 my ($self) = @_;
188              
189 4         16 my $compared = $self->_compare_messages(map { $self->parser->parse($_) } $self->_get_files);
  8         164  
190 4         115 $self->_remove_matching($compared);
191              
192 4         9 return $compared;
193             }
194              
195             sub compare_stringify
196             {
197 3     3 1 197 my ($self) = @_;
198 3         10 my $compared = $self->compare;
199              
200 3         7 my @out;
201 3         5 my $longest = 0;
202 3         8 foreach my $segment (@{$compared}) {
  3         6  
203 7         13 my @stringified;
204 7         10 foreach my $comp (@{$segment->{compared}}) {
  7         15  
205             my $stringified = [
206 23         59 $segment->{segment} . join('', map { "[$_]" } @{$comp->{path}}) . ':',
  13         23  
207 13 100       18 map { defined $_ ? $_ : '(empty)' } @{$comp->{value}}
  26         58  
  13         24  
208             ];
209              
210 13         28 push @stringified, $stringified;
211             }
212              
213 7         18 $longest = max $longest, map { length $_->[0] } @stringified;
  13         39  
214 7         18 my $blueprint = "%-${longest}s %s => %s";
215 7         13 push @out, map { sprintf $blueprint, @{$_} } @stringified;
  13         18  
  13         48  
216             }
217              
218 3         28 return join "\n", @out;
219             }
220              
221             1;
222              
223             # ABSTRACT: compare two HL7 v2 messages against one another
224