9 use Gruta
::Source
::DBI
;
10 use Gruta
::Source
::FS
;
11 use Gruta
::Source
::Mbox
;
12 use Gruta
::Renderer
::Grutatxt
;
13 use Gruta
::Renderer
::HTML
;
14 use Gruta
::Renderer
::Text
;
19 'Copies the full source {src} to {dst}',
23 my $dst = new_source
( $new_src );
27 $g->transfer_to_source( $dst );
32 'Lists the topics in {src}',
36 foreach my $t ($g->source->topics()) {
42 'topic {src} {topic_id}',
48 print get_topic
($g, $topic_id);
52 'new_topic {src} {topic_id}',
53 'Creates a new topic from STDIN',
58 my $fh = File
::Temp
->new();
59 print $fh join('', <>);
60 my $fn = $fh->filename();
63 save_topic
($g, $topic_id, $fn, 1);
67 'update_topic {src} {topic_id}',
68 'Updates a topic from STDIN',
73 my $fh = File
::Temp
->new();
74 print $fh join('', <>);
75 my $fn = $fh->filename();
78 save_topic
($g, $topic_id, $fn);
82 'edit_topic {src} {topic_id}',
88 my $fh = File
::Temp
->new();
89 print $fh get_topic
($g, $topic_id);
90 my $fn = $fh->filename();
93 my $mtime = (stat($fn))[9];
94 system('$EDITOR ' . $fn);
96 if ($mtime != (stat($fn))[9]) {
97 save_topic
($g, $topic_id, $fn);
102 'stories {src} {topic_id}',
103 'Lists all stories of a topic',
106 my $topic_id = arg
();
108 foreach my $s ($g->source->stories($topic_id)) {
114 'story {src} {topic_id} {id}',
118 my $topic_id = arg
();
121 print get_story
($g, $topic_id, $id);
125 'edit_story {src} {topic_id} {id}',
126 'Calls $EDITOR to edit story data',
129 my $topic_id = arg
();
132 my $fh = File
::Temp
->new();
133 print $fh get_story
($g, $topic_id, $id);
134 my $fn = $fh->filename();
137 my $mtime = (stat($fn))[9];
138 system('$EDITOR ' . $fn);
140 if ($mtime != (stat($fn))[9]) {
141 save_story
($g, $topic_id, $id, $fn);
146 'new_story {src} {topic_id} [{id}]',
147 'Creates a new story from STDIN',
150 my $topic_id = arg
();
153 my $fh = File
::Temp
->new();
154 print $fh join('', <>);
155 my $fn = $fh->filename();
158 save_story
($g, $topic_id, $id, $fn, 1);
162 'update_story {src} {topic_id} {id}',
163 'Updates a story from STDIN',
166 my $topic_id = arg
();
169 my $fh = File
::Temp
->new();
170 print $fh join('', <>);
171 my $fn = $fh->filename();
174 save_story
($g, $topic_id, $id, $fn);
178 'filter_story {src} {topic_id} {id} {command}',
179 'Filters story data through command (STDIN, STDOUT)',
182 my $topic_id = arg
();
184 my $filter_cmd = arg
();
186 my $fhr = File
::Temp
->new();
187 print $fhr get_story
($g, $topic_id, $id);
188 my $fnr = $fhr->filename();
191 my $fhw = File
::Temp
->new();
192 my $fnw = $fhw->filename();
195 system("$filter_cmd < $fnr > $fnw");
197 save_story
($g, $topic_id, $id, $fnw);
209 'Lists all tags in {src}',
213 foreach my $t ($g->source->tags()) {
214 print join(' ', @
{$t}), "\n";
218 'stories_by_date' => [
219 'stories_by_date {src} {topic(s)} {num} {offset} [{from}] [{to}] [{future}]',
220 'Searches stories by date',
228 my $future = arg_o
();
231 $topics = [ split(':', $topics) ];
234 foreach my $s ($g->source->stories_by_date(
242 print join(' ', @
{$s}), "\n";
248 'Dumps statistics for {src}',
255 foreach my $t ($g->source->topics()) {
258 foreach my $s ($g->source->stories($t)) {
261 my $story = $g->source->story($t, $s);
263 $n_hits += $story->get('hits') || 0;
267 print "Topics: $n_topics, Stories: $n_stories, Hits: $n_hits\n";
270 'stories_by_tag' => [
271 'stories_by_tag {src} {topic(s)} {tag(s)}',
272 'Searches stories by tag(s)',
279 $topics = [ split(':', $topics) ];
282 foreach my $s ($g->source->stories_by_tag($topics, $tags)) {
283 print join(' ', @
{$s}), "\n";
288 'rename_tag {src} {tag} [new tag]',
289 'Renames (or removes) a tag',
293 my $new_tag = arg_o
();
295 foreach my $s ($g->source->stories_by_tag('', $old_tag)) {
296 my $story = $g->source->story($s->[0], $s->[1]);
298 my @tags = $story->tags();
303 @tags = map { $_ eq $old_tag ?
$new_tag : $_ } @tags;
306 @tags = grep { ! /^$old_tag$/ } @tags;
315 'search {src} {topic(s)} {query}',
316 'Searches stories by content',
323 $topics = [ split(':', $topics) ];
326 foreach my $s ($g->source->stories_by_text($topics, $query)) {
327 print join(' ', @
{$s}), "\n";
332 'top_ten {src} [{num}]',
333 'Shows the top N stories',
336 my $num = arg_o
() || 10;
338 foreach my $s ($g->source->stories_top_ten($num)) {
339 print join(' ', @
{$s}), "\n";
343 'users_by_xdate' => [
344 'users_by_xdate {src} [{max_date}]',
345 'Lists users by expiration date',
348 my $max_date = arg_o
() || '99999999999999';
350 foreach my $id ($g->source->users()) {
351 my $u = $g->source->user($id);
353 my $xdate = $u->get('xdate');
356 $xdate gt Gruta
::Data
::today
() &&
357 $xdate lt $max_date) {
358 print $id, ' ', $u->{email
}, ' ', $xdate, "\n";
363 'set_story_date' => [
364 'set_story_date {src} {topic_id} {id} {date}',
365 'Sets the date of a story (-, now)',
368 my $topic_id = arg
();
372 my $story = $g->source->story($topic_id, $id)
373 or die "Cannot find story '${topic_id}/${id}'";
376 $date = Gruta
::Data
::today
();
379 $story->set('date', $date);
383 'pending_comments' => [
384 'pending_comments {src}',
385 'Lists all comments with approval pending',
389 foreach my $e ($g->source->pending_comments()) {
390 print join(' ', @
{$e}), "\n";
395 'comments {src} [{max}]',
396 'Lists all comments',
401 foreach my $e ($g->source->comments($max)) {
402 print join(' ', @
{$e}), "\n";
407 'comment {src} {topic_id} {story_id} {id}',
408 'Dumps comment data',
411 my $topic_id = arg
();
412 my $story_id = arg
();
415 my $c = $g->source->comment($topic_id, $story_id, $id)
416 or die "Cannot find comment '${topic_id}/${story_id}/${id}'";
418 foreach my $f ($c->fields()) {
419 if ($f ne 'content') {
420 print $f, ': ', ($c->get($f) || ''), "\n";
424 print "\n", $c->get('content'), "\n";
428 'new_comment {src} {topic_id} {story_id} [{author}]',
429 'Adds a new comment from STDIN',
432 my $topic_id = arg
();
433 my $story_id = arg
();
434 my $author = arg_o
() || '';
436 my $s = $g->source->story($topic_id, $story_id)
437 or die "Cannot find story $topic_id, $story_id";
439 my $content = join('', <>);
441 my $c = new Gruta
::Data
::Comment
(
442 topic_id
=> $topic_id,
443 story_id
=> $story_id,
448 $g->source->insert_comment($c);
451 'approve_comment' => [
452 'approve_comment {src} {topic_id} {story_id} {id}',
453 'Approves a comment',
456 my $topic_id = arg
();
457 my $story_id = arg
();
460 my $c = $g->source->comment($topic_id, $story_id, $id)
461 or die "Cannot find comment '${topic_id}/${story_id}/${id}'";
466 'delete_comment' => [
467 'delete_comment {src} {topic_id} {story_id} {id}',
471 my $topic_id = arg
();
472 my $story_id = arg
();
475 my $c = $g->source->comment($topic_id, $story_id, $id)
476 or die "Cannot find comment '${topic_id}/${story_id}/${id}'";
481 'story_comments' => [
482 'story_comments {src} {topic_id} {story_id} [{include_not_approved}]',
483 'Lists all comments for a story',
486 my $topic_id = arg
();
487 my $story_id = arg
();
490 my $story = $g->source->story($topic_id, $story_id)
491 or die "Cannot find story '$topic_id, $story_id'";
493 foreach my $c ($g->source->story_comments($story, $all)) {
494 print join(" ", @
{$c}), "\n";
498 'related_stories' => [
499 'related_stories {src} {topic_id} {id} [{max}]',
500 'Returns a list of stories related to the specified one',
503 my $topic_id = arg
();
504 my $story_id = arg
();
507 my $story = $g->source->story($topic_id, $story_id)
508 or die "Cannot find story '$topic_id, $story_id'";
510 foreach my $i ($g->source->related_stories($story, $max)) {
511 print join(" ", @
{$i}), "\n";
516 'import_rss {src} {topic_id} [{tag(s)}]',
517 'Import an RSS from STDIN into a topic',
520 my $topic_id = arg
();
521 my $tags = arg_o
() || '';
523 my @tags = split(/,\s*/, $tags);
526 use Encode
qw(encode_utf8);
529 my $feed = XML
::Feed
->parse(\
*STDIN
) or die XML
::Feed
->errstr;
531 foreach my $entry ($feed->entries()) {
532 my $title = $entry->title();
534 my $content = "<h1>$title</h1>" . $entry->content->body();
536 my $d = $entry->modified() || $entry->issued();
540 $date = sprintf("%04d%02d%02d%02d%02d%02d",
541 $d->year(), $d->month(), $d->day(),
542 $d->hour(), $d->minute(), $d->second());
545 $date = Gruta
::Data
::today
();
548 my $md5 = Digest
::MD5
->new();
549 $md5->add(encode_utf8
($date));
550 $md5->add(encode_utf8
($content));
551 my $id = $md5->hexdigest();
555 if (not $story = $g->source->story($topic_id, $id)) {
556 $story = Gruta
::Data
::Story
->new (
557 topic_id
=> $topic_id,
562 $story->set('date', $date);
563 $story->set('format', 'html');
564 $story->set('content', $content);
565 $story->set('ctime', time());
569 if ($story->source()) {
570 $story = $story->save();
573 $story = $g->source->insert_story($story);
588 if (not $c = $X->{$cmd}) {
611 return shift(@ARGV) || shift;
616 my $src = new_source
(arg
());
620 Gruta
::Renderer
::Grutatxt
->new(),
621 Gruta
::Renderer
::HTML
->new(),
622 Gruta
::Renderer
::HTML
->new( valid_tags
=> undef ),
623 Gruta
::Renderer
::Text
->new(),
633 print "Gruta Command Line Tool\n";
634 print "=======================\n\n";
636 print "Manipulates Gruta sources directly.\n\n";
637 print "(C) Angel Ortega angel\@triptico.com\n\n";
640 print " gruta {command} {src} [{arguments} ...]\n\n";
642 print "Where {src} is a Gruta source spec. Examples:\n\n";
643 print " * /var/www/site_dir/var (FS type)\n";
644 print " * dbi:SQLite:/var/www/site_dir/var/gruta.db (Perl DBI type)\n";
647 my @keys = sort keys %{$X};
653 foreach my $k (@keys) {
661 print " ", $c->[0], "\n\n";
663 print $c->[1], "\n\n";
674 if ($src_str =~ /^dbi:/) {
675 $src = Gruta
::Source
::DBI
->new( string
=> $src_str );
677 elsif ($src_str =~ /^mbox:(.+)/) {
680 $src = Gruta
::Source
::Mbox
->new(
685 $src = Gruta
::Source
::FS
->new( path
=> $src_str );
694 my $topic_id = shift;
698 my $story = $g->source->story($topic_id, $id)
699 or die "Cannot find story '${topic_id}/${id}'";
701 foreach my $f ($story->fields()) {
702 if ($f ne 'content') {
703 push (@r, $f . ': ' . ($story->get($f) || ''));
707 push(@r, 'tags: ' . join(', ', $story->tags()));
709 push(@r, $story->get('content'));
712 return join("\n", @r);
719 my $topic_id = shift;
722 my $topic = $g->source->topic($topic_id);
724 foreach my $f ($topic->afields()) {
725 push(@r, $f . ': ' . ($topic->get($f) || ''));
728 return join("\n", @r);
735 my $topic_id = shift;
741 open F
, $fn or die "Can't open $fn";
746 $story = $g->source->story($topic_id, $id)
747 or die "Cannot find story '${topic_id}/${id}'";
750 $story = Gruta
::Data
::Story
->new (
751 topic_id
=> $topic_id,
753 date
=> Gruta
::Data
::today
(),
763 my ($key, $value) = (/^(\w+):\s*(.*)$/);
770 if ($key eq 'tags') {
774 $story->set($key, $value);
778 my $c = join('', $_, <F
>);
781 $story->set('content', $c);
784 if ($story->source()) {
788 $g->source->insert_story($story);
792 $story->tags(split(/,\s*/, $tags));
800 my $topic_id = shift;
804 open F
, $fn or die "Can't open $fn";
808 if ($topic_id && !$new) {
809 $topic = $g->source->topic($topic_id);
812 $topic = Gruta
::Data
::Topic
->new (
822 my ($key, $value) = (/^(\w+):\s*(.*)$/);
824 $topic->set($key, $value);
827 if ($topic->source()) {
831 $g->source->insert_topic($topic);