Provi.php

<?php

namespace Tlf;

class ProviOld {


    /**
     * the state of the current request
     */
    public $state = [];
    /**
     * the directory containing all your git repos
     */
    public string $dir;
    /**
     * all urls for documentation will begin with this url. This url will show a list of projects
     */
    public string $base_url;

    /**
     * the directory where all the views are found
     */
    public string $view_dir;

    /**
     * @param $project_dir the directory containing all your git repos
     * @param $base_url all urls for documentation will begin with this url. This url will show a list of projects
     * @param $vendor_name the name of the vendor these projects belong to
     */
    public function __construct(string $project_dir, string $base_url, string $vendor_name){
        $this->dir = $project_dir;
        $this->base_url = $base_url;

        $this->view_dir = dirname(__DIR__).'/view/';
        $this->name = $vendor_name;
    }

    /**
     * Get a response to a request
     * @return false on failure or on success an array containing keys `css`, `js`, and `content`, where `content` is a string, and the other two are arrays of `key=>value` where `key` is url & `value` is file path
     */
    public function route(){
        $url = $_SERVER['REQUEST_URI'];
        $pos = strpos($url, '?');
        if ($pos!==false)$url = substr($url,0, $pos);
        if (substr($url,0,$len=strlen($this->base_url))!==$this->base_url)return false;
        $url = substr($url,$len);
        if ($url[0]!='/')$url = '/'.$url;

        $resources = $this->view('Resources')->resources();
        

        $response_view = $this->get_response($url);
        if ($response_view===false)return false;
        $response_resources = $response_view->resources();

        $all_resources = array_merge($resources, $response_resources);
        $all_resources = $this->cleanup_resources($all_resources);

        $response = $all_resources;
        $response['content'] = $response_view.'';

        return $response;
    }

    /**
     * Get a url-specific response ... @see(route()) wraps this to make it easier to send all resource files
     *
     * @param $url the relative url (base url is removed)
     * @return a view on success or `false` on failure
     */
    public function get_response($url){
        if ($url=='/'){
            $view = $this->view('ProjectSelector');
            return $view;
        }

        $state = $this->get_state($url);
        $this->state = $state;

        // print_r($project);

        if ($state->rel_file_path=='/'){
            $view = $this->view('ProjectLayout',
                [ 'project'=>$state->project,
                ],
            );
            return $view;
        }
    

        return false;
    }

    /**
     * does nothing ... just here so `\Lia\View` doesn't crash 
     */
    public function addResourceFile(){}


    public function all_projects(){
        $projects = [];
        foreach (scandir($this->dir) as $file){
            if ($file=='.'||$file=='..')continue;
            if (!is_dir($path = $this->dir.'/'.$file))continue;
            $projects[$file] = $this->get_project($file);
        }

        return $projects;
    }

    /**
     * @param $project_dir the relative path name inside `$this->dir` 
     */
    public function get_project($project_dir){
        $path = $this->dir.'/'.$project_dir;
        $url = $this->base_url.'/'.$project_dir;
        $url = str_replace('//','/',$url);
        $project = (object)[
            'name'=>basename($project_dir),
            'dir'=>$project_dir,
            // @todo make docs dir configurable
            'url'=>$url,
            // @todo add project descriptions
            'description'=>'project descriptions not yet implemented.',
            'src_url'=>$url.'-src',
            'docs_url'=>$url,
            // 'file'=>
        ];

        return $project;
    }


    /**
     * take the file paths as given by `Lia\Obj\View` and convert them into an array like `['css'=>['/url.css'=>'/path/to/css/file.css'], 'js'=>[...]]` 
     */
    public function cleanup_resources(array $resources){

        $clean = [];
        foreach ($resources as $file_path){
            $url = substr($file_path,strlen($this->view_dir));
            $ext = pathinfo($file_path,PATHINFO_EXTENSION);
            $url = $this->base_url.'/files/'.$url;
            $url = str_replace('//', '/', $url);
            $clean[$ext][$url] = $file_path;
        }

        return $clean;
    }

    public function view($rel_name, $args=[]){

        $args['lia'] = $this;
        $args['provi'] = $this;
        $args['state'] = $this->state;
        $view = new \Lia\Obj\View('Docu/'.$rel_name, $this->view_dir, $args);
        return $view;
    }

    /**
     * @param $url the relative url path
     */
    public function get_state($url){
        $parsed_url = $this->parse_url($url);

        $project = $this->get_project($parsed_url['project_name']);

        $branch = $parsed_url['branch'];
        if (empty($branch))$branch = $this->get_default_branch($project);

        // $state['project'] = $project;

        $branch_dir = $this->dir.'/'.$project->name.'/'.$branch.'/';
        $file_path = $branch_dir.$parsed_url['path'];
        $dir_path = is_dir($file_path) ? $file_path : dirname($file_path);
        
        $state = [
            'type'=>$parsed_url['type'],
            'branch'=> $branch,
            'file_path'=>$file_path,
            'rel_file_path'=>$parsed_url['path'],
            'dir_path'=>$dir_path,
            'project'=>$project,
            'parsed_url'=> $parsed_url,
        ];


        return (object)$state;
        // print_r($state);
    }

