xref: /plugin/dev/cli.php (revision 488499cc0b4ba0361a8f7e1e766105f9c20217f4)
136c0b2b4SAndreas Gohr#!/usr/bin/env php
236c0b2b4SAndreas Gohr<?php
336c0b2b4SAndreas Gohr
436c0b2b4SAndreas Gohruse dokuwiki\Extension\CLIPlugin;
536c0b2b4SAndreas Gohruse dokuwiki\Extension\PluginController;
65586e97bSAndreas Gohruse dokuwiki\plugin\dev\LangProcessor;
770316b84SAndreas Gohruse dokuwiki\plugin\dev\Skeletor;
81a23d1dbSAndreas Gohruse dokuwiki\plugin\dev\SVGIcon;
936c0b2b4SAndreas Gohruse splitbrain\phpcli\Exception as CliException;
1036c0b2b4SAndreas Gohruse splitbrain\phpcli\Options;
1136c0b2b4SAndreas Gohr
1236c0b2b4SAndreas Gohr/**
1336c0b2b4SAndreas Gohr * @license GPL2
1436c0b2b4SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
1536c0b2b4SAndreas Gohr */
1636c0b2b4SAndreas Gohrclass cli_plugin_dev extends CLIPlugin
1736c0b2b4SAndreas Gohr{
1836c0b2b4SAndreas Gohr    /**
1936c0b2b4SAndreas Gohr     * Register options and arguments on the given $options object
2036c0b2b4SAndreas Gohr     *
2136c0b2b4SAndreas Gohr     * @param Options $options
2236c0b2b4SAndreas Gohr     * @return void
2336c0b2b4SAndreas Gohr     */
2436c0b2b4SAndreas Gohr    protected function setup(Options $options)
2536c0b2b4SAndreas Gohr    {
26f2576912SAndreas Gohr        $options->useCompactHelp();
2736c0b2b4SAndreas Gohr        $options->setHelp(
28f2576912SAndreas Gohr            "CLI to help with DokuWiki plugin and template development.\n\n" .
2936c0b2b4SAndreas Gohr            "Run this script from within the extension's directory."
3036c0b2b4SAndreas Gohr        );
3136c0b2b4SAndreas Gohr
32fcb8165bSAndreas Gohr        $options->registerCommand('init', 'Initialize a new plugin or template in the current directory.');
33f2576912SAndreas Gohr        $options->registerCommand('addTest', 'Add the testing framework files and a test. (_test/)');
34f2576912SAndreas Gohr        $options->registerArgument('test', 'Optional name of the new test. Defaults to the general test.', false,
35f2576912SAndreas Gohr            'addTest');
36f2576912SAndreas Gohr        $options->registerCommand('addConf', 'Add the configuration files. (conf/)');
37f2576912SAndreas Gohr        $options->registerCommand('addLang', 'Add the language files. (lang/)');
38719b4841SAndreas Gohr        $options->registerCommand('addAgents', 'Add an initial AGENTS.md file for guiding LLM coding agents');
39*488499ccSAndreas Gohr        $options->registerOption('claude', 'Symlink the AGENTS.md to CLAUDE.md for use with claude code', 'c', false, 'addAgents');
4036c0b2b4SAndreas Gohr
41f2576912SAndreas Gohr        $types = PluginController::PLUGIN_TYPES;
42f2576912SAndreas Gohr        array_walk(
43f2576912SAndreas Gohr            $types,
44f2576912SAndreas Gohr            function (&$item) {
45f2576912SAndreas Gohr                $item = $this->colors->wrap($item, $this->colors::C_BROWN);
46f2576912SAndreas Gohr            }
4736c0b2b4SAndreas Gohr        );
4836c0b2b4SAndreas Gohr
49f2576912SAndreas Gohr        $options->registerCommand('addComponent', 'Add a new plugin component.');
50f2576912SAndreas Gohr        $options->registerArgument('type', 'Type of the component. Needs to be one of ' . join(', ', $types), true,
51f2576912SAndreas Gohr            'addComponent');
52f2576912SAndreas Gohr        $options->registerArgument('name', 'Optional name of the component. Defaults to a base component.', false,
53f2576912SAndreas Gohr            'addComponent');
54f2576912SAndreas Gohr
55f2576912SAndreas Gohr        $options->registerCommand('deletedFiles', 'Create the list of deleted files based on the git history.');
56f2576912SAndreas Gohr        $options->registerCommand('rmObsolete', 'Delete obsolete files.');
571a23d1dbSAndreas Gohr
581a23d1dbSAndreas Gohr        $prefixes = array_keys(SVGIcon::SOURCES);
591a23d1dbSAndreas Gohr        array_walk(
601a23d1dbSAndreas Gohr            $prefixes,
611a23d1dbSAndreas Gohr            function (&$item) {
621a23d1dbSAndreas Gohr                $item = $this->colors->wrap($item, $this->colors::C_BROWN);
631a23d1dbSAndreas Gohr            }
641a23d1dbSAndreas Gohr        );
651a23d1dbSAndreas Gohr
661a23d1dbSAndreas Gohr        $options->registerCommand('downloadSvg', 'Download an SVG file from a known icon repository.');
671a23d1dbSAndreas Gohr        $options->registerArgument('prefix:name',
681a23d1dbSAndreas Gohr            'Colon-prefixed name of the icon. Available prefixes: ' . join(', ', $prefixes), true, 'downloadSvg');
691a23d1dbSAndreas Gohr        $options->registerArgument('output', 'File to save, defaults to <name>.svg in current dir', false,
701a23d1dbSAndreas Gohr            'downloadSvg');
7192738407SAndreas Gohr        $options->registerOption('keep-ns', 'Keep the SVG namespace. Use when the file is not inlined into HTML.', 'k',
7292738407SAndreas Gohr            false, 'downloadSvg');
731a23d1dbSAndreas Gohr
748f82d673SAndreas Gohr        $options->registerCommand('cleanSvg', 'Clean a existing SVG files to reduce their file size.');
758f82d673SAndreas Gohr        $options->registerArgument('files...', 'The files to clean (will be overwritten)', true, 'cleanSvg');
7692738407SAndreas Gohr        $options->registerOption('keep-ns', 'Keep the SVG namespace. Use when the file is not inlined into HTML.', 'k',
7792738407SAndreas Gohr            false, 'cleanSvg');
785586e97bSAndreas Gohr
795586e97bSAndreas Gohr        $options->registerCommand('cleanLang',
805586e97bSAndreas Gohr            'Clean language files from unused language strings. Detecting which strings are truly in use may ' .
815586e97bSAndreas Gohr            'not always correctly work. Use with caution.');
82ec0a687bSAndreas Gohr
8357732a2dSAndreas Gohr        $options->registerCommand(
8457732a2dSAndreas Gohr            'test',
8557732a2dSAndreas Gohr            'Run the unit tests for this extension. (calls phpunit using the proper config and group)'
8657732a2dSAndreas Gohr        );
8757732a2dSAndreas Gohr        $options->registerOption(
8857732a2dSAndreas Gohr            'filter',
8957732a2dSAndreas Gohr            'Filter tests to run by a given string. (passed to phpunit)',
9057732a2dSAndreas Gohr            null,
9157732a2dSAndreas Gohr            true,
9257732a2dSAndreas Gohr            'test'
9357732a2dSAndreas Gohr        );
9457732a2dSAndreas Gohr        $options->registerArgument('files...', 'The test files to run. Defaults to all.', false, 'test');
95ec0a687bSAndreas Gohr
96ec0a687bSAndreas Gohr        $options->registerCommand('check', 'Check for code style violations.');
97ec0a687bSAndreas Gohr        $options->registerArgument('files...', 'The files to check. Defaults to the whole extension.', false, 'check');
98ec0a687bSAndreas Gohr
99ec0a687bSAndreas Gohr        $options->registerCommand('fix', 'Fix code style violations and refactor outdated code.');
100ec0a687bSAndreas Gohr        $options->registerArgument('files...', 'The files to check. Defaults to the whole extension.', false, 'fix');
10136c0b2b4SAndreas Gohr    }
10236c0b2b4SAndreas Gohr
10336c0b2b4SAndreas Gohr    /** @inheritDoc */
10436c0b2b4SAndreas Gohr    protected function main(Options $options)
10536c0b2b4SAndreas Gohr    {
1061a23d1dbSAndreas Gohr        $args = $options->getArgs();
1071a23d1dbSAndreas Gohr
10836c0b2b4SAndreas Gohr        switch ($options->getCmd()) {
10936c0b2b4SAndreas Gohr            case 'init':
11036c0b2b4SAndreas Gohr                return $this->cmdInit();
11136c0b2b4SAndreas Gohr            case 'addTest':
11236c0b2b4SAndreas Gohr                $test = array_shift($args);
11336c0b2b4SAndreas Gohr                return $this->cmdAddTest($test);
11436c0b2b4SAndreas Gohr            case 'addConf':
11536c0b2b4SAndreas Gohr                return $this->cmdAddConf();
11636c0b2b4SAndreas Gohr            case 'addLang':
11736c0b2b4SAndreas Gohr                return $this->cmdAddLang();
118719b4841SAndreas Gohr            case 'addAgents':
119*488499ccSAndreas Gohr                $claude = $options->getOpt('claude');
120*488499ccSAndreas Gohr                return $this->cmdAddAgents($claude);
12136c0b2b4SAndreas Gohr            case 'addComponent':
12236c0b2b4SAndreas Gohr                $type = array_shift($args);
12336c0b2b4SAndreas Gohr                $component = array_shift($args);
12436c0b2b4SAndreas Gohr                return $this->cmdAddComponent($type, $component);
12536c0b2b4SAndreas Gohr            case 'deletedFiles':
12636c0b2b4SAndreas Gohr                return $this->cmdDeletedFiles();
127c5c85a97SAndreas Gohr            case 'rmObsolete':
1281a23d1dbSAndreas Gohr                return $this->cmdRmObsolete();
1291a23d1dbSAndreas Gohr            case 'downloadSvg':
1301a23d1dbSAndreas Gohr                $ident = array_shift($args);
1311a23d1dbSAndreas Gohr                $save = array_shift($args);
13270316b84SAndreas Gohr                $keep = $options->getOpt('keep-ns');
13392738407SAndreas Gohr                return $this->cmdDownloadSVG($ident, $save, $keep);
1341a23d1dbSAndreas Gohr            case 'cleanSvg':
13570316b84SAndreas Gohr                $keep = $options->getOpt('keep-ns');
1368f82d673SAndreas Gohr                return $this->cmdCleanSVG($args, $keep);
1375586e97bSAndreas Gohr            case 'cleanLang':
1385586e97bSAndreas Gohr                return $this->cmdCleanLang();
139ec0a687bSAndreas Gohr            case 'test':
14057732a2dSAndreas Gohr                $filter = $options->getOpt('filter');
14157732a2dSAndreas Gohr                return $this->cmdTest($filter, $args);
142ec0a687bSAndreas Gohr            case 'check':
143ec0a687bSAndreas Gohr                return $this->cmdCheck($args);
144ec0a687bSAndreas Gohr            case 'fix':
145ec0a687bSAndreas Gohr                return $this->cmdFix();
14636c0b2b4SAndreas Gohr            default:
1471a23d1dbSAndreas Gohr                $this->error('Unknown command');
14836c0b2b4SAndreas Gohr                echo $options->help();
14936c0b2b4SAndreas Gohr                return 0;
15036c0b2b4SAndreas Gohr        }
15136c0b2b4SAndreas Gohr    }
15236c0b2b4SAndreas Gohr
15336c0b2b4SAndreas Gohr    /**
15436c0b2b4SAndreas Gohr     * Get the extension name from the current working directory
15536c0b2b4SAndreas Gohr     *
15636c0b2b4SAndreas Gohr     * @throws CliException if something's wrong
15736c0b2b4SAndreas Gohr     * @param string $dir
15836c0b2b4SAndreas Gohr     * @return string[] name, type
15936c0b2b4SAndreas Gohr     */
16036c0b2b4SAndreas Gohr    protected function getTypedNameFromDir($dir)
16136c0b2b4SAndreas Gohr    {
16236c0b2b4SAndreas Gohr        $pdir = fullpath(DOKU_PLUGIN);
16336c0b2b4SAndreas Gohr        $tdir = fullpath(tpl_incdir() . '../');
16436c0b2b4SAndreas Gohr
16536c0b2b4SAndreas Gohr        if (strpos($dir, $pdir) === 0) {
16636c0b2b4SAndreas Gohr            $ldir = substr($dir, strlen($pdir));
16736c0b2b4SAndreas Gohr            $type = 'plugin';
16836c0b2b4SAndreas Gohr        } elseif (strpos($dir, $tdir) === 0) {
16936c0b2b4SAndreas Gohr            $ldir = substr($dir, strlen($tdir));
17036c0b2b4SAndreas Gohr            $type = 'template';
17136c0b2b4SAndreas Gohr        } else {
17236c0b2b4SAndreas Gohr            throw new CliException('Current directory needs to be in plugin or template directory');
17336c0b2b4SAndreas Gohr        }
17436c0b2b4SAndreas Gohr
17536c0b2b4SAndreas Gohr        $ldir = trim($ldir, '/');
17636c0b2b4SAndreas Gohr
17736c0b2b4SAndreas Gohr        if (strpos($ldir, '/') !== false) {
17836c0b2b4SAndreas Gohr            throw new CliException('Current directory has to be main extension directory');
17936c0b2b4SAndreas Gohr        }
18036c0b2b4SAndreas Gohr
18136c0b2b4SAndreas Gohr        return [$ldir, $type];
18236c0b2b4SAndreas Gohr    }
18336c0b2b4SAndreas Gohr
18436c0b2b4SAndreas Gohr    /**
18536c0b2b4SAndreas Gohr     * Interactively ask for a value from the user
18636c0b2b4SAndreas Gohr     *
18736c0b2b4SAndreas Gohr     * @param string $prompt
18836c0b2b4SAndreas Gohr     * @param bool $cache cache given value for next time?
18936c0b2b4SAndreas Gohr     * @return string
19036c0b2b4SAndreas Gohr     */
19136c0b2b4SAndreas Gohr    protected function readLine($prompt, $cache = false)
19236c0b2b4SAndreas Gohr    {
19336c0b2b4SAndreas Gohr        $value = '';
19436c0b2b4SAndreas Gohr        $default = '';
19536c0b2b4SAndreas Gohr        $cachename = getCacheName($prompt, '.readline');
19636c0b2b4SAndreas Gohr        if ($cache && file_exists($cachename)) {
19736c0b2b4SAndreas Gohr            $default = file_get_contents($cachename);
19836c0b2b4SAndreas Gohr        }
19936c0b2b4SAndreas Gohr
20036c0b2b4SAndreas Gohr        while ($value === '') {
20136c0b2b4SAndreas Gohr            echo $prompt;
20236c0b2b4SAndreas Gohr            if ($default) echo ' [' . $default . ']';
20336c0b2b4SAndreas Gohr            echo ': ';
20436c0b2b4SAndreas Gohr
20536c0b2b4SAndreas Gohr            $fh = fopen('php://stdin', 'r');
20636c0b2b4SAndreas Gohr            $value = trim(fgets($fh));
20736c0b2b4SAndreas Gohr            fclose($fh);
20836c0b2b4SAndreas Gohr
20936c0b2b4SAndreas Gohr            if ($value === '') $value = $default;
21036c0b2b4SAndreas Gohr        }
21136c0b2b4SAndreas Gohr
21236c0b2b4SAndreas Gohr        if ($cache) {
21336c0b2b4SAndreas Gohr            file_put_contents($cachename, $value);
21436c0b2b4SAndreas Gohr        }
21536c0b2b4SAndreas Gohr
21636c0b2b4SAndreas Gohr        return $value;
21736c0b2b4SAndreas Gohr    }
21836c0b2b4SAndreas Gohr
21936c0b2b4SAndreas Gohr    /**
22070316b84SAndreas Gohr     * Create the given files with their given content
22136c0b2b4SAndreas Gohr     *
22270316b84SAndreas Gohr     * Ignores all files that already exist
22370316b84SAndreas Gohr     *
22470316b84SAndreas Gohr     * @param array $files A File array as created by Skeletor::getFiles()
22536c0b2b4SAndreas Gohr     */
226fcb8165bSAndreas Gohr    protected function createFiles($files)
227fcb8165bSAndreas Gohr    {
22870316b84SAndreas Gohr        foreach ($files as $path => $content) {
22970316b84SAndreas Gohr            if (file_exists($path)) {
23070316b84SAndreas Gohr                $this->error($path . ' already exists');
23170316b84SAndreas Gohr                continue;
23236c0b2b4SAndreas Gohr            }
23336c0b2b4SAndreas Gohr
23470316b84SAndreas Gohr            io_makeFileDir($path);
23570316b84SAndreas Gohr            file_put_contents($path, $content);
23670316b84SAndreas Gohr            $this->success($path . ' created');
23736c0b2b4SAndreas Gohr        }
23836c0b2b4SAndreas Gohr    }
23936c0b2b4SAndreas Gohr
24036c0b2b4SAndreas Gohr    /**
241c5c85a97SAndreas Gohr     * Delete the given file if it exists
242c5c85a97SAndreas Gohr     *
243c5c85a97SAndreas Gohr     * @param string $file
244c5c85a97SAndreas Gohr     */
245c5c85a97SAndreas Gohr    protected function deleteFile($file)
246c5c85a97SAndreas Gohr    {
247c5c85a97SAndreas Gohr        if (!file_exists($file)) return;
248c5c85a97SAndreas Gohr        if (@unlink($file)) {
249c5c85a97SAndreas Gohr            $this->success('Delete ' . $file);
250c5c85a97SAndreas Gohr        }
251c5c85a97SAndreas Gohr    }
252c5c85a97SAndreas Gohr
253c5c85a97SAndreas Gohr    /**
254c5c85a97SAndreas Gohr     * Run git with the given arguments and return the output
255c5c85a97SAndreas Gohr     *
256c5c85a97SAndreas Gohr     * @throws CliException when the command can't be run
257c5c85a97SAndreas Gohr     * @param string ...$args
258c5c85a97SAndreas Gohr     * @return string[]
259c5c85a97SAndreas Gohr     */
260c5c85a97SAndreas Gohr    protected function git(...$args)
261c5c85a97SAndreas Gohr    {
262c5c85a97SAndreas Gohr        $args = array_map('escapeshellarg', $args);
263c5c85a97SAndreas Gohr        $cmd = 'git ' . join(' ', $args);
264c5c85a97SAndreas Gohr        $output = [];
265c5c85a97SAndreas Gohr        $result = 0;
266c5c85a97SAndreas Gohr
267c5c85a97SAndreas Gohr        $this->info($cmd);
268c5c85a97SAndreas Gohr        $last = exec($cmd, $output, $result);
269c5c85a97SAndreas Gohr        if ($last === false || $result !== 0) {
270c5c85a97SAndreas Gohr            throw new CliException('Running git failed');
271c5c85a97SAndreas Gohr        }
272c5c85a97SAndreas Gohr
273c5c85a97SAndreas Gohr        return $output;
274c5c85a97SAndreas Gohr    }
275c5c85a97SAndreas Gohr
276c5c85a97SAndreas Gohr    // region Commands
277c5c85a97SAndreas Gohr
278c5c85a97SAndreas Gohr    /**
27936c0b2b4SAndreas Gohr     * Intialize the current directory as a plugin or template
28036c0b2b4SAndreas Gohr     *
28136c0b2b4SAndreas Gohr     * @return int
28236c0b2b4SAndreas Gohr     */
28336c0b2b4SAndreas Gohr    protected function cmdInit()
28436c0b2b4SAndreas Gohr    {
28536c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
28636c0b2b4SAndreas Gohr        if ((new FilesystemIterator($dir))->valid()) {
287fcb8165bSAndreas Gohr            // existing directory, initialize from info file
288fcb8165bSAndreas Gohr            $skeletor = Skeletor::fromDir($dir);
289fcb8165bSAndreas Gohr        } else {
290fcb8165bSAndreas Gohr            // new directory, ask for info
29170316b84SAndreas Gohr            [$base, $type] = $this->getTypedNameFromDir($dir);
29236c0b2b4SAndreas Gohr            $user = $this->readLine('Your Name', true);
29336c0b2b4SAndreas Gohr            $mail = $this->readLine('Your E-Mail', true);
29436c0b2b4SAndreas Gohr            $desc = $this->readLine('Short description');
29570316b84SAndreas Gohr            $skeletor = new Skeletor($type, $base, $desc, $user, $mail);
296fcb8165bSAndreas Gohr        }
29770316b84SAndreas Gohr        $skeletor->addBasics();
29870316b84SAndreas Gohr        $this->createFiles($skeletor->getFiles());
29936c0b2b4SAndreas Gohr
300fcb8165bSAndreas Gohr        if (!is_dir("$dir/.git")) {
3018b06c9ddSAndreas Gohr            try {
3028b06c9ddSAndreas Gohr                $this->git('init');
3038b06c9ddSAndreas Gohr            } catch (CliException $e) {
3048b06c9ddSAndreas Gohr                $this->error($e->getMessage());
3058b06c9ddSAndreas Gohr            }
306fcb8165bSAndreas Gohr        }
3078b06c9ddSAndreas Gohr
30836c0b2b4SAndreas Gohr        return 0;
30936c0b2b4SAndreas Gohr    }
31036c0b2b4SAndreas Gohr
31136c0b2b4SAndreas Gohr    /**
31236c0b2b4SAndreas Gohr     * Add test framework
31336c0b2b4SAndreas Gohr     *
31436c0b2b4SAndreas Gohr     * @param string $test Name of the Test to add
31536c0b2b4SAndreas Gohr     * @return int
31636c0b2b4SAndreas Gohr     */
31736c0b2b4SAndreas Gohr    protected function cmdAddTest($test = '')
31836c0b2b4SAndreas Gohr    {
31970316b84SAndreas Gohr        $skeletor = Skeletor::fromDir(getcwd());
32070316b84SAndreas Gohr        $skeletor->addTest($test);
32170316b84SAndreas Gohr        $this->createFiles($skeletor->getFiles());
32236c0b2b4SAndreas Gohr        return 0;
32336c0b2b4SAndreas Gohr    }
32436c0b2b4SAndreas Gohr
32536c0b2b4SAndreas Gohr    /**
32636c0b2b4SAndreas Gohr     * Add configuration
32736c0b2b4SAndreas Gohr     *
32836c0b2b4SAndreas Gohr     * @return int
32936c0b2b4SAndreas Gohr     */
33036c0b2b4SAndreas Gohr    protected function cmdAddConf()
33136c0b2b4SAndreas Gohr    {
33270316b84SAndreas Gohr        $skeletor = Skeletor::fromDir(getcwd());
33370316b84SAndreas Gohr        $skeletor->addConf(is_dir('lang'));
33470316b84SAndreas Gohr        $this->createFiles($skeletor->getFiles());
33536c0b2b4SAndreas Gohr        return 0;
33636c0b2b4SAndreas Gohr    }
33736c0b2b4SAndreas Gohr
33836c0b2b4SAndreas Gohr    /**
33936c0b2b4SAndreas Gohr     * Add language
34036c0b2b4SAndreas Gohr     *
34136c0b2b4SAndreas Gohr     * @return int
34236c0b2b4SAndreas Gohr     */
34336c0b2b4SAndreas Gohr    protected function cmdAddLang()
34436c0b2b4SAndreas Gohr    {
34570316b84SAndreas Gohr        $skeletor = Skeletor::fromDir(getcwd());
34670316b84SAndreas Gohr        $skeletor->addLang(is_dir('conf'));
34770316b84SAndreas Gohr        $this->createFiles($skeletor->getFiles());
34836c0b2b4SAndreas Gohr        return 0;
34936c0b2b4SAndreas Gohr    }
35036c0b2b4SAndreas Gohr
35136c0b2b4SAndreas Gohr    /**
352719b4841SAndreas Gohr     * Add AGENTS.md
353719b4841SAndreas Gohr     *
354719b4841SAndreas Gohr     * @return int
355719b4841SAndreas Gohr     */
356*488499ccSAndreas Gohr    protected function cmdAddAgents($claude)
357719b4841SAndreas Gohr    {
358719b4841SAndreas Gohr        $skeletor = Skeletor::fromDir(getcwd());
359719b4841SAndreas Gohr        $skeletor->addAgents();
360719b4841SAndreas Gohr        $this->createFiles($skeletor->getFiles());
361*488499ccSAndreas Gohr        if($claude && !file_exists('CLAUDE.md')) {
362*488499ccSAndreas Gohr            symlink('AGENTS.md', 'CLAUDE.md') && $this->success('Created symlink CLAUDE.md -> AGENTS.md');
363*488499ccSAndreas Gohr        }
364719b4841SAndreas Gohr        return 0;
365719b4841SAndreas Gohr    }
366719b4841SAndreas Gohr
367719b4841SAndreas Gohr    /**
36836c0b2b4SAndreas Gohr     * Add another component to the plugin
36936c0b2b4SAndreas Gohr     *
37036c0b2b4SAndreas Gohr     * @param string $type
37136c0b2b4SAndreas Gohr     * @param string $component
37236c0b2b4SAndreas Gohr     */
37336c0b2b4SAndreas Gohr    protected function cmdAddComponent($type, $component = '')
37436c0b2b4SAndreas Gohr    {
37570316b84SAndreas Gohr        $skeletor = Skeletor::fromDir(getcwd());
37670316b84SAndreas Gohr        $skeletor->addComponent($type, $component);
37770316b84SAndreas Gohr        $this->createFiles($skeletor->getFiles());
37836c0b2b4SAndreas Gohr        return 0;
37936c0b2b4SAndreas Gohr    }
38036c0b2b4SAndreas Gohr
38136c0b2b4SAndreas Gohr    /**
38236c0b2b4SAndreas Gohr     * Generate a list of deleted files from git
38336c0b2b4SAndreas Gohr     *
38436c0b2b4SAndreas Gohr     * @link https://stackoverflow.com/a/6018049/172068
38536c0b2b4SAndreas Gohr     */
38636c0b2b4SAndreas Gohr    protected function cmdDeletedFiles()
38736c0b2b4SAndreas Gohr    {
3888b06c9ddSAndreas Gohr        if (!is_dir('.git')) throw new CliException('This extension seems not to be managed by git');
38936c0b2b4SAndreas Gohr
3908b06c9ddSAndreas Gohr        $output = $this->git('log', '--no-renames', '--pretty=format:', '--name-only', '--diff-filter=D');
39136c0b2b4SAndreas Gohr        $output = array_map('trim', $output);
39236c0b2b4SAndreas Gohr        $output = array_filter($output);
39336c0b2b4SAndreas Gohr        $output = array_unique($output);
39436c0b2b4SAndreas Gohr        $output = array_filter($output, function ($item) {
39536c0b2b4SAndreas Gohr            return !file_exists($item);
39636c0b2b4SAndreas Gohr        });
39736c0b2b4SAndreas Gohr        sort($output);
39836c0b2b4SAndreas Gohr
39936c0b2b4SAndreas Gohr        if (!count($output)) {
40036c0b2b4SAndreas Gohr            $this->info('No deleted files found');
40136c0b2b4SAndreas Gohr            return 0;
40236c0b2b4SAndreas Gohr        }
40336c0b2b4SAndreas Gohr
40436c0b2b4SAndreas Gohr        $content = "# This is a list of files that were present in previous releases\n" .
40536c0b2b4SAndreas Gohr            "# but were removed later. They should not exist in your installation.\n" .
40636c0b2b4SAndreas Gohr            join("\n", $output) . "\n";
40736c0b2b4SAndreas Gohr
40836c0b2b4SAndreas Gohr        file_put_contents('deleted.files', $content);
40936c0b2b4SAndreas Gohr        $this->success('written deleted.files');
41036c0b2b4SAndreas Gohr        return 0;
41136c0b2b4SAndreas Gohr    }
4128b06c9ddSAndreas Gohr
4138b06c9ddSAndreas Gohr    /**
414c5c85a97SAndreas Gohr     * Remove files that shouldn't be here anymore
4158b06c9ddSAndreas Gohr     */
4161a23d1dbSAndreas Gohr    protected function cmdRmObsolete()
4178b06c9ddSAndreas Gohr    {
418c5c85a97SAndreas Gohr        $this->deleteFile('_test/general.test.php');
419c5c85a97SAndreas Gohr        $this->deleteFile('.travis.yml');
42053bec4caSAndreas Gohr        $this->deleteFile('.github/workflows/phpTestLinux.yml');
4218b06c9ddSAndreas Gohr
422c5c85a97SAndreas Gohr        return 0;
4238b06c9ddSAndreas Gohr    }
4248b06c9ddSAndreas Gohr
4251a23d1dbSAndreas Gohr    /**
4261a23d1dbSAndreas Gohr     * Download a remote icon
4271a23d1dbSAndreas Gohr     *
4281a23d1dbSAndreas Gohr     * @param string $ident
4291a23d1dbSAndreas Gohr     * @param string $save
43092738407SAndreas Gohr     * @param bool $keep
4311a23d1dbSAndreas Gohr     * @return int
4321a23d1dbSAndreas Gohr     * @throws Exception
4331a23d1dbSAndreas Gohr     */
43492738407SAndreas Gohr    protected function cmdDownloadSVG($ident, $save = '', $keep = false)
4351a23d1dbSAndreas Gohr    {
4361a23d1dbSAndreas Gohr        $svg = new SVGIcon($this);
43792738407SAndreas Gohr        $svg->keepNamespace($keep);
4381a23d1dbSAndreas Gohr        return (int)$svg->downloadRemoteIcon($ident, $save);
4391a23d1dbSAndreas Gohr    }
4401a23d1dbSAndreas Gohr
4411a23d1dbSAndreas Gohr    /**
4428f82d673SAndreas Gohr     * @param string[] $files
44392738407SAndreas Gohr     * @param bool $keep
4441a23d1dbSAndreas Gohr     * @return int
4451a23d1dbSAndreas Gohr     * @throws Exception
4461a23d1dbSAndreas Gohr     */
4478f82d673SAndreas Gohr    protected function cmdCleanSVG($files, $keep = false)
4481a23d1dbSAndreas Gohr    {
4491a23d1dbSAndreas Gohr        $svg = new SVGIcon($this);
45092738407SAndreas Gohr        $svg->keepNamespace($keep);
4518f82d673SAndreas Gohr
4528f82d673SAndreas Gohr        $ok = true;
4538f82d673SAndreas Gohr        foreach ($files as $file) {
4548f82d673SAndreas Gohr            $ok = $ok && $svg->cleanSVGFile($file);
4558f82d673SAndreas Gohr        }
4568f82d673SAndreas Gohr        return (int)$ok;
4571a23d1dbSAndreas Gohr    }
4581a23d1dbSAndreas Gohr
4595586e97bSAndreas Gohr    /**
4605586e97bSAndreas Gohr     * @return int
4615586e97bSAndreas Gohr     */
4625586e97bSAndreas Gohr    protected function cmdCleanLang()
4635586e97bSAndreas Gohr    {
4645586e97bSAndreas Gohr        $lp = new LangProcessor($this);
4655586e97bSAndreas Gohr
4665586e97bSAndreas Gohr        $files = glob('./lang/*/lang.php');
4675b2e8f12SAndreas Gohr        foreach ($files as $file) {
4685b2e8f12SAndreas Gohr            $lp->processLangFile($file);
4695b2e8f12SAndreas Gohr        }
4705b2e8f12SAndreas Gohr
4715b2e8f12SAndreas Gohr        $files = glob('./lang/*/settings.php');
4725586e97bSAndreas Gohr        foreach ($files as $file) {
473f4f76afdSAndreas Gohr            $lp->processSettingsFile($file);
4745586e97bSAndreas Gohr        }
4755586e97bSAndreas Gohr
4765586e97bSAndreas Gohr        return 0;
4775586e97bSAndreas Gohr    }
4785586e97bSAndreas Gohr
479ec0a687bSAndreas Gohr    /**
48057732a2dSAndreas Gohr     * Run the unit tests for this extension
48157732a2dSAndreas Gohr     *
48257732a2dSAndreas Gohr     * @param string $filter Optional filter string for phpunit
48357732a2dSAndreas Gohr     * @param string[] $args Additional arguments to pass to phpunit (files)
484ec0a687bSAndreas Gohr     * @return int
485ec0a687bSAndreas Gohr     */
48657732a2dSAndreas Gohr    protected function cmdTest($filter = '', $args = [])
487ec0a687bSAndreas Gohr    {
488ec0a687bSAndreas Gohr        $dir = fullpath(getcwd());
489ec0a687bSAndreas Gohr        [$base, $type] = $this->getTypedNameFromDir($dir);
490ec0a687bSAndreas Gohr
491ec0a687bSAndreas Gohr        if ($this->colors->isEnabled()) {
492ec0a687bSAndreas Gohr            $colors = 'always';
493ec0a687bSAndreas Gohr        } else {
494ec0a687bSAndreas Gohr            $colors = 'never';
495ec0a687bSAndreas Gohr        }
496ec0a687bSAndreas Gohr
49757732a2dSAndreas Gohr        $bin = fullpath(__DIR__ . '/../../../_test/vendor/bin/phpunit');;
49857732a2dSAndreas Gohr        if (!file_exists($bin)) {
49957732a2dSAndreas Gohr            $this->error('Testing framework not found. Please run "composer install" in the _test/ directory first.');
50057732a2dSAndreas Gohr            return 1;
50157732a2dSAndreas Gohr        }
50257732a2dSAndreas Gohr
50357732a2dSAndreas Gohr        $runArgs = [
50457732a2dSAndreas Gohr            $bin,
505ec0a687bSAndreas Gohr            '--verbose',
506ec0a687bSAndreas Gohr            "--colors=$colors",
507ec0a687bSAndreas Gohr            '--configuration', fullpath(__DIR__ . '/../../../_test/phpunit.xml'),
508ec0a687bSAndreas Gohr            '--group', $type . '_' . $base,
509ec0a687bSAndreas Gohr        ];
51057732a2dSAndreas Gohr        if ($filter) {
51157732a2dSAndreas Gohr            $runArgs[] = '--filter';
51257732a2dSAndreas Gohr            $runArgs[] = $filter;
51357732a2dSAndreas Gohr        }
51457732a2dSAndreas Gohr
51557732a2dSAndreas Gohr        $runArgs = array_merge($runArgs, $args);
51657732a2dSAndreas Gohr        $cmd = join(' ', array_map('escapeshellarg', $runArgs));
517ec0a687bSAndreas Gohr        $this->info("Running $cmd");
518ec0a687bSAndreas Gohr
519ec0a687bSAndreas Gohr        $result = 0;
520ec0a687bSAndreas Gohr        passthru($cmd, $result);
521ec0a687bSAndreas Gohr        return $result;
522ec0a687bSAndreas Gohr    }
523ec0a687bSAndreas Gohr
524ec0a687bSAndreas Gohr    /**
525ec0a687bSAndreas Gohr     * @return int
526ec0a687bSAndreas Gohr     */
527ec0a687bSAndreas Gohr    protected function cmdCheck($files = [])
528ec0a687bSAndreas Gohr    {
529ec0a687bSAndreas Gohr        $dir = fullpath(getcwd());
530ec0a687bSAndreas Gohr
531ec0a687bSAndreas Gohr        $args = [
532ec0a687bSAndreas Gohr            fullpath(__DIR__ . '/../../../_test/vendor/bin/phpcs'),
533ec0a687bSAndreas Gohr            '--standard=' . fullpath(__DIR__ . '/../../../_test/phpcs.xml'),
534ec0a687bSAndreas Gohr            ($this->colors->isEnabled()) ? '--colors' : '--no-colors',
535ec0a687bSAndreas Gohr            '--',
536ec0a687bSAndreas Gohr        ];
537ec0a687bSAndreas Gohr
538ec0a687bSAndreas Gohr        if ($files) {
539ec0a687bSAndreas Gohr            $args = array_merge($args, $files);
540ec0a687bSAndreas Gohr        } else {
541ec0a687bSAndreas Gohr            $args[] = fullpath($dir);
542ec0a687bSAndreas Gohr        }
543ec0a687bSAndreas Gohr
544ec0a687bSAndreas Gohr        $cmd = join(' ', array_map('escapeshellarg', $args));
545ec0a687bSAndreas Gohr        $this->info("Running $cmd");
546ec0a687bSAndreas Gohr
547ec0a687bSAndreas Gohr        $result = 0;
548ec0a687bSAndreas Gohr        passthru($cmd, $result);
549ec0a687bSAndreas Gohr        return $result;
550ec0a687bSAndreas Gohr    }
551ec0a687bSAndreas Gohr
552ec0a687bSAndreas Gohr    /**
553ec0a687bSAndreas Gohr     * @return int
554ec0a687bSAndreas Gohr     */
555ec0a687bSAndreas Gohr    protected function cmdFix($files = [])
556ec0a687bSAndreas Gohr    {
557ec0a687bSAndreas Gohr        $dir = fullpath(getcwd());
558ec0a687bSAndreas Gohr
559ec0a687bSAndreas Gohr        // first run rector to refactor outdated code
560ec0a687bSAndreas Gohr        $args = [
561ec0a687bSAndreas Gohr            fullpath(__DIR__ . '/../../../_test/vendor/bin/rector'),
562ec0a687bSAndreas Gohr            ($this->colors->isEnabled()) ? '--ansi' : '--no-ansi',
563ec0a687bSAndreas Gohr            '--config=' . fullpath(__DIR__ . '/../../../_test/rector.php'),
564ec0a687bSAndreas Gohr            '--no-diffs',
565ec0a687bSAndreas Gohr            'process',
566ec0a687bSAndreas Gohr        ];
567ec0a687bSAndreas Gohr
568ec0a687bSAndreas Gohr        if ($files) {
569ec0a687bSAndreas Gohr            $args = array_merge($args, $files);
570ec0a687bSAndreas Gohr        } else {
571ec0a687bSAndreas Gohr            $args[] = fullpath($dir);
572ec0a687bSAndreas Gohr        }
573ec0a687bSAndreas Gohr
574ec0a687bSAndreas Gohr        $cmd = join(' ', array_map('escapeshellarg', $args));
575ec0a687bSAndreas Gohr        $this->info("Running $cmd");
576ec0a687bSAndreas Gohr
577ec0a687bSAndreas Gohr        $result = 0;
578ec0a687bSAndreas Gohr        passthru($cmd, $result);
579ec0a687bSAndreas Gohr        if ($result !== 0) return $result;
580ec0a687bSAndreas Gohr
581ec0a687bSAndreas Gohr        // now run phpcbf to clean up code style
582ec0a687bSAndreas Gohr        $args = [
583ec0a687bSAndreas Gohr            fullpath(__DIR__ . '/../../../_test/vendor/bin/phpcbf'),
584ec0a687bSAndreas Gohr            '--standard=' . fullpath(__DIR__ . '/../../../_test/phpcs.xml'),
585ec0a687bSAndreas Gohr            ($this->colors->isEnabled()) ? '--colors' : '--no-colors',
586ec0a687bSAndreas Gohr            '--',
587ec0a687bSAndreas Gohr        ];
588ec0a687bSAndreas Gohr
589ec0a687bSAndreas Gohr        if ($files) {
590ec0a687bSAndreas Gohr            $args = array_merge($args, $files);
591ec0a687bSAndreas Gohr        } else {
592ec0a687bSAndreas Gohr            $args[] = fullpath($dir);
593ec0a687bSAndreas Gohr        }
594ec0a687bSAndreas Gohr
595ec0a687bSAndreas Gohr        $cmd = join(' ', array_map('escapeshellarg', $args));
596ec0a687bSAndreas Gohr        $this->info("Running $cmd");
597ec0a687bSAndreas Gohr
598ec0a687bSAndreas Gohr        $result = 0;
599ec0a687bSAndreas Gohr        passthru($cmd, $result);
600ec0a687bSAndreas Gohr        return $result;
601ec0a687bSAndreas Gohr    }
602ec0a687bSAndreas Gohr
603c5c85a97SAndreas Gohr    //endregion
60436c0b2b4SAndreas Gohr}
605