Several updates, most noticeably a new ncurses interface (using argument --curses) Mantis: 2691

git-svn-id: file:///srv/svn/scanner/trunk@8 a0501263-5b7a-4423-a8ba-1edf086583e7
This commit is contained in:
Correl Roush 2008-02-28 16:39:33 +00:00
parent fc258c4230
commit 570f8aaa65
7 changed files with 511 additions and 33 deletions

View file

@ -102,6 +102,7 @@ class HTMLOutput extends OutputModule {
</html>' . "\n"
);
fclose( $output );
return true;
}
}

View file

@ -20,6 +20,7 @@ class TextOutput extends OutputModule {
);
}
fclose( $output );
return true;
}
}

View file

@ -64,20 +64,12 @@ class FunctionsModule extends ScannerModule {
$include_files = array_merge( $include_files, $output );
}
err( "Parsing function libraries...\n" );
$counter = 0; $total = count( $include_files ); $lastpct = 0;
$counter = 0; $total = count( $include_files );
foreach( $include_files as $file ) {
$counter++;
if( $counter == 1 ) { err( 0 ); }
else {
$pct = intval( $counter / $total * 100 );
if( $pct != $lastpct && $pct % 2 == 0 ) {
err( $pct % 10 == 0 ? $pct : '.' );
$lastpct = $pct;
}
}
scan_progress( $counter, $total, 'Scanning Function Libraries [%d/%d]' );
$parser->parseFile( $file );
}
err( "\n" );
$this->internal_functions = get_defined_functions();
$this->internal_functions = $this->internal_functions['internal'];
}

27
ncurses/example.php Normal file
View file

@ -0,0 +1,27 @@
<?php
require_once( 'ncurses.php' );
class MyApp extends NcApp {
function run() {
$nc_main = new NcWindow( $this, 0, 0, 0, 0 );
$nc_main->title( 'Hello There' );
$nc_main->write( 0, 0, file_get_contents( __FILE__ ) );
$nc_main->get_char();
$nc_small = new NcWindow( $nc_main, 10, 60, 5, 20 );
$nc_small->title( 'Progress Bars' );
ncurses_attron( NCURSES_A_REVERSE );
$bar_1 = new NcProgressBar( $nc_small, 0, 0, 0, 100, true );
$bar_1->pos( 57 );
$bar_2 = new NcProgressBar( $nc_small, 0, 1, 0, 100, false );
$bar_2->pos( 11 );
$nc_small->write( 3, 0, 'Text Input:' );
$input = new NcTextInput( $nc_small, -12, 3, 12 );
$foo = $input->get();
$nc_small->write_centered( 4, 'Press any key to destroy this window' );
$nc_small->get_char();
$nc_small->hide();
$nc_main->get_char();
}
}
$app = new MyApp();
?>

332
ncurses/ncurses.php Normal file
View file

