• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 11834579899

14 Nov 2024 09:41AM UTC coverage: 82.65% (+0.05%) from 82.602%
11834579899

Pull #726

github

web-flow
Merge ecef5860e into 0bdee919e
Pull Request #726: fix(console): handle nested `style` tags

68 of 68 new or added lines in 3 files covered. (100.0%)

26 existing lines in 5 files now uncovered.

7617 of 9216 relevant lines covered (82.65%)

51.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/src/Tempest/Core/src/PublishesFiles.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Core;
6

7
use Closure;
8
use Tempest\Console\HasConsole;
9
use Tempest\Container\Inject;
10
use Tempest\Generation\ClassManipulator;
11
use Tempest\Generation\DataObjects\StubFile;
12
use Tempest\Generation\Enums\StubFileType;
13
use Tempest\Generation\Exceptions\FileGenerationAbortedException;
14
use Tempest\Generation\Exceptions\FileGenerationFailedException;
15
use Tempest\Generation\StubFileGenerator;
16
use Tempest\Support\PathHelper;
17
use function Tempest\Support\str;
18
use Tempest\Validation\Rules\EndsWith;
19
use Tempest\Validation\Rules\NotEmpty;
20
use Throwable;
21

22
/**
23
 * Provides a bunch of methods to publish and generate files and work with common user input.
24
 */
25
trait PublishesFiles
26
{
27
    use HasConsole;
28

29
    #[Inject]
30
    private readonly Composer $composer;
31

32
    #[Inject]
33
    private readonly StubFileGenerator $stubFileGenerator;
34

35
    private array $publishedFiles = [];
36

37
    private array $publishedClasses = [];
38

39
    /**
40
     * Publishes a file from a source to a destination.
41
     * @param string $source The path to the source file.
42
     * @param string $destination The path to the destination file.
43
     * @param Closure(string $source, string $destination): void|null $callback A callback to run after the file is published.
44
     */
45
    public function publish(
4✔
46
        string $source,
47
        string $destination,
48
        ?Closure $callback = null,
49
    ): void {
50
        try {
51
            if (! $this->console->confirm(
4✔
52
                question: sprintf('Do you want to create "%s"', $destination),
4✔
53
                default: true,
4✔
54
            )) {
4✔
UNCOV
55
                throw new FileGenerationAbortedException('Skipped.');
×
56
            }
57

58
            if (! $this->askForOverride($destination)) {
3✔
UNCOV
59
                throw new FileGenerationAbortedException('Skipped.');
×
60
            }
61

62
            $stubFile = StubFile::from($source);
3✔
63

64
            // Handle class files
65
            if ($stubFile->type === StubFileType::CLASS_FILE) {
3✔
66
                $oldClass = new ClassManipulator($source);
2✔
67

68
                $this->stubFileGenerator->generateClassFile(
2✔
69
                    stubFile: $stubFile,
2✔
70
                    targetPath: $destination,
2✔
71
                    shouldOverride: true,
2✔
72
                    manipulations: [
2✔
73
                        fn (ClassManipulator $class) => $class->removeClassAttribute(DoNotDiscover::class),
2✔
74
                    ],
2✔
75
                );
2✔
76

77
                $newClass = new ClassManipulator($destination);
2✔
78

79
                $this->publishedClasses[$oldClass->getClassName()] = $newClass->getClassName();
2✔
80
            }
81

82
            // Handle raw files
83
            if ($stubFile->type === StubFileType::RAW_FILE) {
3✔
84
                $this->stubFileGenerator->generateRawFile(
2✔
85
                    stubFile: $stubFile,
2✔
86
                    targetPath: $destination,
2✔
87
                    shouldOverride: true,
2✔
88
                );
2✔
89
            }
90

91
            $this->publishedFiles[] = $destination;
3✔
92

93
            if ($callback !== null) {
3✔
94
                $callback($source, $destination);
1✔
95
            }
96

97
            $this->console->success(sprintf('File successfully created at "%s".', $destination));
3✔
UNCOV
98
        } catch (FileGenerationAbortedException $exception) {
×
UNCOV
99
            $this->console->info($exception->getMessage());
×
UNCOV
100
        } catch (Throwable $throwable) {
×
UNCOV
101
            throw new FileGenerationFailedException(
×
UNCOV
102
                message: 'The file could not be published.',
×
UNCOV
103
                previous: $throwable,
×
UNCOV
104
            );
×
105
        }
106
    }
107

108
    /**
109
     * Publishes the imports of the published classes.
110
     * Any published class that is imported in another published class will have its import updated.
111
     */
112
    public function publishImports(): void
1✔
113
    {
114
        foreach ($this->publishedFiles as $file) {
1✔
115
            $contents = str(file_get_contents($file));
1✔
116

117
            foreach ($this->publishedClasses as $old => $new) {
1✔
118
                $contents = $contents->replace($old, $new);
1✔
119
            }
120

121
            file_put_contents($file, $contents);
1✔
122
        }
123
    }
124

125
    /**
126
     * Gets a suggested path for the given class name.
127
     * This will use the user's main namespace as the base path.
128
     * @param string $className The class name to generate the path for, can include path parts (e.g. 'Models/User').
129
     * @param string|null $pathPrefix The prefix to add to the path (e.g. 'Models').
130
     * @param string|null $classSuffix The suffix to add to the class name (e.g. 'Model').
131
     * @return string The fully suggested path including the filename and extension.
132
     */
133
    public function getSuggestedPath(string $className, ?string $pathPrefix = null, ?string $classSuffix = null): string
21✔
134
    {
135
        // Separate input path and classname
136
        $inputClassName = PathHelper::toClassName($className);
21✔
137
        $inputPath = str(PathHelper::make($className))->replaceLast($inputClassName, '')->toString();
21✔
138
        $className = str($inputClassName)
21✔
139
            ->pascal()
21✔
140
            ->finish($classSuffix ?? '')
21✔
141
            ->toString();
21✔
142

143
        // Prepare the suggested path from the project namespace
144
        return str(PathHelper::make(
21✔
145
            $this->composer->mainNamespace->path,
21✔
146
            $pathPrefix ?? '',
21✔
147
            $inputPath,
21✔
148
        ))
21✔
149
            ->finish('/')
21✔
150
            ->append($className . '.php')
21✔
151
            ->toString();
21✔
152
    }
153

154
    /**
155
     * Prompt the user for the target path to save the generated file.
156
     * @param string $suggestedPath The suggested path to show to the user.
157
     * @return string The target path that the user has chosen.
158
     */
159
    public function promptTargetPath(string $suggestedPath): string
6✔
160
    {
161
        $className = PathHelper::toClassName($suggestedPath);
6✔
162

163
        return $this->console->ask(
6✔
164
            question: sprintf('Where do you want to save the file "%s"?', $className),
6✔
165
            default: $suggestedPath,
6✔
166
            validation: [new NotEmpty(), new EndsWith('.php')],
6✔
167
        );
6✔
168
    }
169

170
    /**
171
     * Ask the user if they want to override the file if it already exists.
172
     * @param string $targetPath The target path to check for existence.
173
     * @return bool Whether the user wants to override the file.
174
     */
175
    public function askForOverride(string $targetPath): bool
9✔
176
    {
177
        if (! file_exists($targetPath)) {
9✔
178
            return true;
9✔
179
        }
180

UNCOV
181
        return $this->console->confirm(
×
UNCOV
182
            question: sprintf('The file "%s" already exists. Do you want to override it?', $targetPath),
×
UNCOV
183
        );
×
184
    }
185
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc