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