New command 'update_story' to bin/gruta.
[gruta.git] / bin / gruta
blob4e11e8f00c622d823a1a6f1a890f3ec2ccd6610c
1 #!/usr/bin/perl
3 use strict;
4 use warnings;
6 use File::Temp;
8 use Gruta;
9 use Gruta::Source::DBI;
10 use Gruta::Source::FS;
11 use Gruta::Source::Mbox;
13 my $X = {
14 'copy' => [
15 'copy {src} {dst}',
16 'Copies the full source {src} to {dst}',
17 sub {
18 my $g = init();
19 my $new_src = arg();
20 my $dst = new_source( $new_src );
22 $dst->create();
24 $g->transfer_to_source( $dst );
27 'topics' => [
28 'topics {src}',
29 'Lists the topics in {src}',
30 sub {
31 my $g = init();
33 foreach my $t ($g->topics()) {
34 print $t, "\n";
38 'topic' => [
39 'topic {src} {topic_id}',
40 'Dumps topic data',
41 sub {
42 my $g = init();
43 my $topic_id = arg();
45 print get_topic($g, $topic_id);
48 'edit_topic' => [
49 'edit_topic {src} {topic_id}',
50 'Edits topic data',
51 sub {
52 my $g = init();
53 my $topic_id = arg();
55 my $fh = File::Temp->new();
56 print $fh get_topic($g, $topic_id);
57 my $fn = $fh->filename();
58 $fh->close();
60 my $mtime = (stat($fn))[9];
61 system('$EDITOR ' . $fn);
63 if ($mtime != (stat($fn))[9]) {
64 save_topic($g, $topic_id, $fn);
69 'stories' => [
70 'stories {src} {topic_id}',
71 'Lists all stories of a topic',
72 sub {
73 my $g = init();
74 my $topic_id = arg();
76 foreach my $s ($g->stories($topic_id)) {
77 print $s, "\n";
81 'story' => [
82 'story {src} {topic_id} {id}',
83 'Dumps story data',
84 sub {
85 my $g = init();
86 my $topic_id = arg();
87 my $id = arg();
89 print get_story($g, $topic_id, $id);
92 'edit_story' => [
93 'edit_story {src} {topic_id} {id}',
94 'Calls $EDITOR to edit story data',
95 sub {
96 my $g = init();
97 my $topic_id = arg();
98 my $id = arg();
100 my $fh = File::Temp->new();
101 print $fh get_story($g, $topic_id, $id);
102 my $fn = $fh->filename();
103 $fh->close();
105 my $mtime = (stat($fn))[9];
106 system('$EDITOR ' . $fn);
108 if ($mtime != (stat($fn))[9]) {
109 save_story($g, $topic_id, $id, $fn);
113 'new_story' => [
114 'new_story {src} {topic_id} [{id}]',
115 'Creates a new story from STDIN',
116 sub {
117 my $g = init();
118 my $topic_id = arg();
119 my $id = arg_o();
121 my $fh = File::Temp->new();
122 print $fh join('', <>);
123 my $fn = $fh->filename();
124 $fh->close();
126 save_story($g, $topic_id, $id, $fn, 1);
129 'update_story' => [
130 'update_story {src} {topic_id} {id}',
131 'Updates a story from STDIN',
132 sub {
133 my $g = init();
134 my $topic_id = arg();
135 my $id = arg();
137 my $fh = File::Temp->new();
138 print $fh join('', <>);
139 my $fn = $fh->filename();
140 $fh->close();
142 save_story($g, $topic_id, $id, $fn);
145 'filter_story' => [
146 'filter_story {src} {topic_id} {id} {command}',
147 'Filters story data through command (STDIN, STDOUT)',
148 sub {
149 my $g = init();
150 my $topic_id = arg();
151 my $id = arg();
152 my $filter_cmd = arg();
154 my $fhr = File::Temp->new();
155 print $fhr get_story($g, $topic_id, $id);
156 my $fnr = $fhr->filename();
157 $fhr->close();
159 my $fhw = File::Temp->new();
160 my $fnw = $fhw->filename();
161 $fhw->close();
163 system("$filter_cmd < $fnr > $fnw");
165 save_story($g, $topic_id, $id, $fnw);
168 'create' => [
169 'create {src}',
170 'Creates {src}',
171 sub {
172 init();
175 'tags' => [
176 'tags {src}',
177 'Lists all tags in {src}',
178 sub {
179 my $g = init();
181 foreach my $t ($g->tags()) {
182 print join(' ', @{$t}), "\n";
186 'stories_by_date' => [
187 'stories_by_date {src} {topic(s)} {num} {offset} [{from}] [{to}] [{future}]',
188 'Searches stories by date',
189 sub {
190 my $g = init();
191 my $topics = arg();
192 my $num = arg();
193 my $offset = arg();
194 my $from = arg_o();
195 my $to = arg_o();
196 my $future = arg_o();
198 if ($topics) {
199 $topics = [ split(':', $topics) ];
202 foreach my $s ($g->stories_by_date(
203 $topics,
204 num => $num,
205 offset => $offset,
206 from => $from,
207 to => $to,
208 future => $future
209 ) ) {
210 print join(' ', @{$s}), "\n";
214 'stats' => [
215 'stats {src}',
216 'Dumps statistics for {src}',
217 sub {
218 my $g = init();
219 my $n_topics = 0;
220 my $n_stories = 0;
221 my $n_hits = 0;
223 foreach my $t ($g->topics()) {
224 $n_topics++;
226 foreach my $s ($g->stories($t)) {
227 $n_stories++;
229 my $story = $g->story($t, $s);
231 $n_hits += $story->get('hits') || 0;
235 print "Topics: $n_topics, Stories: $n_stories, Hits: $n_hits\n";
238 'stories_by_tag' => [
239 'stories_by_tag {src} {topic(s)} {tag(s)}',
240 'Searches stories by tag(s)',
241 sub {
242 my $g = init();
243 my $topics = arg();
244 my $tags = arg();
246 if ($topics) {
247 $topics = [ split(':', $topics) ];
250 foreach my $s ($g->stories_by_tag($topics, $tags)) {
251 print join(' ', @{$s}), "\n";
255 'search' => [
256 'search {src} {topic_id} {query}',
257 'Searches stories by content',
258 sub {
259 my $g = init();
260 my $topic_id = arg();
261 my $query = arg();
263 foreach my $s ($g->search_stories($topic_id, $query)) {
264 print $s, "\n";
268 'top_ten' => [
269 'top_ten {src} [{num}]',
270 'Shows the top N stories',
271 sub {
272 my $g = init();
273 my $num = arg_o() || 10;
275 foreach my $s ($g->stories_top_ten($num)) {
276 print join(' ', @{$s}), "\n";
280 'users_by_xdate' => [
281 'users_by_xdate {src} [{max_date}]',
282 'Lists users by expiration date',
283 sub {
284 my $g = init();
285 my $max_date = arg_o() || '99999999999999';
287 foreach my $id ($g->users()) {
288 my $u = $g->user($id);
290 my $xdate = $u->get('xdate');
292 if ($xdate &&
293 $xdate gt Gruta::Data::today() &&
294 $xdate lt $max_date) {
295 print $id, ' ', $u->{email}, ' ', $xdate, "\n";
300 'import_rss' => [
301 'import_rss {src} {topic_id}',
302 'Import an RSS from STDIN into a topic',
303 sub {
304 my $g = init();
305 my $topic_id = arg();
307 use Digest::MD5;
308 require XML::Feed;
310 my $feed = XML::Feed->parse(\*STDIN) or die XML::Feed->errstr;
312 foreach my $entry ($feed->entries()) {
313 my $title = $entry->title();
315 my $content = "<h1>$title</h1>" . $entry->content->body();
317 my $d = $entry->modified() || $entry->issued();
318 my $date;
320 if ($d) {
321 $date = sprintf("%04d%02d%02d%02d%02d%02d",
322 $d->year(), $d->month(), $d->day(),
323 $d->hour(), $d->minute(), $d->second());
325 else {
326 $date = Gruta::Data::today();
329 my $md5 = Digest::MD5->new();
330 $md5->add($date);
331 $md5->add($content);
332 my $id = $md5->hexdigest();
334 my $story;
336 if (not $story = $g->story($topic_id, $id)) {
337 $story = Gruta::Data::Story->new (
338 topic_id => $topic_id,
339 id => $id
343 $story->set('date', $date);
344 $story->set('format', 'html');
345 $story->set('content', $content);
346 $story->set('ctime', time());
348 if ($story->source()) {
349 $story = $story->save();
351 else {
352 $story = $g->insert_story($story);
359 my $cmd = arg();
361 my $c;
363 if (not $c = $X->{$cmd}) {
364 $cmd = undef;
365 usage();
368 # execute
369 $c->[2]();
371 exit 0;
374 sub arg
376 if (@ARGV) {
377 return shift(@ARGV);
380 usage();
384 sub arg_o
386 return shift(@ARGV) || shift;
389 sub init
391 my $src = new_source(arg());
392 my $g = Gruta->new( sources => $src );
394 return $g;
398 sub usage
400 if ($cmd) {
401 my $c = $X->{$cmd};
403 print "Usage: gruta ", $c->[0], "\n\n\t", $c->[1], "\n";
405 else {
406 print "Usage: gruta {cmd} [args...]\n\n";
408 foreach my $k (sort keys %{$X}) {
409 my $c = $X->{$k};
411 print ' ', sprintf('%-50s %s',
412 $c->[0], $c->[1]), "\n";
416 exit 1;
419 sub new_source
421 my $src_str = shift;
422 my $src;
424 if ($src_str =~ /^dbi:/) {
425 $src = Gruta::Source::DBI->new( string => $src_str );
427 elsif ($src_str =~ /^mbox:(.+)/) {
428 my $file = $1;
430 $src = Gruta::Source::Mbox->new(
431 file => $file
434 else {
435 $src = Gruta::Source::FS->new( path => $src_str );
438 return $src;
441 sub get_story
443 my $g = shift;
444 my $topic_id = shift;
445 my $id = shift;
446 my @r = ();
448 my $story = $g->story($topic_id, $id)
449 or die "Cannot find story '${topic_id}/${id}'";
451 foreach my $f ($story->fields()) {
452 if ($f ne 'content') {
453 push (@r, $f . ': ' . ($story->get($f) || ''));
457 push(@r, 'tags: ' . join(', ', $story->tags()));
458 push(@r, '');
459 push(@r, $story->get('content'));
460 push(@r, '');
462 return join("\n", @r);
466 sub get_topic
468 my $g = shift;
469 my $topic_id = shift;
470 my @r = ();
472 my $topic = $g->topic($topic_id);
474 foreach my $f ($topic->afields()) {
475 push(@r, $f . ': ' . ($topic->get($f) || ''));
478 return join("\n", @r);
482 sub save_story
484 my $g = shift;
485 my $topic_id = shift;
486 my $id = shift;
487 my $fn = shift;
488 my $new = shift;
489 my $tags;
491 open F, $fn or die "Can't open $fn";
493 my $story = undef;
495 if ($id && !$new) {
496 $story = $g->story($topic_id, $id)
497 or die "Cannot find story '${topic_id}/${id}'";
499 else {
500 $story = Gruta::Data::Story->new (
501 topic_id => $topic_id,
502 id => $id,
503 date => Gruta::Data::today(),
504 format => 'grutatxt'
508 while (<F>) {
509 chomp();
511 last if /^$/;
513 my ($key, $value) = (/^(\w+):\s*(.*)$/);
515 if ($key eq 'tags') {
516 $tags = $value;
518 else {
519 $story->set($key, $value);
523 my $c = join('', <F>);
524 close F;
526 $story->set('content', $c);
528 if ($story->source()) {
529 $story->save();
531 else {
532 $g->insert_story($story);
535 if ($tags) {
536 $story->tags(split(/,\s*/, $tags));
541 sub save_topic
543 my $g = shift;
544 my $topic_id = shift;
545 my $fn = shift;
547 open F, $fn or die "Can't open $fn";
549 my $topic = undef;
551 if ($topic_id) {
552 $topic = $g->topic($topic_id);
554 else {
555 $topic = Gruta::Data::Topic->new (
556 topic_id => $topic_id,
560 while (<F>) {
561 chomp();
563 last if /^$/;
565 my ($key, $value) = (/^(\w+):\s*(.*)$/);
567 $topic->set($key, $value);
570 if ($topic->source()) {
571 $topic->save();
573 else {
574 $g->insert_topic($topic);