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