Grammar error
[tpope-extra.git] / perl / blogcl
1 #!/usr/bin/perl
2 # $Id$
3 # -*- perl -*- vim: ft=perl sw=4 sts=4
4
5 # For documentation, run perldoc blogcl
6
7 use strict;
8 use Net::MovableType;
9 use Getopt::Long;
10 use File::Temp ();
11 use Term::Complete;
12 use vars qw(%opts $mt);
13
14 if (-r $ENV{HOME} . "/.blogclrc") {
15     open CONFIG, $ENV{HOME} . "/.blogclrc";
16     while(<CONFIG>) {
17         s/\#.*//;
18         next unless m/^([^=]*)=(.*)/;
19         $opts{$1}=$2;
20     }
21     close CONFIG;
22 }
23
24 Getopt::Long::Configure ("bundling", "auto_help");
25 die "Invalid arguments\n" unless
26 GetOptions (\%opts, 'username|l=s', 'password|p=s', 'url|u=s', 'blogid|b=i', 'title|t=s', 'category|c=s', 'file|f=s');
27
28 sub init_mt {
29     die "No url given.\n" unless(defined($opts{url}));
30     die "No username given.\n" unless(defined($opts{username}));
31     die "No passowrd given.\n" unless(defined($opts{password}));
32     $mt = new Net::MovableType($opts{'url'}) || die "$!";
33     $mt->username($opts{'username'});
34     $mt->password($opts{'password'});
35     my $blogid = $opts{'blogid'} || $mt->resolveBlogId($opts{'username'});
36     unless($blogid) {
37         my $userblogs=$mt->getUsersBlogs();
38         if($userblogs && $userblogs->[0]->{blogid}) {
39             $blogid=$userblogs->[0]->{'blogid'};
40         } else {
41             warn "Blog ID unknown.\n";
42         }
43     }
44     $mt->blogId($blogid);
45 }
46
47 sub print_recent {
48     my $entries = $mt->getRecentPosts(shift || 10);
49     while ( my $entry = shift @$entries ) {
50         printf("[%02d] - %s\n",
51             $entry->{postid}, $entry->{title} );
52     }
53 #   postid userid content title description dateCreated
54 }
55
56 sub print_categories {
57     print join("\n",map {$_->{categoryName}} @{$mt->getCategoryList()}), "\n";
58 }
59
60 sub delete_post {
61     my $postid=shift;
62     my $post = $mt->getPost($postid) or die "$!";
63     my $categories = $mt->getPostCategories($postid) or die "$!";
64     if(defined($opts{file})) {
65         if($opts{file} eq "-") {
66             print STDOUT $post->{description}, "\n";
67         } else {
68             open FH, ">".$opts{file} or die "$!";
69             print $opts{file}, "\n";
70             print FH $post->{description}, "\n";
71             close FH;
72         }
73     }
74     $mt->deletePost($postid,1);
75 }
76
77 sub show_post {
78     my $postid=shift;
79     my $post = $mt->getPost($postid) or die "$!";
80     my $categories = $mt->getPostCategories($postid) or die "$!";
81     if(defined($opts{file})) {
82         if($opts{file} eq "-") {
83             print STDOUT $post->{description}, "\n";
84         } else {
85             open FH, ">".$opts{file} or die "$!";
86             print $opts{file}, "\n";
87             print FH $post->{description}, "\n";
88             close FH;
89         }
90     } else {
91         printf("Title: %s (%d)\n", $post->{title}, $post->{postid});
92         if(@$categories>1) {
93             print "Categories:";
94         } elsif (@$categories==1) {
95             print "Category:";
96         }
97         foreach(@$categories) {
98             print " ", $_->{'categoryName'};
99         }
100         print "\n" if(@$categories);
101         print "Body:\n";
102         print $post->{description}, "\n";
103     }
104 }
105
106 sub edit_string {
107     my $string = shift;
108     if(defined($opts{file})) {
109         open FILE, $opts{file} or die "$!";
110         chop($string = join("", <FILE>));
111         close FILE;
112         return $string;
113     }
114     my $editor = $ENV{VISUAL} || $ENV{EDITOR};
115     $editor ||= "/usr/bin/sensible-editor" if (-x "/usr/bin/sensible-editor"); 
116     $editor ||= "/bin/vi" if (-x "/bin/vi"); 
117     $editor ||= "/usr/bin/nano" if (-x "/usr/bin/nano"); 
118     my $tmp = new File::Temp(SUFFIX => '.html');
119     print $tmp $string, "\n";
120     close $tmp;
121     my $mtime = (stat($tmp->filename))[9];
122     system(split (/ /, $editor), $tmp->filename);
123     if($mtime == (stat($tmp->filename))[9]) {
124         return undef;
125     } else {
126         open $tmp;
127         chop($string = join("", <$tmp>));
128         close $tmp;
129         return $string;
130     }
131 }
132
133 sub new_post {
134     my $title = $opts{title} || Complete('Title: ');
135     my $category = $opts{category} || Complete('Categories: ', map {$_->{categoryName}} @{$mt->getCategoryList()});
136     die "No title given.\n" unless($title);
137     my $description = edit_string("");
138     if(defined($description)) {
139         my $postid = $mt->newPost({ 'title' => $title, 'description' => $description });
140         $mt->setPostCategories($postid, $category ? [split(/[, ]+/, $category)] : $category) || warn "$!"
141         if(defined($category));
142         $mt->publishPost($postid);
143     } else {
144         die "No change. Aborting.\n";
145     }
146 }
147
148 sub edit_post {
149     my $postid=shift;
150     my $post = $mt->getPost($postid);
151     my $categories = $mt->getPostCategories($postid);
152     my $title = $opts{title} || Complete('Title: ', $post->{title}) || $post->{title};
153     my $description = edit_string($post->{description});
154     if(defined($description)) {
155         $mt->editPost($postid, { 'title' => $title, 'description' => $description });
156         $mt->setPostCategories($postid, $opts{category} ? [split(/[, ]/, $opts{category})] : $opts{category}) || warn "$!"
157         if(defined($opts{'category'}));
158         $mt->publishPost($postid);
159     } else {
160         die "No change. Aborting.\n";
161     }
162 }
163
164
165 if ($ARGV[0] eq "help") {
166     Getopt::Long::HelpMessage();
167 } elsif ($ARGV[0] eq "categories") {
168     init_mt();
169     print_categories();
170 } elsif ($ARGV[0] eq "list" && $ARGV[1] =~ /^\d*$/) {
171     init_mt();
172     print_recent($ARGV[1]);
173 } elsif ($ARGV[0] eq "new") {
174     init_mt();
175     new_post();
176 } elsif ($ARGV[0] eq "edit" && $ARGV[1] =~ /^\d+$/) {
177     init_mt();
178     edit_post($ARGV[1])
179 } elsif ($ARGV[0] eq "show" && $ARGV[1] =~ /^\d+$/) {
180     init_mt();
181     show_post($ARGV[1])
182 } elsif ($ARGV[0] eq "delete" && $ARGV[1] =~ /^\d+$/) {
183     init_mt();
184     delete_post($ARGV[1])
185 } else {
186     Getopt::Long::HelpMessage();
187     exit(1);
188 }
189 __END__
190 =head1 NAME
191
192 blogcl - Blog from the command-line
193
194 =head1 SYNOPSIS
195
196 B<blogcl> S<[ B<--title>|B<-t> "Title" ]> S<[ B<--category>|B<-c> category[,...] ]> S<[ B<--file>|B<-f> F<file> ]> S<[ B<--username>|B<-l> username ]> S<[ B<--password>|B<-p> password ]> S<[ B<--url>|B<-u> url ]> S<[ B<--blogid>|B<-b> blogid ]> { B<new> | B<edit> postid | B<show> postid | B<delete> postid | B<list> [count] | B<categories> }
197
198 =head1 DESCRIPTION
199
200 This is a simple script used to blog from the command-line.  It was designed
201 for use with Drupal, but may work with any blog featuring a Movable Type
202 compatible interface.
203
204 One of the commands below must be present.
205
206 =over 4
207
208 =item B<new>
209
210 Creates a new blog entry.
211
212 =item B<edit>
213
214 Edit an existing blog entry.  An existing postid must be specified.  To find a
215 post's postid, use B<list>.
216
217 =item B<show>
218
219 Shows the contents of the post specified by the postid given on the
220 command-line.
221
222 =item B<delete>
223
224 Deletes a post.  Specify a B<--file> option to back up the contents (but not
225 the title or category) of the post.
226
227 =item B<list>
228
229 List recents posts and their postids.  An optional integer specifies the number
230 of posts to list.
231
232 =item B<categories>
233
234 Gives categories that are valid parameters to the B<--category> option.
235
236 =item B<help>
237
238 Show usage information.
239
240 =back
241
242 =head1 OPTIONS
243
244 =over 4
245
246 =item B<-b>, B<--blogid>
247
248 Specify a blog ID.  Defaults to the first blog found.
249
250 =item B<-c>, B<--category>
251
252 Specify a category for a new or existing entry.  For multiple categories,
253 seperate them with a comma.  Ignored except when used with either B<new> or
254 B<edit>.
255
256 =item B<-f>, B<--file>
257
258 When used with B<new> or B<edit>, specifies a file from which to read the
259 post's body.  When used with B<show>, specifies a file to which to write the
260 post's body.
261
262 =item B<-l>, B<--username>
263
264 Specify a username.
265
266 =item B<-p>, B<--password>
267
268 Specify a password.
269
270 =item B<-t>, B<--title>
271
272 Specify a title for a new or existing entry.  Ignored except when used with
273 either B<new> or B<edit>.
274
275 =item B<-u>, B<--url>
276
277 Specify the URL of the XMLRPC interface.
278
279 =back
280
281 =head1 FILES
282
283 =over 4
284
285 =item F<~/.blogclrc>
286
287 Contains options of format parameter=value.  Only the long form of
288 options may be used.  Omit leading hyphens.
289
290 =back
291
292 =head1 SEE ALSO
293
294 L<Net::MovableType>
295
296 =head1 BUGS
297
298 Error checking is minimal.
299
300 =head1 COPYRIGHT
301
302 Copyright by Tim Pope. All rights reserved.
303
304 This program is free software; you may redistribute it and/or modify it under
305 the same terms as perl itself.
306
307 =head1 AUTHORS
308
309 Tim Pope, E<lt>perl@relongto.usE<gt>.
310
311 L<http://www.sexygeek.org/>
312
313 =cut