|
1
|
|
|
<?php declare(strict_types=1); |
|
2
|
|
|
|
|
3
|
|
|
namespace EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator; |
|
4
|
|
|
|
|
5
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\Entity\Repositories\AbstractEntityRepositoryFactory; |
|
6
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\Entity\Savers\AbstractSaver; |
|
7
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException; |
|
8
|
|
|
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper; |
|
9
|
|
|
use gossi\codegen\model\PhpClass; |
|
10
|
|
|
|
|
11
|
|
|
class EntityGenerator extends AbstractGenerator |
|
12
|
|
|
{ |
|
13
|
|
|
/** |
|
14
|
|
|
* Flag to determine if a UUID primary key should be used for this entity. |
|
15
|
|
|
* |
|
16
|
|
|
* @var bool |
|
17
|
|
|
*/ |
|
18
|
|
|
protected $useUuidPrimaryKey = false; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* @param string $entityFullyQualifiedName |
|
22
|
|
|
* |
|
23
|
|
|
* @return string - absolute path to created file |
|
24
|
|
|
* @throws DoctrineStaticMetaException |
|
25
|
|
|
* @SuppressWarnings(PHPMD.StaticAccess) |
|
26
|
|
|
*/ |
|
27
|
|
|
public function generateEntity( |
|
28
|
|
|
string $entityFullyQualifiedName |
|
29
|
|
|
): string { |
|
30
|
|
|
try { |
|
31
|
|
|
if (false === strpos($entityFullyQualifiedName, '\\'.AbstractGenerator::ENTITIES_FOLDER_NAME.'\\')) { |
|
32
|
|
|
throw new \RuntimeException( |
|
33
|
|
|
'Fully qualified name ['.$entityFullyQualifiedName |
|
34
|
|
|
.'] does not include the Entities folder name [' |
|
35
|
|
|
.AbstractGenerator::ENTITIES_FOLDER_NAME |
|
36
|
|
|
.']. Please ensure you pass in the full namespace qualified entity name' |
|
37
|
|
|
); |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
|
|
$shortName = MappingHelper::getShortNameForFqn($entityFullyQualifiedName); |
|
41
|
|
|
$plural = MappingHelper::getPluralForFqn($entityFullyQualifiedName); |
|
42
|
|
|
$singular = MappingHelper::getSingularForFqn($entityFullyQualifiedName); |
|
43
|
|
|
|
|
44
|
|
|
if (\strtolower($shortName) === $plural) { |
|
45
|
|
|
throw new \RuntimeException( |
|
46
|
|
|
'Plural entity name used ['.$plural.']. ' |
|
47
|
|
|
. 'Only singular entity names are allowed. ' |
|
48
|
|
|
. 'Please update this to ['.$singular.']' |
|
49
|
|
|
); |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
$this->createEntityTest($entityFullyQualifiedName); |
|
53
|
|
|
$this->createEntityRepository($entityFullyQualifiedName); |
|
54
|
|
|
$this->createEntityRepositoryFactory($entityFullyQualifiedName); |
|
55
|
|
|
$this->createEntitySaver($entityFullyQualifiedName); |
|
56
|
|
|
|
|
57
|
|
|
$this->createInterface($entityFullyQualifiedName); |
|
58
|
|
|
return $this->createEntity($entityFullyQualifiedName); |
|
59
|
|
|
} catch (\Exception $e) { |
|
60
|
|
|
throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e); |
|
61
|
|
|
} |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
protected function createInterface(string $entityFullyQualifiedName) : void |
|
65
|
|
|
{ |
|
66
|
|
|
$entityInterfaceFqn = \str_replace( |
|
67
|
|
|
'\\'.AbstractGenerator::ENTITIES_FOLDER_NAME.'\\', |
|
68
|
|
|
'\\'.AbstractGenerator::ENTITY_INTERFACE_NAMESPACE.'\\', |
|
69
|
|
|
$entityFullyQualifiedName |
|
70
|
|
|
).'Interface'; |
|
71
|
|
|
|
|
72
|
|
|
list($className, $namespace, $subDirectories) = $this->parseFullyQualifiedName( |
|
73
|
|
|
$entityInterfaceFqn, |
|
74
|
|
|
$this->srcSubFolderName |
|
75
|
|
|
); |
|
76
|
|
|
|
|
77
|
|
|
$filePath = $this->copyTemplateAndGetPath( |
|
78
|
|
|
self::ENTITY_INTERFACE_TEMPLATE_PATH, |
|
79
|
|
|
$className, |
|
80
|
|
|
$subDirectories |
|
81
|
|
|
); |
|
82
|
|
|
|
|
83
|
|
|
$this->replaceName($className, $filePath, self::FIND_ENTITY_NAME.'Interface'); |
|
84
|
|
|
$this->replaceEntityInterfaceNamespace($namespace, $filePath); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
protected function createEntity( |
|
88
|
|
|
string $entityFullyQualifiedName |
|
89
|
|
|
): string { |
|
90
|
|
|
list($filePath, $className, $namespace) = $this->parseAndCreate( |
|
91
|
|
|
$entityFullyQualifiedName, |
|
92
|
|
|
$this->srcSubFolderName, |
|
93
|
|
|
self::ENTITY_TEMPLATE_PATH |
|
94
|
|
|
); |
|
95
|
|
|
$this->replaceName($className, $filePath, static::FIND_ENTITY_NAME); |
|
96
|
|
|
$this->replaceEntitiesNamespace($namespace, $filePath); |
|
97
|
|
|
$this->replaceEntityRepositoriesNamespace($namespace, $filePath); |
|
98
|
|
|
|
|
99
|
|
|
if ($this->getUseUuidPrimaryKey()) { |
|
100
|
|
|
$this->findReplace( |
|
101
|
|
|
'IdFieldTrait', |
|
102
|
|
|
'UuidFieldTrait', |
|
103
|
|
|
$filePath |
|
104
|
|
|
); |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
$interfaceNamespace = \str_replace( |
|
108
|
|
|
'\\'.AbstractGenerator::ENTITIES_FOLDER_NAME, |
|
109
|
|
|
'\\'.AbstractGenerator::ENTITY_INTERFACE_NAMESPACE, |
|
110
|
|
|
$namespace |
|
111
|
|
|
); |
|
112
|
|
|
|
|
113
|
|
|
$this->replaceEntityInterfaceNamespace($interfaceNamespace, $filePath); |
|
114
|
|
|
|
|
115
|
|
|
return $filePath; |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
/** |
|
119
|
|
|
* @param string $entityFullyQualifiedName |
|
120
|
|
|
* |
|
121
|
|
|
* @throws DoctrineStaticMetaException |
|
122
|
|
|
*/ |
|
123
|
|
|
protected function createEntityTest(string $entityFullyQualifiedName): void |
|
124
|
|
|
{ |
|
125
|
|
|
try { |
|
126
|
|
|
$abstractTestPath = $this->pathToProjectRoot.'/' |
|
127
|
|
|
.$this->testSubFolderName |
|
128
|
|
|
.'/'.AbstractGenerator::ENTITIES_FOLDER_NAME |
|
129
|
|
|
.'/AbstractEntityTest.php'; |
|
130
|
|
View Code Duplication |
if (!$this->getFilesystem()->exists($abstractTestPath)) { |
|
|
|
|
|
|
131
|
|
|
$this->getFilesystem()->copy(self::ABSTRACT_ENTITY_TEST_TEMPLATE_PATH, $abstractTestPath); |
|
132
|
|
|
$this->fileCreationTransaction::setPathCreated($abstractTestPath); |
|
133
|
|
|
$this->findReplace( |
|
134
|
|
|
self::FIND_PROJECT_NAMESPACE, |
|
135
|
|
|
rtrim($this->projectRootNamespace, '\\'), |
|
136
|
|
|
$abstractTestPath |
|
137
|
|
|
); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
$phpunitBootstrapPath = $this->pathToProjectRoot.'/' |
|
141
|
|
|
.$this->testSubFolderName.'/bootstrap.php'; |
|
142
|
|
|
if (!$this->getFilesystem()->exists($phpunitBootstrapPath)) { |
|
143
|
|
|
$this->getFilesystem()->copy(self::PHPUNIT_BOOTSTRAP_TEMPLATE_PATH, $phpunitBootstrapPath); |
|
144
|
|
|
$this->fileCreationTransaction::setPathCreated($phpunitBootstrapPath); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
list($filePath, $className, $namespace) = $this->parseAndCreate( |
|
148
|
|
|
$entityFullyQualifiedName.'Test', |
|
149
|
|
|
$this->testSubFolderName, |
|
150
|
|
|
self::ENTITY_TEST_TEMPLATE_PATH |
|
151
|
|
|
); |
|
152
|
|
|
$this->findReplace( |
|
153
|
|
|
self::FIND_ENTITIES_NAMESPACE, |
|
154
|
|
|
$this->namespaceHelper->tidy($namespace), |
|
155
|
|
|
$filePath |
|
156
|
|
|
); |
|
157
|
|
|
|
|
158
|
|
|
$this->replaceName($className, $filePath, self::FIND_ENTITY_NAME.'Test'); |
|
159
|
|
|
$this->replaceProjectNamespace($this->projectRootNamespace, $filePath); |
|
160
|
|
|
$this->replaceEntityRepositoriesNamespace($namespace, $filePath); |
|
161
|
|
|
$this->findReplace( |
|
162
|
|
|
'use FQNFor\AbstractEntityTest;', |
|
163
|
|
|
'use '.$this->namespaceHelper->tidy( |
|
164
|
|
|
$this->projectRootNamespace |
|
165
|
|
|
.'\\'.AbstractGenerator::ENTITIES_FOLDER_NAME |
|
166
|
|
|
.'\\AbstractEntityTest;' |
|
167
|
|
|
), |
|
168
|
|
|
$filePath |
|
169
|
|
|
); |
|
170
|
|
|
} catch (\Exception $e) { |
|
171
|
|
|
throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e); |
|
172
|
|
|
} |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* @param string $entityFullyQualifiedName |
|
177
|
|
|
* |
|
178
|
|
|
* @throws DoctrineStaticMetaException |
|
179
|
|
|
*/ |
|
180
|
|
|
protected function createEntityRepository(string $entityFullyQualifiedName): void |
|
181
|
|
|
{ |
|
182
|
|
|
try { |
|
183
|
|
|
$abstractRepositoryPath = $this->pathToProjectRoot |
|
184
|
|
|
.'/'.$this->srcSubFolderName |
|
185
|
|
|
.'/'.AbstractGenerator::ENTITY_REPOSITORIES_FOLDER_NAME |
|
186
|
|
|
.'/AbstractEntityRepository.php'; |
|
187
|
|
View Code Duplication |
if (!$this->getFilesystem()->exists($abstractRepositoryPath)) { |
|
|
|
|
|
|
188
|
|
|
$this->getFilesystem()->copy( |
|
189
|
|
|
self::ABSTRACT_ENTITY_REPOSITORY_TEMPLATE_PATH, |
|
190
|
|
|
$abstractRepositoryPath |
|
191
|
|
|
); |
|
192
|
|
|
$this->fileCreationTransaction::setPathCreated($abstractRepositoryPath); |
|
193
|
|
|
$this->replaceEntityRepositoriesNamespace( |
|
194
|
|
|
$this->projectRootNamespace.'\\' |
|
195
|
|
|
.AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE, |
|
196
|
|
|
$abstractRepositoryPath |
|
197
|
|
|
); |
|
198
|
|
|
} |
|
199
|
|
|
$entityRepositoryFqn = \str_replace( |
|
200
|
|
|
'\\'.AbstractGenerator::ENTITIES_FOLDER_NAME.'\\', |
|
201
|
|
|
'\\'.AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE.'\\', |
|
202
|
|
|
$entityFullyQualifiedName |
|
203
|
|
|
).'Repository'; |
|
204
|
|
|
|
|
205
|
|
|
list($filePath, $className, $namespace) = $this->parseAndCreate( |
|
206
|
|
|
$entityRepositoryFqn, |
|
207
|
|
|
$this->srcSubFolderName, |
|
208
|
|
|
self::REPOSITORIES_TEMPLATE_PATH |
|
209
|
|
|
); |
|
210
|
|
|
$this->findReplace( |
|
211
|
|
|
self::FIND_ENTITY_REPOSITORIES_NAMESPACE, |
|
212
|
|
|
$this->namespaceHelper->tidy($namespace), |
|
213
|
|
|
$filePath |
|
214
|
|
|
); |
|
215
|
|
|
|
|
216
|
|
|
$this->replaceName($className, $filePath, self::FIND_ENTITY_NAME.'Repository'); |
|
217
|
|
|
$this->replaceProjectNamespace($this->projectRootNamespace, $filePath); |
|
218
|
|
|
$this->replaceEntityRepositoriesNamespace($namespace, $filePath); |
|
219
|
|
|
$this->findReplace( |
|
220
|
|
|
'use FQNFor\AbstractEntityRepository;', |
|
221
|
|
|
'use '.$this->namespaceHelper->tidy( |
|
222
|
|
|
$this->projectRootNamespace |
|
223
|
|
|
.'\\'.AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE |
|
224
|
|
|
.'\\AbstractEntityRepository;' |
|
225
|
|
|
), |
|
226
|
|
|
$filePath |
|
227
|
|
|
); |
|
228
|
|
|
} catch (\Exception $e) { |
|
229
|
|
|
throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e); |
|
230
|
|
|
} |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
/** |
|
234
|
|
|
* Create an entity repository factory |
|
235
|
|
|
* |
|
236
|
|
|
* @param string $entityFqn |
|
237
|
|
|
* @throws DoctrineStaticMetaException |
|
238
|
|
|
*/ |
|
239
|
|
View Code Duplication |
protected function createEntityRepositoryFactory(string $entityFqn) |
|
|
|
|
|
|
240
|
|
|
{ |
|
241
|
|
|
$repositoryFactoryFqn = \str_replace( |
|
242
|
|
|
'\\'.AbstractGenerator::ENTITIES_FOLDER_NAME.'\\', |
|
243
|
|
|
AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE.'\\', |
|
244
|
|
|
$entityFqn |
|
245
|
|
|
).'RepositoryFactory'; |
|
246
|
|
|
|
|
247
|
|
|
$abstractRepositoryFactoryFqn = $this->projectRootNamespace |
|
248
|
|
|
.AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE |
|
249
|
|
|
.'\\AbstractEntityRepositoryFactory'; |
|
250
|
|
|
|
|
251
|
|
|
$repositoryFactory = new PhpClass(); |
|
252
|
|
|
$repositoryFactory |
|
253
|
|
|
->setQualifiedName($repositoryFactoryFqn) |
|
254
|
|
|
->setParentClassName('\\'.$abstractRepositoryFactoryFqn); |
|
255
|
|
|
|
|
256
|
|
|
list($className, , $subDirectories) = $this->parseFullyQualifiedName( |
|
257
|
|
|
$repositoryFactoryFqn, |
|
258
|
|
|
$this->srcSubFolderName |
|
259
|
|
|
); |
|
260
|
|
|
|
|
261
|
|
|
$filePath = $this->createSubDirectoriesAndGetPath($subDirectories); |
|
262
|
|
|
|
|
263
|
|
|
$this->codeHelper->generate($repositoryFactory, $filePath.'/'.$className.'.php'); |
|
264
|
|
|
|
|
265
|
|
|
$this->createAbstractEntityRepositoryFactory(); |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
/** |
|
269
|
|
|
* Create the abstract entity repository factory if it doesn't currently exist |
|
270
|
|
|
*/ |
|
271
|
|
View Code Duplication |
protected function createAbstractEntityRepositoryFactory() |
|
|
|
|
|
|
272
|
|
|
{ |
|
273
|
|
|
$abstractRepositoryFactoryPath = $this->pathToProjectRoot |
|
274
|
|
|
.'/'.$this->srcSubFolderName |
|
275
|
|
|
.'/'.AbstractGenerator::ENTITY_REPOSITORIES_FOLDER_NAME |
|
276
|
|
|
.'/AbstractEntityRepositoryFactory.php'; |
|
277
|
|
|
|
|
278
|
|
|
if ($this->getFilesystem()->exists($abstractRepositoryFactoryPath)) { |
|
279
|
|
|
return; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
$abstractFactoryFqn = $this->projectRootNamespace |
|
283
|
|
|
.AbstractGenerator::ENTITY_REPOSITORIES_NAMESPACE |
|
284
|
|
|
.'\\AbstractEntityRepositoryFactory'; |
|
285
|
|
|
|
|
286
|
|
|
$abstractFactory = new PhpClass(); |
|
287
|
|
|
$abstractFactory |
|
288
|
|
|
->setQualifiedName($abstractFactoryFqn) |
|
289
|
|
|
->setParentClassName('\\'.AbstractEntityRepositoryFactory::class); |
|
290
|
|
|
|
|
291
|
|
|
$this->codeHelper->generate($abstractFactory, $abstractRepositoryFactoryPath); |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
/** |
|
295
|
|
|
* Create the abstract entity saver if it doesn't currently exist |
|
296
|
|
|
*/ |
|
297
|
|
View Code Duplication |
protected function createAbstractEntitySaver() |
|
|
|
|
|
|
298
|
|
|
{ |
|
299
|
|
|
$abstractEntitySaverPath = $this->pathToProjectRoot |
|
300
|
|
|
.'/'.$this->srcSubFolderName |
|
301
|
|
|
.'/'.AbstractGenerator::ENTITY_SAVERS_FOLDER_NAME |
|
302
|
|
|
.'/AbstractSaver.php'; |
|
303
|
|
|
|
|
304
|
|
|
if ($this->getFilesystem()->exists($abstractEntitySaverPath)) { |
|
305
|
|
|
return; |
|
306
|
|
|
} |
|
307
|
|
|
|
|
308
|
|
|
$abstractEntitySaverFqn = $this->projectRootNamespace |
|
309
|
|
|
.AbstractGenerator::ENTITY_SAVERS_NAMESPACE |
|
310
|
|
|
.'\\AbstractSaver'; |
|
311
|
|
|
|
|
312
|
|
|
$abstractEntitySaver = new PhpClass(); |
|
313
|
|
|
$abstractEntitySaver |
|
314
|
|
|
->setQualifiedName($abstractEntitySaverFqn) |
|
315
|
|
|
->setParentClassName('\\'.AbstractSaver::class); |
|
316
|
|
|
|
|
317
|
|
|
$this->codeHelper->generate($abstractEntitySaver, $abstractEntitySaverPath); |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
/** |
|
321
|
|
|
* Create an entity saver |
|
322
|
|
|
* |
|
323
|
|
|
* @param string $entityFqn |
|
324
|
|
|
* @throws DoctrineStaticMetaException |
|
325
|
|
|
*/ |
|
326
|
|
View Code Duplication |
protected function createEntitySaver(string $entityFqn) |
|
|
|
|
|
|
327
|
|
|
{ |
|
328
|
|
|
$entitySaverFqn = \str_replace( |
|
329
|
|
|
'\\'.AbstractGenerator::ENTITIES_FOLDER_NAME.'\\', |
|
330
|
|
|
AbstractGenerator::ENTITY_SAVERS_NAMESPACE.'\\', |
|
331
|
|
|
$entityFqn |
|
332
|
|
|
).'Saver'; |
|
333
|
|
|
|
|
334
|
|
|
$abstractEntitySaverFqn = $this->projectRootNamespace |
|
335
|
|
|
.AbstractGenerator::ENTITY_SAVERS_NAMESPACE |
|
336
|
|
|
.'\\AbstractSaver'; |
|
337
|
|
|
|
|
338
|
|
|
$entitySaver = new PhpClass(); |
|
339
|
|
|
$entitySaver |
|
340
|
|
|
->setQualifiedName($entitySaverFqn) |
|
341
|
|
|
->setParentClassName('\\'.$abstractEntitySaverFqn); |
|
342
|
|
|
|
|
343
|
|
|
list($className, , $subDirectories) = $this->parseFullyQualifiedName( |
|
344
|
|
|
$entitySaverFqn, |
|
345
|
|
|
$this->srcSubFolderName |
|
346
|
|
|
); |
|
347
|
|
|
|
|
348
|
|
|
$filePath = $this->createSubDirectoriesAndGetPath($subDirectories); |
|
349
|
|
|
|
|
350
|
|
|
$this->codeHelper->generate($entitySaver, $filePath.'/'.$className.'.php'); |
|
351
|
|
|
|
|
352
|
|
|
$this->createAbstractEntitySaver(); |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
/** |
|
356
|
|
|
* @throws DoctrineStaticMetaException |
|
357
|
|
|
*/ |
|
358
|
|
|
protected function parseAndCreate( |
|
359
|
|
|
string $fullyQualifiedName, |
|
360
|
|
|
string $subDir, |
|
361
|
|
|
string $templatePath |
|
362
|
|
|
): array { |
|
363
|
|
|
try { |
|
364
|
|
|
list($className, $namespace, $subDirectories) = $this->parseFullyQualifiedName( |
|
365
|
|
|
$fullyQualifiedName, |
|
366
|
|
|
$subDir |
|
367
|
|
|
); |
|
368
|
|
|
$filePath = $this->copyTemplateAndGetPath( |
|
369
|
|
|
$templatePath, |
|
370
|
|
|
$className, |
|
371
|
|
|
$subDirectories |
|
372
|
|
|
); |
|
373
|
|
|
|
|
374
|
|
|
return [$filePath, $className, $this->namespaceHelper->tidy($namespace)]; |
|
375
|
|
|
} catch (\Exception $e) { |
|
376
|
|
|
throw new DoctrineStaticMetaException('Exception in '.__METHOD__.': '.$e->getMessage(), $e->getCode(), $e); |
|
377
|
|
|
} |
|
378
|
|
|
} |
|
379
|
|
|
|
|
380
|
|
|
public function setUseUuidPrimaryKey(bool $useUuidPrimaryKey) |
|
381
|
|
|
{ |
|
382
|
|
|
$this->useUuidPrimaryKey = $useUuidPrimaryKey; |
|
383
|
|
|
|
|
384
|
|
|
return $this; |
|
385
|
|
|
} |
|
386
|
|
|
|
|
387
|
|
|
public function getUseUuidPrimaryKey() |
|
388
|
|
|
{ |
|
389
|
|
|
return $this->useUuidPrimaryKey; |
|
390
|
|
|
} |
|
391
|
|
|
} |
|
392
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.