HelpMenuGenerator.php

<?php

namespace Tlf\Bash\Scrawl;

/**
 * Generates help menus for bash libraries
 */
class HelpMenuGenerator extends \Tlf\Scrawl\DoNothingExtension {

    protected $regexes = [
        'function' => '/\ *function\ *([a-zA-Z\_].+)\(\)/'
    ];

    protected $funcList = [];

    /**
     * 
     */
    public function scan_file_processed(string $path, string $relPath, string $file_content, array $file_exports){
        if (!$this->is_core_file($path))return;

        echo "Get ast for $path";
        $ast = $this->get_bash_ast($path);



        if (($count=count($ast['function']??[]))==0){
            $this->scrawl->warn("No functions", "No functions found in file '$relPath'");
            return;
        }

        $this->scrawl->good("\nBash Ast", "Found $count functions in file '$relPath'");
        // add the ast to the output
        foreach ($ast['function'] as $func){
            // add all functions to `bash_function` group
            $this->scrawl->set('bash_function', $func['name'], $func);

            // add function to `bent_function` group if it can be executed through cli by the user
            // so if a file is `core-whatever.bash` and the function is `core_something(){}`, it fits in the `core` group.
            $file = basename($relPath);
            $name = $func['name'];
            $parts = explode("_", $name);
            $group = $parts[0];
            if (substr($file,0,strlen($group))==$group){
                $this->scrawl->set('bent_function', $func['name'], $func);
            }
        }
    }

    /**
     * Generate help menus
     */
    public function scan_filelist_processed(array $code_files, array $all_exports){
        $bent_functions_flat = $this->scrawl->get_group('bent_function');
        $grouped_functions = $this->group_bent_functions($bent_functions_flat);

        $mains = $grouped_functions['-main-'];
        unset($grouped_functions['-main-']);

        $mains_menu = $this->generate_main_help_menu($mains);
        $this->scrawl->write_file('code/help/help_groups.bash', $mains_menu);
        foreach ($grouped_functions as $group=>$functions){
            $help_menu = $this->generate_help_menu($group, $functions);
            $this->scrawl->write_file('code/help/'.$group.'.bash', $help_menu);
        }
    }

    /**
     * Generate a string that shows a help menu using the bash cli
     * @param $group the group name
     * @param $functions an array of functions
     */
    public function generate_help_menu(string $group, array $functions){
        $help_command = "prompt_choose_function \"# $group [command]\${cOff}-\" ";
        foreach ($functions as $function){
            $help_command .= " \\\n";
            $name_parts = explode("_", $function['name']);
            if (count($name_parts)==1){
                $command = '-default-';
                $command_run = '';
            }
            else {
                array_shift($name_parts);
                $command = implode(' ',$name_parts);
                $command_run = $command;
            }

            $description_full = $function['docblock']['tip'] ?? $function['docblock']['description'] ?? '';
            $parts = explode("\n", trim($description_full));
            $description = trim($parts[0]);
            if ($description=='')$description = '-- no description';
            $help_command .= "\"\${help_mode} $command_run\" \"$command\" \"$description\"";
        }

        return $help_command;
    }

    public function generate_main_help_menu(array $functions){
        $help_command = "prompt_choose_function \"# [command]\${cOff}-\" ";
        foreach ($functions as $function){
            $help_command .= " \\\n";
            $command = $function['name'];
            $command_run = $function['name'];

            $description_full = $function['docblock']['tip'] ?? $function['docblock']['description'] ?? '';
            $parts = explode("\n", trim($description_full));
            $description = trim($parts[0]);
            if ($description=='')$description = '-- no description';
            $help_command .= "\"\${help_mode} $command_run\" \"$command\" \"$description\"";
        }

        return $help_command;
    }

    /**
     * Group functions by their group. i.e. `core_something()` goes into `core`. `test_whatever` goes into `test`
     *
     * @param $bent_functions_flat array of all bent functions to group
     * @return array with @key=group_name & @value=array of functions in that group. Also contains `-main-` which is commands with only one part (no underscores).
     */
    public function group_bent_functions(array $bent_functions_flat){
        /** -main- is all the main commands like `core`, `test`, and whatever */
        $bent_functions_grouped = ['-main-'=>[]];
        foreach ($bent_functions_flat as $function){
            $name = $function['name'];
            $parts = explode("_", $name);
            $group = $parts[0];
            if (count($parts)==1)$bent_functions_grouped['-main-'][$group] = $function;
            else if (!isset($bent_functions_grouped['-main-'][$group])){
                $bent_functions_grouped['-main-'][$group] = ['name'=>$group];
            }
            if (!isset($bent_functions_grouped[$group])) $bent_functions_grouped[$group] = [];
            $bent_functions_grouped[$group][] = $function;
        }

        return $bent_functions_grouped;
    }

