PageRenderTime 61ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/util.pm

https://github.com/txdv/psychostats4
Perl | 442 lines | 320 code | 54 blank | 68 comment | 38 complexity | b4417b7ef70d6719580a7e7bf2662852 MD5 | raw file
  1. #
  2. # This file is part of PsychoStats.
  3. #
  4. # Written by Jason Morriss <stormtrooper@psychostats.com>
  5. # Copyright 2008 Jason Morriss
  6. #
  7. # PsychoStats is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # PsychoStats is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with PsychoStats. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. # $Id$
  21. #
  22. package util;
  23. use 5.006;
  24. use strict;
  25. #use warnings;
  26. use POSIX qw( strftime );
  27. use Time::HiRes qw( gettimeofday tv_interval );
  28. use Time::Local;
  29. use Data::Dumper;
  30. require Exporter;
  31. our $VERSION = '1.25.' . (('$Rev$' =~ /(\d+)/)[0] || '000');
  32. our @ISA = qw(Exporter);
  33. our %EXPORT_TAGS = (
  34. 'all' => [ qw(
  35. &ip2int &int2ip &ipwildmask &ipnetmask &ipnetwork &ipbroadcast
  36. &abbrnum &commify &deep_copy
  37. &date &diffdays_ymd &ymd2time &time2ymd &daysinmonth &isleapyear &dayofyear
  38. &compacttime
  39. &simple_interpolate &expandlist &trim &is_regex
  40. &iswindows
  41. &bench &print_r
  42. ) ],
  43. 'win' => [ qw(
  44. &iswindows
  45. ) ],
  46. # :net exports functions dealing with network ipaddrs
  47. 'net' => [ qw(
  48. &ip2int &int2ip &ipwildmask &ipnetmask &ipnetwork &ipbroadcast
  49. ) ],
  50. # :strings exports functions dealing with strings
  51. 'strings' => [ qw(
  52. &simple_interpolate &expandlist &trim &is_regex
  53. ) ],
  54. # :numbers exports functions dealing with numbers
  55. 'numbers' => [ qw(
  56. &abbrnum &commify
  57. ) ],
  58. # :date exports functions dealing with dates
  59. 'date' => [ qw(
  60. &date &diffdays_ymd &daysinmonth &isleapyear &dayofyear
  61. &time2ymd &ymd2time
  62. ) ],
  63. # :time exports functions dealing with time
  64. 'time' => [ qw(
  65. &compacttime
  66. &ymd2time &time2ymd
  67. &bench
  68. ) ],
  69. );
  70. our @EXPORT_OK = ( @{$EXPORT_TAGS{'all'}} );
  71. # I use print_r a lot and I hate having to import it implicitly.
  72. our @EXPORT = qw( &print_r );
  73. # Copies a data structure (hashes and arrays with sub hashes and arrays)
  74. # Objects and ties are not supported. Also, if any circular refs exist they
  75. # will cause an infinite loop.
  76. sub deep_copy {
  77. # credit: http://www.stonehenge.com/merlyn/UnixReview/col30.html
  78. my $this = shift;
  79. if (not ref $this) {
  80. return $this;
  81. } elsif (ref $this eq 'ARRAY') {
  82. return [ map deep_copy($_), @$this ];
  83. } elsif (ref $this eq 'HASH') {
  84. return +{ map { $_ => deep_copy($this->{$_}) } keys %$this };
  85. } else {
  86. # This is harsh, but this sub is only meant to be a simple deep
  87. # copy function. Objects, ties and recursion are not supported.
  88. die "DEEP_COPY error. Unknown type for $this?"
  89. }
  90. }
  91. # Converts an IP "1.2.3.4" into a 32bit integer. Ignores any :port on the IP
  92. sub ip2int {
  93. my ($ip, $port) = split(/:/, shift, 2); # strip off any port if it's present
  94. my ($i1,$i2,$i3,$i4) = split(/\./, $ip);
  95. return $i4 | ($i3 << 8) | ($i2 << 16) | ($i1 << 24);
  96. }
  97. # Converts a 32bit integer into its IP "1.2.3.4" representation
  98. sub int2ip {
  99. my $num = shift;
  100. return join(".",
  101. ($num & 0xFF000000) >> 24,
  102. ($num & 0x00FF0000) >> 16,
  103. ($num & 0x0000FF00) >> 8,
  104. ($num & 0x000000FF)
  105. );
  106. }
  107. # returns the network mask for the bits specified (1..32)
  108. sub ipnetmask {
  109. my $bits = shift;
  110. my $num = 0xFFFFFFFF;
  111. my $mask = ($num >> (32 - $bits)) << (32 - $bits);
  112. return int2ip($mask);
  113. }
  114. # returns the wildcard mask for the bits specified (1..32)
  115. sub ipwildmask {
  116. my $num = ip2int( ipnetmask(shift) );
  117. $num = $num ^ 0xFFFFFFFF;
  118. return int2ip($num);
  119. }
  120. # returns the network IP of the CIDR block given
  121. sub ipnetwork {
  122. my ($num, $bits) = @_;
  123. $num = ip2int($num) unless $num =~ /^\d+$/;
  124. return int2ip($num & ip2int(ipnetmask($bits)));
  125. }
  126. # returns the broadcast IP of the CIDR block given
  127. sub ipbroadcast {
  128. my ($num, $bits) = @_;
  129. $num = ip2int($num) unless $num =~ /^\d+$/;
  130. my @ip = split(/\./, int2ip($num & ip2int(ipnetmask($bits))));
  131. my @wc = split(/\./, ipwildmask($bits));
  132. my $bc = "";
  133. for (my $i=0; $i < 4; $i++) { $ip[$i] += $wc[$i]; }
  134. return join(".",@ip);
  135. }
  136. # converts a large integer into KB,MB, etc totals (1024 = 1 K)
  137. # $digits is the number of decimal places to use (0 by default)
  138. # $blocksize is the size of each step (1024 by default)
  139. # $tail is an arrayref of strings for each blocksize step. (defaults to byte strings, B, KB, MB, etc)
  140. sub abbrnum {
  141. my ($num, $digits, $blocksize, $tail) = @_;
  142. my @size = ref $tail ? @$tail : (' B',' KB',' MB', ' GB', ' TB');
  143. my $i = 0;
  144. $digits ||= 0;
  145. $blocksize ||= 1024;
  146. return "0" . $size[0] unless $num;
  147. while (($num >= $blocksize) and ($i < @size)) {
  148. $num /= $blocksize;
  149. $i++;
  150. }
  151. return sprintf("%." . $digits . "f",$num) . $size[$i];
  152. }
  153. # returns a large number with commas
  154. sub commify {
  155. my $num = reverse shift; # reversing the string first makes things a LOT easier
  156. $num =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g; # insert the commas ...
  157. return scalar reverse $num; # reverse it again to restore the actual number (with commas)
  158. }
  159. # returns the number of days between the two dates, format: "YYYY-MM-DD"
  160. # $char specifies the seperator used in the date, defaults to '-'
  161. sub diffdays_ymd {
  162. my ($d1, $d2, $char) = @_;
  163. my ($date1, $date2, $diff, @ary);
  164. $char ||= '-';
  165. @ary = reverse split($char, $d1);
  166. $ary[1]--;
  167. $ary[2] -= 1900;
  168. $date1 = timelocal(0,0,12,@ary);
  169. @ary = reverse split($char, $d2);
  170. $ary[1]--;
  171. $ary[2] -= 1900;
  172. $date2 = timelocal(0,0,12,@ary);
  173. $diff = $date1 - $date2;
  174. return sprintf("%.0f", $diff / (60*60*24));
  175. }
  176. # converts a date of "YYYY-MM-DD" into a unix epoch timestamp
  177. sub ymd2time {
  178. my ($date, $char) = @_;
  179. $char ||= '-';
  180. my @ary = reverse split($char, $date);
  181. $ary[1]--;
  182. $ary[2] -= 1900;
  183. return timelocal(0,0,12,@ary);
  184. }
  185. sub time2ymd {
  186. my ($time, $char) = @_;
  187. $char ||= '-';
  188. strftime("%Y-%m-%d", localtime($time));
  189. }
  190. {
  191. my @dim = (31,28,31,30,31,30,31,31,30,31,30,31); # static variables for dayssince1bc function ...
  192. my @mdim = (31,29,31,30,31,30,31,31,30,31,30,31);
  193. my $daysin4centuries = 146097; # static variables for datefrom1bc function ...
  194. my $daysin1century = 36524;
  195. my $daysin4years = 1461;
  196. my $daysin1year = 365;
  197. # returns the number of days in the given month (1..12) or epoch
  198. # timestamp, or undef for current epoch time
  199. sub daysinmonth {
  200. my ($year, $month) = @_;
  201. if (!defined $month) { # we assume $year is an epoch timestamp, since there's no month
  202. ($month, $year) = (localtime($year))[4,5];
  203. $year += 1900;
  204. } else {
  205. $month--;
  206. }
  207. return isleapyear($year) ? $mdim[$month] : $dim[$month];
  208. }
  209. } # end of local date variables
  210. # Returns true if the year given is a leap year or false otherwise. the year
  211. # MUST be a 4 digit year '2003'
  212. sub isleapyear {
  213. my ($year) = @_;
  214. return 0 unless $year % 4 == 0;
  215. return 1 unless $year % 100 == 0;
  216. return 0 unless $year % 400 == 0;
  217. return 1;
  218. }
  219. # Returns the day of the year (1 to 366)
  220. sub dayofyear {
  221. my ($year, $month, $day) = @_;
  222. my @days = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365); # total days at the end of each month
  223. my $leapyear = 0;
  224. $leapyear = 1 if $month > 2 and isleapyear($year);
  225. return ($days[$month-1] + $day + $leapyear);
  226. }
  227. # Returns the date formated according to the format given (partially mimics PHPs
  228. # date() function) one could always use the POSIX strftime() function too, which
  229. # is much better than this.
  230. my @weekdays = ('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');
  231. my @weekabbr = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
  232. my @months = ('January','February','March','April','May','June','July','August','September','October','November','December');
  233. my @monthabbr= ('Jan','Feb','Mar','Apr','May','June','July','Aug','Sept','Oct','Nov','Dec');
  234. sub date {
  235. my $format = shift;
  236. my $now = shift || time();
  237. my ($sec,$min,$hour,$day,$mon,$year,$weekday,$yearday,$isdst) = localtime($now);
  238. my $ampm = '';
  239. $mon++;
  240. $year += 1900;
  241. $yearday++;
  242. my $year2k = sprintf("%02d", $year % 100);
  243. foreach my $val ($sec,$min,$hour,$day,$mon) { $val = '0'.$val if length($val) < 2; }
  244. my $tmptime = &getrealtime("$hour:00:00");
  245. my $hour12 = substr($tmptime, 0, 2);
  246. $ampm = substr($tmptime, 8, 2);
  247. $format =~ s/%a/lc $ampm/ge; # am/pm
  248. $format =~ s/%A/uc $ampm/ge; # AM/PM
  249. $format =~ s/%d/$day/ge; # 01..31 day
  250. $format =~ s/%D/$weekabbr[$weekday]/ge; # Sun..Sat
  251. $format =~ s/%F/$months[$mon-1]/ge; # Janurary..December
  252. $format =~ s/%h/$hour12/ge; # 00..12 hour
  253. $format =~ s/%H/$hour/ge; # 00..24 hour
  254. $format =~ s/%i/$min/ge; # 00..59 minutes
  255. $format =~ s/%I/$isdst/ge; # DST=0/1
  256. $format =~ s/%l/$weekdays[$weekday]/ge; # Sunday..Saturday
  257. $format =~ s/%m/$mon/ge; # 01..12 month
  258. $format =~ s/%M/$monthabbr[$mon-1]/ge; # Jan..Dec
  259. $format =~ s/%r/gmtime($now)/ge; # RFC 822 formatted date; i.e. "Thu, 21 Dec 2000 16:01:07" (no gmt diff: +0200)
  260. $format =~ s/%s/$sec/ge; # 00..59 seconds
  261. $format =~ s/%w/$weekday/ge; # 0..6 weekday number (0=sunday .. 6=saturday)
  262. $format =~ s/%Y/$year/ge; # 2001 year
  263. $format =~ s/%y/$year2k/ge; # 01 year
  264. $format =~ s/%z/$yearday/ge; # 0 .. 365 day of the year
  265. return $format;
  266. }
  267. # Converts military time to standard time
  268. sub getrealtime {
  269. my ($thetime) = @_;
  270. my ($h,$m,$s) = split(/:/,$thetime);
  271. my $ampm = "am";
  272. if ($h == 12) { $ampm = "pm"; }
  273. elsif ($h > 12) { $h = $h - 12; $ampm = "pm"; }
  274. elsif ($h == 0) { $h = 12; }
  275. $h = "0$h" if (length($h) < 2);
  276. return "$h:$m:$s" . $ampm;
  277. }
  278. # returns the seconds into total hours, minutes and seconds
  279. sub compacttime {
  280. my ($seconds, $format) = @_;
  281. my ($d,$h,$m,$s) = ('00','00','00','00');
  282. my $str = $format || 'hh:mm:ss';
  283. $seconds ||= 0;
  284. my $old = $seconds;
  285. if ( ($str =~ /dd/) and ($seconds / (60*60*24)) >= 1) { $d = sprintf("%d", $seconds / (60*60*24)); $seconds -= $d * (60*60*24)}
  286. if ( ($str =~ /hh/) and ($seconds / (60*60)) >= 1) { $h = sprintf("%d", $seconds / (60*60)); $seconds -= $h * (60*60)}
  287. if ( ($str =~ /mm/) and ($seconds / 60) >= 1) { $m = sprintf("%d", $seconds / 60); $seconds -= $m * (60)}
  288. if ( ($str =~ /ss/) and ($seconds % 60) >= 1) { $s = sprintf("%d", $seconds % 60);}
  289. $str =~ s/dd/sprintf("%02d",$d)/e;
  290. $str =~ s/hh/sprintf("%02d",$h)/e;
  291. $str =~ s/mm/sprintf("%02d",$m)/e;
  292. $str =~ s/ss/sprintf("%02d",$s)/e;
  293. return $str;
  294. }
  295. # A very simple version of an interpolating routine to do very simple variable
  296. # substitution on a string. This allows for 2 levels of hash variables ONLY. ie:
  297. # $key, or $key.var (but not $key.var.subvar) .. this is only meant to be a
  298. # SIMPLE interpolator :-) ... If a code ref is found in a $token, it will be
  299. # called and it's return value used. This function was updated to use tokens
  300. # like {$var.value} instead of $var.value
  301. sub simple_interpolate {
  302. my ($str, $data, $fill) = @_;
  303. my ($var1,$var2, $rep, $rightpos, $leftpos, $varlen);
  304. $fill ||= 0;
  305. # match $token or $key.token (but not $123token)
  306. while ($str =~ /\{\$([a-z][a-z\d_]+)(?:\.([a-z][a-z\d_]+))?\}/gsi) {
  307. $var1 = lc $1;
  308. $var2 = lc($2 || '');
  309. $varlen = length($var1 . $var2) + 2;
  310. if (exists $data->{$var1}) {
  311. if ($var2 ne '') {
  312. $rep = exists $data->{$var1}{$var2} ? $data->{$var1}{$var2} : ($fill) ? "$var1.$var2" : '';
  313. $varlen++; # must account for the extra '.' in the $token.var
  314. } else {
  315. $rep = $data->{$var1};
  316. }
  317. if (ref $rep eq 'CODE') {
  318. my $value = &$rep;
  319. $rep = $value;
  320. }
  321. } else {
  322. $rep = $fill ? $var1 : '';
  323. }
  324. $rightpos = pos($str) - 1;
  325. $leftpos = $rightpos - $varlen;
  326. substr($str, $leftpos, $rightpos-$leftpos+1, $rep);
  327. }
  328. return $str;
  329. }
  330. sub iswindows { lc substr($^O,0,-2) eq "mswin" }
  331. sub print_r { # mimic PHP.. sorta
  332. #local $Data::Dumper::Indent = 1;
  333. local $Data::Dumper::Sortkeys = 1;
  334. print Dumper(@_);
  335. }
  336. # expands a range of numbers in a list, ie: 1,5,10-20,50-100,123,140
  337. sub expandlist {
  338. my ($str) = @_;
  339. $str =~ s/[^,\d-]//g; # strip everything except numbers, dashes and commas
  340. $str =~ s/-{2,}/-/g; # reduce duplicate dashes
  341. $str =~ s/,{2,}/,/g; # reduce duplicate commas
  342. $str =~ s/,-|-,//g; # remove combinations of ",-" or "-,"
  343. my @parts = split(/,/,$str);
  344. my @range = ();
  345. while (defined(my $part = shift @parts)) {
  346. my ($low, $high) = split(/-/, $part);
  347. if (defined $high) {
  348. $high = $low if $high eq '';
  349. if ($high > $low) {
  350. push(@range, $low..$high);
  351. } else {
  352. push(@range, $high..$low);
  353. }
  354. } else {
  355. push(@range, $low);
  356. }
  357. }
  358. my %uniq;
  359. @range = grep(!$uniq{$_}++, @range);
  360. return wantarray ? @range : [ @range ];
  361. }
  362. sub trim {
  363. my ($str) = @_;
  364. $str =~ s/^\s+//;
  365. $str =~ s/\s+$//;
  366. return $str;
  367. }
  368. # returns true if the string given is a valid regex. Sets $@ on failure.
  369. sub is_regex {
  370. my ($str) = @_;
  371. eval { no re 'eval'; qr/$str/ };
  372. return $@ ? 0 : 1;
  373. }
  374. {
  375. my %b = ();
  376. sub bench {
  377. my $a = $_[0];
  378. if (exists $b{$a}) {
  379. my $t = tv_interval($b{$a});
  380. printf("bench '$a': %0.7f seconds\n", $t);
  381. delete $b{$a};
  382. return $t;
  383. } else {
  384. $b{$a} = [ gettimeofday ];
  385. }
  386. }
  387. }
  388. 1;