NewCli.php

<?php

namespace Tlf\Tester;

class NewCli {

    use OutputTrait;

    /**
     * A combination of default inputs + inputs given via cli
     * @structure a key=>value array
     * @warning use $answers, not configs for most things
     */
    protected $inputs = [];
    /** the direcotry from which this is being executed */
    public $pwd;
    /** the directory in which tests are run */
    public $dir;

    protected $reports = [];

    /**
     * Keys who's values should always be converted to array
     */
    protected $arrayify = [
        'test'=>true,
        'dir.exclude'=>true,
    ];

    /**
     * The configs built from default configs + config file
     * @warning use $answers, not configs for most things
     */
    protected $configs = [];
    /**
     * The combination of cli inputs + configs.
     */
    protected $answers = [];

    /**
     *
     *
     */
    public function __construct(string $pwd, array $args=[]){
        $this->pwd = $pwd;
        $this->inputs = array_merge($this->getDefaultInputs(), $this->parseArgs($args));
        $this->configs = array_merge($this->getDefaultConfigs(), $this->getConfigFile());
        $this->answers = array_merge($this->configs, $this->inputs);
        $this->dir = $pwd.'/'.$this->answers['dir.test'];

        // load autoload file
        $autoloadFile = $pwd.'/'.$this->answers['file.autoloader'];
        if (is_file($autoloadFile)){
            require_once($autoloadFile);
        } else {
            $this->report("answer 'file.autoloader' was '$autoloadFile', but '$pwd/$autoloadFile' does not exist");
        }

        if (empty($this->answers['file.require'])&&!empty($this->answers['autoload'])){
            $this->answers['file.require'] = $this->answers['autoload'];
        }
        foreach ($this->answers['file.require'] as $relFile){
            $path = $pwd.'/'.$relFile;
            if (is_file($path))require_once($path);
            else $this->report("'$relFile' of 'file.require' does not exist inside '$pwd'");
        }
        foreach ($this->answers['dir.require'] as $relFile){
            $path = $pwd.'/'.$relFile;
            if (is_dir($path))$this->requireDir($path);
            else $this->report("'$relFile' of 'dir.require' does not exist inside '$pwd'");
        }
        echo "\n";
    }

    public function requireDir($path){
        foreach (scandir($path) as $file){
            if (substr($file,-4)!='.php')continue;
            require_once($path.'/'.$file);
        }
    }

    public function init(){
        $dir = getcwd().'/test';
        $continue = readline("Initialize test dir at $dir? (y/n) ");
        if ($continue!=='y')return;
        mkdir($dir);
        mkdir($dir.'/run');
        mkdir($dir.'/src');
        $files = [
            'run/Blogs.php',
            'src/Tester.php',
            'bootstrap.php',
            'config.json',
        ];
        foreach ($files as $f){
            copy(dirname(__DIR__).'/.setup/test/'.$f, $dir.'/'.$f);
        }
    }
    public function run(){
        if (($this->inputs['init']??false)==true){
            $this->init();
            return;
        }

        $dir = $this->dir;
        $phpFiles = [];
        foreach ($this->answers['testDirs']??['.'] as $sub_dir){
            $search_dir = $dir.'/'.$sub_dir;
            $phpFiles = array_merge($phpFiles, \Tlf\Tester\Utility::getAllFiles($search_dir,$dir,'.php'));
        }


        $excludes = $this->answers['dir.exclude'];
        $testSet = [];
        foreach ($phpFiles as $relPath){
            foreach ($excludes as $e){
                $re = $relPath;
                if ($re[0]!='/')$re = '/'.$re;
                if ($e[0]!='/')$e = '/'.$e;
                if (substr($relPath,0,strlen($e))==$e)continue 2;
            }
            if (in_array($relPath, $excludes))continue;
            $filePath = $dir.'/'.$relPath;
            $ob_level = Utility::startOb();

            require_once($filePath); 
            $class = Utility::getClassFromFile($filePath);
            if ($class==null){
                $this->report("No class found in $filePath");
                continue;
            }
            if (!is_a($class, '\\Tlf\\Tester', true))continue;
            $tester = new $class($this->answers, $this);
            $results = $tester->run();
            $output = Utility::endOb($ob_level);

            $testSet[$dir][$class] = ['file'=>$filePath, 'object'=>$tester, 'tests'=>$results];
        //I'm trying to return something like this:
                // $allCompletedTests = [
                    // 'dir'=>[
                        // '\\Clasname\\That\\Was\\Tested'=>
                        // [
                            // 'file'=> 'file path',
                            // 'object' => $theTesterInstance,
                            // 'tests'=> [
                //                // for function testMethodName()
                                // 'MethodName' => $resultObject
                            // ]//or should this be an array of the testers??
                        // ],
                        // '\\Another\\Class' => $andItsTests
                    // ]
                // ];
        }


        
        echo $this->cliOutput($testSet);
        $html = $this->htmlOutput($testSet);
        $this->writeTestResults($html);
        echo "\n\n";

        return $testSet;
    }

    public function report($msg){
        $this->reports[] = $msg;   
        echo "\n".$msg;
    }

    /**
     * Get an array of parsed inputs
     */
    public function getInputs(){
        return $this->inputs;
    }

    /** 
     * Get configs as they're stored on disk
     */
    public function getConfigs(){
        return $this->configs;
    }

    /**
     * Get all answers given to this program. Cli inputs, config file, and defaults where answers were not given
     */
    public function getAnswers(){
        return $this->answers;
    }

    protected function parseArgs(array $args){
        $out = [];
        foreach ($args as $index=>$input){
            if ($input[0]=='-'){
                if ($input[1]=='-'){
                    $key = substr($input,2);
                    $out[$key] = true;
                    continue;
                } 
                $key = substr($input,1);
                continue;
            } 
            if ($key!=null){
                if ($input=='true')$input = true;
                else if ($input=='false')$input = false;

                if (isset($this->arrayify[$key]))$out[$key][] = $input;
                else $out[$key] = $input;
                $key = null;
                continue;
            }
            $this->report("input '$input' is not valid at index $index");
        }
        return $out;
    }

    protected function getConfigFile(){
        $configFile = $this->pwd.'/'.$this->inputs['file.config'];
        if (is_file($configFile)){
            // var_dump($configFile);
            $configs = json_decode(file_get_contents($configFile),true);
            if (!is_array($configs))return [];

            // var_dump($configs);
            // exit;

            $confs = $configs;
            foreach ($confs as $key=>$value){
                if (isset($this->arrayify[$key])
                    &&!is_array($value)
                ){
                    $configs[$key][] = $value;
                }
            }
            return $configs;
        }
        //@todo prompt to write a default config file
        return [];
    }

    /**
     *
     * @return an array of default inputs
     */
    public function getDefaultInputs(){
        return [
            'file.config'=>'test/config.json',
            'test'=>null,
            // 'testDir'=>'something',
        ];
    }

    public function getDefaultConfigs(){
        return [
            'dir.test'=>'test',
            'dir.exclude'=>['extra'],
            'file.autoloader'=>'vendor/autoload.php',
            'file.require'=>[],
            'dir.require'=>[],
            'results.writeHtml'=>true,
        ];
    }

    protected function runTestFile($filePath,$options, &$testSet){


    }
}