Scrawl.php

<?php

namespace Lia\Ext;

/**
 * Generates documentation listing:
 * - Packages
 * - Addons
 * - Routes
 * - Views
 * - Configurable Options (and maybe listing configs that have been set)
 */
class Scrawl extends \Tlf\Scrawl\DoNothingExtension implements \Tlf\Scrawl\Extension {

    static public array $config_file_locations = 
            [
                'config/liaison.json', 
                '.config/liaison.json',
                'config/lia.json', 
                '.config/lia.json'
            ];


    /**
     * Actually generate the documentation and write it to the docs dir.
     *
     * @param $lia
     * @param $out_dir the relative directory to write documentation to
     * @param $base_dir the absolute path to the root of the software
     */
    protected function generate_documentation(\Lia $lia, string $out_dir, string $base_dir){
        $args = ['lia'=>$lia, 'base_dir'=>$base_dir, 'scrawl_ext' => $this];
        $this->scrawl->write_doc($out_dir.'/README.md', $this->scrawl->get_template('Liaison-README', $args));
        //$this->scrawl->write_doc($out_dir.'/Packages.md', $this->scrawl->get_template('Liaison-Packages', $args));
        foreach ($lia->packages as $package){
            $p_args = array_merge($args, ['package'=>$package]);
            $this->scrawl->write_doc($out_dir.'/Package/'.$package->fqn.'.md', 
                $this->scrawl->prepare_md_content(
                    $this->scrawl->get_template('Liaison-Package',$p_args)
                )
            );
        }

        $this->scrawl->write_doc($out_dir.'/Views.md',
            $this->scrawl->prepare_md_content(
                $this->scrawl->get_template('Liaison-Views',$args)
            )
        );

        $this->scrawl->write_doc($out_dir.'/Routes.md',
            $this->scrawl->prepare_md_content(
                $this->scrawl->get_template('Liaison-Routes',$args)
            )
        );
    }

    public function get_callable_details($c, $base_dir){
            if ($c instanceof \Closure){
                $refc = new \ReflectionFunction($c);
                return "Anonymous Function defined in: `".$this->get_rel_path($refc->getFilename(), $base_dir)."` on line ".$refc->getStartLine();
                // could use getDocComment() to provide further information, but I think this is fine for now.
            } else if (is_string($c)) {
                return "Function name: `$c(...)`";
            } else if (is_array($c)) {
                if (count($c)==2 && is_object($c[0]) && is_string($c[1])){
                    return "Object method: `".get_class($c[0]).'->'.$c[1]."(...)`";
                } else {
                    return "(*details unavailable*)";
                }
            } else {
                return "(*details unavailable*)";
            }
    }
    public function get_rel_class_file(object $obj, string $base_dir): string{

        $refClass = new \ReflectionClass($obj);
        $class_abs_path = realpath($refClass->getFileName());
        return $this->get_rel_path($class_abs_path, $base_dir);
    }
    public function get_rel_path(string $abs_path, string $base_dir): string{
        $path = null;
        if (substr($abs_path, 0, strlen($base_dir)) != $base_dir){
            $path = '(error)';
        } else {
            $path = substr($abs_path, strlen($base_dir));
        }

        return $path;
    }

    /**
     * Get absolute path to liaison config file, or null if it does not exist.
     */
    static public function get_config_file(string $base_dir): ?string {

        $config_file = null;
        foreach (static::$config_file_locations as $file){
            
            if (is_file($abs_file=$base_dir.'/'.$file)){
                $config_file = $abs_file;
                break;
            }
        }
        return $config_file;
    }

    public function bootstrap(){

        $template_dir = dirname(__DIR__,3).'/.doctemplate/';
        if (!in_array($template_dir, $this->scrawl->template_dirs))$this->scrawl->template_dirs[] = $template_dir;
    }

    /**
     * Called when all files are finished being processed
     *
     * @param $code_files array of all files that were scanned and processed
     * @param $all_exports array of all exports found in the scanned files
     */
    public function scan_filelist_processed(array $code_files, array $all_exports){
        $this->scrawl->good("#### GENERATE LIA DOCS ####", "Begin generation of Liaison docs");
        $base_dir = getcwd();
        $config_file = static::get_config_file($base_dir);
        if ($config_file===null){
            $this->scrawl->warn("Config not found", "A liaison config file was not found. Create one of the following, relative to the current directory: ".implode(", ", static::$config_file_locations));
            return;
        }

        $lia = null;
        $configs = null;
        try {
            $configs = json_decode(file_get_contents($config_file), true);
            $lia = $this->get_lia_instance($base_dir, $configs);
        } catch (\Throwable $t){

        }
        if (!($lia instanceof \Lia)
            || $configs == null){
            $this->scrawl->warn("Lia Docs", "Failed to load liaison instance based onf config file at: $config_file.");
            return;
        }

        if (!isset($configs['liaison_docs_dir'])){
            $this->scrawl->warn("Configuration missing", "You MUST set 'liaison_docs_dir' to a relative path inside your 'docs' dir. Set this config in: $config_file");
            return;
        }

        $this->scrawl->report("Config and Liaison loaded\n");

        $this->generate_documentation($lia, $configs['liaison_docs_dir'].'/', $base_dir);

        $this->scrawl->good("#### FINISH LIA DOCS GENERATION ####", "End of generating lia docs");
    }


    protected function get_lia_instance(string $base_dir, array $configs): \Lia {

        if (!isset($_SERVER['REQUEST_URI'])){
            if (isset($configs['scrawl_route'])){
                $_SERVER['REQUEST_URI'] = $configs['scrawl_route'];
            } else {
                $_SERVER['REQUEST_URI'] = '/';
            }
            $_SERVER['HTTP_HOST'] = null;
            $_SERVER['REQUEST_METHOD'] = 'GET';
        }


        $get_output = function($__deliver_script, $__liaison_varname, &$__liaison_instance): string {
            ob_start();
            require($__deliver_script);

            $__liaison_instance = $$__liaison_varname;

            return ob_get_clean();
        };

        $deliver_script = $base_dir.'/'.$configs['deliver_script'];
        $liaison_varname = $configs['liaison_variable_name'];

        $lia = null;
        $output = $get_output($deliver_script, $liaison_varname, $lia);       

        return $lia;
    }
}