File Coverage

blib/lib/Config/Patch.pm
Criterion Covered Total %
statement 341 396 86.1
branch 108 166 65.0
condition 22 28 78.5
subroutine 47 49 95.9
pod 12 26 46.1
total 530 665 79.7


line stmt bran cond sub pod time code
1             ##################################################
2             package Config::Patcher::Util;
3             ##################################################
4              
5             ##################################################
6             # Poor man's Class::Struct
7             ##################################################
8             sub make_accessor {
9             ##################################################
10 60     60   104 my($package, $name) = @_;
11              
12 4     4   92680 no strict qw(refs);
  4         9  
  4         503  
13              
14 60         194 my $code = <
15             *{"$package\\::$name"} = sub {
16             my(\$self, \$value) = \@_;
17              
18             if(defined \$value) {
19             \$self->{$name} = \$value;
20             }
21             if(exists \$self->{$name}) {
22             return (\$self->{$name});
23             } else {
24             return "";
25             }
26             }
27             EOT
28 60 50       63 if(! defined *{"$package\::$name"}) {
  60         345  
29 60 50   16   8220 eval $code or die "$@";
  16 50       29  
  16 50       40  
  0 100       0  
  16 50       40  
  16 50       184  
  0 50       0  
  42 50       70  
  42 50       93  
  12 50       24  
  42 50       98  
  42 50       392  
  0 50       0  
  21 100       41  
  21 50       52  
  0 50       0  
  21 50       51  
  21 50       247  
  0 50       0  
  22 50       40  
  22 50       58  
  0 100       0  
  22 50       56  
  22 100       537  
  0 50       0  
  13 50       21  
  13 50       25  
  0 100       0  
  13 50       25  
  13 50       213  
  0 50       0  
  54         82  
  54         120  
  0         0  
  54         110  
  54         885  
  0         0  
  49         147  
  49         97  
  11         18  
  49         85  
  49         295  
  0         0  
  2         6  
  2         7  
  0         0  
  2         5  
  2         53  
  0         0  
  8         14  
  8         15  
  0         0  
  8         18  
  8         104  
  0         0  
  25         38  
  25         53  
  0         0  
  25         49  
  25         315  
  0         0  
  16         727  
  16         35  
  5         10  
  16         31  
  16         58  
  0         0  
  14         860  
  14         32  
  2         6  
  14         31  
  14         132  
  0         0  
  3         5  
  3         6  
  0         0  
  3         6  
  3         57  
  0         0  
  40         56  
  40         77  
  1         1  
  40         69  
  40         395  
  0         0  
  59         81  
  59         109  
  0         0  
  59         99  
  59         615  
  0         0  
30             }
31             }
32              
33             ###########################################
34             package Config::Patch::Hunk;
35             ###########################################
36 4     4   3393 use MIME::Base64;
  4         3168  
  4         2274  
37              
38             our @accessors = qw(
39             mode key text pos_from pos_to header
40             content_pos_from content_pos_to regex method
41             as_string
42             );
43             Config::Patcher::Util::make_accessor( __PACKAGE__, $_ ) for @accessors;
44              
45             ###########################################
46             sub new {
47             ###########################################
48 66     66   429 my($class, %options) = @_;
49              
50 66         513 my $self = {
51             comment_char => '#',
52             mode => "append",
53             key => undef,
54             text => undef,
55             %options,
56             };
57              
58 66         335 bless $self, $class;
59             }
60              
61             ###########################################
62             sub patch_marker {
63             ###########################################
64 72     72   74 my($self) = @_;
65              
66 72         1627 return $self->{comment_char} .
67             "(Config::Patch-" .
68             "$self->{key}-" .
69             $self->mode() .
70             ")" .
71             "\n";
72             }
73              
74             ###########################################
75             sub string_generate {
76             ###########################################
77 36     36   42 my($self) = @_;
78              
79 36         91 return $self->patch_marker() .
80             $self->text() .
81             $self->patch_marker();
82             }
83              
84             ###########################################
85             sub freeze {
86             ###########################################
87 13     13   22 my($self, $string) = @_;
88              
89             # Hide an arbitrary string in a comment
90 13         71 my $encoded = encode_base64($string);
91              
92 13         93 $encoded =~ s/^/$self->{comment_char} /gm;
93 13         43 return $encoded;
94             }
95              
96             ###########################################
97             sub thaw {
98             ###########################################
99 9     9   17 my($self, $string) = @_;
100              
101             # Decode a hidden string
102 9         61 $string =~ s/^$self->{comment_char} //gm;
103 9         39 my $decoded = decode_base64($string);
104 9         42 return $decoded;
105             }
106              
107             ###########################################
108             sub replstring_extract {
109             ###########################################
110 9     9   16 my($self) = @_;
111              
112 9         222 my $text = $self->text();
113              
114             # Find the replace string in a patch
115 9         26 my $replace_marker = $self->replace_marker();
116 9         22 $replace_marker = quotemeta($replace_marker);
117 9 50       144 if($text =~ /^$replace_marker\n(.*?)
118             ^$replace_marker/xms) {
119 9         22 my $repl = $1;
120 9         108 $text =~ s/^$replace_marker.*?
121             ^$replace_marker\n//xms;
122              
123 9         32 return($self->thaw($repl), $text);
124             }
125              
126 0         0 return undef;
127             }
128              
129             ###########################################
130             sub replstring_hide {
131             ###########################################
132 13     13   33 my($self, $replstring) = @_;
133              
134             # Add a replace string to a patch
135 13         39 my $replace_marker = $self->replace_marker();
136 13         48 my $encoded = $replace_marker . "\n" .
137             $self->freeze($replstring) .
138             $replace_marker .
139             "\n";
140              
141 13         29 return $encoded;
142             }
143              
144             ###########################################
145             sub replace_marker {
146             ###########################################
147 22     22   29 my($self) = @_;
148              
149 22         75 return $self->{comment_char} .
150             "(Config::Patch::replace)";
151             }
152              
153             ###########################################
154             package Config::Patch;
155             ###########################################
156 4     4   24 use strict;
  4         12  
  4         111  
157 4     4   19 use warnings;
  4         7  
  4         155  
158 4     4   4049 use Set::IntSpan;
  4         50344  
  4         290  
159 4     4   46 use Fcntl qw(:flock);
  4         8  
  4         572  
160 4     4   5652 use Log::Log4perl qw(:easy);
  4         246621  
  4         28  
161              
162             our $VERSION = "0.09";
163              
164             our @accessors = qw(data file comment_char key);
165             Config::Patcher::Util::make_accessor( __PACKAGE__, $_ ) for @accessors;
166              
167             ###########################################
168             sub new {
169             ###########################################
170 20     20 1 4458 my($class, %options) = @_;
171              
172 20         134 my $self = {
173             comment_char => '#',
174             key => undef,
175             file => undef,
176             parsed => 0,
177             read => 0,
178             %options,
179             };
180              
181 20         36 my $package = __PACKAGE__;
182              
183 20         243 $self->{patch_regex} =
184             qr{^$self->{comment_char}\($package-(.*)-(.*?)\)}m;
185              
186 20         126 bless $self, $class;
187             }
188              
189             ###########################################
190             sub read {
191             ###########################################
192 21     21 1 88 my($self, $file) = @_;
193              
194 21 50       53 if(defined $file) {
195 0         0 $self->{file} = $file;
196             }
197              
198 21         59 $self->{data} = $self->slurp( $self->{file} );
199              
200             # fix trailing newline if it's missing
201 21 50       80 $self->{data} .= "\n" unless substr($self->{data}, -1, 1) eq "\n";
202              
203 21         32 $self->{parsed} = 0;
204 21         27 $self->{read} = 1;
205              
206 21         44 return $self->{data};
207             }
208              
209             ###########################################
210             sub error {
211             ###########################################
212 0     0 0 0 my($self, @text) = @_;
213              
214 0 0       0 if(defined $text[0]) {
215 0         0 $self->{error} = join "", @text;
216 0         0 ERROR $self->{error};
217             }
218              
219 0         0 return $self->{error};
220             }
221              
222             ###########################################
223             sub patch_by_stretch {
224             ###########################################
225 6     6 0 11 my($self, $text, $mode) = @_;
226              
227 6 50       18 LOGDIE "No key defined" unless
228             defined $self->{key};
229              
230 6 50       13 LOGDIE "No mode defined" unless
231             defined $mode;
232              
233 6         71 my $patch = Config::Patch::Hunk->new(
234             comment_char => $self->{comment_char},
235             key => $self->{key},
236             text => $text,
237             mode => $mode,
238             );
239              
240 6         19 return $self->apply( $patch );
241             }
242              
243             ###########################################
244             sub apply {
245             ###########################################
246 15     15 1 50 my($self, $patch) = @_;
247              
248 15 100       45 $self->read() unless $self->{read};
249              
250 15         40 $patch->{comment_char} = $self->{comment_char};
251              
252 15         382 my $key = $patch->key();
253              
254             # TODO: Lower-level functions expect it there, but probably should
255             # be carried as an argument.
256 15         29 $self->{key} = $key;
257              
258 15         35 my $patchtext = $patch->string_generate();
259            
260 15 100       89 if ($patch->{mode} eq "prepend") {
    100          
    100          
    100          
    100          
    50          
    0          
261 1         4 $self->{data} = $patchtext . $self->{data};
262              
263             } elsif ($patch->{mode} eq "append") {
264 5         12 $self->{data} .= $patchtext;
265              
266             } elsif ($patch->{mode} eq "replace") {
267 2         41 $self->patch_by_wedge($patch->regex(), $patch->text(), "replace");
268              
269             } elsif ($patch->{mode} eq "insert-before") {
270 4         79 $self->patch_by_wedge($patch->regex(), $patch->text(), "insert");
271              
272             } elsif ($patch->{mode} eq "insert-after") {
273 1         23 $self->patch_by_wedge($patch->regex(), $patch->text(), "insert", 1);
274              
275             } elsif ($patch->{mode} eq "update") {
276 2         40 $self->patch_update( $patch->key(), $patch->text() );
277              
278             } elsif ($patch->{mode} eq "comment_out") {
279 0         0 $self->patch_comment_out( $patch->key(), $patch->regex() );
280              
281             } else {
282 0         0 LOGDIE "Unknown mode '$patch->{mode}'";
283             }
284              
285 15         56 return 1;
286             }
287              
288             ###########################################
289             sub save {
290             ###########################################
291 34     34 1 48 my($self) = @_;
292              
293 34         98 $self->blurt($self->{data}, $self->{file});
294             }
295              
296             ###########################################
297             sub save_as {
298             ###########################################
299 0     0 1 0 my($self, $file) = @_;
300              
301 0 0       0 LOGDIE "No file defined" unless defined $file;
302 0         0 $self->{file} = $file;
303              
304 0         0 return $self->save();
305             }
306              
307             ###########################################
308             sub patched {
309             ###########################################
310 20     20 1 31 my($self, $key) = @_;
311              
312 20         49 my @patches = $self->parse();
313              
314 20 100       48 if( grep { $key eq $_->key() } @patches ) {
  2         88  
315 1         6 return 1;
316             }
317              
318 19         56 return 0;
319             }
320              
321             ###########################################
322             sub parse {
323             ###########################################
324 23     23 1 30 my($self) = @_;
325              
326 23         32 my @patches = ();
327              
328 23         91 $self->{forbidden_zones} = Set::IntSpan->new();
329              
330             $self->data_traverse( sub {
331 6     6   10 my($patcher, $patch) = @_;
332              
333 6         153 $patcher->{forbidden_zones} =
334             Set::IntSpan::union( $patcher->{forbidden_zones},
335             ($patch->pos_from() . "-" . $patch->pos_to()));
336              
337 6         6304 push @patches, $patch;
338 29     29   36 }, sub {
339 23         837 });
340              
341 23         125 $self->{parsed} = 1;
342              
343 23         50 return @patches;
344             }
345              
346             ###########################################
347             sub patch_update {
348             ###########################################
349 2     2 0 4 my($self, $key, $newvalue) = @_;
350              
351 2 50 33     13 if(length $newvalue and
352             substr($newvalue, -1, 1) ne "\n") {
353 2         3 $newvalue .= "\n";
354             }
355              
356 2         3 my $skew = 0;
357              
358 2         5 for my $hunk ( $self->parse() ) {
359 3 50       57 next if $hunk->key() ne $key;
360              
361 3         59 substr($self->{data}, $hunk->content_pos_from + $skew,
362             $hunk->content_pos_to - $hunk->content_pos_from)
363             = $newvalue;
364              
365 3         57 $skew += length($newvalue) - length($hunk->text());
366             }
367             }
368              
369             ###########################################
370             sub patches_only {
371             ###########################################
372 1     1 0 4 my($self) = @_;
373              
374 1         2 my $new_content = "";
375              
376 1         3 for my $hunk ( $self->parse() ) {
377 1         29 $new_content .= $hunk->as_string();
378             }
379              
380 1         6 $self->{data} = $new_content;
381             }
382              
383             ###########################################
384             sub patch_by_wedge {
385             ###########################################
386 20     20 0 39 my($self, $search, $replace, $mode, $after) = @_;
387              
388 20 100       56 if($self->patched( $self->{key} )) {
389 1         8 INFO "$mode cancelled: File already patched with key $self->{key}";
390 1         9 return undef;
391             }
392              
393 19 50       53 if(ref($search) ne "Regexp") {
394 0         0 LOGDIE "$mode search parameter not a regex {$search}";
395             }
396              
397 19 100 100     112 if(length $replace and
398             substr($replace, -1, 1) ne "\n") {
399 10         16 $replace .= "\n";
400             }
401              
402 19         32 my $data = $self->{data};
403              
404 19         66 my $positions = $self->full_line_match($data, $search);
405 19         34 my @pieces = ();
406 19         26 my $rest = $data;
407 19         29 my $offset = 0;
408              
409 19         78 my $patch = Config::Patch::Hunk->new(
410             comment_char => $self->{comment_char},
411             key => $self->{key},
412             mode => $mode,
413             );
414              
415 19         37 for my $pos (@$positions) {
416 21         40 my($from, $to) = @$pos;
417 21         21 my($before, $trail);
418 0         0 my $hide;
419 21 100       71 if ($mode eq "insert" ) {
    50          
420 8 100       17 if ($after) {
421 2         7 $before = substr($data, $offset, $to+1);
422 2         5 $rest = substr($data, $to+1);
423 2         4 $hide = "";
424 2         4 $trail = "";
425             } else {
426 6         13 $before = substr($data, $offset, $from-$offset);
427 6         10 $rest = substr($data, $to+1);
428 6         8 $hide = "";
429 6         12 $trail = substr($data, $from, $to - $from + 1);
430             }
431             } elsif ($mode eq "replace") {
432 13         25 $before = substr($data, $offset, $from-$offset);
433 13         25 $rest = substr($data, $to+1);
434              
435 13         53 $hide = $patch->replstring_hide(
436             substr($data, $from, $to - $from + 1));
437 13         52 $trail = "";
438             }
439              
440 21         577 $patch->text( $replace . $hide );
441 21         50 push @pieces, $before, $patch->string_generate(), $trail;
442 21         66 $offset = $to + 1;
443             }
444              
445 19         34 push @pieces, $rest;
446              
447 19         55 $self->{data} = join '', @pieces;
448              
449 19         135 return 1;
450             }
451              
452             ###########################################
453             sub full_line_match {
454             ###########################################
455 19     19 0 32 my($self, $string, $rex) = @_;
456              
457 19         100 DEBUG "Trying to match '$string' with /$rex/";
458              
459             # Try a regex match and if it succeeds, extend the match
460             # to cover the full first and last line. Return a ref to
461             # an array of from-to offsets of all (extended) matching
462             # regions.
463 19         170 my @positions = ();
464              
465 19         244 while($string =~ /($rex)/g) {
466 26         65 my $first = pos($string) - length($1);
467 26         39 my $last = pos($string) - 1;
468              
469 26         126 DEBUG "Found match at pos $first-$last ($1) pos=", pos($string);
470              
471             # Is this match located in any of the forbidden zones?
472 26         238 my $intersect = Set::IntSpan::intersect(
473             $self->{forbidden_zones}, "$first-$last");
474 26 100       2327 unless(Set::IntSpan::empty($intersect)) {
475 4         41 DEBUG "Match was in forbidden zone - skipped";
476 4         55 next;
477             }
478              
479             # Go back to the start of the line
480 22   100     276 while($first and
481             substr($string, $first, 1) ne "\n") {
482 35         135 $first--;
483             }
484 22 100       49 $first += 1 if $first;
485              
486             # Proceed until the end of the line
487 22   66     110 while($last < length($string) and
488             substr($string, $last, 1) ne "\n") {
489 31         121 $last++;
490             }
491              
492 22         100 DEBUG "Match positions corrected to $first-$last (line start/end)";
493              
494             # Ignore overlapping matches
495 22 100 100     177 if(@positions and $positions[-1]->[1] > $first) {
496 1         3 DEBUG "Detected overlap (two matches in same line) - skipped";
497 1         10 next;
498             }
499              
500 21         166 push @positions, [$first, $last];
501             }
502              
503 19         50 return \@positions;
504             }
505              
506             ###########################################
507             sub comment_out {
508             ###########################################
509 1     1 0 13 my($self, $search) = @_;
510              
511             # Same as "replace by nothing"
512 1         5 return $self->replace($search, "");
513             }
514              
515             ###########################################
516             sub eject {
517             ###########################################
518 19     19 1 759 my($self, $key) = @_;
519              
520 19 50       56 $self->read() unless $self->{read};
521              
522             # We accept a hunk instead of a key as well
523 19 100       49 if(ref $key eq __PACKAGE__ . "::Hunk") {
524 1         26 $key = $key->key();
525             }
526              
527 19 100       54 $key = $self->{key} unless defined $key;
528              
529 19         26 my $new_content = "";
530              
531             $self->data_traverse( sub {
532 21     21   52 my($patcher, $patch) = @_;
533              
534 21         566 DEBUG "Remove: '", $patch->text(), "' (",
535             $patch->pos_from(), "-", $patch->pos_to();
536              
537 21 100       651 if($patch->key() eq $key) {
538 20 100       531 if($patch->mode() eq "replace") {
539             # We've got a replace section, extract its
540             # hidden content and re-establish it
541 9         39 my($hidden, $stripped) = $patch->replstring_extract();
542 9         22 $new_content .= $hidden;
543             } else {
544             # Replace by nothing
545             }
546             } else {
547             # This isn't our patch
548 1         24 $new_content .= $patch->header() .
549             $patch->text() .
550             $patch->header();
551             }
552             }, sub {
553 32     32   52 my($patcher, $text) = @_;
554 32         55 $new_content .= $text;
555 19         151 });
556              
557 19         160 $self->{data} = $new_content;
558             }
559              
560             ###########################################
561             sub data_traverse {
562             ###########################################
563 46     46 0 75 my($self, $patch_cb, $text_cb) = @_;
564              
565 46         49 my $in_patch = 0;
566 46         48 my $patch_text = "";
567 46         58 my $text = "";
568 46         46 my $start_pos;
569             my $end_pos;
570 46         51 my $pos = 0;
571 46         49 my $header;
572              
573 46         214 for my $line (split /\n/, $self->{data}) {
574 304         480 $_ = "$line\n";
575              
576 304         357 $pos += length($_);
577 304 100 100     1221 $patch_text .= $_ if $in_patch and $_ !~ $self->{patch_regex};
578              
579             # text line?
580 304 100 66     1428 if($_ !~ $self->{patch_regex} and !$in_patch) {
581 130         175 $text .= $_;
582             }
583              
584             # closing line of patch
585 304 100 100     1281 if($_ =~ $self->{patch_regex} and
586             $in_patch) {
587 33         46 $end_pos = $pos - 1;
588              
589 33         272 my $patch_obj = Config::Patch::Hunk->new(
590             comment_char => $self->{comment_char},
591             key => $1,
592             mode => $2,
593             text => $patch_text,
594             pos_from => $start_pos,
595             pos_to => $end_pos,
596             header => $header,
597             content_pos_from => $start_pos + length($&) + 1,
598             content_pos_to => $end_pos - length($&),
599             as_string => substr( $self->{data}, $start_pos,
600             $end_pos - $start_pos + 1 ),
601             );
602              
603 33         91 $patch_cb->($self, $patch_obj);
604 33         114 $patch_text = "";
605             }
606              
607             # toggle flag
608 304 100       1182 if($_ =~ $self->{patch_regex}) {
609 66 100       126 if($in_patch) {
610             # End line
611             } else {
612             # Start Line
613 33         75 $text_cb->($self, $text);
614 33         40 $start_pos = $pos - length $_;
615 33         50 $header = $_;
616             }
617 66         79 $text = "";
618 66   50     306 $in_patch = ($in_patch xor 1);
619             }
620             }
621              
622 46 100       174 $text_cb->($self, $text) if length $text;
623              
624 46         82 return 1;
625             }
626              
627             ###############################################
628             sub blurt {
629             ###############################################
630 51     51 0 6677 my($self, $data, $file) = @_;
631              
632 51 50       42875 open FILE, ">$file" or LOGDIE "Cannot open $file ($!)";
633 51         424 print FILE $data;
634 51         2409 close FILE;
635             }
636              
637             ###############################################
638             sub slurp {
639             ###############################################
640 46     46 0 1602 my($self, $file) = @_;
641              
642 46         174 local $/ = undef;
643 46 50       1392 open FILE, "<$file" or LOGDIE "Cannot open $file ($!)";
644 46         867 my $data = ;
645 46         421 close FILE;
646              
647 46         217 return $data;
648             }
649              
650             ###########################################
651             sub lock {
652             ###########################################
653 33     33 0 46 my($self) = @_;
654              
655             # Ignore if locking wasn't requested
656 33 50       113 return if ! $self->{flock};
657              
658             # Already locked?
659 0 0       0 if($self->{locked}) {
660 0         0 $self->{locked}++;
661 0         0 return 1;
662             }
663              
664 0 0       0 open my $fh, "+<$self->{file}" or
665             LOGDIE "Cannot open $self->{file} ($!)";
666              
667 0         0 flock($fh, LOCK_EX);
668              
669 0         0 $self->{fh} = $fh;
670              
671 0         0 $self->{locked} = 1;
672             }
673              
674             ###########################################
675             sub unlock {
676             ###########################################
677 33     33 0 48 my($self) = @_;
678              
679             # Ignore if locking wasn't requested
680 33 50       139 return if ! $self->{flock};
681              
682 0 0       0 if(! $self->{locked}) {
683             # Not locked?
684 0         0 return 1;
685             }
686              
687 0 0       0 if($self->{locked} > 1) {
688             # Multiple lock released?
689 0         0 $self->{locked}--;
690 0         0 return 1;
691             }
692              
693             # Release the last lock
694 0         0 flock($self->{fh}, LOCK_UN);
695 0         0 $self->{locked} = undef;
696 0         0 1;
697             }
698              
699             # LEGACY METHODS
700              
701             ###########################################
702             sub patches {
703             ###########################################
704 4     4 1 20 my($self) = @_;
705              
706             # LEGACY METHOD, DON'T USE
707              
708 4         9 my @patches = ();
709 4         7 my %patches = ();
710              
711             $self->data_traverse(
712 6     6   12 sub { my($patcher, $patch) = @_;
713 6         148 push @patches,
714             [$patch->key(),
715             $patch->mode(),
716             $patch->text(),
717             $patch->pos_from(),
718             $patch->pos_to(),
719             $patch->header(),
720             $patch->content_pos_from(),
721             $patch->content_pos_to(),
722             ];
723 6         142 $patches{ $patch->key() }++;
724             },
725 6     6   8 sub { },
726 4         32 );
727              
728 4         33 return \@patches, \%patches;
729             }
730              
731             ###########################################
732             sub prepend {
733             ###########################################
734 1     1 1 7 my($self, $text) = @_;
735              
736             # LEGACY METHOD, DON'T USE
737              
738 1         4 $self->lock();
739 1 50       8 $self->read() unless $self->{read};
740              
741 1         5 $self->patch_by_stretch( $text, "prepend" );
742              
743 1         4 $self->save();
744 1         6 $self->unlock();
745 1         3 return 1;
746             }
747              
748             ###########################################
749             sub append {
750             ###########################################
751 5     5 1 26 my($self, $text) = @_;
752              
753             # LEGACY METHOD, DON'T USE
754              
755 5         12 $self->lock();
756 5 100       17 $self->read() unless $self->{read};
757              
758 5         13 $self->patch_by_stretch( $text, "append" );
759              
760 5         13 $self->save();
761 5         13 $self->unlock();
762 5         9 return 1;
763             }
764              
765             ###########################################
766             sub replace {
767             ###########################################
768 11     11 1 104 my($self, $search, $patchtext) = @_;
769              
770             # LEGACY METHOD, DON'T USE
771              
772 11         46 $self->lock();
773 11 100       44 $self->read() unless $self->{read};
774              
775 11         34 $self->patch_by_wedge($search, $patchtext, "replace");
776              
777 11         33 $self->save();
778 11         35 $self->unlock();
779             }
780              
781             ###########################################
782             sub insert {
783             ###########################################
784 2     2 0 29 my($self, $search, $data, $after) = @_;
785              
786             # LEGACY METHOD, DON'T USE
787              
788 2         6 $self->lock();
789 2 50       12 $self->read() unless $self->{read};
790              
791 2         9 $self->patch_by_wedge($search, $data, "insert", $after);
792              
793 2         6 $self->save();
794 2         6 $self->unlock();
795             }
796              
797             ###########################################
798             sub remove {
799             ###########################################
800 14     14 0 6544 my($self, $key) = @_;
801              
802             # LEGACY METHOD, DON'T USE
803              
804 14         36 $self->lock();
805 14 100       49 $self->read() unless $self->{read};
806              
807 14         41 $self->eject( $key );
808              
809 14         37 $self->save();
810 14         40 $self->unlock();
811             }
812              
813             1;
814              
815             __END__