Even more CSS tweaks.
[gruta.git] / bin / gruta
blob77a89ab3ac025ca617d8ca6f336e1b7169a773c4
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 'filter_story' => [
114 'filter_story {src} {topic_id} {id} {command}',
115 'Filters story data through command (STDIN, STDOUT)',
116 sub {
117 my $g = init();
118 my $topic_id = arg();
119 my $id = arg();
120 my $filter_cmd = arg();
122 my $fhr = File::Temp->new();
123 print $fhr get_story($g, $topic_id, $id);
124 my $fnr = $fhr->filename();
125 $fhr->close();
127 my $fhw = File::Temp->new();
128 my $fnw = $fhw->filename();
129 $fhw->close();
131 system("$filter_cmd < $fnr > $fnw");
133 save_story($g, $topic_id, $id, $fnw);
136 'create' => [
137 'create {src}',
138 'Creates {src}',
139 sub {
140 init();
143 'tags' => [
144 'tags {src}',
145 'Lists all tags in {src}',
146 sub {
147 my $g = init();
149 foreach my $t ($g->tags()) {
150 print join(' ', @{$t}), "\n";
154 'stories_by_date' => [
155 'stories_by_date {src} {topic(s)} {num} {offset} [{from}] [{to}] [{future}]',
156 'Searches stories by date',
157 sub {
158 my $g = init();
159 my $topics = arg();
160 my $num = arg();
161 my $offset = arg();
162 my $from = arg_o();
163 my $to = arg_o();
164 my $future = arg_o();
166 if ($topics) {
167 $topics = [ split(':', $topics) ];
170 foreach my $s ($g->stories_by_date(
171 $topics,
172 num => $num,
173 offset => $offset,
174 from => $from,
175 to => $to,
176 future => $future
177 ) ) {
178 print join(' ', @{$s}), "\n";
182 'stats' => [
183 'stats {src}',
184 'Dumps statistics for {src}',
185 sub {
186 my $g = init();
187 my $n_topics = 0;
188 my $n_stories = 0;
189 my $n_hits = 0;
191 foreach my $t ($g->topics()) {
192 $n_topics++;
194 foreach my $s ($g->stories($t)) {
195 $n_stories++;
197 my $story = $g->story($t, $s);
199 $n_hits += $story->get('hits') || 0;
203 print "Topics: $n_topics, Stories: $n_stories, Hits: $n_hits\n";
206 'stories_by_tag' => [
207 'stories_by_tag {src} {topic(s)} {tag(s)}',
208 'Searches stories by tag(s)',
209 sub {
210 my $g = init();
211 my $topics = arg();
212 my $tags = arg();
214 if ($topics) {
215 $topics = [ split(':', $topics) ];
218 foreach my $s ($g->stories_by_tag($topics, $tags)) {
219 print join(' ', @{$s}), "\n";
223 'search' => [
224 'search {src} {topic_id} {query}',
225 'Searches stories by content',
226 sub {
227 my $g = init();
228 my $topic_id = arg();
229 my $query = arg();
231 foreach my $s ($g->search_stories($topic_id, $query)) {
232 print $s, "\n";
236 'top_ten' => [
237 'top_ten {src} [{num}]',
238 'Shows the top N stories',
239 sub {
240 my $g = init();
241 my $num = arg_o() || 10;
243 foreach my $s ($g->stories_top_ten($num)) {
244 print join(' ', @{$s}), "\n";
250 my $cmd = arg();
252 my $c;
254 if (not $c = $X->{$cmd}) {
255 $cmd = undef;
256 usage();
259 # execute
260 $c->[2]();
262 exit 0;
265 sub arg
267 if (@ARGV) {
268 return shift(@ARGV);
271 usage();
275 sub arg_o
277 return shift(@ARGV) || shift;
280 sub init
282 my $src = new_source(arg());
283 my $g = Gruta->new( sources => $src );
285 return $g;
289 sub usage
291 if ($cmd) {
292 my $c = $X->{$cmd};
294 print "Usage: gruta ", $c->[0], "\n\n\t", $c->[1], "\n";
296 else {
297 print "Usage: gruta {cmd} [args...]\n\n";
299 foreach my $k (sort keys %{$X}) {
300 my $c = $X->{$k};
302 print ' ', sprintf('%-50s %s',
303 $c->[0], $c->[1]), "\n";
307 exit 1;
310 sub new_source
312 my $src_str = shift;
313 my $src;
315 if ($src_str =~ /^dbi:/) {
316 $src = Gruta::Source::DBI->new( string => $src_str );
318 elsif ($src_str =~ /^mbox:(.+)/) {
319 my $file = $1;
320 my ($topic_id) = ($file =~ /^(\w+)\.?.*$/);
322 $src = Gruta::Source::Mbox->new(
323 file => $file,
324 topic_id => $topic_id,
325 topic_name => $topic_id,
326 index_file => '/tmp/' . $topic_id . '.idx'
329 else {
330 $src = Gruta::Source::FS->new( path => $src_str );
333 return $src;
336 sub get_story
338 my $g = shift;
339 my $topic_id = shift;
340 my $id = shift;
341 my @r = ();
343 my $story = $g->story($topic_id, $id);
345 foreach my $f ($story->fields()) {
346 if ($f ne 'content') {
347 push (@r, $f . ': ' . ($story->get($f) || ''));
351 push(@r, 'tags: ' . join(', ', $story->tags()));
352 push(@r, '');
353 push(@r, $story->get('content'));
354 push(@r, '');
356 return join("\n", @r);
360 sub get_topic
362 my $g = shift;
363 my $topic_id = shift;
364 my @r = ();
366 my $topic = $g->topic($topic_id);
368 foreach my $f ($topic->afields()) {
369 push(@r, $f . ': ' . ($topic->get($f) || ''));
372 return join("\n", @r);
376 sub save_story
378 my $g = shift;
379 my $topic_id = shift;
380 my $id = shift;
381 my $fn = shift;
383 open F, $fn or die "Can't open $fn";
385 my $story = undef;
387 if ($id) {
388 $story = $g->story($topic_id, $id);
390 else {
391 $story = Gruta::Data::Story->new (
392 topic_id => $topic_id,
393 id => $id
397 while (<F>) {
398 chomp();
400 last if /^$/;
402 my ($key, $value) = (/^(\w+):\s*(.*)$/);
404 if ($key eq 'tags') {
405 $story->tags(split(/,\s*/, $value));
407 else {
408 $story->set($key, $value);
412 my $c = join('', <F>);
413 close F;
415 $story->set('content', $c);
417 if ($story->source()) {
418 $story->save();
420 else {
421 $g->insert_story($story);
426 sub save_topic
428 my $g = shift;
429 my $topic_id = shift;
430 my $fn = shift;
432 open F, $fn or die "Can't open $fn";
434 my $topic = undef;
436 if ($topic_id) {
437 $topic = $g->topic($topic_id);
439 else {
440 $topic = Gruta::Data::Topic->new (
441 topic_id => $topic_id,
445 while (<F>) {
446 chomp();
448 last if /^$/;
450 my ($key, $value) = (/^(\w+):\s*(.*)$/);
452 $topic->set($key, $value);
455 if ($topic->source()) {
456 $topic->save();
458 else {
459 $g->insert_topic($topic);