xref: /plugin/dev/cli.php (revision 5b2e8f123702dd4a7bf5310061f1cb273792e789)
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;
71a23d1dbSAndreas Gohruse dokuwiki\plugin\dev\SVGIcon;
836c0b2b4SAndreas Gohruse splitbrain\phpcli\Exception as CliException;
936c0b2b4SAndreas Gohruse splitbrain\phpcli\Options;
1036c0b2b4SAndreas Gohr
1136c0b2b4SAndreas Gohr/**
1236c0b2b4SAndreas Gohr * @license GPL2
1336c0b2b4SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
1436c0b2b4SAndreas Gohr */
1536c0b2b4SAndreas Gohrclass cli_plugin_dev extends CLIPlugin
1636c0b2b4SAndreas Gohr{
1736c0b2b4SAndreas Gohr    /**
1836c0b2b4SAndreas Gohr     * Register options and arguments on the given $options object
1936c0b2b4SAndreas Gohr     *
2036c0b2b4SAndreas Gohr     * @param Options $options
2136c0b2b4SAndreas Gohr     * @return void
2236c0b2b4SAndreas Gohr     */
2336c0b2b4SAndreas Gohr    protected function setup(Options $options)
2436c0b2b4SAndreas Gohr    {
25f2576912SAndreas Gohr        $options->useCompactHelp();
2636c0b2b4SAndreas Gohr        $options->setHelp(
27f2576912SAndreas Gohr            "CLI to help with DokuWiki plugin and template development.\n\n" .
2836c0b2b4SAndreas Gohr            "Run this script from within the extension's directory."
2936c0b2b4SAndreas Gohr        );
3036c0b2b4SAndreas Gohr
31f2576912SAndreas Gohr        $options->registerCommand('init', 'Initialize a new plugin or template in the current (empty) directory.');
32f2576912SAndreas Gohr        $options->registerCommand('addTest', 'Add the testing framework files and a test. (_test/)');
33f2576912SAndreas Gohr        $options->registerArgument('test', 'Optional name of the new test. Defaults to the general test.', false,
34f2576912SAndreas Gohr            'addTest');
35f2576912SAndreas Gohr        $options->registerCommand('addConf', 'Add the configuration files. (conf/)');
36f2576912SAndreas Gohr        $options->registerCommand('addLang', 'Add the language files. (lang/)');
3736c0b2b4SAndreas Gohr
38f2576912SAndreas Gohr        $types = PluginController::PLUGIN_TYPES;
39f2576912SAndreas Gohr        array_walk(
40f2576912SAndreas Gohr            $types,
41f2576912SAndreas Gohr            function (&$item) {
42f2576912SAndreas Gohr                $item = $this->colors->wrap($item, $this->colors::C_BROWN);
43f2576912SAndreas Gohr            }
4436c0b2b4SAndreas Gohr        );
4536c0b2b4SAndreas Gohr
46f2576912SAndreas Gohr        $options->registerCommand('addComponent', 'Add a new plugin component.');
47f2576912SAndreas Gohr        $options->registerArgument('type', 'Type of the component. Needs to be one of ' . join(', ', $types), true,
48f2576912SAndreas Gohr            'addComponent');
49f2576912SAndreas Gohr        $options->registerArgument('name', 'Optional name of the component. Defaults to a base component.', false,
50f2576912SAndreas Gohr            'addComponent');
51f2576912SAndreas Gohr
52f2576912SAndreas Gohr        $options->registerCommand('deletedFiles', 'Create the list of deleted files based on the git history.');
53f2576912SAndreas Gohr        $options->registerCommand('rmObsolete', 'Delete obsolete files.');
541a23d1dbSAndreas Gohr
551a23d1dbSAndreas Gohr        $prefixes = array_keys(SVGIcon::SOURCES);
561a23d1dbSAndreas Gohr        array_walk(
571a23d1dbSAndreas Gohr            $prefixes,
581a23d1dbSAndreas Gohr            function (&$item) {
591a23d1dbSAndreas Gohr                $item = $this->colors->wrap($item, $this->colors::C_BROWN);
601a23d1dbSAndreas Gohr            }
611a23d1dbSAndreas Gohr        );
621a23d1dbSAndreas Gohr
631a23d1dbSAndreas Gohr        $options->registerCommand('downloadSvg', 'Download an SVG file from a known icon repository.');
641a23d1dbSAndreas Gohr        $options->registerArgument('prefix:name',
651a23d1dbSAndreas Gohr            'Colon-prefixed name of the icon. Available prefixes: ' . join(', ', $prefixes), true, 'downloadSvg');
661a23d1dbSAndreas Gohr        $options->registerArgument('output', 'File to save, defaults to <name>.svg in current dir', false,
671a23d1dbSAndreas Gohr            'downloadSvg');
6892738407SAndreas Gohr        $options->registerOption('keep-ns', 'Keep the SVG namespace. Use when the file is not inlined into HTML.', 'k',
6992738407SAndreas Gohr            false, 'downloadSvg');
701a23d1dbSAndreas Gohr
718f82d673SAndreas Gohr        $options->registerCommand('cleanSvg', 'Clean a existing SVG files to reduce their file size.');
728f82d673SAndreas Gohr        $options->registerArgument('files...', 'The files to clean (will be overwritten)', true, 'cleanSvg');
7392738407SAndreas Gohr        $options->registerOption('keep-ns', 'Keep the SVG namespace. Use when the file is not inlined into HTML.', 'k',
7492738407SAndreas Gohr            false, 'cleanSvg');
755586e97bSAndreas Gohr
765586e97bSAndreas Gohr        $options->registerCommand('cleanLang',
775586e97bSAndreas Gohr            'Clean language files from unused language strings. Detecting which strings are truly in use may ' .
785586e97bSAndreas Gohr            'not always correctly work. Use with caution.');
7936c0b2b4SAndreas Gohr    }
8036c0b2b4SAndreas Gohr
8136c0b2b4SAndreas Gohr    /** @inheritDoc */
8236c0b2b4SAndreas Gohr    protected function main(Options $options)
8336c0b2b4SAndreas Gohr    {
841a23d1dbSAndreas Gohr        $args = $options->getArgs();
851a23d1dbSAndreas Gohr
8636c0b2b4SAndreas Gohr        switch ($options->getCmd()) {
8736c0b2b4SAndreas Gohr            case 'init':
8836c0b2b4SAndreas Gohr                return $this->cmdInit();
8936c0b2b4SAndreas Gohr            case 'addTest':
9036c0b2b4SAndreas Gohr                $test = array_shift($args);
9136c0b2b4SAndreas Gohr                return $this->cmdAddTest($test);
9236c0b2b4SAndreas Gohr            case 'addConf':
9336c0b2b4SAndreas Gohr                return $this->cmdAddConf();
9436c0b2b4SAndreas Gohr            case 'addLang':
9536c0b2b4SAndreas Gohr                return $this->cmdAddLang();
9636c0b2b4SAndreas Gohr            case 'addComponent':
9736c0b2b4SAndreas Gohr                $type = array_shift($args);
9836c0b2b4SAndreas Gohr                $component = array_shift($args);
9936c0b2b4SAndreas Gohr                return $this->cmdAddComponent($type, $component);
10036c0b2b4SAndreas Gohr            case 'deletedFiles':
10136c0b2b4SAndreas Gohr                return $this->cmdDeletedFiles();
102c5c85a97SAndreas Gohr            case 'rmObsolete':
1031a23d1dbSAndreas Gohr                return $this->cmdRmObsolete();
1041a23d1dbSAndreas Gohr            case 'downloadSvg':
1051a23d1dbSAndreas Gohr                $ident = array_shift($args);
1061a23d1dbSAndreas Gohr                $save = array_shift($args);
10792738407SAndreas Gohr                $keep = $options->getOpt('keep-ns', false);
10892738407SAndreas Gohr                return $this->cmdDownloadSVG($ident, $save, $keep);
1091a23d1dbSAndreas Gohr            case 'cleanSvg':
11092738407SAndreas Gohr                $keep = $options->getOpt('keep-ns', false);
1118f82d673SAndreas Gohr                return $this->cmdCleanSVG($args, $keep);
1125586e97bSAndreas Gohr            case 'cleanLang':
1135586e97bSAndreas Gohr                return $this->cmdCleanLang();
11436c0b2b4SAndreas Gohr            default:
1151a23d1dbSAndreas Gohr                $this->error('Unknown command');
11636c0b2b4SAndreas Gohr                echo $options->help();
11736c0b2b4SAndreas Gohr                return 0;
11836c0b2b4SAndreas Gohr        }
11936c0b2b4SAndreas Gohr    }
12036c0b2b4SAndreas Gohr
12136c0b2b4SAndreas Gohr    /**
12236c0b2b4SAndreas Gohr     * Get the extension name from the current working directory
12336c0b2b4SAndreas Gohr     *
12436c0b2b4SAndreas Gohr     * @throws CliException if something's wrong
12536c0b2b4SAndreas Gohr     * @param string $dir
12636c0b2b4SAndreas Gohr     * @return string[] name, type
12736c0b2b4SAndreas Gohr     */
12836c0b2b4SAndreas Gohr    protected function getTypedNameFromDir($dir)
12936c0b2b4SAndreas Gohr    {
13036c0b2b4SAndreas Gohr        $pdir = fullpath(DOKU_PLUGIN);
13136c0b2b4SAndreas Gohr        $tdir = fullpath(tpl_incdir() . '../');
13236c0b2b4SAndreas Gohr
13336c0b2b4SAndreas Gohr        if (strpos($dir, $pdir) === 0) {
13436c0b2b4SAndreas Gohr            $ldir = substr($dir, strlen($pdir));
13536c0b2b4SAndreas Gohr            $type = 'plugin';
13636c0b2b4SAndreas Gohr        } elseif (strpos($dir, $tdir) === 0) {
13736c0b2b4SAndreas Gohr            $ldir = substr($dir, strlen($tdir));
13836c0b2b4SAndreas Gohr            $type = 'template';
13936c0b2b4SAndreas Gohr        } else {
14036c0b2b4SAndreas Gohr            throw new CliException('Current directory needs to be in plugin or template directory');
14136c0b2b4SAndreas Gohr        }
14236c0b2b4SAndreas Gohr
14336c0b2b4SAndreas Gohr        $ldir = trim($ldir, '/');
14436c0b2b4SAndreas Gohr
14536c0b2b4SAndreas Gohr        if (strpos($ldir, '/') !== false) {
14636c0b2b4SAndreas Gohr            throw new CliException('Current directory has to be main extension directory');
14736c0b2b4SAndreas Gohr        }
14836c0b2b4SAndreas Gohr
14936c0b2b4SAndreas Gohr        return [$ldir, $type];
15036c0b2b4SAndreas Gohr    }
15136c0b2b4SAndreas Gohr
15236c0b2b4SAndreas Gohr    /**
15336c0b2b4SAndreas Gohr     * Interactively ask for a value from the user
15436c0b2b4SAndreas Gohr     *
15536c0b2b4SAndreas Gohr     * @param string $prompt
15636c0b2b4SAndreas Gohr     * @param bool $cache cache given value for next time?
15736c0b2b4SAndreas Gohr     * @return string
15836c0b2b4SAndreas Gohr     */
15936c0b2b4SAndreas Gohr    protected function readLine($prompt, $cache = false)
16036c0b2b4SAndreas Gohr    {
16136c0b2b4SAndreas Gohr        $value = '';
16236c0b2b4SAndreas Gohr        $default = '';
16336c0b2b4SAndreas Gohr        $cachename = getCacheName($prompt, '.readline');
16436c0b2b4SAndreas Gohr        if ($cache && file_exists($cachename)) {
16536c0b2b4SAndreas Gohr            $default = file_get_contents($cachename);
16636c0b2b4SAndreas Gohr        }
16736c0b2b4SAndreas Gohr
16836c0b2b4SAndreas Gohr        while ($value === '') {
16936c0b2b4SAndreas Gohr            echo $prompt;
17036c0b2b4SAndreas Gohr            if ($default) echo ' [' . $default . ']';
17136c0b2b4SAndreas Gohr            echo ': ';
17236c0b2b4SAndreas Gohr
17336c0b2b4SAndreas Gohr            $fh = fopen('php://stdin', 'r');
17436c0b2b4SAndreas Gohr            $value = trim(fgets($fh));
17536c0b2b4SAndreas Gohr            fclose($fh);
17636c0b2b4SAndreas Gohr
17736c0b2b4SAndreas Gohr            if ($value === '') $value = $default;
17836c0b2b4SAndreas Gohr        }
17936c0b2b4SAndreas Gohr
18036c0b2b4SAndreas Gohr        if ($cache) {
18136c0b2b4SAndreas Gohr            file_put_contents($cachename, $value);
18236c0b2b4SAndreas Gohr        }
18336c0b2b4SAndreas Gohr
18436c0b2b4SAndreas Gohr        return $value;
18536c0b2b4SAndreas Gohr    }
18636c0b2b4SAndreas Gohr
18736c0b2b4SAndreas Gohr    /**
18836c0b2b4SAndreas Gohr     * Download a skeleton file and do the replacements
18936c0b2b4SAndreas Gohr     *
19036c0b2b4SAndreas Gohr     * @param string $skel Skeleton relative to the skel dir in the repo
19136c0b2b4SAndreas Gohr     * @param string $target Target file relative to the main directory
19236c0b2b4SAndreas Gohr     * @param array $replacements
19336c0b2b4SAndreas Gohr     */
19436c0b2b4SAndreas Gohr    protected function loadSkeleton($skel, $target, $replacements)
19536c0b2b4SAndreas Gohr    {
19636c0b2b4SAndreas Gohr        if (file_exists($target)) {
19736c0b2b4SAndreas Gohr            $this->error($target . ' already exists');
19836c0b2b4SAndreas Gohr            return;
19936c0b2b4SAndreas Gohr        }
20036c0b2b4SAndreas Gohr
20136c0b2b4SAndreas Gohr        $base = 'https://raw.githubusercontent.com/dokufreaks/dokuwiki-plugin-wizard/master/skel/';
20236c0b2b4SAndreas Gohr        $http = new \dokuwiki\HTTP\DokuHTTPClient();
20336c0b2b4SAndreas Gohr        $content = $http->get($base . $skel);
20436c0b2b4SAndreas Gohr
20536c0b2b4SAndreas Gohr        $content = str_replace(
20636c0b2b4SAndreas Gohr            array_keys($replacements),
20736c0b2b4SAndreas Gohr            array_values($replacements),
20836c0b2b4SAndreas Gohr            $content
20936c0b2b4SAndreas Gohr        );
21036c0b2b4SAndreas Gohr
21136c0b2b4SAndreas Gohr        io_makeFileDir($target);
21236c0b2b4SAndreas Gohr        file_put_contents($target, $content);
21336c0b2b4SAndreas Gohr        $this->success('Added ' . $target);
21436c0b2b4SAndreas Gohr    }
21536c0b2b4SAndreas Gohr
21636c0b2b4SAndreas Gohr    /**
21736c0b2b4SAndreas Gohr     * Prepare the string replacements
21836c0b2b4SAndreas Gohr     *
21936c0b2b4SAndreas Gohr     * @param array $replacements override defaults
22036c0b2b4SAndreas Gohr     * @return array
22136c0b2b4SAndreas Gohr     */
22236c0b2b4SAndreas Gohr    protected function prepareReplacements($replacements = [])
22336c0b2b4SAndreas Gohr    {
22436c0b2b4SAndreas Gohr        // defaults
22536c0b2b4SAndreas Gohr        $data = [
22636c0b2b4SAndreas Gohr            '@@AUTHOR_NAME@@' => '',
22736c0b2b4SAndreas Gohr            '@@AUTHOR_MAIL@@' => '',
22836c0b2b4SAndreas Gohr            '@@PLUGIN_NAME@@' => '',
22936c0b2b4SAndreas Gohr            '@@PLUGIN_DESC@@' => '',
23036c0b2b4SAndreas Gohr            '@@PLUGIN_URL@@' => '',
23136c0b2b4SAndreas Gohr            '@@PLUGIN_TYPE@@' => '',
23236c0b2b4SAndreas Gohr            '@@INSTALL_DIR@@' => 'plugins',
23336c0b2b4SAndreas Gohr            '@@DATE@@' => date('Y-m-d'),
23436c0b2b4SAndreas Gohr        ];
23536c0b2b4SAndreas Gohr
23636c0b2b4SAndreas Gohr        // load from existing plugin.info
23736c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
23836c0b2b4SAndreas Gohr        [$name, $type] = $this->getTypedNameFromDir($dir);
23936c0b2b4SAndreas Gohr        if (file_exists("$type.info.txt")) {
24036c0b2b4SAndreas Gohr            $info = confToHash("$type.info.txt");
24136c0b2b4SAndreas Gohr            $data['@@AUTHOR_NAME@@'] = $info['author'];
24236c0b2b4SAndreas Gohr            $data['@@AUTHOR_MAIL@@'] = $info['email'];
24336c0b2b4SAndreas Gohr            $data['@@PLUGIN_DESC@@'] = $info['desc'];
24436c0b2b4SAndreas Gohr            $data['@@PLUGIN_URL@@'] = $info['url'];
24536c0b2b4SAndreas Gohr        }
24636c0b2b4SAndreas Gohr        $data['@@PLUGIN_NAME@@'] = $name;
24736c0b2b4SAndreas Gohr        $data['@@PLUGIN_TYPE@@'] = $type;
24836c0b2b4SAndreas Gohr
24936c0b2b4SAndreas Gohr        if ($type == 'template') {
25036c0b2b4SAndreas Gohr            $data['@@INSTALL_DIR@@'] = 'tpl';
25136c0b2b4SAndreas Gohr        }
25236c0b2b4SAndreas Gohr
25336c0b2b4SAndreas Gohr        // merge given overrides
25436c0b2b4SAndreas Gohr        $data = array_merge($data, $replacements);
25536c0b2b4SAndreas Gohr
25636c0b2b4SAndreas Gohr        // set inherited defaults
25736c0b2b4SAndreas Gohr        if (empty($data['@@PLUGIN_URL@@'])) {
25836c0b2b4SAndreas Gohr            $data['@@PLUGIN_URL@@'] =
25936c0b2b4SAndreas Gohr                'https://www.dokuwiki.org/' .
26036c0b2b4SAndreas Gohr                $data['@@PLUGIN_TYPE@@'] . ':' .
26136c0b2b4SAndreas Gohr                $data['@@PLUGIN_NAME@@'];
26236c0b2b4SAndreas Gohr        }
26336c0b2b4SAndreas Gohr
26436c0b2b4SAndreas Gohr        return $data;
26536c0b2b4SAndreas Gohr    }
26636c0b2b4SAndreas Gohr
26736c0b2b4SAndreas Gohr    /**
26836c0b2b4SAndreas Gohr     * Replacements needed for action components.
26936c0b2b4SAndreas Gohr     *
27036c0b2b4SAndreas Gohr     * Not cool but that' what we need currently
27136c0b2b4SAndreas Gohr     *
27236c0b2b4SAndreas Gohr     * @return string[]
27336c0b2b4SAndreas Gohr     */
27436c0b2b4SAndreas Gohr    protected function actionReplacements()
27536c0b2b4SAndreas Gohr    {
27636c0b2b4SAndreas Gohr        $fn = 'handleEventName';
27736c0b2b4SAndreas Gohr        $register = '        $controller->register_hook(\'EVENT_NAME\', \'AFTER|BEFORE\', $this, \'' . $fn . '\');';
27836c0b2b4SAndreas Gohr        $handler = '    public function ' . $fn . '(Doku_Event $event, $param)' . "\n"
27936c0b2b4SAndreas Gohr            . "    {\n"
28036c0b2b4SAndreas Gohr            . "    }\n";
28136c0b2b4SAndreas Gohr
28236c0b2b4SAndreas Gohr        return [
28336c0b2b4SAndreas Gohr            '@@REGISTER@@' => $register . "\n   ",
28436c0b2b4SAndreas Gohr            '@@HANDLERS@@' => $handler,
28536c0b2b4SAndreas Gohr        ];
28636c0b2b4SAndreas Gohr    }
28736c0b2b4SAndreas Gohr
28836c0b2b4SAndreas Gohr    /**
289c5c85a97SAndreas Gohr     * Delete the given file if it exists
290c5c85a97SAndreas Gohr     *
291c5c85a97SAndreas Gohr     * @param string $file
292c5c85a97SAndreas Gohr     */
293c5c85a97SAndreas Gohr    protected function deleteFile($file)
294c5c85a97SAndreas Gohr    {
295c5c85a97SAndreas Gohr        if (!file_exists($file)) return;
296c5c85a97SAndreas Gohr        if (@unlink($file)) {
297c5c85a97SAndreas Gohr            $this->success('Delete ' . $file);
298c5c85a97SAndreas Gohr        }
299c5c85a97SAndreas Gohr    }
300c5c85a97SAndreas Gohr
301c5c85a97SAndreas Gohr    /**
302c5c85a97SAndreas Gohr     * Run git with the given arguments and return the output
303c5c85a97SAndreas Gohr     *
304c5c85a97SAndreas Gohr     * @throws CliException when the command can't be run
305c5c85a97SAndreas Gohr     * @param string ...$args
306c5c85a97SAndreas Gohr     * @return string[]
307c5c85a97SAndreas Gohr     */
308c5c85a97SAndreas Gohr    protected function git(...$args)
309c5c85a97SAndreas Gohr    {
310c5c85a97SAndreas Gohr        $args = array_map('escapeshellarg', $args);
311c5c85a97SAndreas Gohr        $cmd = 'git ' . join(' ', $args);
312c5c85a97SAndreas Gohr        $output = [];
313c5c85a97SAndreas Gohr        $result = 0;
314c5c85a97SAndreas Gohr
315c5c85a97SAndreas Gohr        $this->info($cmd);
316c5c85a97SAndreas Gohr        $last = exec($cmd, $output, $result);
317c5c85a97SAndreas Gohr        if ($last === false || $result !== 0) {
318c5c85a97SAndreas Gohr            throw new CliException('Running git failed');
319c5c85a97SAndreas Gohr        }
320c5c85a97SAndreas Gohr
321c5c85a97SAndreas Gohr        return $output;
322c5c85a97SAndreas Gohr    }
323c5c85a97SAndreas Gohr
324c5c85a97SAndreas Gohr    // region Commands
325c5c85a97SAndreas Gohr
326c5c85a97SAndreas Gohr    /**
32736c0b2b4SAndreas Gohr     * Intialize the current directory as a plugin or template
32836c0b2b4SAndreas Gohr     *
32936c0b2b4SAndreas Gohr     * @return int
33036c0b2b4SAndreas Gohr     */
33136c0b2b4SAndreas Gohr    protected function cmdInit()
33236c0b2b4SAndreas Gohr    {
33336c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
33436c0b2b4SAndreas Gohr        if ((new FilesystemIterator($dir))->valid()) {
33536c0b2b4SAndreas Gohr            throw new CliException('Current directory needs to be empty');
33636c0b2b4SAndreas Gohr        }
33736c0b2b4SAndreas Gohr
33836c0b2b4SAndreas Gohr        [$name, $type] = $this->getTypedNameFromDir($dir);
33936c0b2b4SAndreas Gohr        $user = $this->readLine('Your Name', true);
34036c0b2b4SAndreas Gohr        $mail = $this->readLine('Your E-Mail', true);
34136c0b2b4SAndreas Gohr        $desc = $this->readLine('Short description');
34236c0b2b4SAndreas Gohr
34336c0b2b4SAndreas Gohr        $replacements = [
34436c0b2b4SAndreas Gohr            '@@AUTHOR_NAME@@' => $user,
34536c0b2b4SAndreas Gohr            '@@AUTHOR_MAIL@@' => $mail,
34636c0b2b4SAndreas Gohr            '@@PLUGIN_NAME@@' => $name,
34736c0b2b4SAndreas Gohr            '@@PLUGIN_DESC@@' => $desc,
34836c0b2b4SAndreas Gohr            '@@PLUGIN_TYPE@@' => $type,
34936c0b2b4SAndreas Gohr        ];
35036c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements($replacements);
35136c0b2b4SAndreas Gohr
35236c0b2b4SAndreas Gohr        $this->loadSkeleton('info.skel', $type . '.info.txt', $replacements);
35336c0b2b4SAndreas Gohr        $this->loadSkeleton('README.skel', 'README', $replacements); // fixme needs to be type specific
35436c0b2b4SAndreas Gohr        $this->loadSkeleton('LICENSE.skel', 'LICENSE', $replacements);
35536c0b2b4SAndreas Gohr
3568b06c9ddSAndreas Gohr        try {
3578b06c9ddSAndreas Gohr            $this->git('init');
3588b06c9ddSAndreas Gohr        } catch (CliException $e) {
3598b06c9ddSAndreas Gohr            $this->error($e->getMessage());
3608b06c9ddSAndreas Gohr        }
3618b06c9ddSAndreas Gohr
36236c0b2b4SAndreas Gohr        return 0;
36336c0b2b4SAndreas Gohr    }
36436c0b2b4SAndreas Gohr
36536c0b2b4SAndreas Gohr    /**
36636c0b2b4SAndreas Gohr     * Add test framework
36736c0b2b4SAndreas Gohr     *
36836c0b2b4SAndreas Gohr     * @param string $test Name of the Test to add
36936c0b2b4SAndreas Gohr     * @return int
37036c0b2b4SAndreas Gohr     */
37136c0b2b4SAndreas Gohr    protected function cmdAddTest($test = '')
37236c0b2b4SAndreas Gohr    {
37336c0b2b4SAndreas Gohr        $test = ucfirst(strtolower($test));
37436c0b2b4SAndreas Gohr
37536c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements(['@@TEST@@' => $test]);
37636c0b2b4SAndreas Gohr        $this->loadSkeleton('.github/workflows/phpTestLinux.skel', '.github/workflows/phpTestLinux.yml', $replacements);
37736c0b2b4SAndreas Gohr        if ($test) {
37836c0b2b4SAndreas Gohr            $this->loadSkeleton('_test/StandardTest.skel', '_test/' . $test . 'Test.php', $replacements);
37936c0b2b4SAndreas Gohr        } else {
38036c0b2b4SAndreas Gohr            $this->loadSkeleton('_test/GeneralTest.skel', '_test/GeneralTest.php', $replacements);
38136c0b2b4SAndreas Gohr        }
38236c0b2b4SAndreas Gohr
38336c0b2b4SAndreas Gohr        return 0;
38436c0b2b4SAndreas Gohr    }
38536c0b2b4SAndreas Gohr
38636c0b2b4SAndreas Gohr    /**
38736c0b2b4SAndreas Gohr     * Add configuration
38836c0b2b4SAndreas Gohr     *
38936c0b2b4SAndreas Gohr     * @return int
39036c0b2b4SAndreas Gohr     */
39136c0b2b4SAndreas Gohr    protected function cmdAddConf()
39236c0b2b4SAndreas Gohr    {
39336c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements();
39436c0b2b4SAndreas Gohr        $this->loadSkeleton('conf/default.skel', 'conf/default.php', $replacements);
39536c0b2b4SAndreas Gohr        $this->loadSkeleton('conf/metadata.skel', 'conf/metadata.php', $replacements);
39636c0b2b4SAndreas Gohr        if (is_dir('lang')) {
39736c0b2b4SAndreas Gohr            $this->loadSkeleton('lang/settings.skel', 'lang/en/settings.php', $replacements);
39836c0b2b4SAndreas Gohr        }
39936c0b2b4SAndreas Gohr
40036c0b2b4SAndreas Gohr        return 0;
40136c0b2b4SAndreas Gohr    }
40236c0b2b4SAndreas Gohr
40336c0b2b4SAndreas Gohr    /**
40436c0b2b4SAndreas Gohr     * Add language
40536c0b2b4SAndreas Gohr     *
40636c0b2b4SAndreas Gohr     * @return int
40736c0b2b4SAndreas Gohr     */
40836c0b2b4SAndreas Gohr    protected function cmdAddLang()
40936c0b2b4SAndreas Gohr    {
41036c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements();
41136c0b2b4SAndreas Gohr        $this->loadSkeleton('lang/lang.skel', 'lang/en/lang.php', $replacements);
41236c0b2b4SAndreas Gohr        if (is_dir('conf')) {
41336c0b2b4SAndreas Gohr            $this->loadSkeleton('lang/settings.skel', 'lang/en/settings.php', $replacements);
41436c0b2b4SAndreas Gohr        }
41536c0b2b4SAndreas Gohr
41636c0b2b4SAndreas Gohr        return 0;
41736c0b2b4SAndreas Gohr    }
41836c0b2b4SAndreas Gohr
41936c0b2b4SAndreas Gohr    /**
42036c0b2b4SAndreas Gohr     * Add another component to the plugin
42136c0b2b4SAndreas Gohr     *
42236c0b2b4SAndreas Gohr     * @param string $type
42336c0b2b4SAndreas Gohr     * @param string $component
42436c0b2b4SAndreas Gohr     */
42536c0b2b4SAndreas Gohr    protected function cmdAddComponent($type, $component = '')
42636c0b2b4SAndreas Gohr    {
42736c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
42836c0b2b4SAndreas Gohr        list($plugin, $extension) = $this->getTypedNameFromDir($dir);
42936c0b2b4SAndreas Gohr        if ($extension != 'plugin') throw  new CliException('Components can only be added to plugins');
43036c0b2b4SAndreas Gohr        if (!in_array($type, PluginController::PLUGIN_TYPES)) {
43136c0b2b4SAndreas Gohr            throw new CliException('Invalid type ' . $type);
43236c0b2b4SAndreas Gohr        }
43336c0b2b4SAndreas Gohr
43436c0b2b4SAndreas Gohr        if ($component) {
43536c0b2b4SAndreas Gohr            $path = $type . '/' . $component . '.php';
43636c0b2b4SAndreas Gohr            $class = $type . '_plugin_' . $plugin . '_' . $component;
43736c0b2b4SAndreas Gohr            $self = $plugin . '_' . $component;
43836c0b2b4SAndreas Gohr        } else {
43936c0b2b4SAndreas Gohr            $path = $type . '.php';
44036c0b2b4SAndreas Gohr            $class = $type . '_plugin_' . $plugin;
44136c0b2b4SAndreas Gohr            $self = $plugin;
44236c0b2b4SAndreas Gohr        }
44336c0b2b4SAndreas Gohr
44436c0b2b4SAndreas Gohr        $replacements = $this->actionReplacements();
44536c0b2b4SAndreas Gohr        $replacements['@@PLUGIN_COMPONENT_NAME@@'] = $class;
44636c0b2b4SAndreas Gohr        $replacements['@@SYNTAX_COMPONENT_NAME@@'] = $self;
44736c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements($replacements);
44836c0b2b4SAndreas Gohr        $this->loadSkeleton($type . '.skel', $path, $replacements);
44936c0b2b4SAndreas Gohr
45036c0b2b4SAndreas Gohr        return 0;
45136c0b2b4SAndreas Gohr    }
45236c0b2b4SAndreas Gohr
45336c0b2b4SAndreas Gohr    /**
45436c0b2b4SAndreas Gohr     * Generate a list of deleted files from git
45536c0b2b4SAndreas Gohr     *
45636c0b2b4SAndreas Gohr     * @link https://stackoverflow.com/a/6018049/172068
45736c0b2b4SAndreas Gohr     */
45836c0b2b4SAndreas Gohr    protected function cmdDeletedFiles()
45936c0b2b4SAndreas Gohr    {
4608b06c9ddSAndreas Gohr        if (!is_dir('.git')) throw new CliException('This extension seems not to be managed by git');
46136c0b2b4SAndreas Gohr
4628b06c9ddSAndreas Gohr        $output = $this->git('log', '--no-renames', '--pretty=format:', '--name-only', '--diff-filter=D');
46336c0b2b4SAndreas Gohr        $output = array_map('trim', $output);
46436c0b2b4SAndreas Gohr        $output = array_filter($output);
46536c0b2b4SAndreas Gohr        $output = array_unique($output);
46636c0b2b4SAndreas Gohr        $output = array_filter($output, function ($item) {
46736c0b2b4SAndreas Gohr            return !file_exists($item);
46836c0b2b4SAndreas Gohr        });
46936c0b2b4SAndreas Gohr        sort($output);
47036c0b2b4SAndreas Gohr
47136c0b2b4SAndreas Gohr        if (!count($output)) {
47236c0b2b4SAndreas Gohr            $this->info('No deleted files found');
47336c0b2b4SAndreas Gohr            return 0;
47436c0b2b4SAndreas Gohr        }
47536c0b2b4SAndreas Gohr
47636c0b2b4SAndreas Gohr        $content = "# This is a list of files that were present in previous releases\n" .
47736c0b2b4SAndreas Gohr            "# but were removed later. They should not exist in your installation.\n" .
47836c0b2b4SAndreas Gohr            join("\n", $output) . "\n";
47936c0b2b4SAndreas Gohr
48036c0b2b4SAndreas Gohr        file_put_contents('deleted.files', $content);
48136c0b2b4SAndreas Gohr        $this->success('written deleted.files');
48236c0b2b4SAndreas Gohr        return 0;
48336c0b2b4SAndreas Gohr    }
4848b06c9ddSAndreas Gohr
4858b06c9ddSAndreas Gohr    /**
486c5c85a97SAndreas Gohr     * Remove files that shouldn't be here anymore
4878b06c9ddSAndreas Gohr     */
4881a23d1dbSAndreas Gohr    protected function cmdRmObsolete()
4898b06c9ddSAndreas Gohr    {
490c5c85a97SAndreas Gohr        $this->deleteFile('_test/general.test.php');
491c5c85a97SAndreas Gohr        $this->deleteFile('.travis.yml');
4928b06c9ddSAndreas Gohr
493c5c85a97SAndreas Gohr        return 0;
4948b06c9ddSAndreas Gohr    }
4958b06c9ddSAndreas Gohr
4961a23d1dbSAndreas Gohr    /**
4971a23d1dbSAndreas Gohr     * Download a remote icon
4981a23d1dbSAndreas Gohr     *
4991a23d1dbSAndreas Gohr     * @param string $ident
5001a23d1dbSAndreas Gohr     * @param string $save
50192738407SAndreas Gohr     * @param bool $keep
5021a23d1dbSAndreas Gohr     * @return int
5031a23d1dbSAndreas Gohr     * @throws Exception
5041a23d1dbSAndreas Gohr     */
50592738407SAndreas Gohr    protected function cmdDownloadSVG($ident, $save = '', $keep = false)
5061a23d1dbSAndreas Gohr    {
5071a23d1dbSAndreas Gohr        $svg = new SVGIcon($this);
50892738407SAndreas Gohr        $svg->keepNamespace($keep);
5091a23d1dbSAndreas Gohr        return (int)$svg->downloadRemoteIcon($ident, $save);
5101a23d1dbSAndreas Gohr    }
5111a23d1dbSAndreas Gohr
5121a23d1dbSAndreas Gohr    /**
5138f82d673SAndreas Gohr     * @param string[] $files
51492738407SAndreas Gohr     * @param bool $keep
5151a23d1dbSAndreas Gohr     * @return int
5161a23d1dbSAndreas Gohr     * @throws Exception
5171a23d1dbSAndreas Gohr     */
5188f82d673SAndreas Gohr    protected function cmdCleanSVG($files, $keep = false)
5191a23d1dbSAndreas Gohr    {
5201a23d1dbSAndreas Gohr        $svg = new SVGIcon($this);
52192738407SAndreas Gohr        $svg->keepNamespace($keep);
5228f82d673SAndreas Gohr
5238f82d673SAndreas Gohr        $ok = true;
5248f82d673SAndreas Gohr        foreach ($files as $file) {
5258f82d673SAndreas Gohr            $ok = $ok && $svg->cleanSVGFile($file);
5268f82d673SAndreas Gohr        }
5278f82d673SAndreas Gohr        return (int)$ok;
5281a23d1dbSAndreas Gohr    }
5291a23d1dbSAndreas Gohr
5305586e97bSAndreas Gohr    /**
5315586e97bSAndreas Gohr     * @return int
5325586e97bSAndreas Gohr     */
5335586e97bSAndreas Gohr    protected function cmdCleanLang()
5345586e97bSAndreas Gohr    {
5355586e97bSAndreas Gohr        $lp = new LangProcessor($this);
5365586e97bSAndreas Gohr
5375586e97bSAndreas Gohr        $files = glob('./lang/*/lang.php');
538*5b2e8f12SAndreas Gohr        foreach ($files as $file) {
539*5b2e8f12SAndreas Gohr            $lp->processLangFile($file);
540*5b2e8f12SAndreas Gohr        }
541*5b2e8f12SAndreas Gohr
542*5b2e8f12SAndreas Gohr        $files = glob('./lang/*/settings.php');
5435586e97bSAndreas Gohr        foreach ($files as $file) {
5445586e97bSAndreas Gohr            $lp->processLangFile($file);
5455586e97bSAndreas Gohr        }
5465586e97bSAndreas Gohr
5475586e97bSAndreas Gohr        return 0;
5485586e97bSAndreas Gohr    }
5495586e97bSAndreas Gohr
550c5c85a97SAndreas Gohr    //endregion
55136c0b2b4SAndreas Gohr}
552