php/env.php
<?php
/* php helper functions
err: error handler
out*: output for html or terminal with headers, lists, tables
ideas: outTDrc ==> with row col index (store old stack! and move storing logic to outWork)
outLiL, output several LiItems (not words as outLi), similar: outTDD, outTRD etc.
a2str: anything to string
i2str: a2str for each argument
i2strEC: a2str for each argument with $ = $, ... syntax
*/
###----------- err, dbg and global settings -----------
function err () {
$t = "error: " . i2str(func_get_args());
outErr($t);
throw new Error(strlen($t) < 520 ? $t : substr($t, 0, 500) . '...'); # avoid truncation of stacktrace if message is too long
}
if (phpversion() < '7')
err('php version ', phpversion(), 'not supported must be >= 7...');
define('EnvCL', ! isset($_SERVER['REQUEST_METHOD']));
# define('EnvDE', EnvCL or ini_get('display_errors'));
if ( ! defined('OutHtml'))
define('OutHtml', ! EnvCL);
$outNL = OutHtml ? "\n<br>" : "\n";
$dbg ??= 0;
$dbgPre ??= 'dbg: ';
function dbg($lf, $a, ...$b) {
global $dbg;
if ((int) $lf <= $dbg)
dbgOut(trim($lf, '1- 0123456789'), $a, $b);
}
function dbQ(...$a) { # unconditional dbg output, to be removed when problem is analysed
out('dbQ:', ...$a);
}
function dbgOut($ff, $a, $b) {
global $dbgPre;
$f = "out$ff";
$f($dbgPre . a2str($a), ...$b);
}
function dbg1($a, ...$b) {
global $dbg;
if (1 <= $dbg)
dbgOut('', $a, $b);
}
#--- try to activate assertions
function envAssert($al=null) {
$o = ini_set('zend.assertions', $al);
if ($al === null)
return $o;
$n = ini_get('zend.assertions');
return "zend.assertions=$n " . ($n == $al ? "changed from $o" : "could not change to $al");
}
$errHHCat = $errHHCat ?? null;
$errHHRet = $errHHRet ?? false;
$errHHMax = $errHHMax ?? 20;
function errHH(int $errno , string $errstr , string $errfile , int $errline) {
static $cnt = 0;
global $errHHCat, $errHHRet, $errHHMax;
$cnt++;
$b = substr($errstr, 0, max(3, min(12, strcspn($errstr, ' ()[],.'))));
$errHHCat[$b] = 1 + ($errHHCat[$b] ?? 0);
out("errHH $cnt no=$errno, msg=$errstr, file=$errfile, line=$errline", $errHHCat);
if ($cnt > $errHHMax)
err("errHH $cnt errors are too much:", $errHHCat);
return $errHHRet;
}
function errHHAct($actDeact = true) { # activate or deactivate errorHandler errHH
$res = set_error_handler ($actDeact ? 'errHH' : null, E_ALL);
out("errorhandler set res $res");
}
###----------- arguments (url or commandline) -----------
if ( EnvCL) { # we are in terminal (CommandLine) mode
function envScript() {
global $argv;
return $argv[0];
}
function envArgs($def=null) {
static $old = null;
if ($old === null) {
global $argv;
$old = $argv;
unset($old[0]); # get rid of script
}
return ($def === null or count($old) > 0) ? $old : preg_split('/\s+/', $def, -1, PREG_SPLIT_NO_EMPTY);
}
} else {
function envScript() {
return $_SERVER['SCRIPT_FILENAME'];
}
function envArgs($def=null) {
static $old = null;
if ($old === null) {
$a = $_SERVER['QUERY_STRING'];
if (strpos($a, '&') !== false)
$old = array_map(fn($v) => trim(urldecode($v)), explode('&', $a));
else
$old = preg_split('/\s+/', urldecode($a), -1, PREG_SPLIT_NO_EMPTY);
}
return ($def === null or count($old) > 0) ? $old : preg_split('/\s+/', $def, -1, PREG_SPLIT_NO_EMPTY);
}
}
class EnvArg {
public $type = [];
public $val = [];
public $itr1 = null;
public $opr = null;
function __get($nm) {
return $this->val[$nm === 'ops' ? '' : $nm];
}
public function __construct($tps) {
/* $tps types space separated words in syntax
natd
n name (0, 1 or more characters)
a array info
= not an array
empty not an array, name is first character
[ array (multiple values allowed)
> array, reroute operands to this name
< reroute operands to '' (reset >)
t type
b boolean (no data, following opt or data allowed in same argument)
i integer
s string
h help
d default (for arrays d='' means [], d='-' means [''] d='-xyz' means ['xyz'])
*/
foreach (explode(' ', $tps) as $o) {
if ( $o === '' )
continue;
if ( 0 === $r = preg_match('/([^[<>=]*)([[<>=])(.?)(.*)/', $o, $mm))
$mm = [$o, substr($o, 0 , 1), '=', substr($o, 1 , 1), substr($o, 2)];
else if ($r !== 1)
err("EnvArg regex failure on $o");
if (isset($this->type[$mm[1]]))
err('already set', $o, $this->type);
if ($mm[3] === '')
$mm[3] = 's';
if ($mm[3] === 'b' or $mm[3] === 'h')
$mm[4] = $mm[4] === '' ? false : (bool) $mm[3];
else if ($mm[3] === 'i')
$mm[4] = $mm[4] === '' ? 0 : (int) $mm[4];
else if ($mm[3] !== 's' )
err ("bad type {$mm[3]} in opt", $mm);
$this->type[$mm[1]] = $mm[2] . $mm[3] . $mm[4];
if ($mm[2] === '<')
$this->type[''] = $this->type[$mm[1]];
else if ($mm[3] !== 'h')
$this->val[$mm[1]] = $mm[2] === '=' ? $mm[4] : [];
}
$this->type[''] = '[' . substr($this->type[''] ?? '?s', 1);
$this->val[''] = [];
$this->val0 = $this->val;
}
# set arg to array of strings, with syntax -opt operand or key = value syntax
function arg($args, $syn = null) {
$this->arg = $args;
if ($syn === null)
$syn = (strpos($a = implode(' ', $args), '=') === false or strpos($a, '-') !== false and strpos($a, '-') < strpos($a, '=')) ? '-' : '=';
$this->itr1 = $syn === '-' ? 'iterMinus' : 'iterEqual';
foreach ($this->iterate() as $k => $v)
continue;
return $this->val[''];
}
# iterate over array of strings, with the key=value syntax
function iterEqual() {
foreach ($this->arg as $a) {
if (false !== $x = strpos($a, '='))
yield trim(substr($a, 0, $x)) => substr($a, $x+1);
else
yield '' => $a;
}
}
# iterate over array of strings, with the minus option syntax (unix style)
function iterMinus() {
$lOp = null;
foreach ($this->arg as $a) {
$x = substr($a, 0, 2) === '--' ? 2 : (int) (substr($a, 0, 1) === '-' or substr($a, 0, 1) === '+');
$pr = substr($a, 0, $x);
if ($x <= 0) {
yield $lOp ?? '' => $a;
$lOp = null;
} else {
if ($lOp !== null) {
yield $lOp => '';
$lOp = null;
}
while (true) {
for ($y=strlen($a) - $x; $y > 0 and ! isset($this->type[substr($a, $x, $y)]); $y--)
continue;
if ($y < 1)
err('option unknown', substr($a, $x), 'in', $a, 'in', $this->arg);
$nm = substr($a, $x, $y);
$t = $this->type[$nm][1];
$x += $y;
if ($t === 'b' or $t === 'h') {
yield $nm => $pr != '+';
if ($x >= strlen($a))
break;
} else {
if ($x >= strlen($a))
$lOp = $nm;
else
yield $nm => substr($a, $x);
break;
}
}
}
}
if ($lOp !== null)
err('option expected after', $lOp);
}
function iterate() {
$this->val = $this->val0;
$this->opr = null;
$redir = '';
foreach ($this->{$this->itr1}() as $k => $v) {
$t = $this->type[$k];
if ($t[0] === '>')
$redir = $k;
else if ($t[0] === '<')
$t = $this->type[$redir = $k = ''];
if ($t[1] === 'b') {
$v = (bool) $v;
} else if ($t[1] === 'h') {
$help = true;
continue;
} else if ($t[1] === 'i') {
$v = (int) $v;
} else if ($t[1] !== 's') {
err("bad type $t for key $k, val $v");
}
if ($k === '')
$k = $redir;
if (is_array($this->val[$k]))
$this->val[$k][] = $v;
else
$this->val[$k] = $v;
if ($k === '')
yield $this->opr = $v;
}
foreach ($this->type as $k => $v) {
if ($v[0] !== '=' and $v[0] !== '<' and strlen($v) > 2 and count($this->val[$k]) < 1) {
$this->val[$k][] = substr($v, $v[2] === '-' ? 3 : 2);
if ($k === '')
yield $this->opr = $this->val[''][0];
}
}
if (isset($help))
exit(help('EnvArg got', $this->val));
}
# parse args an call callback $cbt
function do($cbt, $undef = null) {
if (! isset($undef))
$undef = function($k, $v) use($cbt) {err("bad arg $k => $v. supported are", implode(', ', array_keys($cbt))); };
foreach ($this->{$this->itr1}() as $k => $v) {
if (isset($cbt[$k]))
$cbt[$k]($v);
else
$undef($k, $v);
}
}
}
###----------- out: output formatting, for commandline or html -----------
require_once 'envFiber.php' ;
#--- send output to mai
function out2mail($main, $email, $sub, $from) {
ob_start();
dbg1('out2mail after ob_start');
try {
$main();
} catch(Throwable $t) {
outThr($t, "out2mail caught");
error_log("out2mail calling main catch: $t");
}
dbg1('before ob_end, last_error:', error_get_last());
$c = ob_get_contents();
ob_end_clean();
dbg1("got contents from main: $c");
error_clear_last();
$r = mail($email, $sub, $c, "From: $from\r\nContent-Type: text/html\r\nMIME-Version: 1.0\r\n\r\n");
dbg1('after mail returns', $r);
if (! $r) {
error_log("could not mail: " . error_get_last());
err("could not mail:", error_get_last());
$c .= "<br>\nerror: could not mail: " . error_get_last();
}
return $c;
}
#--- catch errors and write them to out
function envCatch2out($main) {
#if (EnvDE)
# return $main();
try {
$main();
} catch (Throwable $e) {
outThr($e, "error: caught");
}
}
#--- out a Throwable and format stacktrace
function outThr($e, $m) {
out($m . ' ' . get_class($e) . ': ' . $e->getMessage());
outOL("{$e->getFile()}: {$e->getLine()}");
foreach($e->getTrace() as $a)
outLi("{$a['file']}: {$a['line']} {$a['function']}(", isset($a['args']) ? i2str($a['args'], ', ') : '', ')');
outOLEnd("2str< $e >2str");
}
#--- out help
function help(...$m) {
out();
if (count($m) > 0)
out(...$m);
$f = fopen(envScript(), 'r');
while (false !== ($li = fgets($f)) and strpos($li, '/*') === false) {}
if ($li === false) {
out('no help in ' . envScript());
} else {
out('help of', envScript());
for ( ; $li !== false; $li = fgets($f)) {
out((strlen($li) >= strlen(PHP_EOL) and substr($li, $ll = strlen($li)-strlen(PHP_EOL)) == PHP_EOL)
? substr($li, 0, $ll): $li);
if (strpos($li, '*/') !== false)
break;
}
}
fclose($f);
}
#--- err plus help
function helpErr(...$m) {
help("error", ...$m);
err(...$m);
}
###----------- a2str, i2str, etc.: format any type -----------
if (PHP_VERSION_ID < 80100) {
function array_is_list($a) { # this is a function in inbuilt from php 8.1 and laster
return array_keys($a) === range(0, count($a) - 1);
}
function str_starts_with(string $haystack, string $needle) {
return substr($haystack, 0, strlen($needle)) === $needle;
}
function str_starts_with(string $haystack, string $needle) {
return substr($haystack, - strlen($needle)) === $needle;
}
}
$a2strLevel=2;
function a2str($a, $st=0) {
global $a2strLevel;
if ($a === null)
return 'null';
elseif ($a === 0 or $a === 1)
return "$a";
elseif ($a === false)
return 'false';
elseif ($a === true)
return 'true';
elseif (is_object($a)) {
if (method_exists($a, 'a2str'))
return $a->a2str($st);
if ($st > $a2strLevel)
return method_exists($a, '__tostring') ? ((string) $a) : (gettype($a) . ':' . get_class($a));
$r = '';
foreach (get_object_vars($a) as $k => $v) # attention, foreach $a as also works, however if $a is an iterator, it will iterate ....
$r .= ", $k -> " . a2str($v, $st+1);
return '{' . gettype($a) . ':' . get_class($a) . ': ' . substr($r, 2) . '}';
} elseif (! is_array($a))
return (string) $a;
elseif (count($a) <= 0)
return '[]';
elseif ($st > $a2strLevel)
return '[...' . count($a) . '...]';
$r = '';
if (array_is_list($a))
foreach ($a as $v)
$r .= ", " . a2str($v, $st+1);
else
foreach ($a as $k => $v)
$r .= ", $k => " . a2str($v, $st+1);
return '[' . substr($r, 2) . ']';
}
function i2str($a, $sep=' ') { # implode a2str of each element of $a with separator $sep
if (1 > $l = count($a))
return '';
$o = a2str($a[0]);
for ($i=1; $i < $l; $i++)
$o .= $sep . a2str($a[$i]);
return $o;
}
function i2strEC($a, $s1= '=', $s2=', ') { # implode a2str of each element of $a with 2 separators
if (1 > ($l = count($a) - 1))
return $l < 0 ? '' : a2str($a[0]);
$o = a2str($a[0]) . $s1 . a2str($a[1]);
for($i=2; $i < $l; $i+=2)
$o .= $s2 . a2str($a[$i]) . $s1 . a2str($a[$i+1]);
return $i > $l ? $o : $o . $s2 . a2str($a[$i]);
}
#--- format a number with decimal prefixes (k, M, G, ...)
function fmtG($n, $w=8, $si='') {
static $fmtG2n = ['k' => 1e3, 'M' => 1e6, 'G' => 1e9, 'T' => 1e12, 'P' => 1e15, 'E' => 1e18];
$a = abs($n);
$u = null;
foreach ($fmtG2n as $k => $v) {
if ($a < $v)
break;
$u = $k;
}
$s = $n < 0 ? '-' : $si;
if ($u === null)
return str_pad($s . round($a), $w-1, ' ', STR_PAD_LEFT) . ' ';
else if ($u === $k)
return fmtE($n, $w-1, $si) . ' ';
$l = $w - 1;
$p = $w - 5 - ($s !== '');
$m = $n/$fmtG2n[$u];
$l = $w - 1;
$p = $w - 5 - ($si !== '' or $s !== '' and abs($m) >= 100);
$r = sprintf("%{$si}{$l}.{$p}f", $m);
if (strlen($r) > $l) {
$p--;
$r = sprintf("%{$si}{$l}.{$p}f", $m);
}
return $r . $u;
}
#--- format a number ibn scientific notation economise necessary space
function fmtE($n, $w=6, $si='') {
$a = abs($n);
$p = max(0, $w + ($a >= 1 or $a == 0) - 5 - ($n < 0 or $si !== '') - ($a >= 1e10 or $a < 1e-9 and $a != 0));
$r = str_replace('e+', 'e', sprintf("%{$si}1.{$p}e", $n));
if (strlen($r) == $w)
return $r;
if (0 <= $p += strlen($r) < $w ? 1 : -1) { # retry with correct
$r2 = str_replace('e+', 'e', sprintf("%{$si}1.{$p}e", $n));
if (strlen($r2) == $w)
return $r2;
if (strlen($r2) < $w)
$r = $r2;
}
if (strlen($r) < $w)
return str_pad($r, $w, ' ', STR_PAD_LEFT);
if ($a >= 1e10)
return str_repeat($n < 0 ? '-' : '+', $w);
else if ($a >= 1e-8)
return str_repeat('?', $w);
$s = $n < 0 ? '-' : $si;
$r = sprintf("{$s}%1de-9", round($a*1e9));
#echo "\n e9 {$r}\n";
return strlen($r) <= $w ? str_pad($r, $w, ' ', STR_PAD_LEFT) : str_repeat('?', $w);
}