    /**
     * Get an ast
     * @param $file_path absolute path to the file to aprse
     * @return array of the generated ast 
     */
    public function get_bash_ast(string $file_path){
        $lexer = new \Tlf\Lexer();
        $bashGrammar = new \Tlf\Lexer\BashGrammar();
        $lexer->addGrammar($bashGrammar, null, false);
        // $lexer->addGrammar($bashGrammar, 'this', false);
        foreach ($bashGrammar->getDirectives(':bash') as $directive){
            $lexer->addDirective($directive);
        }
        $startingAst = new \Tlf\Lexer\Ast('file', []);
        $ast = $lexer->lex(file_get_contents($file_path),$startingAst);
        $tree = $ast->getTree();

        return $tree;
    }

    /**
     * Check if the target file is in the `code/core` dir and has a `.bash` extension
     */
    public function is_core_file($path){
        if (pathinfo($path,PATHINFO_EXTENSION)!='bash')return false;
        $dir = dirname($path);
        if (substr($dir,-strlen('/code/core'))!='/code/core')return false;

        return true;
    }

    /**
     *
     * old help file generation ...
     * it's a mess
     *
     *
     * keep this around until the rest the code is processed out of it. Primarily, that is the `uasort()` that uses docblock attribute @order to order items in the help menu. Second is the generation of a help menu that shows all the command groups.
     *
     * There might need to be additional changes/improvements to the help menu generation ... idunner. 
     */
    public function old_scan_filelist_processed(array $code_files, array $all_exports){
        // print_r($this->scrawl->getOutputs('function'));
        // exit;
        $funcs = $this->scrawl->get_group('bash_function');

        // print_r(array_keys($funcs));
        // exit;

        $byGroup = [];
        foreach ($funcs as $f){
            $parts = explode('_', $f['name']);
            $group = $parts[0];
            // if ($group!='log')continue;
            if ($group==$f['name'])$byGroup[$group]['main']=$f;
            else $byGroup[$group][] = $f;
        }
        // print_r(array_keys($byGroup));
        // exit;


        $mains = [];
        /** Generate help menu for each command group
         */
        foreach ($byGroup as $group => $list){
            $help = [];
            if (!isset($list['main'])){
                // var_dump($group);
                // var_dump($list);
                // exit;
                // continue;
                $list['main'] = [
                    'type'=>'function',
                    'name'=>$group,
                    'docblock'=>[
                        'type'=>'docblock',
                        'src'=>'--',
                        'description'=>'--',
                    ],
                ];
            }

            $f = $list['main'];
            // var_dump($f);
            // exit;
            unset($list['main']);
            array_unshift($list,$f);
            $mains[$f['name']] = $f;
            $descript = $f['docblock']['tip'] ?? $f['docblock']['description'] ?? '';
            $help[] = "# [bent $group] \${cOff}- $descript";
            // if ($group=='log'){
                // print_r($f);exit;
            // }
            foreach ($list as $function){
                // "run core check" "check" "Check the status of your project"
                // if (($function['docblock']??null)==null){
                    // $function['docblock']['']
                    // continue;
                // }
                $parts = explode('_', $function['name']);
                $help[] = '${help_mode} '.implode(" ", $parts);
                $help[] = implode(" ", count($parts)>1 ? array_slice($parts,1) : $parts);
                $help[] = $function['docblock']['tip'] ?? $function['docblock']['description'] ?? '';
            }
            $help = array_map(function($v){return "\"$v\"";}, $help);

            $helpStr ='prompt_choose_function '.implode(" ", $help);
            // $this->scrawl->addOutput('file', '../code/help/'.$group.'.bash', $helpStr);
            $this->scrawl->write_file('code/help/'.$group.'.bash', $helpStr);
        }

        uasort($mains,
            function($a, $b){
                $orderA = (int)($a['docblock']['order'] ?? 9999);
                $orderB = (int)($b['docblock']['order'] ?? 9999);
                if ($orderA > $orderB)return 1;
                else if ($orderA == $orderB)return 0;
                else return -1;
            }, 
        );


        // print_r($mains);
        // exit;
        /** Generate help menu for [bent help] */
        $help = [];
        unset ($mains['']);
        // $helpFunc = $mains['help'];
        // unset($mains['help']);
        // $help[] = "# ".($helpFunc['docblock']['description'] ?? $helpFunc['docblock']['tip'] ?? '');
        foreach ($mains as $name=>$function){
            $parts = explode('_', $function['name']);
            $help[] = '${help_mode} '.implode(" ", $parts);
            $help[] = implode(" ", array_slice($parts, 0,1));
            $help[] = $function['docblock']['tip'] ?? $function['docblock']['description'] ?? '';
        }

        $help = array_map(function($v){return "\"$v\"";}, $help);
        $helpStr = 'prompt_choose_function '.implode(" ", $help);
        // $this->scrawl->addOutput('file', '../code/help/help_groups.bash', $helpStr);
        $this->scrawl->write_file('code/help/help_groups.bash', $helpStr);
    }

}