1
package Padre::Wx::Dialog::Patch;
8
use Padre::Wx::FBP::Patch ();
11
# use Data::Printer { caller_info => 1 };
13
our $VERSION = '0.92';
24
my $self = $class->SUPER::new(@_);
26
$self->CenterOnParent;
28
$self->{action_request} = 'Patch';
29
$self->{selection} = 0;
39
my $current = $self->current;
44
# TODO but I want nonModal, ie $self->Show;
46
my $result = $self->ShowModal;
48
if ( $result == Wx::ID_CANCEL ) {
50
# As we leave the Find dialog, return the user to the current editor
51
# window so they don't need to click it.
52
my $editor = $current->editor;
53
$editor->SetFocus if $editor;
70
# test for local svn_local
73
# generate open file bucket
74
$self->current_files();
76
# display default saved file lists
77
$self->file_lists_saved();
79
# display correct file-2 list
80
$self->file2_list_type();
82
$self->against->SetSelection(0);
88
# Event Handler process_clicked
93
my $file1 = @{ $self->{file1_list_ref} }[ $self->file1->GetSelection() ];
94
my $file2 = @{ $self->{file2_list_ref} }[ $self->file2->GetCurrentSelection() ];
96
TRACE( '$self->file1->GetSelection(): ' . $self->file1->GetSelection() ) if DEBUG;
97
TRACE( '$file1: ' . $file1 ) if DEBUG;
98
TRACE( '$self->file2->GetCurrentSelection(): ' . $self->file2->GetCurrentSelection() ) if DEBUG;
99
TRACE( '$file2: ' . $file2 ) if DEBUG;
100
TRACE( $self->action->GetStringSelection() ) if DEBUG;
102
if ( $self->action->GetStringSelection() eq 'Patch' ) {
103
$self->apply_patch( $file1, $file2 );
106
if ( $self->action->GetStringSelection() eq 'Diff' ) {
107
if ( $self->against->GetStringSelection() eq 'File-2' ) {
108
$self->make_patch_diff( $file1, $file2 );
109
} elsif ( $self->against->GetStringSelection() eq 'SVN' ) {
110
$self->make_patch_svn($file1);
114
# reset dialogue's display information
121
# Event Handler on_action
126
# re-generate open file bucket
127
$self->current_files();
129
if ( $self->action->GetStringSelection() eq 'Patch' ) {
131
$self->{action_request} = 'Patch';
133
$self->against->Enable(0);
134
$self->file2->Enable(1);
137
$self->{action_request} = 'Diff';
139
$self->against->Enable(1);
140
$self->file2->Enable(1);
142
# as we can not added items to a radio-box,
143
# we can only enable & disable when radio-box enabled
144
unless ( $self->{svn_local} ) {
145
$self->against->EnableItem( 1, 0 );
147
$self->against->SetSelection(0);
154
# Event Handler on_against
159
if ( $self->against->GetStringSelection() eq 'File-2' ) {
161
# show saved files only
162
$self->file2->Enable(1);
163
$self->file_lists_saved();
165
} elsif ( $self->against->GetStringSelection() eq 'SVN' ) {
167
# SVN only display files that are part of a SVN
168
$self->file2->Enable(0);
169
$self->file1_list_svn();
176
# Method current_files
180
my $main = $self->main;
181
my $current = $main->current;
182
my $notebook = $current->notebook;
183
my @label = $notebook->labels;
185
# get last element # not size
186
$self->{tab_cardinality} = $#label;
189
my @file_vcs = map { $_->project->vcs } $self->main->documents;
191
# create a bucket for open file info, as only a current file bucket exist
192
for ( 0 .. $self->{tab_cardinality} ) {
193
$self->{open_file_info}->{$_} = (
195
'URL' => $label[$_][1],
196
'filename' => $notebook->GetPageText($_),
198
'vcs' => $file_vcs[$_],
202
if ( $notebook->GetPageText($_) =~ /^\*/sxm ) {
203
TRACE("Found an unsaved file, will ignore: $notebook->GetPageText($_)") if DEBUG;
204
$self->{open_file_info}->{$_}->{'changed'} = 1;
208
# nb enable Data::Printer above to use
209
# p $self->{open_file_info};
215
# Composed Method file2_list_type
217
sub file2_list_type {
220
if ( $self->{action_request} eq 'Patch' ) {
222
# update File-2 = *.patch
223
$self->file2_list_patch();
226
# File-1 = File-2 = saved files
227
$self->file_lists_saved();
234
# Composed Method file_lists_saved
236
sub file_lists_saved {
238
my @file_lists_saved;
239
for ( 0 .. $self->{tab_cardinality} ) {
240
unless ( $self->{open_file_info}->{$_}->{'changed'}
241
|| $self->{open_file_info}->{$_}->{'filename'} =~ /(patch|diff)$/sxm )
243
push @file_lists_saved, $self->{open_file_info}->{$_}->{'filename'};
247
TRACE("file_lists_saved: @file_lists_saved") if DEBUG;
250
$self->file1->Append( \@file_lists_saved );
251
$self->{file1_list_ref} = \@file_lists_saved;
252
$self->set_selection_file1();
253
$self->file1->SetSelection( $self->{selection} );
256
$self->file2->Append( \@file_lists_saved );
257
$self->{file2_list_ref} = \@file_lists_saved;
258
$self->set_selection_file2();
259
$self->file2->SetSelection( $self->{selection} );
265
# Composed Method file2_list_patch
267
sub file2_list_patch {
270
my @file2_list_patch;
271
for ( 0 .. $self->{tab_cardinality} ) {
272
if ( $self->{open_file_info}->{$_}->{'filename'} =~ /(patch|diff)$/sxm ) {
273
push @file2_list_patch, $self->{open_file_info}->{$_}->{'filename'};
277
TRACE("file2_list_patch: @file2_list_patch") if DEBUG;
280
$self->file2->Append( \@file2_list_patch );
281
$self->{file2_list_ref} = \@file2_list_patch;
282
$self->set_selection_file2();
283
$self->file2->SetSelection( $self->{selection} );
289
# Composed Method file1_list_svn
294
@{ $self->{file1_list_ref} } = ();
295
for ( 0 .. $self->{tab_cardinality} ) {
296
if ( ( $self->{open_file_info}->{$_}->{'vcs'} eq 'SVN' )
297
&& !( $self->{open_file_info}->{$_}->{'changed'} )
298
&& !( $self->{open_file_info}->{$_}->{'filename'} =~ /(patch|diff)$/sxm ) )
300
push @{ $self->{file1_list_ref} }, $self->{open_file_info}->{$_}->{'filename'};
304
TRACE("file1_list_svn: @{ $self->{file1_list_ref} }") if DEBUG;
307
$self->file1->Append( $self->{file1_list_ref} );
308
$self->set_selection_file1();
309
$self->file1->SetSelection( $self->{selection} );
315
# Composed Method set_selection_file1
317
sub set_selection_file1 {
319
my $main = $self->main;
321
$self->{selection} = 0;
322
if ( $main->current->title =~ /(patch|diff)$/sxm ) {
324
my @pathch_target = split( /\./, $main->current->title, 2 );
326
# TODO this is a padre internal issue
327
# remove obtuse leading space if exists
328
$pathch_target[0] =~ s/^\p{Space}{1}//;
329
TRACE("Looking for File-1 to apply a patch to: $pathch_target[0]") if DEBUG;
331
# SetSelection should be Patch target file
332
foreach ( 0 .. $#{ $self->{file1_list_ref} } ) {
334
# add optional leading space \p{Space}?
335
if ( @{ $self->{file1_list_ref} }[$_] =~ /^\p{Space}?$pathch_target[0]/ ) {
336
$self->{selection} = $_;
342
# SetSelection should be current file
343
foreach ( 0 .. $#{ $self->{file1_list_ref} } ) {
345
if ( @{ $self->{file1_list_ref} }[$_] eq $main->current->title ) {
346
$self->{selection} = $_;
356
# Composed Method set_selection_file2
358
sub set_selection_file2 {
360
my $main = $self->main;
362
$self->{selection} = 0;
364
# SetSelection should be current file
365
foreach ( 0 .. $#{ $self->{file2_list_ref} } ) {
367
if ( @{ $self->{file2_list_ref} }[$_] eq $main->current->title ) {
368
$self->{selection} = $_;
377
# Composed Method filename_url
381
my $filename = shift;
383
# given tab name get url of file
384
for ( 0 .. $self->{tab_cardinality} ) {
385
if ( $self->{open_file_info}->{$_}->{'filename'} eq $filename ) {
386
return $self->{open_file_info}->{$_}->{'URL'};
397
my $file1_name = shift;
398
my $file2_name = shift;
399
my $main = $self->main;
401
$main->show_output(1);
402
my $output = $main->output;
405
my ( $source, $diff );
407
my $file1_url = $self->filename_url($file1_name);
408
my $file2_url = $self->filename_url($file2_name);
410
if ( -e $file1_url ) {
411
TRACE("found file1 => $file1_name: $file1_url") if DEBUG;
412
$source = File::Slurp::read_file($file1_url);
415
if ( -e $file2_url ) {
416
TRACE("found file2 => $file2_name: $file2_url") if DEBUG;
417
$diff = File::Slurp::read_file($file2_url);
418
unless ( $file2_url =~ /(patch|diff)$/sxm ) {
419
$main->info( Wx::gettext('Patch file should end in .patch or .diff, you should reselect & try again') );
424
if ( -e $file1_url && -e $file2_url ) {
428
if ( eval { $our_patch = Text::Patch::patch( $source, $diff, { STYLE => 'Unified' } ) } ) {
430
TRACE($our_patch) if DEBUG;
432
# Open the patched file as a new file
433
$main->new_document_from_string( $our_patch => 'application/x-perl', );
434
$main->info( Wx::gettext('Patch successful, you should see a new tab in editor called Unsaved #') );
436
TRACE("error trying to patch: $@") if DEBUG;
438
$output->AppendText("Patch Dialog failed to Complete.\n");
439
$output->AppendText("Your requested Action Patch, with following parameters.\n");
440
$output->AppendText("File-1: $file1_url \n");
441
$output->AppendText("File-2: $file2_url \n");
442
$output->AppendText("What follows is the error I received from Text::Patch::patch, if any: \n");
443
$output->AppendText($@);
446
Wx::gettext('Sorry, patch failed, are you sure your choice of files was correct for this action') );
455
# Method make_patch_diff
457
sub make_patch_diff {
459
my $file1_name = shift;
460
my $file2_name = shift;
461
my $main = $self->main;
463
$main->show_output(1);
464
my $output = $main->output;
467
my $file1_url = $self->filename_url($file1_name);
468
my $file2_url = $self->filename_url($file2_name);
470
if ( -e $file1_url ) {
471
TRACE("found file1 => $file1_name: $file1_url") if DEBUG;
474
if ( -e $file2_url ) {
475
TRACE("found file2 => $file2_name: $file2_url") if DEBUG;
478
if ( -e $file1_url && -e $file2_url ) {
481
if ( eval { $our_diff = Text::Diff::diff( $file1_url, $file2_url, { STYLE => 'Unified' } ) } ) {
482
TRACE($our_diff) if DEBUG;
484
my $patch_file = $file1_url . '.patch';
486
File::Slurp::write_file( $patch_file, $our_diff );
487
TRACE("writing file: $patch_file") if DEBUG;
489
$main->setup_editor($patch_file);
490
$main->info( sprintf(Wx::gettext('Diff successful, you should see a new tab in editor called %s'), $patch_file) );
492
TRACE("error trying to patch: $@") if DEBUG;
494
$output->AppendText("Patch Dialog failed to Complete.\n");
495
$output->AppendText("Your requested Action Diff, with following parameters.\n");
496
$output->AppendText("File-1: $file1_url \n");
497
$output->AppendText("File-2: $file2_url \n");
498
$output->AppendText("What follows is the error I received from Text::Diff::diff, if any: \n");
499
$output->AppendText($@);
502
Wx::gettext('Sorry Diff Failed, are you sure your choice of files was correct for this action') );
511
# Composed Method test_svn
515
my $main = $self->main;
517
$self->{svn_local} = 0;
519
my $svn_client_version = 0;
520
my $required_svn_version = '1.6.2';
522
if ( File::Which::which('svn') ) {
525
if ( $svn_client_version = Padre::Util::run_in_directory_two('svn --version --quiet') ) {
526
chomp($svn_client_version);
528
require Sort::Versions;
530
# This is so much better, now we are testing for version as well
531
if ( Sort::Versions::versioncmp( $required_svn_version, $svn_client_version, ) == -1 ) {
532
TRACE("Found local SVN v$svn_client_version, good to go.") if DEBUG;
533
$self->{svn_local} = 1;
536
TRACE("Found SVN v$svn_client_version but require v$required_svn_version") if DEBUG;
539
Wx::gettext('Warning: found SVN v%s but we require SVN v%s and it is now called "Apache Subversion"'),
541
$required_svn_version
551
# Method make_patch_svn
552
# inspired by P-P-SVN
556
my $file1_name = shift;
557
my $main = $self->main;
559
$main->show_output(1);
560
my $output = $main->output;
563
my $file1_url = $self->filename_url($file1_name);
565
TRACE("file1_url to svn: $file1_url") if DEBUG;
568
if ( $self->{svn_local} ) {
569
TRACE('found local SVN, Good to go') if DEBUG;
571
if ( eval { $diff_str = qx{ svn diff $file1_url} } ) {
573
TRACE($diff_str) if DEBUG;
575
my $patch_file = $file1_url . '.patch';
577
File::Slurp::write_file( $patch_file, $diff_str );
578
TRACE("writing file: $patch_file") if DEBUG;
580
$main->setup_editor($patch_file);
581
$main->info( sprintf(Wx::gettext('SVN Diff successful. You should see a new tab in editor called %s.'), $patch_file) );
583
TRACE("Error trying to get an SVN Diff: $@") if DEBUG;
585
$output->AppendText("Patch Dialog failed to Complete.\n");
586
$output->AppendText("Your requested Action Diff against SVN, with following parameters.\n");
587
$output->AppendText("File-1: $file1_url \n");
588
$output->AppendText("What follows is the error I received from SVN, if any: \n");
590
$output->AppendText($@);
593
"Sorry, Diff to SVN failed. There are any diffrences in this file: $file1_name");
597
Wx::gettext('Sorry, Diff failed. Are you sure your have access to the repository for this action') );
610
Padre::Wx::Dialog::Patch - The Padre Patch dialog
614
You will find more infomation in our L<wiki|http://padre.perlide.org/trac/wiki/Features/EditPatch/> pages.
616
A very simplistic tool, only works on open saved files, in the Padre editor.
618
Patch a single file, in the editor with a patch/diff file that is also open.
620
Diff between two open files, the resulting patch file will be in Unified form.
622
Diff a single file to svn, only display files that are part of an SVN already, the resulting patch file will be in Unified form.
624
All results will be a new Tab.
630
Constructor. Should be called with C<$main> by C<Patch::load_dialog_main()>.
634
C<run> configures the dialogue for your environment
638
C<set_up> configures the dialogue for your environment
642
Event handler for action, adjust dialogue accordingly
646
Event handler for against, adjust dialogue accordingly
648
=head2 process_clicked
650
Event handler for process_clicked, perform your chosen action, all results go into a new tab in editor.
654
extracts file info from Padre about all open files in editor
658
A convenience method to apply patch to chosen file.
662
=head2 make_patch_diff
664
A convenience method to generate a patch/diff file from two selected files.
670
test for a local copy of svn in Path and version greater than 1.6.2.
672
=head2 make_patch_svn
674
A convenience method to generate a patch/diff file from a selected file and svn if applicable,
675
ie file has been checked out.
677
=head2 file2_list_type
685
=head2 set_selection_file1
689
=head2 set_selection_file2
693
=head2 file1_list_svn
697
=head2 file2_list_patch
701
=head2 file_lists_saved
705
=head1 BUGS AND LIMITATIONS
707
List Order is that of load order, if you move your Tabs the List Order will not follow suite.
709
If you have multiple files open with same name but with different paths only the first will get matched.
713
BOWTIE E<lt>kevin.dawson@btclick.comE<gt>
715
Adam Kennedy E<lt>adamk@cpan.orgE<gt>
717
=head1 LICENSE AND COPYRIGHT
719
Copyright 2008-2011 The Padre development team as listed in Padre.pm.
721
This program is free software; you can redistribute
722
it and/or modify it under the same terms as Perl 5 itself.
724
The full text of the license can be found in the
725
LICENSE file included with this module.
729
# Copyright 2008-2011 The Padre development team as listed in Padre.pm.
731
# This program is free software; you can redistribute it and/or
732
# modify it under the same terms as Perl 5 itself.