File Coverage

blib/lib/TiddlyWeb/EditPage.pm
Criterion Covered Total %
statement 18 114 15.7
branch 0 28 0.0
condition 0 15 0.0
subroutine 6 14 42.8
pod 3 3 100.0
total 27 174 15.5


line stmt bran cond sub pod time code
1             package TiddlyWeb::EditPage;
2 1     1   1945 use warnings;
  1         4  
  1         39  
3 1     1   7 use strict;
  1         2  
  1         42  
4 1     1   6 use Carp qw/croak/;
  1         2  
  1         61  
5 1     1   2189 use File::Temp;
  1         29520  
  1         95  
6 1     1   8 use JSON::XS;
  1         2  
  1         56  
7 1     1   5 use Encode;
  1         2  
  1         1758  
8              
9             =head1 NAME
10              
11             TiddlyWeb::EditPage - Edit a wiki page using your favourite EDITOR.
12              
13             =cut
14              
15             our $VERSION = '0.04';
16              
17             =head1 SYNOPSIS
18              
19             Fetch a page, edit it, and then post it.
20              
21             use TiddlyWeb::EditPage;
22              
23             # The rester is set with the server and workspace
24             my $rester = TiddlyWeb::Resting->new(%opts);
25              
26             my $s = TiddlyWeb::EditPage->new(rester => $rester);
27             $s->edit_page('Snakes on a Plane');
28              
29             =head1 FUNCTIONS
30              
31             =head2 new( %opts )
32              
33             Arguments:
34              
35             =over 4
36              
37             =item rester
38              
39             Users must provide a TiddlyWeb::Resting object setup to use the desired
40             workspace and server.
41              
42             =item pull_includes
43              
44             If true, C wafls will be inlined into the page as extraclude
45             blocks.
46              
47             =back
48              
49             =cut
50              
51             sub new {
52 0     0 1   my ($class, %opts) = @_;
53 0           my $self = { %opts };
54 0           bless $self, $class;
55 0           return $self;
56             }
57              
58             =head2 C
59              
60             This method will fetch the page content, and then run $EDITOR on the file.
61             After the file has been edited, it will be put back on the wiki server.
62              
63             Arguments:
64              
65             =over 4
66              
67             =item page
68              
69             The name of the page you wish to edit.
70              
71             =item callback
72              
73             If supplied, callback will be called after the page has been edited. This
74             function will be passed the edited content, and should return the content to
75             be put onto the server.
76              
77             =item summary_callback
78              
79             If supplied, callback will be called after the page has been edit. This
80             function should return the edit summary text for this edit, if desired.
81              
82             =item tags
83              
84             If supplied, these tags will be applied to the page after it is updated.
85              
86             =item output
87              
88             If supplied, the page will be saved to the given file instead of edited.
89             The page will not be uploaded to the server.
90              
91             =item template
92              
93             If specified, this page will be used as the template for a new page.
94              
95             =item line
96              
97             If specified, the editor will be sent to this line to begin editing.
98              
99             =back
100              
101             =cut
102              
103             sub edit_page {
104 0     0 1   my $self = shift;
105 0           my %args = @_;
106 0           my $page = $self->{page} = delete $args{page};
107 0 0         croak "page is mandatory" unless $page;
108              
109 0           my $rester = $self->{rester};
110 0           my $content = $self->_get_page($page);
111              
112 0           my $orig_content = $content->{text};
113 0           my $orig_tags = $content->{tags};
114 0           my $bag = $content->{bag};
115 0           my $orig_fields = $content->{fields};
116 0           while (1) {
117 0           my $new_content = $orig_content;
118 0           $new_content = $self->_edit_content($new_content);
119              
120 0 0         if ($orig_content eq $new_content) {
121 0           print "$page did not change.\n";
122 0           return;
123             }
124              
125 0 0         $new_content = $args{callback}->($new_content) if $args{callback};
126              
127 0           eval {
128 0           $rester->put_page($page, {
129             text => $new_content,
130             tags => $orig_tags,
131             fields => $orig_fields,
132             bag => $bag,
133             }
134             );
135             };
136 0 0         last unless $@;
137 0 0         if ($@ =~ /412/) { # collision detected!
138 0           print "$@\nA collision was detected. I will merge the changes and "
139             . "re-open your editor.\nHit enter.\n";
140 0           sleep 2;
141 0           print "Merging...\n";
142 0           $orig_content = $self->_get_page($page);
143 0           my $updated_file = _write_file(undef, $orig_content);
144 0           my $orig_file = _write_file(undef, $content);
145 0           my $our_file = _write_file(undef, $new_content);
146              
147             # merge the content and re-edit
148             # XXX: STDERR is not redirected. Should use IPC::Run. However,
149             # it's nice to be able to create pages w/ quotes and other shell
150             # characters in their name.
151 0           system(qw(merge -L yours -L original -L), "new edit", $our_file,
152             $orig_file, $updated_file);
153              
154 0           $content = _read_file($our_file);
155             }
156             else {
157 0           $self->_handle_error($@, $page, $new_content);
158             }
159             }
160              
161 0           print "Updated page $page\n";
162             }
163              
164             =head2 C
165              
166             This method will retrieve a last of all pages tagged with the supplied
167             tag, and then open the latest one for edit.
168              
169             Arguments are passed through to edit_page(), accept for:
170              
171             =over 4
172              
173             =item tag
174              
175             The name of the tag you wish to edit.
176              
177             =back
178              
179             =cut
180              
181             sub edit_last_page {
182 0     0 1   my $self = shift;
183 0           my %opts = @_;
184              
185 0   0       my $tag = delete $opts{tag} || croak "tag is mandatory";
186 0           my $rester = $self->{rester};
187 0           $rester->accept('application/json');
188 0           my $pages = decode_json($rester->get_taggedpages($tag));
189 0 0         unless (@$pages) {
190 0           die "No pages found tagged '$tag'\n";
191             }
192 0           my @pages = sort { $b->{modified_time} <=> $a->{modified_time} }
  0            
193             @$pages;
194 0           my $newest_page = shift @pages;
195 0           print "Editing '$newest_page->{name}'\n";
196 0           $self->edit_page(page => $newest_page->{page_id}, %opts);
197             }
198              
199             sub _get_page {
200 0     0     my $self = shift;
201 0           my $page_name = shift;
202 0           my $rester = $self->{rester};
203 0           $rester->accept('perl_hash');
204              
205 0           my $page = $rester->get_page($page_name);
206              
207 0           return $page;
208             }
209              
210             sub _edit_content {
211 0     0     my $self = shift;
212 0           my $content = shift;
213              
214 0   0       my $workspace = $self->{rester}->workspace || '';
215 0           (my $page = $self->{page}) =~ s#/#_#g;
216 0           my $filename = File::Temp->new(
217             TEMPLATE => "$workspace.$page.XXXX",
218             SUFFIX => '.wiki'
219             );
220 0           _write_file($filename, $content);
221 0   0       my $editor = $ENV{EDITOR} || '/usr/bin/vim';
222              
223 0 0 0       if (defined $self->{command} and $editor =~ /vi/) {
224 0           my $c = $self->{command};
225 0 0         if ($c eq 'o') {
    0          
    0          
    0          
226 0           system $editor, "+normal gg$self->{line}Go", "+startinsert", $filename;
227             }
228             elsif ($c eq 'i') {
229 0           system $editor, "+normal gg$self->{line}G$self->{col}|", "+startinsert", $filename;
230             }
231             elsif ($c eq 'a') {
232 0           system $editor, "+normal gg$self->{line}G$self->{col}|l", "+startinsert", $filename;
233             }
234             elsif ($c eq 'A') {
235 0           system $editor, "+normal gg$self->{line}G", "+startinsert!", $filename;
236             }
237             else {
238 0           system( $editor, $filename );
239             }
240             } else {
241 0           system( $editor, $filename );
242             }
243              
244 0           return _read_file($filename);
245             }
246              
247             sub _handle_error {
248 0     0     my ($self, $err, $page, $content) = @_;
249 0           my $file = $page . ".sav";
250 0           my $i = 0;
251 0           while (-f $file) {
252 0           $i++;
253 0           $file =~ s/\.sav(?:\.\d+)?$/\.sav\.$i/;
254             }
255 0           warn "Failed to write '$page', saving to $file\n";
256 0           _write_file($file, $content);
257 0           die "wrote backup to: $file\n$err\n";
258             }
259              
260             sub _write_file {
261 0     0     my ($filename, $content) = @_;
262 0   0       $filename ||= File::Temp->new( SUFFIX => '.wiki' );
263 0 0         open(my $fh, ">$filename") or die "Can't open $filename: $!";
264 0   0       print $fh $content || '';
265 0 0         close $fh or die "Can't write $filename: $!";
266 0           return $filename;
267             }
268              
269             sub _read_file {
270 0     0     my $filename = shift;
271 0 0         open(my $fh, $filename) or die "unable to open $filename $!\n";
272 0           my $new_content;
273             {
274 0           local $/;
  0            
275 0           $new_content = <$fh>;
276             }
277 0           close $fh;
278 0           $new_content = decode("UTF-8", $new_content);
279 0           return $new_content;
280             }
281              
282             =head1 AUTHOR
283              
284             Luke Closs, C<< >>
285              
286             =head1 BUGS
287              
288             Please report any bugs or feature requests to
289             C, or through the web interface at
290             L.
291             I will be notified, and then you'll automatically be notified of progress on
292             your bug as I make changes.
293              
294             =head1 SUPPORT
295              
296             You can find documentation for this module with the perldoc command.
297              
298             perldoc TiddlyWeb::EditPage
299              
300             You can also look for information at:
301              
302             =over 4
303              
304             =item * AnnoCPAN: Annotated CPAN documentation
305              
306             L
307              
308             =item * CPAN Ratings
309              
310             L
311              
312             =item * RT: CPAN's request tracker
313              
314             L
315              
316             =item * Search CPAN
317              
318             L
319              
320             =back
321              
322             =head1 ACKNOWLEDGEMENTS
323              
324             =head1 COPYRIGHT & LICENSE
325              
326             Copyright 2006 Luke Closs, all rights reserved.
327              
328             This program is free software; you can redistribute it and/or modify it
329             under the same terms as Perl itself.
330              
331             =cut
332              
333             1;