Merge branch 'thh-checkinput' into next
[usenet/newsstats.git] / groupstats.pl
1 #! /usr/bin/perl
2 #
3 # groupstats.pl
4 #
5 # This script will get statistical data on newgroup usage
6 # from a database.
7
8 # It is part of the NewsStats package.
9 #
10 # Copyright (c) 2010-2012 Thomas Hochstein <thh@inter.net>
11 #
12 # It can be redistributed and/or modified under the same terms under 
13 # which Perl itself is published.
14
15 BEGIN {
16   our $VERSION = "0.01";
17   use File::Basename;
18   push(@INC, dirname($0));
19 }
20 use strict;
21 use warnings;
22
23 use NewsStats qw(:DEFAULT :TimePeriods :Output :SQLHelper ReadGroupList);
24
25 use DBI;
26 use Getopt::Long qw(GetOptions);
27 Getopt::Long::config ('bundling');
28
29 ################################# Main program #################################
30
31 ### read commandline options
32 my ($OptBoundType,$OptCaptions,$OptCheckgroupsFile,$OptComments,
33     $OptFileTemplate,$OptFormat,$OptGroupBy,$OptGroupsDB,$LowBound,$OptMonth,
34     $OptNewsgroups,$OptOrderBy,$OptReportType,$OptSums,$UppBound);
35 GetOptions ('b|boundary=s'   => \$OptBoundType,
36             'c|captions!'    => \$OptCaptions,
37             'checkgroups=s'  => \$OptCheckgroupsFile,
38             'comments!'      => \$OptComments,
39             'filetemplate=s' => \$OptFileTemplate,
40             'f|format=s'     => \$OptFormat,
41             'g|group-by=s'   => \$OptGroupBy,
42             'groupsdb=s'     => \$OptGroupsDB,
43             'l|lower=i'      => \$LowBound,
44             'm|month=s'      => \$OptMonth,
45             'n|newsgroups=s' => \$OptNewsgroups,
46             'o|order-by=s'   => \$OptOrderBy,
47             'r|report=s'     => \$OptReportType,
48             's|sums!'        => \$OptSums,
49             'u|upper=i'      => \$UppBound,
50             'h|help'         => \&ShowPOD,
51             'V|version'      => \&ShowVersion) or exit 1;
52 # parse parameters
53 # $OptComments defaults to TRUE
54 $OptComments = 1 if (!defined($OptComments));
55 # force --nocomments when --filetemplate is used
56 $OptComments = 0 if ($OptFileTemplate);
57 # parse $OptBoundType
58 if ($OptBoundType) {
59   if ($OptBoundType =~ /level/i) {
60     $OptBoundType = 'level';
61   } elsif ($OptBoundType =~ /av(era)?ge?/i) {
62     $OptBoundType = 'average';
63   } elsif ($OptBoundType =~ /sums?/i) {
64     $OptBoundType = 'sum';
65   } else {
66     $OptBoundType = 'default';
67   }
68 }
69 # parse $OptReportType
70 if ($OptReportType) {
71   if ($OptReportType =~ /av(era)?ge?/i) {
72     $OptReportType = 'average';
73   } elsif ($OptReportType =~ /sums?/i) {
74     $OptReportType = 'sum';
75   } else {
76     $OptReportType  = 'default';
77   }
78 }
79 # read list of newsgroups from --checkgroups
80 # into a hash reference
81 my $ValidGroups = &ReadGroupList($OptCheckgroupsFile) if $OptCheckgroupsFile;
82
83 ### read configuration
84 my %Conf = %{ReadConfig($HomePath.'/newsstats.conf')};
85
86 ### override configuration via commandline options
87 my %ConfOverride;
88 $ConfOverride{'DBTableGrps'} = $OptGroupsDB if $OptGroupsDB;
89 &OverrideConfig(\%Conf,\%ConfOverride);
90
91 ### init database
92 my $DBHandle = InitDB(\%Conf,1);
93
94 ### get time period and newsgroups, prepare SQL 'WHERE' clause
95 # get time period
96 # and set caption for output and expression for SQL 'WHERE' clause
97 my ($CaptionPeriod,$SQLWherePeriod) = &GetTimePeriod($OptMonth);
98 # bail out if --month is invalid
99 &Bleat(2,"--month option has an invalid format - ".
100          "please use 'YYYY-MM', 'YYYY-MM:YYYY-MM' or 'ALL'!") if !$CaptionPeriod;
101 # get list of newsgroups and set expression for SQL 'WHERE' clause
102 # with placeholders as well as a list of newsgroup to bind to them
103 my ($SQLWhereNewsgroups,@SQLBindNewsgroups);
104 if ($OptNewsgroups) {
105   ($SQLWhereNewsgroups,@SQLBindNewsgroups) = &SQLGroupList($OptNewsgroups);
106   # bail out if --newsgroups is invalid
107   &Bleat(2,"--newsgroups option has an invalid format!")
108     if !$SQLWhereNewsgroups;
109 }
110
111 ### build SQL WHERE clause (and HAVING clause, if needed)
112 my ($SQLWhereClause,$SQLHavingClause);
113 # $OptBoundType 'level'
114 if ($OptBoundType and $OptBoundType ne 'default') {
115   $SQLWhereClause = SQLBuildClause('where',$SQLWherePeriod,
116                                    $SQLWhereNewsgroups,&SQLHierarchies($OptSums));
117   $SQLHavingClause = SQLBuildClause('having',&SQLSetBounds($OptBoundType,
118                                                            $LowBound,$UppBound));
119 # $OptBoundType 'threshold' / 'default' or none
120 } else {
121   $SQLWhereClause = SQLBuildClause('where',$SQLWherePeriod,
122                                    $SQLWhereNewsgroups,&SQLHierarchies($OptSums),
123                                    &SQLSetBounds('default',$LowBound,$UppBound));
124 }
125
126 ### get sort order and build SQL 'ORDER BY' clause
127 # default to 'newsgroup' for $OptBoundType 'level' or 'average'
128 $OptGroupBy = 'newsgroup' if (!$OptGroupBy and
129                               $OptBoundType and $OptBoundType ne 'default');
130 # force to 'month' for $OptReportType 'average' or 'sum'
131 $OptGroupBy = 'month' if ($OptReportType and $OptReportType ne 'default');
132 # parse $OptGroupBy to $GroupBy, create ORDER BY clause $SQLOrderClause
133 my ($GroupBy,$SQLOrderClause) = SQLSortOrder($OptGroupBy, $OptOrderBy);
134 # $GroupBy will contain 'month' or 'newsgroup' (parsed result of $OptGroupBy)
135 # set it to 'month' or 'key' for OutputData()
136 $GroupBy = ($GroupBy eq 'month') ? 'month' : 'key';
137
138 ### get report type and build SQL 'SELECT' query
139 my $SQLSelect;
140 my $SQLGroupClause = '';
141 my $Precision = 0;       # number of digits right of decimal point for output
142 if ($OptReportType and $OptReportType ne 'default') {
143   $SQLGroupClause = 'GROUP BY newsgroup';
144   # change $SQLOrderClause: replace everything before 'postings'
145   $SQLOrderClause =~ s/BY.+postings/BY postings/;
146   if ($OptReportType eq 'average') {
147     $SQLSelect = "'All months',newsgroup,AVG(postings)";
148     $Precision = 2;
149     # change $SQLOrderClause: replace 'postings' with 'AVG(postings)'
150     $SQLOrderClause =~ s/postings/AVG(postings)/;
151   } elsif ($OptReportType eq 'sum') {
152     $SQLSelect = "'All months',newsgroup,SUM(postings)";
153     # change $SQLOrderClause: replace 'postings' with 'SUM(postings)'
154     $SQLOrderClause =~ s/postings/SUM(postings)/;
155   }
156  } else {
157   $SQLSelect = 'month,newsgroup,postings';
158 };
159
160 ### get length of longest newsgroup name delivered by query
161 ### for formatting purposes
162 my $Field = ($GroupBy eq 'month') ? 'newsgroup' : 'month';
163 my $MaxLength = &GetMaxLength($DBHandle,$Conf{'DBTableGrps'},
164                               $Field,$SQLWhereClause,$SQLHavingClause,
165                               @SQLBindNewsgroups);
166
167 ### build and execute SQL query
168 my ($DBQuery);
169 # special query preparation for $OptBoundType 'level', 'average' or 'sums'
170 if ($OptBoundType and $OptBoundType ne 'default') {
171   # prepare and execute first query:
172   # get list of newsgroups meeting level conditions
173   $DBQuery = $DBHandle->prepare(sprintf('SELECT newsgroup FROM %s.%s %s '.
174                                         'GROUP BY newsgroup %s',
175                                         $Conf{'DBDatabase'},$Conf{'DBTableGrps'},
176                                         $SQLWhereClause,$SQLHavingClause));
177   $DBQuery->execute(@SQLBindNewsgroups)
178     or &Bleat(2,sprintf("Can't get groups data for %s from %s.%s: %s\n",
179                         $CaptionPeriod,$Conf{'DBDatabase'},$Conf{'DBTableGrps'},
180                         $DBI::errstr));
181   # add newsgroups to a comma-seperated list ready for IN(...) query
182   my $GroupList;
183   while (my ($Newsgroup) = $DBQuery->fetchrow_array) {
184     $GroupList .= ',' if $GroupList;
185     $GroupList .= "'$Newsgroup'";
186   };
187   # enhance $WhereClause
188   if ($GroupList) {
189     $SQLWhereClause = SQLBuildClause('where',$SQLWhereClause,
190                                      sprintf('newsgroup IN (%s)',$GroupList));
191   } else {
192     # condition cannot be satisfied;
193     # force query to fail by adding '0=1'
194     $SQLWhereClause = SQLBuildClause('where',$SQLWhereClause,'0=1');
195   }
196 }
197
198 # prepare query
199 $DBQuery = $DBHandle->prepare(sprintf('SELECT %s FROM %s.%s %s %s %s',
200                                       $SQLSelect,
201                                       $Conf{'DBDatabase'},$Conf{'DBTableGrps'},
202                                       $SQLWhereClause,$SQLGroupClause,
203                                       $SQLOrderClause));
204
205 # execute query
206 $DBQuery->execute(@SQLBindNewsgroups)
207   or &Bleat(2,sprintf("Can't get groups data for %s from %s.%s: %s\n",
208                       $CaptionPeriod,$Conf{'DBDatabase'},$Conf{'DBTableGrps'},
209                       $DBI::errstr));
210
211 ### output results
212 # set default to 'pretty'
213 $OptFormat = 'pretty' if !$OptFormat;
214 # print captions if --caption is set
215 if ($OptCaptions && $OptComments) {
216   # print time period with report type
217   my $CaptionReportType= '(number of postings for each month)';
218   if ($OptReportType and $OptReportType ne 'default') {
219     $CaptionReportType= '(average number of postings for each month)'
220       if $OptReportType eq 'average';
221     $CaptionReportType= '(number of all postings for that time period)'
222       if $OptReportType eq 'sum';
223   }
224   printf("# ----- Report for %s %s\n",$CaptionPeriod,$CaptionReportType);
225   # print newsgroup list if --newsgroups is set
226   printf("# ----- Newsgroups: %s\n",join(',',split(/:/,$OptNewsgroups)))
227     if $OptNewsgroups;
228   # print boundaries, if set
229   my $CaptionBoundary= '(counting only month fulfilling this condition)';
230   if ($OptBoundType and $OptBoundType ne 'default') {
231     $CaptionBoundary= '(every single month)'  if $OptBoundType eq 'level';
232     $CaptionBoundary= '(on average)'          if $OptBoundType eq 'average';
233     $CaptionBoundary= '(all month summed up)' if $OptBoundType eq 'sum';
234   }
235   printf("# ----- Threshold: %s %s x %s %s %s\n",
236          $LowBound ? $LowBound : '',$LowBound ? '=>' : '',
237          $UppBound ? '<=' : '',$UppBound ? $UppBound : '',$CaptionBoundary)
238     if ($LowBound or $UppBound);
239   # print primary and secondary sort order
240   printf("# ----- Grouped by %s (%s), sorted %s%s\n",
241          ($GroupBy eq 'month') ? 'Months' : 'Newsgroups',
242          ($OptGroupBy and $OptGroupBy =~ /-?desc$/i) ? 'descending' : 'ascending',
243          ($OptOrderBy and $OptOrderBy =~ /posting/i) ? 'by number of postings ' : '',
244          ($OptOrderBy and $OptOrderBy =~ /-?desc$/i) ? 'descending' : 'ascending');
245 }
246  
247 # output data
248 &OutputData($OptFormat,$OptComments,$GroupBy,$Precision,
249             $OptCheckgroupsFile ? $ValidGroups : '',
250             $OptFileTemplate,$DBQuery,$MaxLength);
251
252 ### close handles
253 $DBHandle->disconnect;
254
255 __END__
256
257 ################################ Documentation #################################
258
259 =head1 NAME
260
261 groupstats - create reports on newsgroup usage
262
263 =head1 SYNOPSIS
264
265 B<groupstats> [B<-Vhcs> B<--comments>] [B<-m> I<YYYY-MM>[:I<YYYY-MM>] | I<all>] [B<-n> I<newsgroup(s)>] [B<--checkgroups> I<checkgroups file>] [B<-r> I<report type>] [B<-l> I<lower boundary>] [B<-u> I<upper boundary>] [B<-b> I<boundary type>] [B<-g> I<group by>] [B<-o> I<order by>] [B<-f> I<output format>] [B<--filetemplate> I<filename template>] [B<--groupsdb> I<database table>]
266
267 =head1 REQUIREMENTS
268
269 See L<doc/README>.
270
271 =head1 DESCRIPTION
272
273 This script create reports on newsgroup usage (number of postings per
274 group per month) taken from result tables created by
275 B<gatherstats.pl>.
276
277 =head2 Features and options
278
279 =head3 Time period and newsgroups
280
281 The time period to act on defaults to last month; you can assign another
282 time period or a single month (or drop all time constraints) via the
283 B<--month> option (see below).
284
285 B<groupstats> will process all newsgroups by default; you can limit
286 processing to only some newsgroups by supplying a list of those groups via
287 B<--newsgroups> option (see below). You can include hierarchy levels in
288 the output by adding the B<--sums> switch (see below). Optionally
289 newsgroups not present in a checkgroups file can be excluded from output,
290 sse B<--checkgroups> below.
291
292 =head3 Report type
293
294 You can choose between different B<--report> types: postings per month,
295 average postings per month or all postings summed up; for details, see
296 below.
297
298 =head3 Upper and lower boundaries
299
300 Furthermore you can set an upper and/or lower boundary to exclude some
301 results from output via the B<--lower> and B<--upper> options,
302 respectively. By default, all newsgroups with more and/or less postings
303 per month will be excluded from the result set (i.e. not shown and not
304 considered for average and sum reports). You can change the meaning of
305 those boundaries with the B<--boundary> option. For details, please see
306 below.
307
308 =head3 Sorting and formatting the output
309
310 By default, all results are grouped by month; you can group results by
311 newsgroup instead via the B<--groupy-by> option. Within those groups, the
312 list of newsgroups (or months) is sorted alphabetically (or
313 chronologically, respectively) ascending. You can change that order (and
314 sort by number of postings) with the B<--order-by> option. For details and
315 exceptions, please see below.
316
317 The results will be formatted as a kind of table; you can change the
318 output format to a simple list or just a list of newsgroups and number of
319 postings with the B<--format> option. Captions will be added by means of
320 the B<--caption> option; all comments (and captions) can be supressed by
321 using B<--nocomments>.
322
323 Last but not least you can redirect all output to a number of files, e.g.
324 one for each month, by submitting the B<--filetemplate> option, see below.
325 Captions and comments are automatically disabled in this case.
326
327 =head2 Configuration
328
329 B<groupstats> will read its configuration from F<newsstats.conf>
330 which should be present in the same directory via Config::Auto.
331
332 See doc/INSTALL for an overview of possible configuration options.
333
334 You can override some configuration options via the B<--groupsdb> option.
335
336 =head1 OPTIONS
337
338 =over 3
339
340 =item B<-V>, B<--version>
341
342 Print out version and copyright information and exit.
343
344 =item B<-h>, B<--help>
345
346 Print this man page and exit.
347
348 =item B<-m>, B<--month> I<YYYY-MM[:YYYY-MM]|all> 
349
350 Set processing period to a single month in YYYY-MM format or to a time
351 period between two month in YYYY-MM:YYYY-MM format (two month, separated
352 by a colon). By using the keyword I<all> instead, you can set no
353 processing period to process the whole database.
354
355 =item B<-n>, B<--newsgroups> I<newsgroup(s)>
356
357 Limit processing to a certain set of newsgroups. I<newsgroup(s)> can
358 be a single newsgroup name (de.alt.test), a newsgroup hierarchy
359 (de.alt.*) or a list of either of these, separated by colons, for
360 example
361
362    de.test:de.alt.test:de.newusers.*
363
364 =item B<-s>, B<--sums|--nosums> (sum per hierarchy level)
365
366 Include "virtual" groups for every hierarchy level in output, for
367 example:
368
369     de.alt.ALL 10
370     de.alt.test 5
371     de.alt.admin 7
372
373 See the B<gatherstats> man page for details.
374
375 =item B<--checkgroups> I<filename>
376
377 Restrict output to those newgroups present in a file in checkgroups format
378 (one newgroup name per line; everything after the first whitespace on each
379 line is ignored). All other newsgroups will be removed from output.
380
381 =item B<-r>, B<--report> I<default|average|sums>
382
383 Choose the report type: I<default>, I<average> or I<sums>
384
385 By default, B<groupstats> will report the number of postings for each
386 newsgroup in each month. But it can also report the average number of
387 postings per group for all months or the total sum of postings per group
388 for all months.
389
390 For report types I<average> and I<sums>, the B<group-by> option has no
391 meaning and will be silently ignored (see below).
392
393 =item B<-l>, B<--lower> I<lower boundary>
394
395 Set the lower boundary. See B<--boundary> below.
396
397 =item B<-l>, B<--upper> I<upper boundary>
398
399 Set the upper boundary. See B<--boundary> below.
400
401 =item B<-b>, B<--boundary> I<boundary type>
402
403 Set the boundary type to one of I<default>, I<level>, I<average> or
404 I<sums>.
405
406 By default, all newsgroups with more postings per month than the upper
407 boundary and/or less postings per month than the lower boundary will be
408 excluded from further processing. For the default report that means each
409 month only newsgroups with a number of postings between the boundaries
410 will be displayed. For the other report types, newsgroups with a number of
411 postings exceeding the boundaries in all (!) months will not be
412 considered.
413
414 For example, lets take a list of newsgroups like this:
415
416     ----- 2012-01:
417     de.comp.datenbanken.misc               6
418     de.comp.datenbanken.ms-access         84
419     de.comp.datenbanken.mysql             88
420     ----- 2012-02:
421     de.comp.datenbanken.misc               8
422     de.comp.datenbanken.ms-access        126
423     de.comp.datenbanken.mysql             21
424     ----- 2012-03:
425     de.comp.datenbanken.misc              24
426     de.comp.datenbanken.ms-access         83
427     de.comp.datenbanken.mysql             36
428
429 With C<groupstats --month 2012-01:2012-03 --lower 25 --report sums>,
430 you'll get the following result:
431
432     ----- All months:
433     de.comp.datenbanken.ms-access        293
434     de.comp.datenbanken.mysql            124
435
436 de.comp.datenbanken.misc has not been considered even though it has 38
437 postings in total, because it has less than 25 postings in every single
438 month. If you want to list all newsgroups with more than 25 postings U<in
439 total>, you'll have to set the boundary type to I<sum>, see below.
440
441 A boundary type of I<level> will show only those newsgroups - at all -
442 that satisfy the boundaries in each and every single month. With the above
443 list of newsgroups and
444 C<groupstats --month 2012-01:2012-03 --lower 25 --boundary level --report sums>,
445 you'll get this result:
446
447     ----- All months:
448     de.comp.datenbanken.ms-access        293
449
450 de.comp.datenbanken.mysql has not been considered because it had less than
451 25 postings in 2012-02.
452
453 You can use that to get a list of newsgroups that have more (or less) then
454 x postings during the whole reporting period.
455
456 A boundary type of I<average> will show only those newsgroups - at all -that
457 satisfy the boundaries on average. With the above list of newsgroups and
458 C<groupstats --month 2012-01:2012-03 --lower 25 --boundary avg --report sums>,
459 you'll get this result:
460
461    ----- All months:
462    de.comp.datenbanken.ms-access        293
463    de.comp.datenbanken.mysql            145
464
465 The average number of postings in the three groups is:
466
467     de.comp.datenbanken.misc           12.67
468     de.comp.datenbanken.ms-access      97.67
469     de.comp.datenbanken.mysql          48.33
470
471 Last but not least, a boundary type of I<sums> will show only those
472 newsgroups - at all - that satisfy the boundaries with the total sum of
473 all postings during the reporting period. With the above list of
474 newsgroups and
475 C<groupstats --month 2012-01:2012-03 --lower 25 --boundary sum --report sums>,
476 you'll finally get this result:
477
478     ----- All months:
479     de.comp.datenbanken.misc              38
480     de.comp.datenbanken.ms-access        293
481     de.comp.datenbanken.mysql            145
482
483
484 =item B<-g>, B<--group-by> I<month[-desc]|newsgroups[-desc]>
485
486 By default, all results are grouped by month, sorted chronologically in
487 ascending order, like this:
488
489     ----- 2012-01:
490     de.comp.datenbanken.ms-access         84
491     de.comp.datenbanken.mysql             88
492     ----- 2012-02:
493     de.comp.datenbanken.ms-access        126
494     de.comp.datenbanken.mysql             21
495
496 The results can be grouped by newsgroups instead via
497 B<--group-by> I<newsgroup>:
498
499     ----- de.comp.datenbanken.ms-access:
500     2012-01         84
501     2012-02        126
502     ----- de.comp.datenbanken.mysql:
503     2012-01         88
504     2012-02         21
505
506 By appending I<-desc> to the group-by option parameter, you can reverse
507 the sort order - e.g. B<--group-by> I<month-desc> will give:
508
509     ----- 2012-02:
510     de.comp.datenbanken.ms-access        126
511     de.comp.datenbanken.mysql             21
512     ----- 2012-01:
513     de.comp.datenbanken.ms-access         84
514     de.comp.datenbanken.mysql             88
515
516 Average and sums reports (see above) will always be grouped by months;
517 this option will therefore be ignored.
518
519 =item B<-o>, B<--order-by> I<default[-desc]|postings[-desc]>
520
521 Within each group (a single month or single newsgroup, see above), the
522 report will be sorted by newsgroup names in ascending alphabetical order
523 by default. You can change the sort order to descending or sort by number
524 of postings instead.
525
526 =item B<-f>, B<--format> I<pretty|list|dump>
527
528 Select the output format, I<pretty> being the default:
529
530     ----- 2012-01:
531     de.comp.datenbanken.ms-access         84
532     de.comp.datenbanken.mysql             88
533     ----- 2012-02:
534     de.comp.datenbanken.ms-access        126
535     de.comp.datenbanken.mysql             21
536
537 I<list> format looks like this:
538
539     2012-01 de.comp.datenbanken.ms-access 84
540     2012-01 de.comp.datenbanken.mysql 88
541     2012-02 de.comp.datenbanken.ms-access 126
542     2012-02 de.comp.datenbanken.mysql 21
543
544 And I<dump> format looks like this:
545
546     # 2012-01:
547     de.comp.datenbanken.ms-access 84
548     de.comp.datenbanken.mysql 88
549     # 2012-02:
550     de.comp.datenbanken.ms-access 126
551     de.comp.datenbanken.mysql 21
552
553 You can remove the comments by using B<--nocomments>, see below.
554
555 =item B<-c>, B<--captions|--nocaptions>
556
557 Add captions to output, like this:
558
559     ----- Report for 2012-01 to 2012-02 (number of postings for each month)
560     ----- Newsgroups: de.comp.datenbanken.*
561     ----- Threshold: 10 => x <= 20 (on average)
562     ----- Grouped by Newsgroups (ascending), sorted by number of postings descending
563
564 False by default.
565
566 =item B<--comments|--nocomments>
567
568 Add comments (group headers) to I<dump> and I<pretty> output. True by default.
569
570 Use I<--nocomments> to suppress anything except newsgroup names/months and
571 numbers of postings. This is enforced when using B<--filetemplate>, see below.
572
573 =item B<--filetemplate> I<filename template>
574
575 Save output to file(s) instead of dumping it to STDOUT. B<groupstats> will
576 create one file for each month (or each newsgroup, accordant to the
577 setting of B<--group-by>, see above), with filenames composed by adding
578 year and month (or newsgroup names) to the I<filename template>, for
579 example with B<--filetemplate> I<stats>:
580
581     stats-2012-01
582     stats-2012-02
583     ... and so on
584
585 B<--nocomments> is enforced, see above.
586
587 =item B<--groupsdb> I<database table>
588
589 Override I<DBTableGrps> from F<newsstats.conf>.
590
591 =back
592
593 =head1 INSTALLATION
594
595 See L<doc/INSTALL>.
596
597 =head1 EXAMPLES
598
599 Show number of postings per group for lasth month in I<pretty> format:
600
601     groupstats
602
603 Show that report for January of 2010 and de.alt.* plus de.test,
604 including display of hierarchy levels:
605
606     groupstats --month 2010-01 --newsgroups de.alt.*:de.test --sums
607
608 Only show newsgroups with 30 postings or less last month, ordered
609 by number of postings, descending, in I<pretty> format:
610
611     groupstats --upper 30 --order-by postings-desc
612
613 Show the total of all postings for the year of 2010 for all groups that
614 had 30 postings or less in every single month in that year, ordered by
615 number of postings in descending order:
616
617     groupstats -m 2010-01:2010-12 -u 30 -b level -r sums -o postings-desc
618
619 The same for the average number of postings in the year of 2010:
620
621     groupstats -m 2010-01:2010-12 -u 30 -b level -r avg -o postings-desc
622
623 List number of postings per group for eacht month of 2010 and redirect
624 output to one file for each month, namend stats-2010-01 and so on, in
625 machine-readable form (without formatting):
626
627     groupstats -m 2010-01:2010-12 -f dump --filetemplate stats
628
629
630 =head1 FILES
631
632 =over 4
633
634 =item F<groupstats.pl>
635
636 The script itself.
637
638 =item F<NewsStats.pm>
639
640 Library functions for the NewsStats package.
641
642 =item F<newsstats.conf>
643
644 Runtime configuration file.
645
646 =back
647
648 =head1 BUGS
649
650 Please report any bugs or feature requests to the author or use the
651 bug tracker at L<http://bugs.th-h.de/>!
652
653 =head1 SEE ALSO
654
655 =over 2
656
657 =item -
658
659 L<doc/README>
660
661 =item -
662
663 l>doc/INSTALL>
664
665 =item -
666
667 gatherstats -h
668
669 =back
670
671 This script is part of the B<NewsStats> package.
672
673 =head1 AUTHOR
674
675 Thomas Hochstein <thh@inter.net>
676
677 =head1 COPYRIGHT AND LICENSE
678
679 Copyright (c) 2010-2012 Thomas Hochstein <thh@inter.net>
680
681 This program is free software; you may redistribute it and/or modify it
682 under the same terms as Perl itself.
683
684 =cut
This page took 0.028929 seconds and 4 git commands to generate.