<?php
#!/usr/bin/php
/**
* @package console
* @since 2005-04-20
* @author hide@address.com
* @licence LGPL
* @changelog - 2006-08-17 - new parameter $dfltIsYes for method msg_confirm()
* - 2006-05-10 - progress_bar() and refresh_progress_bar() now support array(msg,tag) as $msg parameter
* - 2006-05-04 - new parameters $styles and $return for console_app::print_table()
* - 2006-05-02 - now read will return false on CTRL+D if property _captureEOT is FALSE
* - 2006-04-26 - call of console_app::read() as a static method will now autodetect and use the readline extension
* - 2006-04-23 - now console_app::read() and console_app::msg_ read() even as a static method
* - 2006-04-16 - now console_app::get_arg() can also retrieve unknown_args ($console_app->get_arg(0))
* - 2006-04-15 - now auto add realine history if readline enable.
* - new parameter _captureEOT to capture EndOfTransmission signal (CTRL+d)
* - 2006-04-14 - new property console_app::_lnOnRead to remove the automaticly added new line on read msg.
* - new methods progress_bar() and refresh_progress_bar()
* - 2006-04-13 - now you can use "\n" in args descriptions
* - new method print_table() for table rendering
* - 2006-02-08 - remove some E_NOTICE on previously added readline support
* - 2006-02-03 - now can read multiple SHORT FLAG at once (ie: -AZEXC instead of -A -Z -E -X -C )
* - 2006-01-30 - some visual amelioration on the display_help
* - 2006-01-12 - new msg_read method
* - 2005-12-29 - now use STDIN/STDOUT/STDERR and add a fancyerror parameter
* @example
* @code
# set the app
$app = &new console_app();
$app->set_app_desc("This is a sample console app.");
# set parameters
$app->define_arg('name','n','','your name');
# don't forget to parse the command line to get args
$app->parse_args();
# get the value of 'name' like this
$name = $app->get_arg('name');
if( $name =='' )
$name = $app->read("enter your name");
# display a hello msg
$app->msg("Hello $name",'blue');
@endcode
@todo add better support for numeric args to permit to define some help for them too and know them at all
@todo better table support
*/
class console_app{
/** args known by this class */
var $known_args = array();
/** flags known by this class */
var $known_flags = array();
/** unknown given args */
var $unknown_args = array();
/** setted args on command line */
var $setted_args = array();
/** setted flags on command line */
var $setted_flags = array();
var $app_desc = '';
var $required_args;
var $required_flags;
/** is there a readline extension available or not */
var $_readline = FALSE;
var $_lnOnRead = TRUE;
var $_captureEOT = TRUE; # exit on EndOfTransmission (CTRL+d)
/** Manage history file Require readline extension */
var $_history_file = FALSE;
function console_app(){
$this->_readline = function_exists('readline');
# $this->workingdir = getcwd();
# $this->appdir = dirname($_SERVER['PHP_SELF']);
}
# function set_working_dir($dir=null){
# switch($dir){
# case null: $dir = $this->workingdir; break;
# case '@pp': $dir = $this->appdir; break;
# }
# chdir($dir);
# }
function get_arg($longname){
# try args
if( isset($this->setted_args[$longname]) )
return $this->setted_args[$longname];
if( isset($this->setted_flags[$longname]) )
return $this->setted_flags[$longname];
if( isset($this->unknown_args[$longname]) )
return $this->unknown_args[$longname];
return FALSE;
}
function get_args(){
return array_merge($this->setted_args,$this->setted_flags,$this->unknown_args);
}
/**
* define the flags the programm will wait for
* @param string $flagname the name of the flag (so --flagname will work)
* @param mixed $sf the short flag to use can be a string or an array
* if an array is used then the 1st value will set flag to true and the 2d to false
* if so a --no-flagname will be created to
* @param bool $dflt the default value for this flag leave null if this is a required flag
* (DON'T USE 'unset' ANYMORE to leave null a flag without setting him as required, SET IT TO FALSE will do the same)
* @param string $desc description for the help screen
*/
function define_flag($flagname,$sf=null,$dflt=null,$desc='** no description available **'){
$this->known_flags['--'.$flagname] = $flagname;
# $this->flags[$flagname]['longname']= $name;
$this->flags[$flagname]['desc'] = $desc;
if($dflt==='unset') $dflt=FALSE; # this line is just here to handle old silly 'unset' value for old prog
# check for default value or set it as required
if( is_null($dflt) )
$this->required_flags[] = $flagname;
else
$this->setted_flags[$flagname] = $this->flags[$flagname]['dflt'] = (bool) $dflt;
# check short tags
if($sf){
if(is_array($sf))
list($sf,$usf) = $sf;
$this->known_flags['-'.$sf] = $flagname;
}
# define optionnal unflag
if(isset($usf))
$this->define_unflag($flagname,"no-$flagname",$usf);
}
/**
* set a unflag flag for$flagname
* @param string $flagname flagname to set an unflag for
* @param string $unflagname the unflag longname
* @param string $shortunflagname the short unflag name
*/
function define_unflag($flagname,$unflagname=null,$shortunflagname=null){
if(! isset($this->flags[$flagname])){
$this->msg_error('unflag set for non existing flag: '.$flagname);
return FALSE;
}
if(! is_null($unflagname))
$this->unflags['--'.$unflagname] = $flagname;
if(! is_null($shortunflagname))
$this->unflags['-'.$shortunflagname] = $flagname;
return TRUE;
}
function set_flag($flag,$value=TRUE){
# try in known_flags
if( isset($this->knwon_flags[$flag]) ){
$this->setted_flags[$this->knwon_flags[$flag]] = $value;
return TRUE;
}
# try in unflags
if( isset($this->unflags[$flag]) ){
$this->setted_flags[$this->unflags[$flag]] =(bool) (! $value);
return TRUE;
}
#try as flagname
if(isset($this->flags[$flag])){
$this->setted_flags[$flag] = $value;
return TRUE;
}
$this->msg_error('Try to set an unknwon flag: '.$flag);
}
/**
* set possible arg for this application
* @param str $longname this is the name you will use to access this arg inside your application.
* the program may so have a --longname argument
* @param str $shortname set an optionnal short name for arg, so your app will take a -S arg (S for shortname :))
* @param str $default optionnal default value to set this arg to, if not passed on the command line
* Setting a default value (!== null) will mark this arg as optionnal else it will be a required arg
* @param str $desc set the description for this argument used for the --help command
* @param mixed $valid_cb You can use a callback function to check your argument at the start time
* such function will receive the value given to arg by user on the command line
* the callback func must return either: FALSE -> so program will display an error and exit
* TRUE or null -> nothing happen all is ok
* mixed -> the value will be replaced by the returned mixed
* @param str $delim delimiter used to explode multiple value agurment
*/
function define_arg($longname,$shortname=null,$default=null,$desc='** no description available **',
$valid_cb=null,$delim=null){
$this->known_args['--'.$longname] = $longname;
$this->_args[$longname] = array('longname'=>$longname,'desc'=>$desc);
if(! is_null($valid_cb) )
$this->_args[$longname]['validation_callback'] = $valid_cb;
if(! is_null($delim) )
$this->_args[$longname]['delim'] = $delim;
if( is_null($default) ){
$this->required_args[] = $longname;
}else{
$this->_args[$longname]['dflt'] = $default;
$this->setted_args[$longname] = $default;
}
if(! is_null($shortname)){
$this->_args[$longname]['short'] = $shortname;
$this->known_args['-'.$shortname]= $longname;
}
}
/**
* affiche l'aide du programme
*/
function display_help($exitcode=0){
$help = '';
$max_len=0;
$appname = $this->tagged_string(basename($_SERVER['SCRIPT_NAME']),'bold|blue');
if(strlen($this->app_desc))
fwrite(STDOUT,"$this->app_desc\n");
fwrite(STDOUT,"-h,--help display this help\n");
$i=0;
# Display help for args
if( count($this->known_args)){
ksort($this->known_args);
$rows[$i] = "\n### OPTIONS LIST FOR $appname\n";
foreach($this->_args as $argname=>$arg){
# $this->tagged_string(print_r($arg,1),'red',1);
$rows[++$i][0] = ($arg['short']?'-'.$arg['short'].',':'')." --$argname";
$rows[$i][1] = (@$arg['dflt']?"(Default value: '$arg[dflt]') ":'').$arg['desc']
.(@strlen($arg['delim'])?" multiple values can be separated by '$arg[delim]'":'');
if( (! is_array($this->required_args))|| (! in_array($argname,$this->required_args)) )
$rows[$i][0] = "[".$rows[$i][0]."]";
$max_len = max($max_len,strlen($rows[$i][0]));
$parsed_arg[$arg['longname']]=TRUE;
}
}
# Display help for flags
if( isset($this->flags) && count($this->flags)){
ksort($this->flags);
$rows[++$i] = "\n### FLAGS / SWITCHES LIST FOR $appname\n";
# $this->show($this);
foreach($this->flags as $flagname=>$flag){
$rows[++$i][0] = implode(', ',array_keys($this->known_flags,$flagname));
$rows[$i][1] = (isset($flag['dflt'])?"(Default: ".($flag['dflt']?'on':'off').") ":'')."switch '".$flagname."' to on. ".$flag['desc'];
# is optional?
if( (! is_array($this->required_flags)) || (! in_array($flagname,$this->required_flags)) )
$rows[$i][0] = '['.$rows[$i][0].']';
$max_len = max($max_len,strlen($rows[$i][0]));
# check for unflags
if( @is_array($this->unflags) && $unflags = array_keys($this->unflags,$flagname)){
$rows[++$i][0] = implode(', ',$unflags);
$rows[$i][1] = 'switch \''.$flagname.'\' to off.';
if( (!is_array($this->required_flags)) || (! in_array($flagname,$this->required_flags)) )
$rows[$i][0] = '['.$rows[$i][0].']';
$max_len = max($max_len,strlen($rows[$i][0]));
}
}
}
$max_len +=4; $split = 74;
$blank = str_repeat(' ',$max_len);
$desclen = $split-$max_len;
foreach($rows as $row){
if(is_string($row)){fwrite(STDOUT,$row);continue;} # echo single lines
list($col1,$col2) = $row;
# print first col
fwrite(STDOUT,$col1.str_repeat(' ',max(0,strlen($blank)-strlen($col1))) );
# print 2d col
while(strlen($col2) > $desclen){
if( ($lnpos = strpos($col2,"\n")) && $lnpos < $desclen){
fwrite(STDOUT,substr($col2,0,$lnpos)."\n$blank");
$col2 = substr($col2,$lnpos+1);
continue;
}
$last_isspace = (bool) ($col2[$desclen-1]==' ');
$next_isspace = (bool) ($col2[$desclen]==' ');
$next2_isspace=(bool) (isset($col2[$desclen+1])?($col2[$desclen+1]==' '):TRUE);
if($last_isspace){
fwrite(STDOUT,substr($col2,0,$desclen)."\n$blank");
$col2 = substr($col2,$desclen);
}elseif($next_isspace){
fwrite(STDOUT,substr($col2,0,$desclen)."\n$blank");
$col2 = substr($col2,$desclen+1);
}elseif($next2_isspace){
fwrite(STDOUT,substr($col2,0,$desclen+1)."\n$blank");
$col2 = substr($col2,$desclen+2);
}else{
fwrite(STDOUT,substr($col2,0,$desclen)."-\n$blank");
$col2 = substr($col2,$desclen);
}
}
fwrite(STDOUT,$col2."\n");
}
exit($exitcode);
}
/** add an header string to explain the programm behaviour, will be displayed on the help screen */
function set_app_desc($string){
$this->app_desc = $string;
}
/**
* parse command line parameters.
* You must call this method after args and flags definition and before any console_app::get_arg() call
*/
function parse_args(){
$argv = $_SERVER['argv'];
$argc = $_SERVER['argc'];
# si pas d'argument on retourne
if(! $argc>1)
return FALSE;
# we parse each args
for($i=1;$i<$argc;$i++){
$arg = $argv[$i];
if( in_array($arg,array('--help','-h')) ) # check for help
return $this->display_help(0);
if( substr($arg,0,1)!='-' ){ # not a flag or arg
$this->unknown_args[] = $arg;
continue;
}
if( isset($this->known_args[$arg]) ){ # Known argument so we process it
$name = $this->known_args[$arg]; # get arg name
# get his value
if(! isset($argv[$i+1]) ) continue;
if(! isset($this->_args[$name]['delim']) )# unique value entry
$this->setted_args[$name] = isset($argv[++$i])?$argv[$i]:FALSE;
else # multiple value argument
$this->setted_args[$name] = split($this->_args[$name]['delim'],$argv[++$i]);
if(isset($this->_args[$name]['validation_callback'])){ # run optionnal validation callback
$cb_ret = call_user_func($this->_args[$name]['validation_callback'],$this->setted_args[$name]);
if($cb_ret===FALSE){ # callback failed so display error message and then help
$this->tagged_string("** '".$arg .' '.$argv[$i]."' Invalid value given for $name **",'red|bold',1);
return $this->display_help(-1);
}elseif(! in_array($cb_ret,array(TRUE,NULL),TRUE) ){ # callback returned a value so we override user value with this one
$this->setted_args[$name] = $cb_ret; # get the args value
}
}
}elseif( isset($this->known_flags[$arg]) ){ # known flag
$name = $this->known_flags[$arg]; # get arg name
$this->setted_flags[$name] = TRUE;
}elseif( isset($this->unflags[$arg])){ # known unflag
$this->setted_flags[$this->unflags[$arg]] = FALSE;
}else{ # unknown flag or args
$_arg = substr($arg,1);
if(isset($this->known_flags)){
foreach($this->known_flags as $k=>$v){
if(! strlen($_arg) ) break;
$k=substr($k,1);
if( substr_count($_arg,$k) ){
$this->setted_flags[$v] = TRUE;
$_arg = str_replace($k,'',$_arg);
}
}
}
if(isset($this->unflags)){
foreach($this->unflags as $k=>$v){
if(! strlen($_arg) ) break;
$k=substr($k,1);
if( substr_count($_arg,$k) ){
$this->setted_flags[$v] = FALSE;
$_arg = str_replace($k,'',$_arg);
}
}
}
if( strlen($_arg) ){
$this->tagged_string("** undefined parameter $arg **",'red',1);
return $this->display_help(-1);
}
}
}
if( is_array($this->required_args))
foreach($this->required_args as $arg){
if(! isset($this->setted_args[$arg]))
$this->msg_error("** Missing required $arg parameter (".($this->_args[$arg]['short']?'-'.$this->_args[$arg]['short'].', ':'')."--$arg)**",TRUE);
}
if( is_array($this->required_flags) )
foreach($this->required_flags as $flag){
if(! isset($this->setted_flags[$flag]))
$this->msg_error("** Missing required flag: $flag ("
.($this->flags[$flag]['short']?'-'.$this->flags[$flag]['short'].', ':'')
."--$flag) **",TRUE);
}
}
/**
* return a tagged console string, used to print color string on the command line interface.
* @param string $string the string to display
* @param string $tag the tag identifier to use (like color name) multiple tags can be passed separated by '|'
* @param bool $stdout if set to true then send string to stdout instead of return it
*/
function tagged_string($string,$tag='blink',$stdout=FALSE){
# define some escaped commands code
$codes = array( # some cool stuff
'reset' => 0, 'bold' => 1,
'underline' => 4, 'nounderline'=> 24,
'blink' => 5, 'reverse' => 7,
'normal' => 22, 'blinkoff' => 25,
'reverse' => 7, 'reverseoff' => 27,
# some foreground colors
'black' => 30, 'red' => 31,
'green' => 32, 'brown' => 33,
'blue' => 34, 'magenta' => 35,
'cyan' => 36, 'grey' => 37,
# Some background colors
'bg_black' => 40, 'bg_red' => 41,
'bg_green' => 42, 'bg_brown' => 43,
'bg_blue' => 44, 'bg_magenta' => 45,
'bg_cyan' => 46, 'bg_white' => 47,
);
if(substr_count($tag,'|')){ # parse multiple tags
$tags = explode('|',$tag);
$str='';
foreach($tags as $tag){
$str[]= isset($codes[$tag])?$codes[$tag]:30;
}
$str = "\033[".implode(';',$str).'m'.$string."\033[0m";
}else{
if( in_array($codes[$tag],array(4,5,7)) ){
$end = "\033[2".$codes[$tag].'m';
}else{
$end = "\033[0m";
}
$str = "\033[".(isset($codes[$tag])?$codes[$tag]:30).'m'.$string.$end;
}
if(! $stdout)
return $str;
fwrite(STDOUT,"$str\n");
}
function clear_screen(){
fwrite(STDOUT,"\033[2J");
}
/**
* helper methods to refresh a progress bar
* @param mixed $value int/float current value relative to $max
* @param string $msg message to display if you want to replace the old one.
* @param bool $dontclean if set to TRUE then won't replace previous displayed bar but just print at the end of the script
*/
function refresh_progress_bar($value,$msg=null,$dontclean=FALSE){
console_app::progress_bar($value,$msg,null,null,null,null,!$dontclean);
}
/**
* set and display a progress bar
* @param mixed $val int/float value relative to $max
* @param string $msg message to display with the bar (you can either pass an array(string msg,string tag))
* @param int $w length of the bar (in character)
* @param mixed $max int/float maximum value to display
* @param string $formatString is a string to custom the display of your progress bar
* this are the replacement made in the string before displaying:
* %V will be replaced by the value
* %S will be replaced by the msg
* %M will be replaced by the maxvalue
* %B will be replaced by the bar
* %P will be replaced by the percent value
* @param array $style permit you to custom the bar style array(done_style,todo_style)
* where (todo|done)_style can be a string (character) or an array( (str) char,(str) tag as in tagged_string)
* @param bool $refresh set this to true if you want to erase last printed progress
* (you mustn't have any output between this call and the last one to get it work properly)
* normaly you won't have to use this, use the helper methods refresh_progress_bar().
*/
function progress_bar($val,$msg=null,$max=100,$w=40,$formatString="%S\n\t%B %P",$style=array(array('=','bold|green'),' '),$refresh=FALSE){
static $pgdatas;
$args = array('msg','w','max','formatString','style');
foreach($args as $arg){
if( is_null($$arg) ) # set null args to previous values
$$arg = isset($pgdatas[$arg])?$pgdatas[$arg]:null;
else # keep trace for nex call
$pgdatas[$arg] = $$arg;
}
# clear previously displayed bar
if($refresh && isset($pgdatas['nbline']) ){
for($i=0;$i<$pgdatas['nbline'];$i++)
fwrite(STDOUT,"\033[1A\033[K"); # up one line and clear it
}
# calc some datas
$good = max(0,round($val*$w/$max));
$bad = max(0,$w-$good);
# make some style
list($done_chr,$todo_chr) = $style;
if(is_array($done_chr))
list($done_chr,$done_tag) = $done_chr;
if(is_array($todo_chr))
list($todo_chr,$todo_tag) = $todo_chr;
$good = str_repeat($done_chr,$good);
$bad = str_repeat($todo_chr,$bad);
if( isset($done_tag) )
$good = console_app::tagged_string($good,$done_tag);
if( isset($todo_tag) )
$bad = console_app::tagged_string($bad,$todo_tag);
# then render the bar
if( is_array($msg) ) $msg = console_app::tagged_string($msg[0],$msg[1]);
$bar = '['.$good.$bad.']';
$per = round($val/$max*100,1);
$str = str_replace(array('%V','%M','%S','%B','%P'),array($val,$max,$msg,$bar,"$per%"),$formatString)."\n";
$pgdatas['nbline'] = substr_count($str,"\n");
fwrite(STDOUT,$str);
}
/**
* print a 2D array as a table
* @param array $table
* @param array $styles list of tag for each row /cells -> array(rowid=>array(colid=>'tag','dflt'=>'tag')) where default will apply to all cell in row
* @param bool $return if true then return the string instead of printing it to the screen
*/
function print_table($table,$styles=null,$return=FALSE){
if(! (is_array($table) && isset($table[0]) && is_array($table[0]) ) )
return FALSE;
list($headers) = $table;
$headers = array_keys($headers);
if( is_string($headers[0]) ){
foreach($headers as $k=>$v){
unset($headers[$k]);
$headers[$v] = $v;
}
array_unshift($table,$headers);
if(! is_null($styles) )
array_unshift($styles,null);
}else{
$headers = FALSE;
}
# calc width for each cols
foreach($table as $rowid=>$row){
foreach($row as $colid=>$col){
$clines = explode("\n",$col);
$nbline = count($clines);
foreach($clines as $l){
$widths[$colid] = isset($widths[$colid])?max($widths[$colid],strlen($l)):strlen($l);
}
$heights[$rowid] = isset($heights[$rowid])?max($heights[$rowid],$nbline):$nbline;
}
}
$twidth = array_sum($widths) + 3*count($widths) + 1;
$nbr=0;
# now render
$strOut = '';
foreach($table as $rid=>$row){ # each table rows
$strOut .= str_repeat('-',$twidth)."\n"; # draw top row border
# on doit ajouter les lignes manquantes à chaque cellules
$rowlines = array();
$rowstyle = (isset($styles[$rid])&&isset($styles[$rid]['dflt']))?$styles[$rid]['dflt']:null;
foreach($row as $cid=>$col){
$col = explode("\n",$col);
$colstyle = $rowstyle.((isset($styles[$rid])&&isset($styles[$rid][$cid]))?($rowstyle?'|':'').$styles[$rid][$cid]:null);
for($i=0;$i<$heights[$rid];$i++){
if(! isset($col[$i]) ){
$rowlines[$i][$cid] = str_repeat(' ',$widths[$cid]);
}else{
$cline = $colstyle?console_app::tagged_string($col[$i],$colstyle):$col[$i];
$rowlines[$i][$cid] = $cline.str_repeat(' ',max(0,$widths[$cid]-strlen($col[$i])));
}
}
}
# print row
for($i=0;$i<$heights[$rid];$i++){
if( $nbr==0 && $headers)
$strOut .= console_app::tagged_string('| '.implode(' | ',$rowlines[$i]).' |','reverse')."\n";
else
$strOut .= '| '.implode(' | ',$rowlines[$i])." |\n";
}
$nbr++;
}
$strOut .= str_repeat('-',$twidth)."\n"; # draw bottom table border
if($return)
return $strOut ;
fwrite(STDOUT,$strOut);
}
function msg($msg,$tag=null){
if($tag)
console_app::tagged_string($msg,$tag,1);
else
fwrite(STDOUT,"$msg\n");
}
/**
* display an information msg to the user (blue)
* @param string $msg;
*/
function msg_info($msg){
console_app::msg('::information:: '.$msg,'blue');
return FALSE;
}
/**
* display an error message
* @param string $msg
* @param bool $fatal stop the script if set to yes
* @param int $exitcode code to return on fatal error (-1 is default)
* @return FALSE
*/
function msg_error($msg,$fatal=FALSE,$exitcode=-1){
$msg = console_app::tagged_string('::'.($fatal?'FATAL_':'').'ERROR:: '.$msg,'red|bold',FALSE);
fwrite(STDERR,"$msg\n");
error_log($msg); # do this for Qaleo
if($fatal)
exit($exitcode);
return FALSE;
}
/**
* display a confirmation message (yes|no choice)
* @param string $msg
* @param string $tag optionnaly tagg the string as in tagged string
* @param bool $dfltIsYes if true then the default value is yes instead of no
* @return bool
*/
function msg_confirm($msg,$tag='brown',$dfltIsYes=FALSE){
$dflt= $dfltIsYes?'yes':'no';
$msg = "::confirmation:: $msg\nYes | No ($dflt)?";
$res = console_app::read($tag?console_app::tagged_string($msg,$tag):$msg,$dflt);
if(preg_match('!^[yo]([eu][si])?$!i',$res))
return TRUE;
else
return FALSE;
}
/**
* same as the read method but with the possibility to tagg the displayed message
*/
function msg_read($msg,$tag='blue',$dflt=null,$check_exit=TRUE){
if(isset($this))
return $this->read(console_app::tagged_string($msg,$tag),$dflt,$check_exit);
else
return console_app::read(console_app::tagged_string($msg,$tag),$dflt,$check_exit);
}
/**
* lit une entree utilisateur sur STDIN
* @param string $string message a afficher
* @param string $dflt valeur par defaut optionnelle,
* si l'utilisateur ne saisie rien alors c'est la valeur par defaut qui sera retourné.
* @param bool $check_exit if the user input exit on a read request the program default behaviour is to exit itelf
* you can disablle this feature by setting this to FALSE.
* return string
*/
function read($string='Wait for input:',$dflt=null,$check_exit=TRUE){
$_lnOnRead = isset($this)? $this->_lnOnRead : TRUE;
$_readline = isset($this)? $this->_readline : function_exists('readline');
# $_readline = isset($this)? $this->_readline : FALSE;
$_captureEOT = isset($this)? $this->_captureEOT: TRUE;
if( $_lnOnRead )
$string .= "\n";
if( $_readline ){
$read = readline($string);
if( $read === FALSE ){ # capture End Of Transmission (CTRL+D)
if( $_captureEOT )
exit();
return FALSE;
}
}else{
if($string) fwrite(STDOUT,$string);
$read=fgets(STDIN,4096);
if(! $read){ # capture End Of Transmission (CTRL+D)
if($_captureEOT)
exit();
else
return FALSE;
}
# strip newline
$read = preg_replace('![\r\n]+$!','',$read);
}
if($check_exit && $read =='exit'){
console_app::msg_info('execution stopped by user.');
exit(0);
}
# check default value
if( (!$read) && !is_null($dflt))
return $dflt;
if( $_readline )
readline_add_history($read);
return $read;
}
/**
* debug function, print the structure of any variable
* @param mixed $var
* @param string $tag
* @param bool $exit
*/
function show($var,$tag='bold|red',$exit=FALSE){
console_app::tagged_string(print_r($var,1),$tag,1);
if($exit) exit;
}
/**
* interactively reconfigure a simple configuration file
* @param string $cfg_file the config file to reconfigure
* @param string $cfg_template template file to use for the config file
* @param array $dflt_vals you can pass an array of default value for the user prompt
* ie: array('CONFIG_PARAM'=>'PARAM_VALUE'[,...]);
* @param bool $exitonerror will stop the programm on error if set to true
* @return bool
* @require write_conf_file(),parse_conf_file()
*/
function interactive_reconfigure($cfg_file,$cfg_template=null,$dflt_vals=null,$exitonerror=FALSE,$returnconf=FALSE){
require_once(dirname(__file__).'/fx-conf.php');
console_app::msg("** Interactive Reconfigure -> $cfg_file **",'blue');
# prepare the template config array
if(is_null($cfg_template))
$cfg_template = $cfg_file;
if(! is_file($cfg_template)){
console_app::msg_error("Invalid configuration file will try: $cfg_file");
if(! is_file($cfg_template = $cfg_file) ){
if(! is_array($dflt_vals) )
return console_app::msg_error("Can't reconfigure without any valid configuration template!",$exitonerror);
else
$tpl_cfg = $dflt_vals;
}
}
# parse template config
if( (! @is_array($tpl_cfg)) && ! is_array($tpl_cfg = parse_conf_file($cfg_template,TRUE)) )
return console_app::msg_error("Empty configuration file. Don't know what to configure",$exitonerror);
# user reconfigure
foreach($tpl_cfg as $k=>$v){
if(isset($dflt_vals[$k]))
$v = $dflt_vals[$k];
$tpl_cfg[$k] = console_app::read("set '$k' ($v): ",$v);
}
if(! write_conf_file($cfg_file,$tpl_cfg,TRUE) )
return console_app::msg_error("Can't write file : $cfg_file",$exitonerror);
fwrite(STDOUT,"configuration DONE.\n".str_repeat('-',70)."\n");
if($returnconf)
return $tpl_cfg;
return TRUE;
}
}
?>