From 570f8aaa654c3bef4ba83f68b5fe1aa26c4ee800 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Thu, 28 Feb 2008 16:39:33 +0000 Subject: [PATCH] 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 --- modules/output_html.php | 1 + modules/output_text.php | 1 + modules/scanner_functions.php | 12 +- ncurses/example.php | 27 +++ ncurses/ncurses.php | 332 ++++++++++++++++++++++++++++++++++ parser.php | 10 +- scanner.php | 161 +++++++++++++++-- 7 files changed, 511 insertions(+), 33 deletions(-) create mode 100644 ncurses/example.php create mode 100644 ncurses/ncurses.php diff --git a/modules/output_html.php b/modules/output_html.php index e5c33b7..5c762a9 100644 --- a/modules/output_html.php +++ b/modules/output_html.php @@ -102,6 +102,7 @@ class HTMLOutput extends OutputModule { ' . "\n" ); fclose( $output ); + return true; } } diff --git a/modules/output_text.php b/modules/output_text.php index a98c6dd..4172a9d 100644 --- a/modules/output_text.php +++ b/modules/output_text.php @@ -20,6 +20,7 @@ class TextOutput extends OutputModule { ); } fclose( $output ); + return true; } } diff --git a/modules/scanner_functions.php b/modules/scanner_functions.php index 5dc5b44..7aa525d 100644 --- a/modules/scanner_functions.php +++ b/modules/scanner_functions.php @@ -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']; } diff --git a/ncurses/example.php b/ncurses/example.php new file mode 100644 index 0000000..5da6385 --- /dev/null +++ b/ncurses/example.php @@ -0,0 +1,27 @@ +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(); +?> diff --git a/ncurses/ncurses.php b/ncurses/ncurses.php new file mode 100644 index 0000000..19aa919 --- /dev/null +++ b/ncurses/ncurses.php @@ -0,0 +1,332 @@ +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; + } +} +?> diff --git a/parser.php b/parser.php index 65a6807..0999ca7 100644 --- a/parser.php +++ b/parser.php @@ -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, diff --git a/scanner.php b/scanner.php index ff4a60e..ff9133c 100644 --- a/scanner.php +++ b/scanner.php @@ -1,5 +1,6 @@ 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 ) ) ); +} ?>