    /**
     * Parse a url into its project-parts. ONLY looks at the url ... does not load any configs or defaults or anything.
     */
    public function parse_url(string $url){
        $parts = explode('/',$url);
        array_shift($parts); // remove the empty element
        $project_name = array_shift($parts);
        $project_name_parts = explode(':',$project_name);
        $project_name = $project_name_parts[0];
        if (substr($project_name,-4)=='-src'){
            $project_name = substr($project_name,0,-4);
            $type = 'src';
        } else $type = 'docs';
        $branch = $project_name_parts[1] ?? null;

        $parsed = [
            'project_name' => $project_name,
            'type' => $type,
            'branch' => $branch, // state will have to fill this in if it's null
            'path' => '/'.implode('/', $parts),
        ];

        return $parsed;
    }

    /**
     * @return false if not a request for provi. return array containing parsed info if is a request for provi
     */
    public function parse_url2(string $url){
        $parsed = [];

        $base_url = $this->base_url;

        // check if it's a request to provi
        if (substr($url,0,strlen($base_url))==$base_url){
            $parsed['prefix'] = $base_url;
        } else {
            return false;
        }
        // check if request to project listing
        $rel_url = substr($url,strlen($base_url));
        if ($rel_url==''){
            $project = new \Tlf\Provi\Project();
            $project->type = 'project_listing';
            $project->prefix = $base_url;
            return $project;
        }
        // parse out the project name & branch
        $parts = explode('/', $rel_url);
            // array_shift($parts); // might need this if the prefix does not end with a `/`
        $project_name_and_branch = array_shift($parts);
        $parsed['project_url'] = $base_url.'/'.$project_name_and_branch.'/';
        $pnab_parts = explode(':', $project_name_and_branch);

        if (count($pnab_parts)>1){
            $parsed['branch'] = $pnab_parts[1];
        } else {
            // what info do i need to get the default branch?
            // project name
            $parsed['branch'] = 'v0.9';//$this->get_default_branch();
        }

        // parse out the type (src or docs)
        $project_name_and_type = $pnab_parts[0];
        if (substr($project_name_and_type,-4)=='-src'){
            $parsed['project_name'] = substr($project_name_and_type,0,-4);
            $parsed['type']='src';
        } else {
            $parsed['project_name'] = $project_name_and_type;
            $parsed['type'] = 'docs';
        }

        // determine the branch dir
        $project_branch_dir = $this->dir.'/'.$parsed['project_name'].'/'.$parsed['branch'].'/';
        $parsed['branch_dir'] = $project_branch_dir;
        if ($parsed['type']=='docs'){
            $docs_dir = $this->get_docs_dir($parsed['branch_dir']);
            $file_base_dir = $project_branch_dir.'/'.$docs_dir.'/';//$this->get_docs_dir();
        } else {
            $file_base_dir = $project_branch_dir;
        }

        // build the absolute paths
        $parsed['docs_dir'] = $project_branch_dir.'/'.$this->get_docs_dir($parsed['branch_dir']).'/';
        $parsed['rel_file_path'] = implode('/', $parts);
        $parsed['abs_file_path'] = str_replace('//','/',
            $file_base_dir.'/'.$parsed['rel_file_path']
        );

        if (is_dir($parsed['abs_file_path'])){
            $parsed['rel_dir_path'] = $parsed['rel_file_path'].'/';
            $parsed['abs_dir_path'] = $parsed['abs_file_path'].'/';
            // check for a README
            if (file_exists($readme_path=$parsed['abs_file_path'].'README.md')){
                $parsed['abs_file_path'] = $readme_path;
                $parsed['rel_file_path'] .= 'README.md';
            }
        } else {
            $parsed['rel_dir_path'] = dirname($parsed['rel_file_path']).'/';
            if ($parsed['rel_dir_path']=='./')$parsed['rel_dir_path'] = '/';
            $parsed['abs_dir_path'] = dirname($parsed['abs_file_path']).'/';
        }


        // remove duplicate forward slashes
        $parsed = array_map(
            function($v){
                return str_replace('//', '/',$v);
            },
            $parsed
        );

        // convert array to object
        $project = new \Tlf\Provi\Project();
        foreach ($parsed as $k=>$v){
            $project->$k = $v;
        }

        return $project; 
    }

    /**
     * @return false on failure or the relative path inside the branch dir in which docs are contained
     */
    public function get_docs_dir(string $branch_dir){
        $try = [
            'doc','docs'
        ];
        foreach ($try as $d){
            if (is_dir($branch_dir.'/'.$d)){
                return $d;
            }
        }

        return false;
    }

    /**
     * @param $project the project as from get_project()
     */
    public function get_default_branch(object $project){
        //@todo actually figure out default branch
        return 'v0.9';
    }

    /**
     * get an array of files & directories in the given dir.
     *
     * @return array ['files'=>['file1','file2'], 'dirs'=>['dir1','dir2']]
     */
    public function files_in_dir(string $dir){

        $files = [];
        foreach (scandir($dir) as $f){
            if ($f=='..'||$f=='.')continue;
            if (is_file($dir.'/'.$f))$files['files'][] = $f;
            else $files['dirs'][] = $f;
        }

        rsort($files['dirs']);
        rsort($files['files']);
        

        return $files;
    }
}