@ -0,0 +1,332 @@
<?php
class NcApp {
var $screen;
function __construct( $echo = false, $cursor = false ) {
ncurses_init();
$echo ? ncurses_echo() : ncurses_noecho();
ncurses_curs_set( (bool)$cursor );
$this->screen = ncurses_newwin( 0, 0, 0, 0 );
ncurses_refresh();
$this->run();
}
function __destruct() {
ncurses_clear();
ncurses_refresh();
ncurses_end();
}
function run() {
}
}
class NcWindow {
var $frame;
var $window;
var $panels = array();
var $children = array();
var $parent = false;
var $x;
var $y;
var $width;
var $height;
var $title;
function __construct( &$parent, $height, $width, $y, $x, $framed = true ) {
if( $parent instanceof NcWindow ) {
$this->parent = $parent;
//TODO: Find out why this drives php nuts at script termination
$this->parent->add_child( $this );
$x += $parent->x;
$y += $parent->y;
$width += $width <= 0 ? $parent->width : 0;
$height += $height <= 0 ? $parent->height : 0;
}
if( $framed ) {
$this->frame = ncurses_newwin( $height, $width, $y, $x );
ncurses_getmaxyx( $this->frame, $max_y, $max_x );
if( $max_y >= 2 && $max_x >= 2 ) {
$this->window = ncurses_newwin( $max_y - 2, $max_x - 2, $y + 1, $x + 1 );
ncurses_wborder( $this->frame, 0,0, 0,0, 0,0, 0,0 );
ncurses_wrefresh( $this->frame );
$this->panels[] = ncurses_new_panel( $this->frame );
} else {
$this->window = $this->frame;
$this->frame = false;
}
} else {
$this->frame = false;
$this->window = ncurses_newwin( $height, $width, $y, $x );
ncurses_getmaxyx( $this->window, $max_y, $max_x );
}
$this->width = $this->frame ? $max_x - 2 : $max_x;
$this->height = $this->frame ? $max_y - 2 : $max_y;
$this->x = $this->frame ? $x + 1 : $x;
$this->y = $this->frame ? $y + 1 : $y;
ncurses_wrefresh( $this->window );
$this->panels[] = ncurses_new_panel( $this->window );
ncurses_update_panels();
}
function __destruct() {
foreach( $this->panels as $id => $panel ) {
ncurses_del_panel( $this->panels[$id] );
}
ncurses_delwin( $this->window );
if( $this->frame !== false ) {
ncurses_delwin( $this->frame );
}
}
function title( $value = false ) {
if( $value === false ) {
return $this->title;
} elseif( $this->frame !== false ) {
$this->title = $value;
ncurses_wborder( $this->frame, 0,0, 0,0, 0,0, 0,0 );
ncurses_mvwaddstr( $this->frame, 0, 1, $this->title );
ncurses_update_panels();
ncurses_doupdate();
}
}
function attribute_on( $attribute ) {
ncurses_wattron( $this->window, $attribute );
}
function attribute_off( $attribute ) {
ncurses_wattroff( $this->window, $attribute );
}
function write( $y, $x, $text, $update = true ) {
$y = $y < 0 ? $this->height + $y : $y;
$x = $x < 0 ? $this->width + $x: $x;
$lines = preg_split( '/\r?(\n|\r)/', $text );
foreach( $lines as $id => $line ) {
ncurses_mvwaddstr( $this->window, $y + $id, $x, $line );
}
if( $update ) {
ncurses_update_panels();
ncurses_doupdate();
}
}
function write_centered( $y, $text, $update = true ) {
$this->write(
$y,
floor( $this->width / 2 ) - floor( strlen( $text ) / 2 ),
$text,
$update
);
}
function write_fixed( $y, $x, $text, $width = 0, $update = true ) {
$width = $width > 0 ? $width : $this->width - $x;
$this->write( $y, $x, $this->_string_fixed( $text, $width ), $update );
}
function _string_fixed( $text, $width = 0 ) {
$width = $width < 0 ? 0 : $width;
$text = substr( $text, 0, $width );
$text .= str_repeat( ' ', $width - strlen( $text ) );
return $text;
}
function add_child( $child ) {
$this->children[] = $child;
}
function hide( $update = true ) {
foreach( $this->panels as $panel ) {
ncurses_hide_panel( $panel );
}
foreach( $this->children as $child ) {
$child->hide( false );
}
if( $update ) {
ncurses_update_panels();
ncurses_doupdate();
}
}
function show( $update = true ) {
foreach( $this->panels as $panel ) {
ncurses_show_panel( $panel );
}
foreach( $this->children as $child ) {
$child->show( false );
}
ncurses_update_panels();
ncurses_doupdate();
}
function erase() {
ncurses_werase( $this->window );
ncurses_wrefresh( $this->window );
ncurses_doupdate();
}
function get_char( $flush = true ) {
if( $flush ) {
ncurses_flushinp();
}
return ncurses_getch();
//return ncurses_wgetch( $this->window );
}
}
class NcProgressBar extends NcWindow {
var $max;
var $value;
var $show_pct;
function __construct( &$parent, $width, $y, $x, $max = 100, $show_pct = true ) {
parent::__construct( $parent, 1, $width, $y, $x, false );
$this->max = $max;
$this->show_pct = $show_pct;
$this->pos( 0 );
}
function pos( $value = false ) {
if( $value === false ) {
return $this->value;
} else {
$value = $value > $this->max ? $this->max : $value;
$this->value = $value;
$this->write( 0, 0, '[', false );
$width = $this->width - ( $this->show_pct ? 7 : 2 );
$width = $width >= 0 ? $width : 0;
$complete = floor( $width * $value / $this->max );
$this->write( 0, 1, str_repeat( '#', $complete ), false );
$this->write( 0, 1 + $complete, str_repeat( ' ', $width - $complete ), false );
$this->write( 0, $width + 1, ']', false );
if( $this->show_pct ) {
$this->write( 0, $width + 3, sprintf( '%3d%%', floor( $value / $this->max * 100 ) ), false );
}
ncurses_update_panels();
ncurses_doupdate();
}
}
function max( $value = false ) {
if( $value === false ) {
return $this->max;
} else {
$this->max = $value > 0 ? $value : $this->max;
$this->pos( $this->value );
}
}
}
class NcTextInput extends NcWindow {
var $value = '';
var $active = false;
var $filter = false;
function __construct( &$parent, $width, $y, $x, $value = '', $filter = false ) {
parent::__construct( $parent, 1, $width, $y, $x, false );
$this->value = $value;
$this->filter = $filter !== false && @preg_match( $filter, '' ) !== false ? $filter : false;
$this->update();
}
function update() {
$this->attribute_on( NCURSES_A_UNDERLINE );
if( $this->active ) {
$this->attribute_on( NCURSES_A_REVERSE );
}
$this->write_fixed( 0, 0, substr( $this->value, - $this->width ) );
$this->attribute_off( NCURSES_A_UNDERLINE );
$this->attribute_off( NCURSES_A_REVERSE );
}
function value( $value = false ) {
if( $value === false ) {
return $this->value;
} else {
$this->value = $value;
}
}
function get() {
$this->active = true;
$this->update();
while( $this->active ) {
$key = $this->get_char();
switch( $key ) {
case NCURSES_KEY_BACKSPACE:
$this->value = substr( $this->value, 0, strlen( $this->value ) - 1 );
break;
case 13:
$this->active = false;
break;
default:
if( in_array( $key, range( 32, 126 ) ) ) {
$this->value .= chr( $key );
}
}
$this->update();
}
$this->update();
return $this->filter !== false && @preg_match( $this->filter, $this->value ) < 1 ? false : $this->value;
}
}
class NcTableView extends NcWindow {
var $columns;
var $records;
var $selected;
function __construct( &$parent, $height, $width, $y, $x, $params = array() ) {
parent::__construct( $parent, $height, $width, $y, $x );
$params = !is_array( $params ) ? array() : $params;
$this->columns = isset( $params['columns'] ) && is_array( $params['columns'] ) ? $params['columns'] : array();
$this->records = isset( $params['records'] ) && is_array( $params['records'] ) ? array_values( $params['records'] ) : array();
}
function update( $selected = 0 ) {
$selected = $selected >= 0 ? $selected : 0;
$selected = $selected < count( $this->records ) ? $selected : count( $this->records ) - 1;
$this->selected = $selected;
$cols = isset( $this->columns ) && is_array( $this->columns ) ? $this->columns : array();
if( count( $cols ) == 0 ) {
// If there's no columns defined, try and grab 'em from the keys from the first record
$record = $this->records[0];
$record = is_array( $record ) ? $record : array( $record );
foreach( array_keys( $record ) as $col ) {
$cols[$col] = $col;
}
}
$column_ids = array_keys( $cols );
$column_widths = $rows = array();
foreach( $cols as $id => $column ) {
$column_widths[$id] = strlen( $column );
}
foreach( $this->records as $id => $record ) {
$record = is_array( $record ) ? $record : array( $record );
$fields = array();
foreach( $column_ids as $col ) {
$field = isset( $record[$col] ) ? $record[$col] : '';
$field = is_scalar( $field ) ? $field : gettype( $field );
$fields[$col] = $field;
$column_widths[$col] = $column_widths[$col] < strlen( $field ) ? strlen( $field ) : $column_widths[$col];
}
$rows[] = $fields;
}
foreach( $cols as $id => $col ) {
$cols[$id] = $col . str_repeat( ' ', $column_widths[$id] - strlen( $col ) );
}
$this->attribute_on( NCURSES_A_UNDERLINE );
$this->attribute_on( NCURSES_A_BOLD );
$this->write_fixed( 0, 0, implode( ' | ', $cols ) );
$this->attribute_off( NCURSES_A_UNDERLINE );
$this->attribute_off( NCURSES_A_BOLD );
$max_items = ( $this->height - 2 );
$offset = ( $selected > $max_items ) ? $selected - $max_items : 0;
foreach( $rows as $id => $row ) {
if( $id < $offset ) {
continue;
} elseif( $id - $offset > $max_items ) {
// We've reached the maximum row height for this window
break;
}
foreach( $row as $col => $field ) {
$row[$col] = $field . str_repeat( ' ', $column_widths[$col] - strlen( $field ) );
}
if( $selected == $id ) {
$this->attribute_on( NCURSES_A_REVERSE );
}
$this->write_fixed( $id - $offset + 1, 0, implode( ' | ', $row ) );
$this->attribute_off( NCURSES_A_REVERSE );
}
}
function load( $records ) {
if( !is_array( $records ) ) {
return false;
}
$this->records = array_values( $records );
$this->update();
return true;
}
}
?>

View file

@ -144,7 +144,7 @@ class PHPParser {
'type' => PHPPARSER_FUNCTION_CALL,
'name' => strtolower( $string ),
'file' => $this->file_name,
'context' => $lines[$line],
'context' => $lines[$line - 1],
'line' => $line,
'block' => $block,
'depth' => $depth,
@ -161,7 +161,7 @@ class PHPParser {
'type' => PHPPARSER_EXPRESSION,
'name' => trim( $expression ),
'file' => $this->file_name,
'context' => $lines[$line],
'context' => $lines[$line - 1],
'line' => $line,
'block' => $block,
'depth' => $depth,
@ -190,7 +190,7 @@ class PHPParser {
'type' => PHPPARSER_CLASS_DEF,
'name' => strtolower( $string ),
'file' => $this->file_name,
'context' => $lines[$line],
'context' => $lines[$line - 1],
'line' => $line,
'block' => $block,
'depth' => $depth,
@ -229,7 +229,7 @@ class PHPParser {
'type' => PHPPARSER_INCLUDE,
'name' => trim( str_replace( '\'', '', $text ) ),
'file' => $this->file_name,
'context' => $lines[$line],
'context' => $lines[$line - 1],
'line' => $line,
'block' => $block,
'depth' => $depth,
@ -280,7 +280,7 @@ class PHPParser {
'type' => PHPPARSER_LANGUAGE_CONSTRUCT,
'name' => $text,
'file' => $this->file_name,
'context' => $lines[$line],
'context' => $lines[$line - 1],
'line' => $line,
'block' => $block,
'depth' => $depth,

View file

@ -1,5 +1,6 @@
<?php
require_once( 'parser.php' );
require_once( 'ncurses/ncurses.php' );
define( 'FAULT_MINOR', 0 );
define( 'FAULT_MEDIUM', 1 );
@ -26,6 +27,8 @@ Options:
--base-path path to scan individual files in a code base that don't
live in the project's base directory.
--curses Enables the ncurses GUI
-f format Select the output format. Defaults to text.
--format format
@ -72,11 +75,12 @@ class ScannerModule {
'module' => get_class( $this ),
'object' => $object,
'file' => $this->file,
'line' => $object['line'],
'level' => $level,
'reason' => $reason,
'svn' => ( $config['svn'] === true ) ? $this->blame[$object['line']] : ''
'author' => ( $config['svn'] === true ) ? $this->blame[$object['line']]['author'] : '',
'revision' => ( $config['svn'] === true ) ? $this->blame[$object['line']]['revision'] : ''
);
//var_dump( $faults ); die();
}
function parserCallback( $object ) {
}
@ -139,17 +143,27 @@ function filename( $filename ) {
return $filename;
}
function err( $string ) {
global $stderr, $config;
if( $config['quiet'] === false ) {
global $stderr, $config, $curses;
if( $config['quiet'] === false && $curses === false ) {
fputs( $stderr, $string );
}
}
function scan_progress( $pos, $max, $title ) {
global $curses, $nc_progress, $nc_progress_bar;
$title = !empty( $title ) ? $title : 'Scanning [%d/%d]';
$title = @sprintf( $title, $pos, $max );
if( $curses ) {
$nc_progress->write_fixed( 0, 1, $title );
$nc_progress_bar->max( $max );
$nc_progress_bar->pos( $pos );
}
}
// Handle application arguments
$revisions = array();
$files = array();
$base_path = false;
$curses = false;
for( $i = 1; $i < $argc; $i++ ) {
switch( $argv[$i] ) {
case '-b':
@ -159,6 +173,9 @@ for( $i = 1; $i < $argc; $i++ ) {
$base_path = realpath( $new_base ) . '/';
}
break;
case '--curses':
$curses = function_exists( 'ncurses_init' );
break;
case '-f':
case '--format':
$config['output_format'] = $argv[++$i];
@ -254,6 +271,32 @@ if( count( $files ) == 0 ) {
}
}
if( $curses ) {
ncurses_init();
ncurses_noecho();
ncurses_curs_set( false );
$nc_screen = ncurses_newwin( 0, 0, 0, 0 );
ncurses_refresh();
$nc_main = new NcWindow( $nc_screen, 0, 0, 0, 0 );
$nc_main->title( 'Code Scanner' );
$nc_progress = new NcWindow( $nc_main, 4, 0, 0, 0 );
$nc_progress->title( 'Scanning Progress' );
$nc_progress_bar = new NcProgressBar( $nc_progress, 0, 1, 0 );
$nc_faults_columns = array(
'module' => 'Module',
'level' => 'Level',
'file' => 'File',
'line' => 'Line',
'author' => 'Author',
'revision' => 'Revision',
'reason' => 'Reason',
);
$nc_faults = new NcTableView( $nc_main, -5, 0, 4, 0, array( 'columns' => $nc_faults_columns ) );
$nc_faults->title( 'Faults' );
$nc_main->write( -1, 0, '[Q] Quit | [Return] Fault Info' );
}
// Dig into the modules folder and load up what we find
$module_files = scandir( 'modules' );
foreach( $module_files as $module_file ) {
@ -277,29 +320,111 @@ foreach( $module_files as $module_file ) {
}
}
}
$parser = new PHPParser();
$parser->registerCallback( '_callback' );
err( "Parsing files...\n" );
$current_file = 0; $total = count( $files ); $lastpct = 0;
$current_file = 0; $total = count( $files );
foreach( $files as $file ) {
$current_file++;
if( $current_file == 1 ) { err( 0 ); }
else {
$pct = intval( $current_file / $total * 100 );
if( $pct != $lastpct && $pct % 2 == 0 ) {
err( $pct % 10 == 0 ? $pct : '.' );
$lastpct = $pct;
}
scan_progress( $current_file, $total, 'Scanning %d of %d source files' );
if( $curses ) {
// Do this to avoid getting fouled up during scanning...
// Apparently, system calls (like exec(), used while scanning) break curses keyboard input somehow
ncurses_def_prog_mode();
}
foreach( $modules['scanner'] as $module ) { $module->preScan( $file ); }
$file_contents = ( $file['revision'] > 0 ? shell_exec( "svn cat -r {$rev} {$svn_root}{$svn_base}/{$file['filename']} 2>/dev/null" ) : file_get_contents( $file['filename'] ) );
$parser->parse( $file_contents );
if( $curses ) {
ncurses_reset_prog_mode();
$nc_faults->title( sprintf( 'Faults [%d found]', count( $faults ) ) );
}
}
err( "\n" );
$modules['output']->write( $config['output_file'] );
sleep( 1 );
err( sprintf( "Found %d faults in %d files.\n", count( $faults ), count( $files ) ) );
if( $curses ) {
$nc_faults->load( $faults );
$nc_faults->update( count( $faults ) );
$done = false;
while( !$done ) {
$key = $nc_main->get_char();
switch( $key ) {
case NCURSES_KEY_UP:
$nc_faults->update( $nc_faults->selected - 1 );
break;
case NCURSES_KEY_DOWN:
$nc_faults->update( $nc_faults->selected + 1 );
break;
case 10:
case 13:
$nc_fault_info = new NcWindow( $nc_main, -10, -10, 5, 5 );
$fault = &$faults[$nc_faults->selected];
$nc_fault_info->attribute_on( NCURSES_A_BOLD );
$nc_fault_info->write_centered( 0, 'Fault Information' );
$nc_fault_info->write( 3, 0, "Module:\nLevel:\nFilename:\nLine Number:\nAuthor:\nRevision:\nReason:\n\nContext:" );
$nc_fault_info->attribute_off( NCURSES_A_BOLD );
$nc_fault_info->write( 3, 20, "{$fault['module']}\n{$fault['level']}\n{$fault['file']}\n{$fault['line']}\n{$fault['author']}\n{$fault['revision']}\n{$fault['reason']}\n\n{$fault['object']['context']}" );
$nc_fault_info->get_char();
$nc_fault_info->hide();
unset( $nc_fault_info );
break;
case ord( 's' ):
case ord( 'S' );
$nc_save = new NcWindow( $nc_main, -10, -10, 5, 5 );
$nc_save->attribute_on( NCURSES_A_BOLD );
$nc_save->write_centered( 0, 'Save Faults To File' );
$nc_save->attribute_off( NCURSES_A_BOLD );
$nc_save->write( 2, 0, 'File to save to:' );
$nc_save_file_input = new NcTextInput( $nc_save, -4, 3, 2 );
$filename_selected = false;
while( !$filename_selected ) {
$filename = $nc_save_file_input->get();
$nc_save_confirm = new NcWindow( $nc_save, -10, -10, 5, 5 );
$nc_save_confirm->title( get_class( $modules['output'] ) );
$nc_save_confirm->write_centered( floor( $nc_save_confirm->height / 2 ) - 2, "Save results to '{$filename}'?" );
$nc_save_confirm->write_centered( floor( $nc_save_confirm->height / 2 ), '[Y]es [N]o [C]ancel' );
$valid_key = false;
while( !$valid_key ) {
switch( $nc_save_confirm->get_char() ) {
case ord( 'y' ):
case ord( 'Y' ):
$save_file = true;
case ord( 'c' ):
case ord( 'C' ):
$filename_selected = true;
case ord( 'n' ):
case ord( 'N' ):
$valid_key = true;
}
}
$nc_save_confirm->hide();
}
if( $save_file ) {
$result = $modules['output']->write( $filename );
$save_message = ( $result === false ) ? 'Save Failed' : 'Saved successfully';
$nc_save_confirm->erase();
$nc_save_confirm->write_centered( floor( $nc_save_confirm->height / 2 ) - 2, $save_message );
$nc_save_confirm->write_centered( floor( $nc_save_confirm->height / 2 ), 'Press any key to continue' );
$nc_save_confirm->show();
$nc_save_confirm->get_char();
}
$nc_save->hide();
unset( $nc_save );
break;
case ord( 'q' ):
case ord( 'Q' ):
$done = true;
break;
}
}
ncurses_clear();
ncurses_refresh();
ncurses_end();
} else {
$modules['output']->write( $config['output_file'] );
sleep( 1 );
err( sprintf( "Found %d faults in %d files.\n", count( $faults ), count( $files ) ) );
}
?>