TwigGenerator   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Importance

Changes 20
Bugs 8 Features 4
Metric Value
eloc 97
c 20
b 8
f 4
dl 0
loc 216
rs 9.92
wmc 31

9 Methods

Rating   Name   Duplication   Size   Complexity  
A initTwig() 0 35 2
A generate() 0 28 5
A shouldGenerate() 0 17 4
A __construct() 0 6 1
A generateDocs() 0 21 4
A generateTests() 0 19 4
A mayOverride() 0 16 4
A doGenerate() 0 15 4
A getRelativeTarget() 0 10 3
1
<?php
2
3
/*
4
 * This file is part of the PHINT package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Phint\Generator;
13
14
use Ahc\Phint\Util\Inflector;
15
use Ahc\Phint\Util\Path;
16
17
class TwigGenerator implements GeneratorInterface
18
{
19
    /** @var \Twig_Environment */
20
    protected $twig;
21
22
    /** @var Path */
23
    protected $pathUtil;
24
25
    /** @var Inflector */
26
    protected $inflector;
27
28
    /** @var array */
29
    protected $templatePaths;
30
31
    /** @var string */
32
    protected $cachePath;
33
34
    /** @var array Templates required for type 'project' only */
35
    protected $projectTemplates = [
36
        '.env.example' => true,
37
        'package.json' => true,
38
    ];
39
40
    /** @var array Templates only loaded by some specific commands */
41
    protected $commandTemplates = [
42
        'test' => true,
43
        'docs' => true,
44
    ];
45
46
    public function __construct(array $templatePaths, string $cachePath)
