DB-Hooks

 view release on metacpan or  search on metacpan

lib/DB/Commands.pm  view on Meta::CPAN

	my( $one, $count, $format ) =  shift =~ m/^(-?)(\d+)(?:\s+(\w*))?$/;
	$format //=  'oneline'; # TODO: Get default format from options

	# TODO: Clarify code
	my $frames =  DB::frames( defined $count? $count +($one?1:0) : () );
	my $fn =  0; # frame number
	if( $one ) { # At this case $count means target frame
		return get_command 'interact'   unless $count <= @$frames;
		$fn     =  $count;
		$frames =  [ $frames->[ $fn ] ];
	}

	for my $frame ( @$frames ) {
		my( $package, $file, $line, $subname ) =  @$frame[1..4];
		$fn =  0   if $subname eq 'DB::DB';
		my $args    =  $frame->[5] ? $frame->[0] : '';
		my $context =  $frame->[6]? '@' : defined $frame->[6]? '$' : ';';

		if( $args ) {
			$args =  join ', ', map{ defined $_ ? $_ : 'undef' } @$args;
			$args = "($args)";
		}

		DB::say eval $trace_format->{ $format };
		DB::say $@   if $@;
		$fn++;
	}

	return get_command 'interact';
}



sub cmd_debug_expr {
	my( $expr ) =  @_;

	$expr =~ s/(?<!\\);/;\n/;
	$expr =~ s/\\;/;/;

	return [ stay {
		DB::say "Debug result: @{ $_[0] }";
	}
		,"\$^D |= (1<<30);\$DB::single= 1;\n" .$expr
	]
}



sub get_stash {
    require 'Package/Stash.pm'; # BUG? spoils DB:: by emacs, dbline

    my $pkg =  DB::state( 'package' );
    my $stash =  Package::Stash->new( $pkg )->get_all_symbols();

    return $stash, $pkg;
}



sub get_variables {
    require 'PadWalker.pm';
    # my @subs;
    # my $package_name =  caller( DB::state( 'level.frame' ) )[0];
    # foreach $name ( keys %{$package_name .'::'} ) {
    #   push @subs, $name;
    # }

    my $my  =  PadWalker::peek_my ( DB::state( 'level.frame' ) );
    my $our =  PadWalker::peek_our( DB::state( 'level.frame' ) );



    return $my, $our;
}



