Add comments and POD.
[usenet/newsstats.git] / groupstats.pl
1 #! /usr/bin/perl -W\r
2 #\r
3 # groupstats.pl\r
4 #\r
5 # This script will get statistical data on newgroup usage\r
6 # form a database.\r
7\r
8 # It is part of the NewsStats package.\r
9 #\r
10 # Copyright (c) 2010 Thomas Hochstein <thh@inter.net>\r
11 #\r
12 # It can be redistributed and/or modified under the same terms under \r
13 # which Perl itself is published.\r
14 \r
15 BEGIN {\r
16   our $VERSION = "0.01";\r
17   use File::Basename;\r
18   push(@INC, dirname($0));\r
19 }\r
20 use strict;\r
21 \r
22 use NewsStats qw(:DEFAULT :TimePeriods :Output :SQLHelper);\r
23 \r
24 use DBI;\r
25 \r
26 ################################# Main program #################################\r
27 \r
28 ### read commandline options\r
29 my %Options = &ReadOptions('m:p:n:o:t:l:b:iscqdg:');\r
30 \r
31 ### read configuration\r
32 my %Conf = %{ReadConfig('newsstats.conf')};\r
33 \r
34 ### override configuration via commandline options\r
35 my %ConfOverride;\r
36 $ConfOverride{'DBTableGrps'}  = $Options{'g'} if $Options{'g'};\r
37 &OverrideConfig(\%Conf,\%ConfOverride);\r
38 \r
39 ### check for incompatible command line options\r
40 # you can't mix '-t', '-b' and '-l'\r
41 # -b/-l take preference over -t, and -b takes preference over -l\r
42 if ($Options{'b'} or $Options{'l'}) {\r
43   if ($Options{'t'}) {\r
44     # drop -t\r
45     warn ("$MySelf: W: You cannot combine thresholds (-t) and top lists (-b) or levels (-l). Threshold '-t $Options{'t'}' was ignored.\n");\r
46     undef($Options{'t'});\r
47   };\r
48   if ($Options{'b'} and $Options{'l'}) {\r
49     # drop -l\r
50     warn ("$MySelf: W: You cannot combine top lists (-b) and levels (-l). Level '-l $Options{'l'}' was ignored.\n");\r
51     undef($Options{'l'});\r
52   };\r
53   # -q/-d don't work with -b or -l\r
54   warn ("$MySelf: W: Sorting by number of postings (-q) ignored due to top list mode (-b) / levels (-l).\n") if $Options{'q'};\r
55   warn ("$MySelf: W: Reverse sorting (-d) ignored due to top list mode (-b) / levels (-l).\n") if $Options{'d'};\r
56 };\r
57 \r
58 ### check output type\r
59 # default output type to 'dump'\r
60 $Options{'o'} = 'dump' if !$Options{'o'};\r
61 # fail if more than one newsgroup is combined with 'dumpgroup' type\r
62 die ("$MySelf: E: You cannot combine newsgroup lists (-n) with more than one group with '-o dumpgroup'!\n") if ($Options{'o'} eq 'dumpgroup' and defined($Options{'n'}) and $Options{'n'} =~ /:|\*/);\r
63 # accept 'dumpgroup' only with -n\r
64 if ($Options{'o'} eq 'dumpgroup' and !defined($Options{'n'})) {\r
65   $Options{'o'} = 'dump';\r
66   warn ("$MySelf: W: You must submit exactly one newsgroup ('-n news.group') for '-o dumpgroup'. Output type was set to 'dump'.\n");\r
67 };\r
68 # set output type to 'pretty' for -l\r
69 if ($Options{'l'}) {\r
70   $Options{'o'} = 'pretty';\r
71   warn ("$MySelf: W: Output type forced to '-o pretty' due to usage of '-l'.\n");\r
72 };\r
73 \r
74 ### get time period\r
75 my ($StartMonth,$EndMonth) = &GetTimePeriod($Options{'m'},$Options{'p'});\r
76 # reset to one month for 'dump' output type\r
77 if ($Options{'o'} eq 'dump' and $Options{'p'}) {\r
78   $StartMonth = $EndMonth;\r
79   warn ("$MySelf: W: You cannot combine time periods (-p) with '-o dump'. Month was set to $StartMonth.\n");\r
80 };\r
81 \r
82 ### init database\r
83 my $DBHandle = InitDB(\%Conf,1);\r
84 \r
85 ### create report\r
86 # get list of newsgroups (-n)\r
87 my ($QueryPart,@GroupList);\r
88 my $Newsgroups = $Options{'n'};\r
89 if ($Newsgroups) {\r
90   # explode list of newsgroups for WHERE clause\r
91   ($QueryPart,@GroupList) = &SQLGroupList($Newsgroups);\r
92 } else {\r
93   # set to dummy value (always true)\r
94   $QueryPart = 1;\r
95 };\r
96 \r
97 # manage thresholds\r
98 if (defined($Options{'t'})) {\r
99   if ($Options{'i'}) {\r
100     # -i: list groups below threshold\r
101     $QueryPart .= ' AND postings < ?';\r
102   } else {\r
103     # default: list groups above threshold\r
104     $QueryPart .= ' AND postings > ?';\r
105   };\r
106   # push threshold to GroupList to match number of binding vars for DBQuery->execute\r
107   push @GroupList,$Options{'t'};\r
108 }\r
109 \r
110 # construct WHERE clause\r
111 # $QueryPart is "list of newsgroup" (or 1),\r
112 # &SQLHierarchies() takes care of the exclusion of hierarchy levels (.ALL)\r
113 # according to setting of -s\r
114 my $WhereClause = sprintf('month BETWEEN ? AND ? AND %s %s',$QueryPart,&SQLHierarchies($Options{'s'}));\r
115 \r
116 # get lenght of longest newsgroup delivered by query for formatting purposes\r
117 # FIXME\r
118 my $MaxLength = &GetMaxLenght($DBHandle,$Conf{'DBTableGrps'},'newsgroup',$WhereClause,$StartMonth,$EndMonth,@GroupList);\r
119 \r
120 my ($OrderClause,$DBQuery);\r
121 # -b (best of / top list) defined?\r
122 if (!defined($Options{'b'}) and !defined($Options{'l'})) {\r
123   # default: neither -b nor -l\r
124   # set ordering (ORDER BY) to "newsgroups" or "postings", "ASC" or "DESC"\r
125   # according to -q and -d\r
126   $OrderClause = 'newsgroup';\r
127   $OrderClause = 'postings' if $Options{'q'};\r
128   $OrderClause .= ' DESC' if $Options{'d'};\r
129   # prepare query: get number of postings per group from groups table for given months and newsgroups\r
130   $DBQuery = $DBHandle->prepare(sprintf("SELECT month,newsgroup,postings FROM %s.%s WHERE %s ORDER BY month,%s",$Conf{'DBDatabase'},$Conf{'DBTableGrps'},$WhereClause,$OrderClause));\r
131 } elsif ($Options{'b'}) {\r
132   # -b is set (then -l can't be!)\r
133   # set sorting order (-i)\r
134   if ($Options{'i'}) {\r
135     $OrderClause = 'postings';\r
136   } else {\r
137     $OrderClause = 'postings DESC';\r
138   };\r
139   # push LIMIT to GroupList to match number of binding vars for DBQuery->execute\r
140   push @GroupList,$Options{'b'};\r
141   # prepare query: get sum of postings per group from groups table for given months and newsgroups with LIMIT\r
142   $DBQuery = $DBHandle->prepare(sprintf("SELECT newsgroup,SUM(postings) AS postings FROM %s.%s WHERE %s GROUP BY newsgroup ORDER BY %s,newsgroup LIMIT ?",$Conf{'DBDatabase'},$Conf{'DBTableGrps'},$WhereClause,$OrderClause));\r
143 } else {\r
144   # -l must be set now, as all other cases have been taken care of\r
145   # set sorting order (-i)\r
146   if ($Options{'i'}) {\r
147     $OrderClause = '<';\r
148   } else {\r
149     $OrderClause = '>';\r
150   };\r
151   # push level and $StartMonth,$EndMonth - again - to GroupList to match number of binding vars for DBQuery->execute\r
152   # FIXME -- together with the query (see below)\r
153   push @GroupList,$Options{'l'};\r
154   push @GroupList,$StartMonth,$EndMonth;\r
155   # prepare query: get number of postings per group from groups table for given months and \r
156   # FIXME -- this query is ... in dire need of impromevent\r
157   $DBQuery = $DBHandle->prepare(sprintf("SELECT month,newsgroup,postings FROM %s.%s WHERE newsgroup IN (SELECT newsgroup FROM %s.%s WHERE %s GROUP BY newsgroup HAVING MAX(postings) %s ?) AND %s ORDER BY newsgroup,month",$Conf{'DBDatabase'},$Conf{'DBTableGrps'},$Conf{'DBDatabase'},$Conf{'DBTableGrps'},$WhereClause,$OrderClause,$WhereClause));\r
158 };\r
159 \r
160 # execute query\r
161 $DBQuery->execute($StartMonth,$EndMonth,@GroupList)\r
162   or die sprintf("$MySelf: E: Can't get groups data for %s to %s from %s.%s: %s\n",$StartMonth,$EndMonth,$Conf{'DBDatabase'},$Conf{'DBTableGrps'},$DBI::errstr);\r
163 \r
164 # output results\r
165 # print caption (-c) with time period if -m or -p is set\r
166 # FIXME - month or period should handled differently\r
167 printf ("----- Report from %s to %s\n",$StartMonth,$EndMonth) if $Options{'c'} and ($Options{'m'} or $Options{'p'});\r
168 # print caption (-c) with newsgroup list if -n is set\r
169 printf ("----- Newsgroups: %s\n",join(',',split(/:/,$Newsgroups))) if $Options{'c'} and $Options{'n'};\r
170 # print caption (-c) with threshold if -t is set, taking -i in account\r
171 printf ("----- Threshold: %s %u\n",$Options{'i'} ? '<' : '>',$Options{'t'}) if $Options{'c'} and $Options{'t'};\r
172 if (!defined($Options{'b'})  and !defined($Options{'l'})) {\r
173   # default: neither -b nor -l\r
174   &OutputData($Options{'o'},$DBQuery,$MaxLength);\r
175 } elsif ($Options{'b'}) {\r
176   # -b is set (then -l can't be!)\r
177   # we have to read in the query results ourselves, as they do not have standard layout\r
178   while (my ($Newsgroup,$Postings) = $DBQuery->fetchrow_array) {\r
179     # we just assign "top x" or "bottom x" instead of a month for the caption\r
180     # FIXME\r
181     print &FormatOutput($Options{'o'}, ($Options{'i'} ? 'Bottom ' : 'Top ').$Options{'b'}, $Newsgroup, $Postings, $MaxLength);\r
182   };\r
183 } else {\r
184   # -l must be set now, as all other cases have been taken care of\r
185   # we have to read in the query results ourselves, as they do not have standard layout\r
186   while (my ($Month,$Newsgroup,$Postings) = $DBQuery->fetchrow_array) {\r
187     # we just switch $Newsgroups and $Month for output generation\r
188     # FIXME\r
189     print &FormatOutput($Options{'o'}, $Newsgroup, $Month, $Postings, 7);\r
190   };\r
191 };\r
192 \r
193 ### close handles\r
194 $DBHandle->disconnect;\r
195 \r
196 __END__\r
197 \r
198 ################################ Documentation #################################\r
199 \r
200 =head1 NAME\r
201 \r
202 groupstats - create reports on newsgroup usage\r
203 \r
204 =head1 SYNOPSIS\r
205 \r
206 B<groupstats> [B<-Vhiscqd>] [B<-m> I<YYYY-MM>] [B<-p> I<YYYY-MM:YYYY-MM>] [B<-n> I<newsgroup(s)>] [B<-t> I<threshold>] [B<-l> I<level>] [B<-b> I<number>] [B<-o> I<output type>] [B<-g> I<database table>]\r
207 \r
208 =head1 REQUIREMENTS\r
209 \r
210 See doc/README: Perl 5.8.x itself and the following modules from CPAN:\r
211 \r
212 =over 2\r
213 \r
214 =item -\r
215 \r
216 Config::Auto\r
217 \r
218 =item -\r
219 \r
220 DBI\r
221 \r
222 =back\r
223 \r
224 =head1 DESCRIPTION\r
225 \r
226 This script create reports on newsgroup usage (number of postings per\r
227 group per month) taken from result tables created by\r
228 F<gatherstats.pl>.\r
229 \r
230 The time period to act on defaults to last month; you can assign\r
231 another month via the B<-m> switch or a time period via the B<-p>\r
232 switch; the latter takes preference.\r
233 \r
234 B<groupstats> will process all newsgroups by default; you can limit\r
235 that to only some newsgroups by supplying a list of those groups via\r
236 B<-n> (see below). You can include hierarchy levels in the output by\r
237 adding the B<-s> switch (see below).\r
238 \r
239 Furthermore you can set a threshold via B<-t> so that only newsgroups\r
240 with more postings per month will be included in the report. You can\r
241 invert that by the B<-i> switch so only newsgroups with less than\r
242 I<threshold> postings per month will be included.\r
243 \r
244 You can sort the output by number of postings per month instead of the\r
245 default (alphabetical list of newsgroups) by using B<-q>; you can\r
246 reverse the sorting order (from highest to lowest or in reversed\r
247 alphabetical order) by using B<-d>.\r
248 \r
249 Furthermore, you can create a list of newsgroups that had consistently\r
250 more (or less) than x postings per month during the whole report\r
251 period by using B<-l> (together with B<i> as needed).\r
252 \r
253 Last but not least you can create a "best of" list of the top x\r
254 newsgroups via B<-b> (or a "worst of" list by adding B<i>).\r
255 \r
256 By default, B<groupstats> will dump a very simple alphabetical list of\r
257 newsgroups, one per line, followed by the number of postings in that\r
258 month. This output format of course cannot sensibly be combined with\r
259 time periods, so you can set the output format by using B<-o> (see\r
260 below). Captions can be added by setting the B<-c> switch.\r
261 \r
262 =head2 Configuration\r
263 \r
264 F<groupstats.pl> will read its configuration from F<newsstats.conf>\r
265 which should be present in the same directory via Config::Auto.\r
266 \r
267 See doc/INSTALL for an overview of possible configuration options.\r
268 \r
269 You can override configuration options via the B<-g> switch.\r
270 \r
271 =head1 OPTIONS\r
272 \r
273 =over 3\r
274 \r
275 =item B<-V> (version)\r
276 \r
277 Print out version and copyright information on B<yapfaq> and exit.\r
278 \r
279 =item B<-h> (help)\r
280 \r
281 Print this man page and exit.\r
282 \r
283 =item B<-m> I<YYYY-MM> (month)\r
284 \r
285 Set processing period to a month in YYYY-MM format. Ignored if B<-p>\r
286 is set.\r
287 \r
288 =item B<-p> I<YYYY-MM:YYYY-MM> (period)\r
289 \r
290 Set processing period to a time period between two month, each in\r
291 YYYY-MM format, separated by a colon. Overrides B<-m>.\r
292 \r
293 =item B<-n> I<newsgroup(s)> (newsgroups)\r
294 \r
295 Limit processing to a certain set of newsgroups. I<newsgroup(s)> can\r
296 be a single newsgroup name (de.alt.test), a newsgroup hierarchy\r
297 (de.alt.*) or a list of either of these, separated by colons, for\r
298 example\r
299 \r
300    de.test:de.alt.test:de.newusers.*\r
301 \r
302 =item B<-t> I<threshold> (threshold)\r
303 \r
304 Only include newsgroups with more than I<threshold> postings per\r
305 month. Can be inverted by the B<-i> switch so that only newsgroups\r
306 with less than I<threshold> postings will be included.\r
307 \r
308 This setting will be ignored if B<-l> or B<-b> is set.\r
309 \r
310 =item B<-l> I<level> (level)\r
311 \r
312 Only include newsgroups with more than I<level> postings per\r
313 month, every month during the whole reporting period. Can be inverted\r
314 by the B<-i> switch so that only newsgroups with less than I<level>\r
315 postings every single month will be included. Output will be ordered\r
316 by newsgroup name, followed by month.\r
317 \r
318 This setting will be ignored if B<-b> is set. Overrides B<-t> and\r
319 can't be used together with B<-q> or B<-d>.\r
320 \r
321 =item B<-b> I<n> (best of)\r
322 \r
323 Create a list of the I<n> newsgroups with the most postings over the\r
324 whole reporting period. Can be inverted by the B<-i> switch so that a\r
325 list of the I<n> newsgroups with the least postings over the whole\r
326 period is generated. Output will be ordered by sum of postings.\r
327 \r
328 Overrides B<-t> and B<-l> and can't be used together with B<-q> or\r
329 B<-d>. Output format is set to I<pretty> (see below).\r
330 \r
331 =item B<-i> (invert)\r
332 \r
333 Used in conjunction with B<-t>, B<-l> or B<-b> to set a lower\r
334 threshold or level or generate a "bottom list" instead of a top list.\r
335 \r
336 =item B<-s> (sum per hierarchy level)\r
337 \r
338 Include "virtual" groups for every hierarchy level in output, for\r
339 example:\r
340 \r
341     de.alt.ALL 10\r
342     de.alt.test 5\r
343     de.alt.admin 7\r
344 \r
345 See the B<gatherstats> man page for details.\r
346 \r
347 =item B<-o> I<output type> (output format)\r
348 \r
349 Set output format. Default is I<dump>, consisting of an alphabetical\r
350 list of newsgroups, each on a new line, followed by the number of\r
351 postings in that month. This default format can't be used with time\r
352 periods of more than one month.\r
353 \r
354 I<list> format is like I<dump>, but will print the month in front of\r
355 the newsgroup name.\r
356 \r
357 I<dumpgroup> format can only be use with a group list (see B<-n>) of\r
358 exactly one newsgroup and is like I<dump>, but will output months,\r
359 followed by the number of postings.\r
360 \r
361 If you don't need easily parsable output, you'll mostly use I<pretty>\r
362 format, which will print a header for each new month and try to align\r
363 newsgroup names and posting counts. Usage of B<-b> will force this\r
364 format.\r
365 \r
366 =item B<-c> (captions)\r
367 \r
368 Add captions to output (reporting period, newsgroups list, threshold).\r
369 \r
370 =item B<-q> (quantity of postings)\r
371 \r
372 Sort by number of postings instead of by newsgroup names.\r
373 \r
374 Cannot be used with B<-l> or B<-b>.\r
375 \r
376 =item B<-d> (descending)\r
377 \r
378 Change sort order to descending.\r
379 \r
380 Cannot be used with B<-l> or B<-b>.\r
381 \r
382 =item B<-g> I<table> (postings per group table)\r
383 \r
384 Override I<DBTableGrps> from F<newsstats.conf>.\r
385 \r
386 =back\r
387 \r
388 =head1 INSTALLATION\r
389 \r
390 See doc/INSTALL.\r
391 \r
392 =head1 EXAMPLES\r
393 \r
394 Show number of postings per group for lasth month in I<dump> format:\r
395 \r
396     groupstats\r
397 \r
398 Show that report for January of 2010 and de.alt.* plus de.test,\r
399 including display of hierarchy levels:\r
400 \r
401     groupstats -m 2010-01 -n de.alt.*:de.test -s\r
402 \r
403 Show that report for the year of 2010 in I<pretty> format:\r
404 \r
405     groupstats -p 2010-01:2010-12 -o pretty\r
406 \r
407 Only show newsgroups with less than 30 postings last month, ordered\r
408 by number of postings, descending, in I<pretty> format:\r
409 \r
410     groupstats -iqdt 30 -o pretty\r
411 \r
412 Show top 10 for the first half-year of of 2010 in I<pretty> format:\r
413 \r
414     groupstats -p 2010-01:2010-06 -b 10 -o pretty\r
415 \r
416 Report all groups that had less than 30 postings every singele month\r
417 in the year of 2010 (I<pretty> format is forced)\r
418 \r
419     groupstats -p 2010-01:2010-12 -il 30\r
420 \r
421 =head1 FILES\r
422 \r
423 =over 4\r
424 \r
425 =item F<groupstats.pl>\r
426 \r
427 The script itself.\r
428 \r
429 =item F<NewsStats.pm>\r
430 \r
431 Library functions for the NewsStats package.\r
432 \r
433 =item F<newsstats.conf>\r
434 \r
435 Runtime configuration file for B<yapfaq>.\r
436 \r
437 =back\r
438 \r
439 =head1 BUGS\r
440 \r
441 Please report any bugs or feature requests to the author or use the\r
442 bug tracker at L<http://bugs.th-h.de/>!\r
443 \r
444 =head1 SEE ALSO\r
445 \r
446 =over 2\r
447 \r
448 =item -\r
449 \r
450 doc/README\r
451 \r
452 =item -\r
453 \r
454 doc/INSTALL\r
455 \r
456 =item -\r
457 \r
458 gatherstats -h\r
459 \r
460 =back\r
461 \r
462 This script is part of the B<NewsStats> package.\r
463 \r
464 =head1 AUTHOR\r
465 \r
466 Thomas Hochstein <thh@inter.net>\r
467 \r
468 =head1 COPYRIGHT AND LICENSE\r
469 \r
470 Copyright (c) 2010 Thomas Hochstein <thh@inter.net>\r
471 \r
472 This program is free software; you may redistribute it and/or modify it\r
473 under the same terms as Perl itself.\r
474 \r
475 =cut\r
This page took 0.025107 seconds and 4 git commands to generate.