47
    {
48
        $this->templatePaths = $templatePaths;
49
        $this->cachePath     = $cachePath;
50
        $this->pathUtil      = new Path;
51
        $this->inflector     = new Inflector;
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function generate(string $targetPath, array $parameters, CollisionHandlerInterface $handler = null): int
58
    {
59
        $count = 0;
60
61
        if (!$this->twig) {
62
            $this->initTwig();
63
        }
64
65
        $processed = [];
66
        $templates = $this->pathUtil->findFiles($this->templatePaths, '.twig', false);
67
68
        foreach ($templates as $template) {
69
            $relativePath = $this->pathUtil->getRelativePath($template, ...$this->templatePaths);
70
71
            if ($processed[$relativePath] ?? null) {
72
                continue;
73
            }
74
75
            $processed[$relativePath] = true;
76
77
            if ($this->shouldGenerate($template, $parameters)) {
78
                $count += (int) $this->doGenerate($relativePath, $targetPath, $parameters, $handler);
79
            }
80
        }
81
82
        $this->pathUtil->createBinaries($parameters['bin'] ?? [], $parameters['path'] ?? '');
83
84
        return $count;
85
    }
86
87
    public function generateTests(array $testMetadata, array $parameters): int
88
    {
89
        if (!$this->twig) {
90
            $this->initTwig();
91
        }
92
93
        $count = 0;
94
95
        foreach ($testMetadata as $metadata) {
96
            // Skip existing
97
            if (\is_file($targetFile = $metadata['testPath'])) {
98
                continue;
99
            }
100
101
            $content = $this->twig->render('tests/test.twig', $metadata + $parameters);
102
            $count += (int) $this->pathUtil->writeFile($targetFile, $content);
103
        }
104
105
        return $count;
106
    }
107
108
    public function generateDocs(array $docsMetadata, array $parameters): int
109
    {
110
        if (!$this->twig) {
111
            $this->initTwig();
112
        }
113
114
        $targetFile = $parameters['output'] ?? 'README.md';
115
        $docContent = $this->twig->render('docs/docs.twig', \compact('docsMetadata') + $parameters);
116
        $docContent = "<!-- DOCS START -->\n$docContent\n<!-- DOCS END -->";
117
118
        if (null === $oldContent = \trim($this->pathUtil->read($targetFile))) {
0 ignored issues
show
Bug introduced by
It seems like $this->pathUtil->read($targetFile) can also be of type null; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

118
        if (null === $oldContent = \trim(/** @scrutinizer ignore-type */ $this->pathUtil->read($targetFile))) {
Loading history...
119
            return (int) $this->pathUtil->writeFile($targetFile, $docContent);
120
        }
121
122
        if (\strpos($oldContent, '<!-- DOCS START -->') !== false) {
123
            $docContent = \preg_replace('~<!-- DOCS START -->.*?<!-- DOCS END -->~s', $docContent, $oldContent);
124
125
            return (int) $this->pathUtil->writeFile($targetFile, $docContent);
126
        }
127
128
        return (int) $this->pathUtil->writeFile($targetFile, \trim("$oldContent\n\n$docContent"));
129
    }
130
131
    protected function initTwig()
132
    {
133
        $options = [
134
            'auto_reload' => true,
135
            'cache'       => false,
136
        ];
137
138
        if ($this->cachePath) {
139
            $this->pathUtil->ensureDir($this->cachePath);
140
            $options['cache'] = $this->cachePath;
141
        }
142
143
        $this->twig = new \Twig_Environment(
0 ignored issues
show
Deprecated Code introduced by
The class Twig_Environment has been deprecated: since Twig 2.7, use "Twig\Environment" instead ( Ignorable by Annotation )

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

143
        $this->twig = /** @scrutinizer ignore-deprecated */ new \Twig_Environment(
Loading history...
144
            new \Twig_Loader_Filesystem($this->templatePaths),
0 ignored issues
show
Deprecated Code introduced by
The class Twig_Loader_Filesystem has been deprecated: since Twig 2.7, use "Twig\Loader\FilesystemLoader" instead ( Ignorable by Annotation )

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

144
            /** @scrutinizer ignore-deprecated */ new \Twig_Loader_Filesystem($this->templatePaths),
Loading history...
145
            $options
146
        );
147
148
        $this->twig->addFilter(new \Twig_SimpleFilter('snake', function ($x) {
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFilter has been deprecated: since Twig 2.7, use "Twig\TwigFilter" instead ( Ignorable by Annotation )

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

148
        $this->twig->addFilter(/** @scrutinizer ignore-deprecated */ new \Twig_SimpleFilter('snake', function ($x) {
Loading history...
149
            return $this->inflector->snakeCase($x);
150
        }));
151
152
        $this->twig->addFilter(new \Twig_SimpleFilter('lcfirst', function ($x) {
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFilter has been deprecated: since Twig 2.7, use "Twig\TwigFilter" instead ( Ignorable by Annotation )

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

152
        $this->twig->addFilter(/** @scrutinizer ignore-deprecated */ new \Twig_SimpleFilter('lcfirst', function ($x) {
Loading history...
153
            return \lcfirst($x);
154
        }));
155
156
        $this->twig->addFilter(new \Twig_SimpleFilter('ucfirst', function ($x) {
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFilter has been deprecated: since Twig 2.7, use "Twig\TwigFilter" instead ( Ignorable by Annotation )

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

156
        $this->twig->addFilter(/** @scrutinizer ignore-deprecated */ new \Twig_SimpleFilter('ucfirst', function ($x) {
Loading history...
157
            return \ucfirst($x);
158
        }));
159
160
        $this->twig->addFunction(new \Twig_Function('gmdate', function ($f = null) {
0 ignored issues
show
Deprecated Code introduced by
The class Twig_Function has been deprecated: since Twig 2.7, use "Twig\TwigFunction" instead ( Ignorable by Annotation )

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

160
        $this->twig->addFunction(/** @scrutinizer ignore-deprecated */ new \Twig_Function('gmdate', function ($f = null) {
Loading history...
161
            return \gmdate($f ?? 'Y-m-d H:i:s');
162
        }));
163
164
        $this->twig->addFunction(new \Twig_Function('call', function ($fn) {
0 ignored issues
show
Deprecated Code introduced by
The class Twig_Function has been deprecated: since Twig 2.7, use "Twig\TwigFunction" instead ( Ignorable by Annotation )

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

164
        $this->twig->addFunction(/** @scrutinizer ignore-deprecated */ new \Twig_Function('call', function ($fn) {
Loading history...
165
            return $fn(...\array_slice(\func_get_args(), 1));
166
        }));
167
    }
168
169
    protected function doGenerate(string $relativePath, string $targetPath, array $parameters, CollisionHandlerInterface $handler = null): bool
170
    {
171
        $targetFile   = $this->pathUtil->join($targetPath, $this->getRelativeTarget($parameters, $relativePath));
172
        $fileExists   = \is_file($targetFile);
173
        $content      = $this->twig->render($relativePath, $parameters);
174
175
        if ($handler && $fileExists) {
176
            return $handler->handle($targetFile, $content, $parameters);
177
        }
178
179
        if ($this->mayOverride($fileExists, $parameters)) {
180
            return $this->pathUtil->writeFile($targetFile, $content);
181
        }
182
183
        return false;
184
    }
185
186
    protected function getRelativeTarget(array $parameters, string $relativePath): string
187
    {
188
        $fileName   = \basename($relativePath, '.twig');
189
        $targetFile = \str_replace('.twig', '', $relativePath);
190
191
        if (!empty($parameters['ghTemplate']) && \in_array($fileName, ['ISSUE_TEMPLATE.md', 'PULL_REQUEST_TEMPLATE.md'])) {
192
            $targetFile = '.github/' . $fileName;
193
        }
194
195
        return $targetFile;
196
    }
197
198
    protected function shouldGenerate(string $template, array $parameters)
199
    {
200
        $name = \basename($template, '.twig');
201
202
        if (isset($this->projectTemplates[$name])) {
203
            return $parameters['type'] === 'project';
204
        }
205
206
        if (isset($this->commandTemplates[$name])) {
207
            return false;
208
        }
209
210
        if (empty($parameters['travis'])) {
211
            return $name !== '.travis.yml';
212
        }
213
214
        return true;
215
    }
216
217
    protected function mayOverride(bool $fileExists, array $parameters)
218
    {
219
        if (!$fileExists) {
220
            return true;
221
        }
222
223
        // If using reference package then we dont overwrite!
224
        if (!empty($parameters['using'])) {
225
            return false;
226
        }
227
228
        if (!empty($parameters['sync'])) {
229
            return false;
230
        }
231
232
        return true;
233
    }
234
}
235