Passed
Pull Request — master (#8)
by Jitendra
01:46
created

InitCommand::execute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 2
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
1
<?php
2
3
namespace Ahc\Phint\Console;
4
5
use Ahc\Phint\Generator\CollisionHandler;
6
use Ahc\Phint\Generator\TwigGenerator;
7
use Ahc\Phint\Util\Composer;
8
use Ahc\Phint\Util\Git;
9
use Ahc\Phint\Util\Inflector;
10
use Ahc\Phint\Util\Path;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
class InitCommand extends BaseCommand
17
{
18
    /** @var Git */
19
    protected $git;
20
21
    /** @var Composer */
22
    protected $composer;
23
24
    /**
25
     * Configure the command options.
26
     *
27
     * @return void
28
     */
29
    protected function configure()
30
    {
31
        $this
32
            ->setName('init')
33
            ->setDescription('Scaffold a bare new PHP project')
34
            ->addArgument('project', InputArgument::REQUIRED, 'The project name without slashes')
35
            ->addOption('path', null, InputOption::VALUE_NONE, 'The project path (Auto resolved)')
36
            ->addOption('force', 'f', InputOption::VALUE_NONE, 'Run even if the project exists')
37
            ->addOption('description', 'd', InputOption::VALUE_OPTIONAL, 'Project description')
38
            ->addOption('name', 'm', InputOption::VALUE_OPTIONAL, 'Vendor full name, defaults to git name')
39
            ->addOption('username', 'u', InputOption::VALUE_OPTIONAL, 'Vendor handle/username')
40
            ->addOption('email', 'e', InputOption::VALUE_OPTIONAL, 'Vendor email, defaults to git email')
41
            ->addOption('namespace', 's', InputOption::VALUE_OPTIONAL, 'Root namespace')
42
            ->addOption('year', 'y', InputOption::VALUE_OPTIONAL, 'License Year', date('Y'))
43
            ->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'Project type')
44
            ->addOption('using', 'z', InputOption::VALUE_OPTIONAL, 'Packagist name of reference project (eg: laravel/lumen)')
45
            ->addOption('keywords', 'l', InputOption::VALUE_OPTIONAL, 'Project Keywords')
46
            ->addOption('php', 'p', InputOption::VALUE_OPTIONAL, 'Minimum PHP version project needs')
47
            ->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'JSON filepath to read config from')
48
            ->setHelp(<<<'EOT'
49
The <info>init</info> command creates a new project with all basic files and
50
structures in the <project-name> directory. See some examples below:
51
52
<info>phint init</info> project-name <comment>--force --description "My awesome project" --name "Your Name" --email "[email protected]"</comment>
53
<info>phint init</info> project-name <comment>--using laravel/lumen --namespace Project/Api --type project</comment>
54
55
EOT
56
            );
57
    }
58
59
    /**
60
     * Execute the command.
61
     *
62
     * @param InputInterface  $input
63
     * @param OutputInterface $output
64
     *
65
     * @return void
66
     */
67
    protected function execute(InputInterface $input, OutputInterface $output)
68
    {
69
        $parameters = $this->input->getOptions() + $this->input->getArguments();
70
71
        if (null !== $using = $parameters['using']) {
72
            $this->output->writeln('Using <comment>' . $using . '</comment> to create project');
73
74
            $this->composer->createProject($parameters['path'], $using);
75
        }
76
77
        $this->output->writeln('<comment>Generating files ...</comment>');
78
79
        $this->generate($parameters['path'], $parameters);
80
81
        $this->output->writeln('Setting up <info>git</info>');
82
83
        $this->git->init()->addRemote($parameters['username'], $parameters['project']);
84
85
        $this->output->writeln('Setting up <info>composer</info>');
86
87
        $this->composer->install();
88
89
        $output->writeln('<comment>Done</comment>');
90
    }
91
92
    protected function prepareProjectPath()
93
    {
94
        $path = $this->input->getArgument('project');
95
96
        if (!(new Path)->isAbsolute($path)) {
97
            $path = \getcwd() . '/' . $path;
98
        }
99
100
        if (\file_exists($path)) {
101
            if (!$this->input->getOption('force')) {
102
                throw new \InvalidArgumentException('Something with the same name already exists!');
103
            }
104
105
            if (!$this->input->getOption('using')) {
106
                $this->output->writeln('<error>You have set force flag, existing files will be overwritten</error>');
107
            }
108
        } else {
109
            \mkdir(\rtrim($path, '/') . '/src', 0777, true);
110
        }
111
112
        return $path;
113
    }
114
115
    protected function interact(InputInterface $input, OutputInterface $output)
