xref: /plugin/dev/cli.php (revision 36c0b2b4c5f16c5457bf7ab09e312b78a925687e)
1*36c0b2b4SAndreas Gohr#!/usr/bin/env php
2*36c0b2b4SAndreas Gohr<?php
3*36c0b2b4SAndreas Gohr
4*36c0b2b4SAndreas Gohruse dokuwiki\Extension\CLIPlugin;
5*36c0b2b4SAndreas Gohruse dokuwiki\Extension\PluginController;
6*36c0b2b4SAndreas Gohruse splitbrain\phpcli\Exception as CliException;
7*36c0b2b4SAndreas Gohruse splitbrain\phpcli\Options;
8*36c0b2b4SAndreas Gohr
9*36c0b2b4SAndreas Gohr/**
10*36c0b2b4SAndreas Gohr * @license GPL2
11*36c0b2b4SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
12*36c0b2b4SAndreas Gohr */
13*36c0b2b4SAndreas Gohrclass cli_plugin_dev extends CLIPlugin
14*36c0b2b4SAndreas Gohr{
15*36c0b2b4SAndreas Gohr
16*36c0b2b4SAndreas Gohr    /**
17*36c0b2b4SAndreas Gohr     * Register options and arguments on the given $options object
18*36c0b2b4SAndreas Gohr     *
19*36c0b2b4SAndreas Gohr     * @param Options $options
20*36c0b2b4SAndreas Gohr     * @return void
21*36c0b2b4SAndreas Gohr     */
22*36c0b2b4SAndreas Gohr    protected function setup(Options $options)
23*36c0b2b4SAndreas Gohr    {
24*36c0b2b4SAndreas Gohr        $options->setHelp(
25*36c0b2b4SAndreas Gohr            "CLI to help with plugin and template development\n\n" .
26*36c0b2b4SAndreas Gohr            "Run this script from within the extension's directory."
27*36c0b2b4SAndreas Gohr        );
28*36c0b2b4SAndreas Gohr
29*36c0b2b4SAndreas Gohr        $options->registerCommand('init', 'initialize a new plugin or template');
30*36c0b2b4SAndreas Gohr        $options->registerCommand('addTest', 'add the testing framework files and optionall a new test file');
31*36c0b2b4SAndreas Gohr        $options->registerArgument('test', 'The name of the new test, leave out for general test.', false, 'addTest');
32*36c0b2b4SAndreas Gohr        $options->registerCommand('addConf', 'add the configuration files');
33*36c0b2b4SAndreas Gohr        $options->registerCommand('addLang', 'add the language files');
34*36c0b2b4SAndreas Gohr
35*36c0b2b4SAndreas Gohr        $options->registerCommand('addComponent', 'add a component');
36*36c0b2b4SAndreas Gohr        $options->registerArgument('type', 'The type of the component. Needs to be one of ' .
37*36c0b2b4SAndreas Gohr            join(', ', PluginController::PLUGIN_TYPES),
38*36c0b2b4SAndreas Gohr            true, 'addComponent'
39*36c0b2b4SAndreas Gohr        );
40*36c0b2b4SAndreas Gohr        $options->registerArgument('name', 'Optional name of the component', false, 'addComponent');
41*36c0b2b4SAndreas Gohr
42*36c0b2b4SAndreas Gohr        $options->registerCommand('deletedFiles', 'create the list of deleted files base on the git history');
43*36c0b2b4SAndreas Gohr    }
44*36c0b2b4SAndreas Gohr
45*36c0b2b4SAndreas Gohr    /** @inheritDoc */
46*36c0b2b4SAndreas Gohr    protected function main(Options $options)
47*36c0b2b4SAndreas Gohr    {
48*36c0b2b4SAndreas Gohr        switch ($options->getCmd()) {
49*36c0b2b4SAndreas Gohr            case 'init':
50*36c0b2b4SAndreas Gohr                return $this->cmdInit();
51*36c0b2b4SAndreas Gohr            case 'addTest':
52*36c0b2b4SAndreas Gohr                $args = $options->getArgs();
53*36c0b2b4SAndreas Gohr                $test = array_shift($args);
54*36c0b2b4SAndreas Gohr                return $this->cmdAddTest($test);
55*36c0b2b4SAndreas Gohr            case 'addConf':
56*36c0b2b4SAndreas Gohr                return $this->cmdAddConf();
57*36c0b2b4SAndreas Gohr            case 'addLang':
58*36c0b2b4SAndreas Gohr                return $this->cmdAddLang();
59*36c0b2b4SAndreas Gohr            case 'addComponent':
60*36c0b2b4SAndreas Gohr                $args = $options->getArgs();
61*36c0b2b4SAndreas Gohr                $type = array_shift($args);
62*36c0b2b4SAndreas Gohr                $component = array_shift($args);
63*36c0b2b4SAndreas Gohr                return $this->cmdAddComponent($type, $component);
64*36c0b2b4SAndreas Gohr            case 'deletedFiles':
65*36c0b2b4SAndreas Gohr                return $this->cmdDeletedFiles();
66*36c0b2b4SAndreas Gohr            default:
67*36c0b2b4SAndreas Gohr                echo $options->help();
68*36c0b2b4SAndreas Gohr                return 0;
69*36c0b2b4SAndreas Gohr        }
70*36c0b2b4SAndreas Gohr    }
71*36c0b2b4SAndreas Gohr
72*36c0b2b4SAndreas Gohr    /**
73*36c0b2b4SAndreas Gohr     * Get the extension name from the current working directory
74*36c0b2b4SAndreas Gohr     *
75*36c0b2b4SAndreas Gohr     * @throws CliException if something's wrong
76*36c0b2b4SAndreas Gohr     * @param string $dir
77*36c0b2b4SAndreas Gohr     * @return string[] name, type
78*36c0b2b4SAndreas Gohr     */
79*36c0b2b4SAndreas Gohr    protected function getTypedNameFromDir($dir)
80*36c0b2b4SAndreas Gohr    {
81*36c0b2b4SAndreas Gohr        $pdir = fullpath(DOKU_PLUGIN);
82*36c0b2b4SAndreas Gohr        $tdir = fullpath(tpl_incdir() . '../');
83*36c0b2b4SAndreas Gohr
84*36c0b2b4SAndreas Gohr        if (strpos($dir, $pdir) === 0) {
85*36c0b2b4SAndreas Gohr            $ldir = substr($dir, strlen($pdir));
86*36c0b2b4SAndreas Gohr            $type = 'plugin';
87*36c0b2b4SAndreas Gohr        } elseif (strpos($dir, $tdir) === 0) {
88*36c0b2b4SAndreas Gohr            $ldir = substr($dir, strlen($tdir));
89*36c0b2b4SAndreas Gohr            $type = 'template';
90*36c0b2b4SAndreas Gohr        } else {
91*36c0b2b4SAndreas Gohr            throw new CliException('Current directory needs to be in plugin or template directory');
92*36c0b2b4SAndreas Gohr        }
93*36c0b2b4SAndreas Gohr
94*36c0b2b4SAndreas Gohr        $ldir = trim($ldir, '/');
95*36c0b2b4SAndreas Gohr
96*36c0b2b4SAndreas Gohr        if (strpos($ldir, '/') !== false) {
97*36c0b2b4SAndreas Gohr            throw new CliException('Current directory has to be main extension directory');
98*36c0b2b4SAndreas Gohr        }
99*36c0b2b4SAndreas Gohr
100*36c0b2b4SAndreas Gohr        return [$ldir, $type];
101*36c0b2b4SAndreas Gohr    }
102*36c0b2b4SAndreas Gohr
103*36c0b2b4SAndreas Gohr    /**
104*36c0b2b4SAndreas Gohr     * Interactively ask for a value from the user
105*36c0b2b4SAndreas Gohr     *
106*36c0b2b4SAndreas Gohr     * @param string $prompt
107*36c0b2b4SAndreas Gohr     * @param bool $cache cache given value for next time?
108*36c0b2b4SAndreas Gohr     * @return string
109*36c0b2b4SAndreas Gohr     */
110*36c0b2b4SAndreas Gohr    protected function readLine($prompt, $cache = false)
111*36c0b2b4SAndreas Gohr    {
112*36c0b2b4SAndreas Gohr        $value = '';
113*36c0b2b4SAndreas Gohr        $default = '';
114*36c0b2b4SAndreas Gohr        $cachename = getCacheName($prompt, '.readline');
115*36c0b2b4SAndreas Gohr        if ($cache && file_exists($cachename)) {
116*36c0b2b4SAndreas Gohr            $default = file_get_contents($cachename);
117*36c0b2b4SAndreas Gohr        }
118*36c0b2b4SAndreas Gohr
119*36c0b2b4SAndreas Gohr        while ($value === '') {
120*36c0b2b4SAndreas Gohr            echo $prompt;
121*36c0b2b4SAndreas Gohr            if ($default) echo ' [' . $default . ']';
122*36c0b2b4SAndreas Gohr            echo ': ';
123*36c0b2b4SAndreas Gohr
124*36c0b2b4SAndreas Gohr            $fh = fopen('php://stdin', 'r');
125*36c0b2b4SAndreas Gohr            $value = trim(fgets($fh));
126*36c0b2b4SAndreas Gohr            fclose($fh);
127*36c0b2b4SAndreas Gohr
128*36c0b2b4SAndreas Gohr            if ($value === '') $value = $default;
129*36c0b2b4SAndreas Gohr        }
130*36c0b2b4SAndreas Gohr
131*36c0b2b4SAndreas Gohr        if ($cache) {
132*36c0b2b4SAndreas Gohr            file_put_contents($cachename, $value);
133*36c0b2b4SAndreas Gohr        }
134*36c0b2b4SAndreas Gohr
135*36c0b2b4SAndreas Gohr        return $value;
136*36c0b2b4SAndreas Gohr    }
137*36c0b2b4SAndreas Gohr
138*36c0b2b4SAndreas Gohr    /**
139*36c0b2b4SAndreas Gohr     * Download a skeleton file and do the replacements
140*36c0b2b4SAndreas Gohr     *
141*36c0b2b4SAndreas Gohr     * @param string $skel Skeleton relative to the skel dir in the repo
142*36c0b2b4SAndreas Gohr     * @param string $target Target file relative to the main directory
143*36c0b2b4SAndreas Gohr     * @param array $replacements
144*36c0b2b4SAndreas Gohr     */
145*36c0b2b4SAndreas Gohr    protected function loadSkeleton($skel, $target, $replacements)
146*36c0b2b4SAndreas Gohr    {
147*36c0b2b4SAndreas Gohr        if (file_exists($target)) {
148*36c0b2b4SAndreas Gohr            $this->error($target . ' already exists');
149*36c0b2b4SAndreas Gohr            return;
150*36c0b2b4SAndreas Gohr        }
151*36c0b2b4SAndreas Gohr
152*36c0b2b4SAndreas Gohr        $base = 'https://raw.githubusercontent.com/dokufreaks/dokuwiki-plugin-wizard/master/skel/';
153*36c0b2b4SAndreas Gohr        $http = new \dokuwiki\HTTP\DokuHTTPClient();
154*36c0b2b4SAndreas Gohr        $content = $http->get($base . $skel);
155*36c0b2b4SAndreas Gohr
156*36c0b2b4SAndreas Gohr        $content = str_replace(
157*36c0b2b4SAndreas Gohr            array_keys($replacements),
158*36c0b2b4SAndreas Gohr            array_values($replacements),
159*36c0b2b4SAndreas Gohr            $content
160*36c0b2b4SAndreas Gohr        );
161*36c0b2b4SAndreas Gohr
162*36c0b2b4SAndreas Gohr        io_makeFileDir($target);
163*36c0b2b4SAndreas Gohr        file_put_contents($target, $content);
164*36c0b2b4SAndreas Gohr        $this->success('Added ' . $target);
165*36c0b2b4SAndreas Gohr    }
166*36c0b2b4SAndreas Gohr
167*36c0b2b4SAndreas Gohr    /**
168*36c0b2b4SAndreas Gohr     * Prepare the string replacements
169*36c0b2b4SAndreas Gohr     *
170*36c0b2b4SAndreas Gohr     * @param array $replacements override defaults
171*36c0b2b4SAndreas Gohr     * @return array
172*36c0b2b4SAndreas Gohr     */
173*36c0b2b4SAndreas Gohr    protected function prepareReplacements($replacements = [])
174*36c0b2b4SAndreas Gohr    {
175*36c0b2b4SAndreas Gohr        // defaults
176*36c0b2b4SAndreas Gohr        $data = [
177*36c0b2b4SAndreas Gohr            '@@AUTHOR_NAME@@' => '',
178*36c0b2b4SAndreas Gohr            '@@AUTHOR_MAIL@@' => '',
179*36c0b2b4SAndreas Gohr            '@@PLUGIN_NAME@@' => '',
180*36c0b2b4SAndreas Gohr            '@@PLUGIN_DESC@@' => '',
181*36c0b2b4SAndreas Gohr            '@@PLUGIN_URL@@' => '',
182*36c0b2b4SAndreas Gohr            '@@PLUGIN_TYPE@@' => '',
183*36c0b2b4SAndreas Gohr            '@@INSTALL_DIR@@' => 'plugins',
184*36c0b2b4SAndreas Gohr            '@@DATE@@' => date('Y-m-d'),
185*36c0b2b4SAndreas Gohr        ];
186*36c0b2b4SAndreas Gohr
187*36c0b2b4SAndreas Gohr        // load from existing plugin.info
188*36c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
189*36c0b2b4SAndreas Gohr        [$name, $type] = $this->getTypedNameFromDir($dir);
190*36c0b2b4SAndreas Gohr        if (file_exists("$type.info.txt")) {
191*36c0b2b4SAndreas Gohr            $info = confToHash("$type.info.txt");
192*36c0b2b4SAndreas Gohr            $data['@@AUTHOR_NAME@@'] = $info['author'];
193*36c0b2b4SAndreas Gohr            $data['@@AUTHOR_MAIL@@'] = $info['email'];
194*36c0b2b4SAndreas Gohr            $data['@@PLUGIN_DESC@@'] = $info['desc'];
195*36c0b2b4SAndreas Gohr            $data['@@PLUGIN_URL@@'] = $info['url'];
196*36c0b2b4SAndreas Gohr        }
197*36c0b2b4SAndreas Gohr        $data['@@PLUGIN_NAME@@'] = $name;
198*36c0b2b4SAndreas Gohr        $data['@@PLUGIN_TYPE@@'] = $type;
199*36c0b2b4SAndreas Gohr
200*36c0b2b4SAndreas Gohr        if ($type == 'template') {
201*36c0b2b4SAndreas Gohr            $data['@@INSTALL_DIR@@'] = 'tpl';
202*36c0b2b4SAndreas Gohr        }
203*36c0b2b4SAndreas Gohr
204*36c0b2b4SAndreas Gohr        // merge given overrides
205*36c0b2b4SAndreas Gohr        $data = array_merge($data, $replacements);
206*36c0b2b4SAndreas Gohr
207*36c0b2b4SAndreas Gohr        // set inherited defaults
208*36c0b2b4SAndreas Gohr        if (empty($data['@@PLUGIN_URL@@'])) {
209*36c0b2b4SAndreas Gohr            $data['@@PLUGIN_URL@@'] =
210*36c0b2b4SAndreas Gohr                'https://www.dokuwiki.org/' .
211*36c0b2b4SAndreas Gohr                $data['@@PLUGIN_TYPE@@'] . ':' .
212*36c0b2b4SAndreas Gohr                $data['@@PLUGIN_NAME@@'];
213*36c0b2b4SAndreas Gohr        }
214*36c0b2b4SAndreas Gohr
215*36c0b2b4SAndreas Gohr        return $data;
216*36c0b2b4SAndreas Gohr    }
217*36c0b2b4SAndreas Gohr
218*36c0b2b4SAndreas Gohr    /**
219*36c0b2b4SAndreas Gohr     * Replacements needed for action components.
220*36c0b2b4SAndreas Gohr     *
221*36c0b2b4SAndreas Gohr     * Not cool but that' what we need currently
222*36c0b2b4SAndreas Gohr     *
223*36c0b2b4SAndreas Gohr     * @return string[]
224*36c0b2b4SAndreas Gohr     */
225*36c0b2b4SAndreas Gohr    protected function actionReplacements()
226*36c0b2b4SAndreas Gohr    {
227*36c0b2b4SAndreas Gohr        $fn = 'handleEventName';
228*36c0b2b4SAndreas Gohr        $register = '        $controller->register_hook(\'EVENT_NAME\', \'AFTER|BEFORE\', $this, \'' . $fn . '\');';
229*36c0b2b4SAndreas Gohr        $handler = '    public function ' . $fn . '(Doku_Event $event, $param)' . "\n"
230*36c0b2b4SAndreas Gohr            . "    {\n"
231*36c0b2b4SAndreas Gohr            . "    }\n";
232*36c0b2b4SAndreas Gohr
233*36c0b2b4SAndreas Gohr        return [
234*36c0b2b4SAndreas Gohr            '@@REGISTER@@' => $register . "\n   ",
235*36c0b2b4SAndreas Gohr            '@@HANDLERS@@' => $handler,
236*36c0b2b4SAndreas Gohr        ];
237*36c0b2b4SAndreas Gohr    }
238*36c0b2b4SAndreas Gohr
239*36c0b2b4SAndreas Gohr    /**
240*36c0b2b4SAndreas Gohr     * Intialize the current directory as a plugin or template
241*36c0b2b4SAndreas Gohr     *
242*36c0b2b4SAndreas Gohr     * @return int
243*36c0b2b4SAndreas Gohr     */
244*36c0b2b4SAndreas Gohr    protected function cmdInit()
245*36c0b2b4SAndreas Gohr    {
246*36c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
247*36c0b2b4SAndreas Gohr        if ((new FilesystemIterator($dir))->valid()) {
248*36c0b2b4SAndreas Gohr            throw new CliException('Current directory needs to be empty');
249*36c0b2b4SAndreas Gohr        }
250*36c0b2b4SAndreas Gohr
251*36c0b2b4SAndreas Gohr        [$name, $type] = $this->getTypedNameFromDir($dir);
252*36c0b2b4SAndreas Gohr        $user = $this->readLine('Your Name', true);
253*36c0b2b4SAndreas Gohr        $mail = $this->readLine('Your E-Mail', true);
254*36c0b2b4SAndreas Gohr        $desc = $this->readLine('Short description');
255*36c0b2b4SAndreas Gohr
256*36c0b2b4SAndreas Gohr        $replacements = [
257*36c0b2b4SAndreas Gohr            '@@AUTHOR_NAME@@' => $user,
258*36c0b2b4SAndreas Gohr            '@@AUTHOR_MAIL@@' => $mail,
259*36c0b2b4SAndreas Gohr            '@@PLUGIN_NAME@@' => $name,
260*36c0b2b4SAndreas Gohr            '@@PLUGIN_DESC@@' => $desc,
261*36c0b2b4SAndreas Gohr            '@@PLUGIN_TYPE@@' => $type,
262*36c0b2b4SAndreas Gohr        ];
263*36c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements($replacements);
264*36c0b2b4SAndreas Gohr
265*36c0b2b4SAndreas Gohr        $this->loadSkeleton('info.skel', $type . '.info.txt', $replacements);
266*36c0b2b4SAndreas Gohr        $this->loadSkeleton('README.skel', 'README', $replacements); // fixme needs to be type specific
267*36c0b2b4SAndreas Gohr        $this->loadSkeleton('LICENSE.skel', 'LICENSE', $replacements);
268*36c0b2b4SAndreas Gohr
269*36c0b2b4SAndreas Gohr        return 0;
270*36c0b2b4SAndreas Gohr    }
271*36c0b2b4SAndreas Gohr
272*36c0b2b4SAndreas Gohr    /**
273*36c0b2b4SAndreas Gohr     * Add test framework
274*36c0b2b4SAndreas Gohr     *
275*36c0b2b4SAndreas Gohr     * @param string $test Name of the Test to add
276*36c0b2b4SAndreas Gohr     * @return int
277*36c0b2b4SAndreas Gohr     */
278*36c0b2b4SAndreas Gohr    protected function cmdAddTest($test = '')
279*36c0b2b4SAndreas Gohr    {
280*36c0b2b4SAndreas Gohr        $test = ucfirst(strtolower($test));
281*36c0b2b4SAndreas Gohr
282*36c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements(['@@TEST@@' => $test]);
283*36c0b2b4SAndreas Gohr        $this->loadSkeleton('.github/workflows/phpTestLinux.skel', '.github/workflows/phpTestLinux.yml', $replacements);
284*36c0b2b4SAndreas Gohr        if ($test) {
285*36c0b2b4SAndreas Gohr            $this->loadSkeleton('_test/StandardTest.skel', '_test/' . $test . 'Test.php', $replacements);
286*36c0b2b4SAndreas Gohr        } else {
287*36c0b2b4SAndreas Gohr            $this->loadSkeleton('_test/GeneralTest.skel', '_test/GeneralTest.php', $replacements);
288*36c0b2b4SAndreas Gohr        }
289*36c0b2b4SAndreas Gohr
290*36c0b2b4SAndreas Gohr        return 0;
291*36c0b2b4SAndreas Gohr    }
292*36c0b2b4SAndreas Gohr
293*36c0b2b4SAndreas Gohr    /**
294*36c0b2b4SAndreas Gohr     * Add configuration
295*36c0b2b4SAndreas Gohr     *
296*36c0b2b4SAndreas Gohr     * @return int
297*36c0b2b4SAndreas Gohr     */
298*36c0b2b4SAndreas Gohr    protected function cmdAddConf()
299*36c0b2b4SAndreas Gohr    {
300*36c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements();
301*36c0b2b4SAndreas Gohr        $this->loadSkeleton('conf/default.skel', 'conf/default.php', $replacements);
302*36c0b2b4SAndreas Gohr        $this->loadSkeleton('conf/metadata.skel', 'conf/metadata.php', $replacements);
303*36c0b2b4SAndreas Gohr        if (is_dir('lang')) {
304*36c0b2b4SAndreas Gohr            $this->loadSkeleton('lang/settings.skel', 'lang/en/settings.php', $replacements);
305*36c0b2b4SAndreas Gohr        }
306*36c0b2b4SAndreas Gohr
307*36c0b2b4SAndreas Gohr        return 0;
308*36c0b2b4SAndreas Gohr    }
309*36c0b2b4SAndreas Gohr
310*36c0b2b4SAndreas Gohr    /**
311*36c0b2b4SAndreas Gohr     * Add language
312*36c0b2b4SAndreas Gohr     *
313*36c0b2b4SAndreas Gohr     * @return int
314*36c0b2b4SAndreas Gohr     */
315*36c0b2b4SAndreas Gohr    protected function cmdAddLang()
316*36c0b2b4SAndreas Gohr    {
317*36c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements();
318*36c0b2b4SAndreas Gohr        $this->loadSkeleton('lang/lang.skel', 'lang/en/lang.php', $replacements);
319*36c0b2b4SAndreas Gohr        if (is_dir('conf')) {
320*36c0b2b4SAndreas Gohr            $this->loadSkeleton('lang/settings.skel', 'lang/en/settings.php', $replacements);
321*36c0b2b4SAndreas Gohr        }
322*36c0b2b4SAndreas Gohr
323*36c0b2b4SAndreas Gohr        return 0;
324*36c0b2b4SAndreas Gohr    }
325*36c0b2b4SAndreas Gohr
326*36c0b2b4SAndreas Gohr    /**
327*36c0b2b4SAndreas Gohr     * Add another component to the plugin
328*36c0b2b4SAndreas Gohr     *
329*36c0b2b4SAndreas Gohr     * @param string $type
330*36c0b2b4SAndreas Gohr     * @param string $component
331*36c0b2b4SAndreas Gohr     */
332*36c0b2b4SAndreas Gohr    protected function cmdAddComponent($type, $component = '')
333*36c0b2b4SAndreas Gohr    {
334*36c0b2b4SAndreas Gohr        $dir = fullpath(getcwd());
335*36c0b2b4SAndreas Gohr        list($plugin, $extension) = $this->getTypedNameFromDir($dir);
336*36c0b2b4SAndreas Gohr        if ($extension != 'plugin') throw  new CliException('Components can only be added to plugins');
337*36c0b2b4SAndreas Gohr        if (!in_array($type, PluginController::PLUGIN_TYPES)) {
338*36c0b2b4SAndreas Gohr            throw new CliException('Invalid type ' . $type);
339*36c0b2b4SAndreas Gohr        }
340*36c0b2b4SAndreas Gohr
341*36c0b2b4SAndreas Gohr        if ($component) {
342*36c0b2b4SAndreas Gohr            $path = $type . '/' . $component . '.php';
343*36c0b2b4SAndreas Gohr            $class = $type . '_plugin_' . $plugin . '_' . $component;
344*36c0b2b4SAndreas Gohr            $self = $plugin . '_' . $component;
345*36c0b2b4SAndreas Gohr        } else {
346*36c0b2b4SAndreas Gohr            $path = $type . '.php';
347*36c0b2b4SAndreas Gohr            $class = $type . '_plugin_' . $plugin;
348*36c0b2b4SAndreas Gohr            $self = $plugin;
349*36c0b2b4SAndreas Gohr        }
350*36c0b2b4SAndreas Gohr
351*36c0b2b4SAndreas Gohr        $replacements = $this->actionReplacements();
352*36c0b2b4SAndreas Gohr        $replacements['@@PLUGIN_COMPONENT_NAME@@'] = $class;
353*36c0b2b4SAndreas Gohr        $replacements['@@SYNTAX_COMPONENT_NAME@@'] = $self;
354*36c0b2b4SAndreas Gohr        $replacements = $this->prepareReplacements($replacements);
355*36c0b2b4SAndreas Gohr        $this->loadSkeleton($type . '.skel', $path, $replacements);
356*36c0b2b4SAndreas Gohr
357*36c0b2b4SAndreas Gohr        return 0;
358*36c0b2b4SAndreas Gohr    }
359*36c0b2b4SAndreas Gohr
360*36c0b2b4SAndreas Gohr    /**
361*36c0b2b4SAndreas Gohr     * Generate a list of deleted files from git
362*36c0b2b4SAndreas Gohr     *
363*36c0b2b4SAndreas Gohr     * @link https://stackoverflow.com/a/6018049/172068
364*36c0b2b4SAndreas Gohr     */
365*36c0b2b4SAndreas Gohr    protected function cmdDeletedFiles()
366*36c0b2b4SAndreas Gohr    {
367*36c0b2b4SAndreas Gohr        $cmd = 'git log --no-renames --pretty=format: --name-only --diff-filter=D';
368*36c0b2b4SAndreas Gohr        $output = [];
369*36c0b2b4SAndreas Gohr        $result = 0;
370*36c0b2b4SAndreas Gohr
371*36c0b2b4SAndreas Gohr        $last = exec($cmd, $output, $result);
372*36c0b2b4SAndreas Gohr        if ($last === false || $result !== 0) {
373*36c0b2b4SAndreas Gohr            throw new CliException('Running git failed');
374*36c0b2b4SAndreas Gohr        }
375*36c0b2b4SAndreas Gohr
376*36c0b2b4SAndreas Gohr        $output = array_map('trim', $output);
377*36c0b2b4SAndreas Gohr        $output = array_filter($output);
378*36c0b2b4SAndreas Gohr        $output = array_unique($output);
379*36c0b2b4SAndreas Gohr        $output = array_filter($output, function ($item) {
380*36c0b2b4SAndreas Gohr            return !file_exists($item);
381*36c0b2b4SAndreas Gohr        });
382*36c0b2b4SAndreas Gohr        sort($output);
383*36c0b2b4SAndreas Gohr
384*36c0b2b4SAndreas Gohr        if (!count($output)) {
385*36c0b2b4SAndreas Gohr            $this->info('No deleted files found');
386*36c0b2b4SAndreas Gohr            return 0;
387*36c0b2b4SAndreas Gohr        }
388*36c0b2b4SAndreas Gohr
389*36c0b2b4SAndreas Gohr        $content = "# This is a list of files that were present in previous releases\n" .
390*36c0b2b4SAndreas Gohr            "# but were removed later. They should not exist in your installation.\n" .
391*36c0b2b4SAndreas Gohr            join("\n", $output) . "\n";
392*36c0b2b4SAndreas Gohr
393*36c0b2b4SAndreas Gohr        file_put_contents('deleted.files', $content);
394*36c0b2b4SAndreas Gohr        $this->success('written deleted.files');
395*36c0b2b4SAndreas Gohr        return 0;
396*36c0b2b4SAndreas Gohr    }
397*36c0b2b4SAndreas Gohr}
398