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