sub cmd_variables {
	my( $level, $flags, $expr ) =
		(' '.shift) =~ m/^(?:\s+-(\d+))?(?:\s+([amogucs]+))?(?:\s+(.*))?$/;

	$flags //=  '';

	my $type =  0;
	for( split '', $flags ) {
		$type |= ~0   if /^a|all$/;
		$type |= 1    if /^m|my$/;
		$type |= 2    if /^o|our$/;
		$type |= 4    if /^g|global$/;
		$type |= 8    if /^u|used$/;
		$type |= 16   if /^c|closured$/;
		$type |= 24   if /^s|sub$/;       #u+c
	}
	$level //=  DB::state( 'list.level' ) // 0;
	$type  ||=  DB::state( 'vars.type' ) || 3   unless $expr;

	my( $frames, $evals ) =  ( 0, 0 );
	{ # Count debugger and eval frames
		my @frame;
		while( @frame =  caller( $frames++ )  and  $frame[3] ne 'DB::DB' ) {
			$evals++   if $frame[3] eq '(eval)';
		}
		while( @frame =  caller( $frames++ )  and  $level-- > 0 ) {
			$evals++   if $frame[3] eq '(eval)';
		}
		$frames--;
	}



	#FIX: When we debug debugger we can not 'go <line>' we always stops at
	#require at third line at PadWalker.pm. Debug who set $DB::state = 1
	require 'PadWalker.pm';

	my $my  =  PadWalker::peek_my ( $frames -$evals );
	my $our =  PadWalker::peek_our( $frames -$evals );

	if( $type & 1 ) {
		# TODO: for terminals which support color show
		# 1. not used variables as grey
		# 2. closed over variables as green or bold
		DB::say "MY:", join( ', ', sort keys %$my );
	}

	if( $type & 2 ) {
		DB::say "OUR:", join( ', ', sort keys %$our );
	}

	if( $type & 4 ) {
		require 'Package/Stash.pm'; # BUG? spoils DB:: by emacs, dbline

		my $pkg =  DB::state( 'package' );
		my $stash =  Package::Stash->new( $pkg )->get_all_symbols();
		# Show only user defined variables
		# TODO? implement verbose flag
		# Probably, as MST adviced, we can capture the contents of 'main'
		# *before* you evaluate anything
		if( $pkg eq 'main' ) {
			for( keys %$stash ) {
				delete $stash->{ $_ }   if /::$/;
				delete $stash->{ $_ }   if /^_</;
				delete $stash->{ $_ }   if /^[\x00-\x1f]/; #Remove $^ variables
			}

			delete @$stash{ qw# STDERR stderr STDIN stdin STDOUT stdout # };
			delete @$stash{ qw# BEGIN INIT CHECK END # };
			delete @$stash{ qw# SIG INC F ] ENV ; > < ) ( $ " _ # }; # a b
			delete @$stash{ qw# - + ` & ' #, 0..99 };
			# BUG? warning still exists despite on explicit escaping of ','
			delete @$stash{ ',', qw# ARGV ARGVOUT . / \\ | # };
			delete @$stash{ qw# % - : = ^ ~ # };
			delete @$stash{ qw# ! @ ? # };
		}
		delete $stash->{ sub }   if $pkg eq 'DB';

		my @globals =  ();
		my %sigil =  ( SCALAR => '$', ARRAY => '@', HASH => '%' );
		for my $key ( keys %$stash ) {
			my $glob =  $stash->{ $key };
			for my $type ( keys %sigil ) {
				next   unless defined *{ $glob }{ $type };
				next   if $type eq 'SCALAR'  &&  !defined $$glob;
				next   if $key =~ /::/;
				push @globals, $sigil{ $type } .$key;
			}
		}

		DB::say "GLOBAL:", join( ', ', sort @globals );
	}

	if( $type & 8 ) {
		DB::say "USED:";

		# First element starts at -1 subscript
		# FIX: When debug debugger and we step over this statement
		# the $sub contain reference to &vars instead of name of last
		# client's sub
		my $sub =  DB::frames( $level +2 )->[-1][4];
		if( !defined $sub ) {
			# TODO: Mojolicious::__ANON__[/home/feelsafe/perl_lib/lib/perl5/Mojolicious.pm:119]
			# convert this to subroutine refs
			DB::say "Not in a sub: $sub";
		}
		else {
			$sub =  \&$sub;
			DB::say join( ', ', sort keys %{ PadWalker::peek_sub( $sub ) } );
		}
	}

	if( $type & 16 ) {
		DB::say "CLOSED OVER:";

		# First elements starts at -1 subscript
		my $sub =  DB::frames( $level +2 )->[-1][4];
		if( !defined $sub ) {
			DB::say "Not in a sub: $sub";
		}
		else {
			$sub =  \&$sub;
			DB::say join( ', ', sort keys %{ (PadWalker::closed_over( $sub ))[0] } );
		}
	}

	if( $expr ) {
		(my $vars, $expr) =  split ';', $expr, 2;                          # We can explicitly define variables by enumerating them
		my @vars =  ( $vars || $expr ) =~ m/([\$\%\@]\w+)/g;               # Extract variables from varlist or expression
		$expr //=  $vars;                                                  # By default just dump variables
		@vars =  keys %{{ map{ $_ => 1 } @vars }};                         # Make vars uniq

		# PadWalder returns references to variables. We derefference them at
		# our subroutine. Thus we able to evalutate @array, %hash variables
		# along with $scalar
		$vars =  ''; my $idx =  0;
		for( @vars ) {
			m/^([\$\@\%])/;
			# TODO: Create alias'es instead of new variables
			$vars .=  "\tmy $_ =  $1\{ \$_[$idx] };\n";
			$idx++;
		}

		# TODO: We should eval at user's package to make sub_name( ... ) work
		# Currently we must use Package::Name::sub_name( ... )
		my $result =  eval "return sub{ \n$vars\t$expr\n }";
		DB::say( $@ ), return 1   if $@;


		# Replace variables by theirs values
		for( @vars ) {
			($_ =  $my ->{ $_ }), next   if exists $my ->{ $_ };
			($_ =  $our->{ $_ }), next   if exists $our->{ $_ };

			DB::say "Variable '$_' does not exists at this scope";
			$_ =  \undef;
		}

		DB::say DB::dumper $_   for $result->( @vars );
	}

	return 1;
}



sub cmd_edit_file {
	die 'You should setup EDITOR environment variable'   unless $ENV{ EDITOR };

	## INIT
	my( $file, $line, $editor_args ) =  shift =~ m/^${file_line}?(.*)$/;
	$editor_args //=  '';

	if( !defined $line ) {
		$line =  DB::state( 'list.line' ) -1;

		# Put cursor at required line. EDITOR should center scroll to this line
		$line -=  defined DB::state( 'list.level' )? $lines_before : $lines_after;
	}
	$line =  ':' .$line;

	$file =  current_file( $file ) =~ s/^~/$DB::options{ pwd }/r;
	$file =  `realpath --relative-base $ENV{ PWD } $file`;# Get relative to current symlink



( run in 1.872 second using v1.01-cache-2.11-cpan-b301d465b3d )