##################################################################### # # The Perl::Tidy::Logger class writes the .LOG and .ERR files # ##################################################################### package Perl::Tidy::Logger; use strict; use warnings; our $VERSION = '20210111'; sub AUTOLOAD { # Catch any undefined sub calls so that we are sure to get # some diagnostic information. This sub should never be called # except for a programming error. our $AUTOLOAD; return if ( $AUTOLOAD =~ /\bDESTROY$/ ); my ( $pkg, $fname, $lno ) = caller(); my $my_package = __PACKAGE__; print STDERR < undef, log_file => undef, warning_file => undef, fh_stderr => undef, saw_extruce => undef, display_name => undef, is_encoded_data => undef, ); my %args = ( %defaults, @args ); my $rOpts = $args{rOpts}; my $log_file = $args{log_file}; my $warning_file = $args{warning_file}; my $fh_stderr = $args{fh_stderr}; my $saw_extrude = $args{saw_extrude}; my $display_name = $args{display_name}; my $is_encoded_data = $args{is_encoded_data}; my $fh_warnings = $rOpts->{'standard-error-output'} ? $fh_stderr : undef; # remove any old error output file if we might write a new one unless ( $fh_warnings || ref($warning_file) ) { if ( -e $warning_file ) { unlink($warning_file) or Perl::Tidy::Die( "couldn't unlink warning file $warning_file: $!\n"); } } my $logfile_gap = defined( $rOpts->{'logfile-gap'} ) ? $rOpts->{'logfile-gap'} : 50; if ( $logfile_gap == 0 ) { $logfile_gap = 1 } my $filename_stamp = $display_name ? $display_name . ':' : "??"; my $input_stream_name = $display_name ? $display_name : "??"; return bless { _log_file => $log_file, _logfile_gap => $logfile_gap, _rOpts => $rOpts, _fh_warnings => $fh_warnings, _last_input_line_written => 0, _at_end_of_file => 0, _use_prefix => 1, _block_log_output => 0, _line_of_tokens => undef, _output_line_number => undef, _wrote_line_information_string => 0, _wrote_column_headings => 0, _warning_file => $warning_file, _warning_count => 0, _complaint_count => 0, _is_encoded_data => $is_encoded_data, _saw_code_bug => -1, # -1=no 0=maybe 1=for sure _saw_brace_error => 0, _saw_extrude => $saw_extrude, _output_array => [], _input_stream_name => $input_stream_name, _filename_stamp => $filename_stamp, }, $class; } sub get_input_stream_name { my $self = shift; return $self->{_input_stream_name}; } sub get_warning_count { my $self = shift; return $self->{_warning_count}; } sub get_use_prefix { my $self = shift; return $self->{_use_prefix}; } sub block_log_output { my $self = shift; $self->{_block_log_output} = 1; return; } sub unblock_log_output { my $self = shift; $self->{_block_log_output} = 0; return; } sub interrupt_logfile { my $self = shift; $self->{_use_prefix} = 0; $self->warning("\n"); $self->write_logfile_entry( '#' x 24 . " WARNING " . '#' x 25 . "\n" ); return; } sub resume_logfile { my $self = shift; $self->write_logfile_entry( '#' x 60 . "\n" ); $self->{_use_prefix} = 1; return; } sub we_are_at_the_last_line { my $self = shift; unless ( $self->{_wrote_line_information_string} ) { $self->write_logfile_entry("Last line\n\n"); } $self->{_at_end_of_file} = 1; return; } # record some stuff in case we go down in flames sub black_box { my ( $self, $line_of_tokens, $output_line_number ) = @_; my $input_line = $line_of_tokens->{_line_text}; my $input_line_number = $line_of_tokens->{_line_number}; # save line information in case we have to write a logfile message $self->{_line_of_tokens} = $line_of_tokens; $self->{_output_line_number} = $output_line_number; $self->{_wrote_line_information_string} = 0; my $last_input_line_written = $self->{_last_input_line_written}; if ( ( ( $input_line_number - $last_input_line_written ) >= $self->{_logfile_gap} ) || ( $input_line =~ /^\s*(sub|package)\s+(\w+)/ ) ) { my $structural_indentation_level = $line_of_tokens->{_level_0}; $structural_indentation_level = 0 if ( $structural_indentation_level < 0 ); $self->{_last_input_line_written} = $input_line_number; ( my $out_str = $input_line ) =~ s/^\s*//; chomp $out_str; $out_str = ( '.' x $structural_indentation_level ) . $out_str; if ( length($out_str) > 35 ) { $out_str = substr( $out_str, 0, 35 ) . " ...."; } $self->logfile_output( "", "$out_str\n" ); } return; } sub write_logfile_entry { my ( $self, @msg ) = @_; # add leading >>> to avoid confusing error messages and code $self->logfile_output( ">>>", "@msg" ); return; } sub write_column_headings { my $self = shift; $self->{_wrote_column_headings} = 1; my $routput_array = $self->{_output_array}; push @{$routput_array}, <>>) lines levels i k (code begins with one '.' per indent level) ------ ----- - - -------- ------------------------------------------- EOM return; } sub make_line_information_string { # make columns of information when a logfile message needs to go out my $self = shift; my $line_of_tokens = $self->{_line_of_tokens}; my $input_line_number = $line_of_tokens->{_line_number}; my $line_information_string = ""; if ($input_line_number) { my $output_line_number = $self->{_output_line_number}; my $brace_depth = $line_of_tokens->{_curly_brace_depth}; my $paren_depth = $line_of_tokens->{_paren_depth}; my $square_bracket_depth = $line_of_tokens->{_square_bracket_depth}; my $guessed_indentation_level = $line_of_tokens->{_guessed_indentation_level}; my $structural_indentation_level = $line_of_tokens->{_level_0}; $self->write_column_headings() unless $self->{_wrote_column_headings}; # keep logfile columns aligned for scripts up to 999 lines; # for longer scripts it doesn't really matter my $extra_space = ""; $extra_space .= ( $input_line_number < 10 ) ? " " : ( $input_line_number < 100 ) ? " " : ""; $extra_space .= ( $output_line_number < 10 ) ? " " : ( $output_line_number < 100 ) ? " " : ""; # there are 2 possible nesting strings: # the original which looks like this: (0 [1 {2 # the new one, which looks like this: {{[ # the new one is easier to read, and shows the order, but # could be arbitrarily long, so we use it unless it is too long my $nesting_string = "($paren_depth [$square_bracket_depth {$brace_depth"; my $nesting_string_new = $line_of_tokens->{_nesting_tokens_0}; my $ci_level = $line_of_tokens->{_ci_level_0}; if ( $ci_level > 9 ) { $ci_level = '*' } my $bk = ( $line_of_tokens->{_nesting_blocks_0} =~ /1$/ ) ? '1' : '0'; if ( length($nesting_string_new) <= 8 ) { $nesting_string = $nesting_string_new . " " x ( 8 - length($nesting_string_new) ); } $line_information_string = "L$input_line_number:$output_line_number$extra_space i$guessed_indentation_level:$structural_indentation_level $ci_level $bk $nesting_string"; } return $line_information_string; } sub logfile_output { my ( $self, $prompt, $msg ) = @_; return if ( $self->{_block_log_output} ); my $routput_array = $self->{_output_array}; if ( $self->{_at_end_of_file} || !$self->{_use_prefix} ) { push @{$routput_array}, "$msg"; } else { my $line_information_string = $self->make_line_information_string(); $self->{_wrote_line_information_string} = 1; if ($line_information_string) { push @{$routput_array}, "$line_information_string $prompt$msg"; } else { push @{$routput_array}, "$msg"; } } return; } sub get_saw_brace_error { my $self = shift; return $self->{_saw_brace_error}; } sub increment_brace_error { my $self = shift; $self->{_saw_brace_error}++; return; } sub brace_warning { my ( $self, $msg ) = @_; #use constant BRACE_WARNING_LIMIT => 10; my $BRACE_WARNING_LIMIT = 10; my $saw_brace_error = $self->{_saw_brace_error}; if ( $saw_brace_error < $BRACE_WARNING_LIMIT ) { $self->warning($msg); } $saw_brace_error++; $self->{_saw_brace_error} = $saw_brace_error; if ( $saw_brace_error == $BRACE_WARNING_LIMIT ) { $self->warning("No further warnings of this type will be given\n"); } return; } sub complain { # handle non-critical warning messages based on input flag my ( $self, $msg ) = @_; my $rOpts = $self->{_rOpts}; # these appear in .ERR output only if -w flag is used if ( $rOpts->{'warning-output'} ) { $self->warning($msg); } # otherwise, they go to the .LOG file else { $self->{_complaint_count}++; $self->write_logfile_entry($msg); } return; } sub warning { # report errors to .ERR file (or stdout) my ( $self, $msg ) = @_; #use constant WARNING_LIMIT => 50; my $WARNING_LIMIT = 50; # Always bump the warn count, even if no message goes out Perl::Tidy::Warn_count_bump(); my $rOpts = $self->{_rOpts}; unless ( $rOpts->{'quiet'} ) { my $warning_count = $self->{_warning_count}; my $fh_warnings = $self->{_fh_warnings}; my $is_encoded_data = $self->{_is_encoded_data}; if ( !$fh_warnings ) { my $warning_file = $self->{_warning_file}; ( $fh_warnings, my $filename ) = Perl::Tidy::streamhandle( $warning_file, 'w', $is_encoded_data ); $fh_warnings or Perl::Tidy::Die("couldn't open $filename $!\n"); Perl::Tidy::Warn_msg("## Please see file $filename\n") unless ref($warning_file); $self->{_fh_warnings} = $fh_warnings; $fh_warnings->print("Perltidy version is $Perl::Tidy::VERSION\n"); } my $filename_stamp = $self->{_filename_stamp}; if ( $warning_count < $WARNING_LIMIT ) { if ( !$warning_count ) { # On first error always write a line with the filename. Note # that the filename will be 'perltidy' if input is from stdin # or from a data structure. if ($filename_stamp) { $fh_warnings->print( "\n$filename_stamp Begin Error Output Stream\n"); } # Turn off filename stamping unless error output is directed # to the standard error output (with -se flag) if ( !$rOpts->{'standard-error-output'} ) { $filename_stamp = ""; $self->{_filename_stamp} = $filename_stamp; } } if ( $self->get_use_prefix() > 0 ) { $self->write_logfile_entry("WARNING: $msg"); # add prefix 'filename:line_no: ' to message lines my $input_line_number = Perl::Tidy::Tokenizer::get_input_line_number(); if ( !defined($input_line_number) ) { $input_line_number = -1 } my $pre_string = $filename_stamp . $input_line_number . ': '; chomp $msg; $msg =~ s/\n/\n$pre_string/g; $msg = $pre_string . $msg . "\n"; $fh_warnings->print($msg); } else { $self->write_logfile_entry($msg); # add prefix 'filename: ' to message lines if ($filename_stamp) { my $pre_string = $filename_stamp . " "; chomp $msg; $msg =~ s/\n/\n$pre_string/g; $msg = $pre_string . $msg . "\n"; } $fh_warnings->print($msg); } } $warning_count++; $self->{_warning_count} = $warning_count; if ( $warning_count == $WARNING_LIMIT ) { $fh_warnings->print( $filename_stamp . "No further warnings will be given\n" ); } } return; } # programming bug codes: # -1 = no bug # 0 = maybe, not sure. # 1 = definitely sub report_possible_bug { my $self = shift; my $saw_code_bug = $self->{_saw_code_bug}; $self->{_saw_code_bug} = ( $saw_code_bug < 0 ) ? 0 : $saw_code_bug; return; } sub report_definite_bug { my $self = shift; $self->{_saw_code_bug} = 1; return; } sub ask_user_for_bug_report { my ( $self, $infile_syntax_ok, $formatter ) = @_; my $saw_code_bug = $self->{_saw_code_bug}; if ( ( $saw_code_bug == 0 ) && ( $infile_syntax_ok == 1 ) ) { $self->warning(<{_saw_extrude} ) { $self->warning(<warning(<get_added_semicolon_count(); }; if ( $added_semicolon_count > 0 ) { $self->warning(<{_saw_code_bug}; my $rOpts = $self->{_rOpts}; return $saw_code_bug == 1 || $rOpts->{'logfile'} || $rOpts->{'check-syntax'}; } sub finish { # called after all formatting to summarize errors my ( $self, $infile_syntax_ok, $formatter ) = @_; my $rOpts = $self->{_rOpts}; my $warning_count = $self->{_warning_count}; my $saw_code_bug = $self->{_saw_code_bug}; my $save_logfile = ( $saw_code_bug == 0 && $infile_syntax_ok == 1 ) || $saw_code_bug == 1 || $rOpts->{'logfile'}; my $log_file = $self->{_log_file}; if ($warning_count) { if ($save_logfile) { $self->block_log_output(); # avoid echoing this to the logfile $self->warning( "The logfile $log_file may contain useful information\n"); $self->unblock_log_output(); } if ( $self->{_complaint_count} > 0 ) { $self->warning( "To see $self->{_complaint_count} non-critical warnings rerun with -w\n" ); } if ( $self->{_saw_brace_error} && ( $self->{_logfile_gap} > 1 || !$save_logfile ) ) { $self->warning("To save a full .LOG file rerun with -g\n"); } } $self->ask_user_for_bug_report( $infile_syntax_ok, $formatter ); if ($save_logfile) { my $log_file = $self->{_log_file}; my $is_encoded_data = $self->{_is_encoded_data}; my ( $fh, $filename ) = Perl::Tidy::streamhandle( $log_file, 'w', $is_encoded_data ); if ($fh) { my $routput_array = $self->{_output_array}; foreach ( @{$routput_array} ) { $fh->print($_) } if ( $log_file ne '-' && !ref $log_file ) { eval { $fh->close() }; } } } return; } 1;