-
Notifications
You must be signed in to change notification settings - Fork 17
/
Softpano.pm
330 lines (314 loc) · 11.3 KB
/
Softpano.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
package Softpano;
## Simplified implementation of a subset of "defensive programming" toolkit stemming from my experience as a compiler writer.
## ABSTRACT: Murphy principle states "Anything that can go wrong will go wrong" but you better be informed if something happens ;-)
## Includes logging subroutine(logme), autocommit, banner, abend, out and helpme
## Copyright Nikolai Bezroukov, 2019-2020.
## Licensed under Perl Artistic license
# Ver Date Who Modification
# ===== ========== ======== ==============================================================
# 01.00 2019/10/09 BEZROUN Initial implementation
# 01.10 2019/10/10 BEZROUN autocommit now allow to save multiple modules in addition to the main program
# 01.20 2019/11/19 BEZROUN mylib parameter added -- location of modules (usually during debugging this is '.'== the current working directory)
# 01.21 2020/08/04 BEZROUN autocommit will works only if $::debug > 0
# 01.22 2020/08/05 BEZROUN out now works with multiple arguments
# 01.30 2020/08/10 BEZROUN tag "##" is not used as the comment prefix for help. Minor chages and corrections
# 01.40 2020/08/17 BEZROUN getops is now implemented in Softpano.pm to allow the repetition of option letter to set the value of options ( -ddd)
# 01.50 2020/09/03 BEZROUN standard_options sub introduced. Logic of logme imporved. Messages summary convered to a sspearate sun -- summary
# 01.60 2020/10/12 BEZROUN Changes in abend. Other small polishing and simplifications of the code.
use v5.10;
use warnings;
use strict 'subs';
use feature 'state';
require Exporter;
our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
@ISA = qw(Exporter);
@EXPORT = qw(autocommit abend banner logme summary out getopts standard_options);
$VERSION = '1.10';
state ($verbosity, $msg_cutlevel2, @ermessage_db, @ercounter); # remember they are statically scoped
$verbosity=3;
#
# NOTE: autocommit used only in debugging mode
# In debug mode it created backup and commit script to GIT repository, if there were changes from previous Version.
#
##autocommit - save script if it runs for subsequnt push into GIT or other versioning system
sub autocommit
{
# parameters
my $archive_dir=shift; # typically home or $HOME/bin
my $mylib=shift; # typically home or $HOME/bin
my @project=@_; # list of files in the project (maintained in the main script)
# local vars
my ($script_timestamp,$fqn);
#
# commit each running Version to the repository to central GIT
#
return if ($::debug==0);
( ! -d $archive_dir ) && `mkdir -p $archive_dir`;
$script_name=substr($0,rindex($0,'/')+1);
_compare_and_save($archive_dir,$0,$script_name);
foreach my $script_name (@project) {
$fqn=$mylib.'/'.$script_name;
_compare_and_save($archive_dir,$fqn,$script_name);
}
} # autocommit
##_compare_and_save -- save the script (internal sub called from autocommit)
sub _compare_and_save
{
my ($archive_dir,$fqn,$script_name)=@_;
my $script_delta=1;
if( -f "$archive_dir/$script_name" ){
if( (-s $fqn ) == (-s "$archive_dir/$script_name") ){
`diff $fqn $archive_dir/$script_name`;
$script_delta=( $? == 0 )? 0: 1;
}
if( $script_delta ){
chomp($script_timestamp=`date -r $archive_dir/$script_name +"%y%m%d_%H%M"`);
`mv $archive_dir/$script_name $archive_dir/$script_name.$script_timestamp`;
}
}
if( $script_delta ){
`cp -p $fqn $archive_dir/$script_name`;
# `cd $archive_dir && git commit $script_name`; # actual commit
}
} #_compare_and_save
#
##helpme -- Read script and extract help from comments starting with ##
#
sub helpme
{
open(SYSHELP,'<',$0);
while($line=<SYSHELP> ){
if( substr($line,0,2) eq "##" ){
print STDERR substr($line,2);
}
} # for
close SYSHELP;
exit;
}
#
## abend Terminate program (variant without mailing)
#
sub abend
{
my $message;
my ($package, $filename, $lineno) = caller;
$message="ABEND in module $package. Line $. : $Pythonizer::IntactLine";
if( scalar(@_)>0 ){
$message.="\n$_[0]";
}
# Syslog might not be availble
out($message);
exit(-255);
} # abend
#
## banner -- Open log and output the banner; if additional arguments given treat them as subtitles
## Depends of two Variable from the main namespace: VERSION and debug
sub banner {
#
# Sanity check
#
state $logfile;
#
# Decode obligatory arguments
#
state $my_log_dir=$_[0];
my $script_name=$_[1];
my $title=$_[2]; # this is an optional argumnet which is print STDERRed as subtitle after the title.
my $log_retention_period=$_[3];
my ($script_mod_stamp,$day);
chomp($script_mod_stamp=`date -r $0 +"%y%m%d_%H%M"`);
if( -d $my_log_dir ){
chomp($day=`date '+%d'`);
if( 1 == $day && $log_retention_period>0 ){
#Note: in debugging script home dir is your home dir and the last thing you want is to clean it ;-)
`find $my_log_dir -name "*.log" -type f -mtime +$log_retention_period -delete`; # monthly cleanup
}
}else{
`mkdir -p $my_log_dir`;
}
my $logstamp=`date +"%y%m%d_%H%M"`; chomp $logstamp;
$logfile="$my_log_dir/$script_name.$logstamp.log";
open(SYSLOG, ">$logfile") || die("Fatal error: unable to open $logfile\n\n");
my $timestamp=`date "+%y/%m/%d %H:%M"`; chomp $timestamp;
$title="\n\n".uc($script_name).": $title (mtime $script_mod_stamp) Started at $timestamp";
out($title);
out("\nLogs are at $logfile. Type -h for help.");
out("=" x length($title));
} #banner
sub summary
# print summary of diagnistic messages
{
my $summary=(scalar(@_)>0) ? $_[0] : 'ERROR STATISTICS: ';
return 0 unless( scalar(@ermessage_db));
for( my $counter=0; $counter<length('WEST'); $counter++ ){
if( defined($ercounter[$counter]) ){
$summary.=" ".substr('WEST',$counter,1).": ".$ercounter[$counter];
}else{
$ercounter[$counter]=0;
}
} # for
($summary) && out("$summary");
if( $ercounter[0] + $ercounter[1] + $ercounter[2] ){
# replicate diagnostics
for( my $severity=2; $severity>=0; $severity-- ){
( $ercounter[$severity] > 0 ) && out("$ermessage_db[$severity]\n\n");
}
($ercounter[2]>0) && out("\n*** PLEASE CHECK $ercounter[2] SERIOUS MESSAGES ABOVE");
return $ercounter[1] + $ercounter[2];
}
return($ercounter[2]);
}
sub logme
# logme -- Simple message generator: Record message in log and STDERR
# PARAMETERS:
# severity, message
# Arg1 Error code (the first letter is severity, the second letter can be used -- T is timestamp -- put timestamp inthe message)
# Arg3 Text of the message
# NOTE: $top_severity, $Verbosity1, $Verbosity1 are state Variables that are initialized via special call to sp:: sp::logmes
{
#our $top_severity; -- should be defined globally
my $error_code=uc(substr($_[0],0,1));
my $error_suffix=(length($_[0])>1) ? substr($_[0],1,1):''; # suffix T means add timestamp
my $message=$_[1];
chomp($message); # we will add \n ourselves
#
# special cases -- ercode "D" means set msglevel1 and msglevel2, ' ' means print STDERR in log and console -- essentially out with messsage header
#
unless( $error_code ){
# Blank error code is old equivalent of out: put obligatory message on console and into log
out($message);
return;
}
#
# detect caller.
#
my ($package, $filename, $lineno) = caller;
#
# Generate diagnostic message from error code, line number and message (optionally timestamp is suffix of error code is T)
#
my $prefix=defined($.) ? "LINE $." : '';
$message="$prefix [$package-$error_code$lineno]: $message";
my $severity=index("WEST",uc($error_code));
if( $severity == -1 ){
# all unknown codes.
out($message);
return;
}
$ercounter[$severity]++; #Increase messages counter for given severity (supressed messages are counted too)
$ermessage_db[$severity] .= "\n\n$message"; #Error history for the ercodes E and S
($severity >= 3-$verbosity ) && say STDERR $message;
say SYSLOG $message;
return;
} # logme
#
## Output message to both log and STDERR
#
sub out
{
if( scalar(@_)==0 ){
say STDERR;
say SYSLOG;
}else{
say STDERR @_;
say SYSLOG @_;
}
}
#
## Invokes the debugger with the message
#
sub stepin
{
if (scalar(@_)) {
logme('S',$_[0]);
}else{
logme('S',"Attempt to activate interactive debugger stepping (works only if Perl is running with -d option) ");
}
return unless($::debug);
$DB::single = 1;
}
sub getopts
{
my ($options_def,$options_hash)=@_;
my ($first,$rest,$pos,$cur_opt);
while(@ARGV){
$cur_opt=$ARGV[0];
last if( substr($cur_opt,0,1) ne '-' );
if ($cur_opt eq '--'){
shift @ARGV;
last;
}
$first=substr($cur_opt,1,1);
$pos = index($options_def,$first);
if( $pos==-1) {
warn("Undefined option -$first skipped without processing\n");
shift(@ARGV);
next;
}
$rest=substr($cur_opt,2);
if( $pos<length($options_def)-1 && substr($options_def,$pos+1,1) eq ':' ){
# option with parameters
if( $rest eq ''){
shift(@ARGV); # get the value of option
unless( @ARGV ){
warn("End of line reached for option -$first which requires argument\n");
$$options_hash{$first}='';
last;
}
if ( $ARGV[0] =~/^-/ ) {
warn("Option -$first requires argument\n");
$$options_hash{$first} = '';
}else{
$$options_hash{$first}=$ARGV[0];
shift(@ARGV); # get next chunk
}
} else {
#value is concatenated with option like -ddd
if( ($first x length($rest)) eq $rest ){
$$options_hash{$first} = length($rest)+1;
}else{
$$options_hash{$first}=$rest;
}
shift(@ARGV);
}
}else {
$$options_hash{$first} = 1; # set the option
if ($rest eq '') {
shift(@ARGV);
} else {
$ARGV[0] = "-$rest"; # there can be other options without arguments after the first
}
}
}
}
sub standard_options
{
my $options_hash=$_[0];
if( exists $$options_hash{'h'} ){
helpme();
}
if( exists $$options_hash{'d'} ){
$$options_hash{'d'}=1 if $$options_hash{'d'} eq '';
if( $$options_hash{'d'} =~/^\d$/ ){
$::debug=$$options_hash{'d'};
}else{
logme('S',"Wrong value of option -d. If can be iether set of d letters like -ddd or an integer like -d 3 . You supplied the value $$options_hash{'d'}\n");
exit 255;
}
($::debug) && logme('W',"Debug flag is set to $::debug");
}
if( exists $$options_hash{'v'} ){
if( $$options_hash{'v'} eq '' ){
$verbosity=2;
}elsif( $$options_hash{'v'} =~/\d/ && length($$options_hash{'v'})==1 ){
$verbosity=3-$$options_hash{'v'};
}elsif( $$options_hash{'v'} =~/\d/ && length($$options_hash{'v'})==2 ){
$verbosity=3-substr($$options_hash{'v'},0,1);
$msg_cutlevel2=3-substr($$options_hash{'v'},1,1);
}
if ($verbosity<0 || $verbosity>3 ){
logme('S',"Wrong value of option -v. Should be an integer from 1 to 3 or letter v repeation -v -vv or -vvv. The ddefault -v 3 (or -vvv)");
exit 255;
}
}
}
1;