116
    {
117
        $this->input  = $input;
118
        $this->output = $output;
1 ignored issue
show
Documentation Bug introduced by
It seems like $output of type Symfony\Component\Console\Output\OutputInterface is incompatible with the declared type Ahc\Phint\Console\OutputInterface of property $output.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
119
120
        $project = $input->getArgument('project');
121
122
        if (empty($project) || !preg_match('/[a-z0-9_-]/i', $project)) {
123
            throw new \InvalidArgumentException('Project argument is required and should only contain [a-z0-9_-]');
124
        }
125
126
        $this->loadConfig($this->input->getOption('config'));
127
128
        $this->input->setOption('path', $path = $this->prepareProjectPath());
129
130
        $this->git      = (new Git)->withOutput($this->output)->withWorkDir($path);
131
        $this->composer = (new Composer)->withOutput($this->output)->withWorkDir($path);
132
133
        $this->output->writeln('<info>Phint Setup</info>');
134
        $this->output->writeln('<comment>Just press ENTER if you want to use the [default] or skip<comment>');
135
        $this->output->writeln('');
136
137
        $this->input->setOption('type', $this->input->getOption('type') ?: $this->prompt(
138
            'Project type (project/library)',
139
            'library',
140
            ['project', 'library', 'composer-plugin']
141
        ));
142
143
        $this->input->setOption('name', $this->input->getOption('name') ?: $this->prompt(
144
            'Vendor full name',
145
            $this->git->getConfig('user.name')
146
        ));
147
148
        $this->input->setOption('email', $this->input->getOption('email') ?: $this->prompt(
149
            'Vendor email',
150
             $this->git->getConfig('user.email')
151
        ));
152
153
        $this->input->setOption('description', $this->input->getOption('description') ?: $this->prompt(
154
            'Brief project description'
155
        ));
156
157
        $this->input->setOption('username', $username = $this->input->getOption('username') ?: $this->prompt(
158
            'Vendor handle (often github username)',
159
            getenv('VENDOR_USERNAME') ?: null
160
        ));
161
162
        $inflector = new Inflector;
163
164
        $namespace = $this->input->getOption('namespace') ?: $this->prompt(
165
            'Project root namespace (forward slashes are auto fixed)',
166
            (getenv('VENDOR_NAMESPACE') ?: $inflector->stuldyCase($username))
167
                . '/' . $inflector->stuldyCase($project)
168
        );
169
170
        $this->input->setOption('namespace', \str_replace('/', '\\\\', $namespace));
171
172
        $keywords = $this->input->getOption('keywords') ?: $this->prompt(
173
            'Project keywords (CSV)',
174
            "php, $project"
175
        );
176
177
        $this->input->setOption('keywords', array_map('trim', explode(',', $keywords)));
1 ignored issue
show
Bug introduced by
array_map('trim', explode(',', $keywords)) of type array is incompatible with the type string|boolean expected by parameter $value of Symfony\Component\Consol...tInterface::setOption(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

177
        $this->input->setOption('keywords', /** @scrutinizer ignore-type */ array_map('trim', explode(',', $keywords)));
Loading history...
178
179
        $this->input->setOption('php', floatval($this->input->getOption('php') ?: $this->prompt(
180
            'Minimum PHP version project needs',
181
            PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
182
            ['5.4', '5.5', '5.6', '7.0', '7.1']
183
        )));
184
    }
185
186
    protected function generate($projectPath, array $parameters)
187
    {
188
        $templatePath = __DIR__ . '/../../resources';
189
        $cachePath    = __DIR__ . '/../../.cache';
190
191
        $generator = new TwigGenerator($templatePath, $cachePath);
192
193
        $generator->generate($projectPath, $parameters, new CollisionHandler);
194
    }
195
196
    protected function loadConfig($path = null)
197
    {
198
        if (empty($path)) {
199
            return;
200
        }
201
202
        $pathUtil = new Path;
203
204
        if (!$pathUtil->isAbsolute($path)) {
205
            $path = getcwd() . '/' . $path;
206
        }
207
208
        if (!is_file($path)) {
209
            $this->output->writeln('<error>Invalid path specified for config</error>');
210
211
            return;
212
        }
213
214
        $config = (new Path)->readAsJson($path);
215
216
        if (empty($config)) {
217
            return;
218
        }
219
220
        unset($config['path']);
221
222
        foreach ($config as $key => $value) {
223
            if ($this->input->hasOption($key)) {
224
                $this->input->setOption($key, $value);
225
            }
226
227
            if ($key === 'vendor_namespace') {
228
                putenv('VENDOR_NAMESPACE='.$value);
229
            }
230
        }
231
    }
232
}
233