Completed
Pull Request — master (#638)
by
unknown
12:52
created

Writer::getMarkdownToPrepend()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Mpociot\ApiDoc\Writing;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\Storage;
8
use Mpociot\ApiDoc\Tools\DocumentationConfig;
9
use Mpociot\Documentarian\Documentarian;
10
11
class Writer
12
{
13
    /**
14
     * @var Collection
15
     */
16
    protected $routes;
17
18
    /**
19
     * @var Command
20
     */
21
    protected $output;
22
23
    /**
24
     * @var DocumentationConfig
25
     */
26
    private $config;
27
28
    /**
29
     * @var string
30
     */
31
    private $baseUrl;
32
33
    /**
34
     * @var bool
35
     */
36
    private $forceIt;
37
38
    /**
39
     * @var bool
40
     */
41
    private $shouldGeneratePostmanCollection = true;
42
43
    /**
44
     * @var Documentarian
45
     */
46
    private $documentarian;
47
48
    public function __construct(Collection $routes, bool $forceIt, Command $output, DocumentationConfig $config = null)
49
    {
50
        $this->routes = $routes;
51
        // If no config is injected, pull from global
52
        $this->config = $config ?: new DocumentationConfig(config('apidoc'));
53
        $this->baseUrl = $this->config->get('base_url') ?? config('app.url');
54
        $this->forceIt = $forceIt;
55
        $this->output = $output;
56
        $this->shouldGeneratePostmanCollection = $this->config->get('postman.enabled', false);
57
        $this->documentarian = new Documentarian();
58
    }
59
60
    public function writeDocs()
61
    {
62
        // The source files (index.md, js/, css/, and images/) always go in resources/docs/source.
63
        // The static assets (js/, css/, and images/) always go in public/docs/.
64
        // For 'static' docs, the output files (index.html, collection.json) go in public/docs/.
65
        // For 'laravel' docs, the output files (index.blade.php, collection.json)
66
        // go in resources/views/apidoc/ and storage/app/apidoc/ respectively.
67
        $isStatic = $this->config->get('type') === 'static';
68
69
        $sourceOutputPath = 'resources/docs';
70
        $outputPath = $isStatic ? 'public/docs' : 'resources/views/apidoc';
71
72
        $this->writeMarkdownAndSourceFiles($this->routes, $sourceOutputPath);
73
74
        $this->writeHtmlDocs($sourceOutputPath, $outputPath, $isStatic);
75
76
        $this->writePostmanCollection($this->routes, $outputPath, $isStatic);
77
    }
78
79
    /**
80
     * @param  Collection $parsedRoutes
81
     *
82
     * @return void
83
     */
84
    public function writeMarkdownAndSourceFiles(Collection $parsedRoutes, string $sourceOutputPath)
85
    {
86
        $targetFile = $sourceOutputPath.'/source/index.md';
87
        $compareFile = $sourceOutputPath.'/source/.compare.md';
88
89
        $infoText = view('apidoc::partials.info')
90
            ->with('outputPath', 'docs')
91
            ->with('showPostmanCollectionButton', $this->shouldGeneratePostmanCollection);
92
93
        $settings = ['languages' => $this->config->get('example_languages')];
94
        // Generate Markdown for each route
95
        $parsedRouteOutput = $this->generateMarkdownOutputForEachRoute($parsedRoutes, $settings);
96
97
        $frontmatter = view('apidoc::partials.frontmatter')
98
            ->with('settings', $settings);
99
100
        /*
101
         * If the target file already exists,
102
         * we check if the documentation was modified
103
         * and skip the modified parts of the routes.
104
         */
105
        if (file_exists($targetFile) && file_exists($compareFile)) {
106
            $generatedDocumentation = file_get_contents($targetFile);
107
            $compareDocumentation = file_get_contents($compareFile);
108
109
            $parsedRouteOutput->transform(function (Collection $routeGroup) use ($generatedDocumentation, $compareDocumentation) {
110
                return $routeGroup->transform(function (array $route) use ($generatedDocumentation, $compareDocumentation) {
111
                    if (preg_match('/<!-- START_'.$route['id'].' -->(.*)<!-- END_'.$route['id'].' -->/is', $generatedDocumentation, $existingRouteDoc)) {
112
                        $routeDocumentationChanged = (preg_match('/<!-- START_'.$route['id'].' -->(.*)<!-- END_'.$route['id'].' -->/is', $compareDocumentation, $lastDocWeGeneratedForThisRoute) && $lastDocWeGeneratedForThisRoute[1] !== $existingRouteDoc[1]);
113
                        if ($routeDocumentationChanged === false || $this->forceIt) {
114
                            if ($routeDocumentationChanged) {
115
                                $this->output->warn('Discarded manual changes for route ['.implode(',', $route['methods']).'] '.$route['uri']);
116
                            }
117
                        } else {
118
                            $this->output->warn('Skipping modified route ['.implode(',', $route['methods']).'] '.$route['uri']);
119
                            $route['modified_output'] = $existingRouteDoc[0];
120
                        }
121
                    }
122
123
                    return $route;
124
                });
125
            });
126
        }
127
128
        $prependFileContents = $this->getMarkdownToPrepend($sourceOutputPath);
129
        $appendFileContents = $this->getMarkdownToAppend($sourceOutputPath);
130
131
        $markdown = view('apidoc::documentarian')
132
            ->with('writeCompareFile', false)
133
            ->with('frontmatter', $frontmatter)
134
            ->with('infoText', $infoText)
135
            ->with('prependMd', $prependFileContents)
136
            ->with('appendMd', $appendFileContents)
137
            ->with('outputPath', $this->config->get('output'))
138
            ->with('showPostmanCollectionButton', $this->shouldGeneratePostmanCollection)
139
            ->with('parsedRoutes', $parsedRouteOutput);
140
141
        $this->output->info('Writing index.md and source files to: '.$sourceOutputPath);
142
143
        if (! is_dir($sourceOutputPath)) {
144
            $documentarian = new Documentarian();
145
            $documentarian->create($sourceOutputPath);
146
        }
147
148
        // Write output file
149
        file_put_contents($targetFile, $markdown);
150
151
        // Write comparable markdown file
152
        $compareMarkdown = view('apidoc::documentarian')
153
            ->with('writeCompareFile', true)
154
            ->with('frontmatter', $frontmatter)
155
            ->with('infoText', $infoText)
156
            ->with('prependMd', $prependFileContents)
157
            ->with('appendMd', $appendFileContents)
158
            ->with('outputPath', $this->config->get('output'))
159
            ->with('showPostmanCollectionButton', $this->shouldGeneratePostmanCollection)
160
            ->with('parsedRoutes', $parsedRouteOutput);
161
162
        file_put_contents($compareFile, $compareMarkdown);
163
164
        $this->output->info('Wrote index.md and source files to: '.$sourceOutputPath);
165
    }
166
167
    public function generateMarkdownOutputForEachRoute(Collection $parsedRoutes, array $settings): Collection
168
    {
169
        $parsedRouteOutput = $parsedRoutes->map(function (Collection $routeGroup) use ($settings) {
170
            return $routeGroup->map(function (array $route) use ($settings) {
171
                if (count($route['cleanBodyParameters']) && ! isset($route['headers']['Content-Type'])) {
172
                    // Set content type if the user forgot to set it
173
                    $route['headers']['Content-Type'] = 'application/json';
174
                }
175
176
                $hasRequestOptions = ! empty($route['headers']) || ! empty($route['cleanQueryParameters']) || ! empty($route['cleanBodyParameters']);
177
                $route['output'] = (string) view('apidoc::partials.route')
0 ignored issues
show
Bug introduced by
The method with does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
178
                    ->with('hasRequestOptions', $hasRequestOptions)
179
                    ->with('route', $route)
180
                    ->with('settings', $settings)
181
                    ->with('baseUrl', $this->baseUrl)
182
                    ->render();
183
184
                return $route;
185
            });
186
        });
187
188
        return $parsedRouteOutput;
189
    }
190
191
    protected function writePostmanCollection(Collection $parsedRoutes, string $outputPath, bool $isStatic): void
192
    {
193
        if ($this->shouldGeneratePostmanCollection) {
194
            $this->output->info('Generating Postman collection');
195
196
            $collection = $this->generatePostmanCollection($parsedRoutes);
197
            if ($isStatic) {
198
                $collectionPath = "{$outputPath}/collection.json";
199
                file_put_contents($collectionPath, $collection);
200
            } else {
201
                Storage::disk('local')->put('apidoc/collection.json', $collection);
202
                $collectionPath = 'storage/app/apidoc/collection.json';
203
            }
204
205
            $this->output->info("Wrote Postman collection to: {$collectionPath}");
206
        }
207
    }
208
209
    /**
210
     * Generate Postman collection JSON file.
211
     *
212
     * @param Collection $routes
213
     *
214
     * @return string
215
     */
216
    public function generatePostmanCollection(Collection $routes)
217
    {
218
        $writer = new PostmanCollectionWriter($routes, $this->baseUrl);
219
220
        return $writer->getCollection();
221
    }
222
223
    /**
224
     * @param string $sourceOutputPath
225
     *
226
     * @return string
227
     */
228
    protected function getMarkdownToPrepend(string $sourceOutputPath): string
229
    {
230
        $prependFile = $sourceOutputPath.'/source/prepend.md';
231
        $prependFileContents = file_exists($prependFile)
232
            ? file_get_contents($prependFile)."\n" : '';
233
234
        return $prependFileContents;
235
    }
236
237
    /**
238
     * @param string $sourceOutputPath
239
     *
240
     * @return string
241
     */
242
    protected function getMarkdownToAppend(string $sourceOutputPath): string
243
    {
244
        $appendFile = $sourceOutputPath.'/source/append.md';
245
        $appendFileContents = file_exists($appendFile)
246
            ? "\n".file_get_contents($appendFile) : '';
247
248
        return $appendFileContents;
249
    }
250
251
    protected function copyAssetsFromSourceFolderToPublicFolder(string $sourceOutputPath, bool $isStatic = true): void
252
    {
253
        $publicPath = 'public/docs';
254
        if (! is_dir($publicPath)) {
255
            mkdir($publicPath, 0777, true);
256
            mkdir("{$publicPath}/css");
257
            mkdir("{$publicPath}/js");
258
        }
259
        copy("{$sourceOutputPath}/js/all.js", "{$publicPath}/js/all.js");
260
        rcopy("{$sourceOutputPath}/images", "{$publicPath}/images");
261
        rcopy("{$sourceOutputPath}/css", "{$publicPath}/css");
262
263
        if ($logo = $this->config->get('logo')) {
264
            if ($isStatic) {
265
                copy($logo, "{$publicPath}/images/logo.png");
266
            }
267
        }
268
    }
269
270
    protected function moveOutputFromSourceFolderToTargetFolder(string $sourceOutputPath, string $outputPath, bool $isStatic): void
271
    {
272
        if ($isStatic) {
273
            // Move output (index.html, css/style.css and js/all.js) to public/docs
274
            rename("{$sourceOutputPath}/index.html", "{$outputPath}/index.html");
275
        } else {
276
            // Move output to resources/views
277
            if (! is_dir($outputPath)) {
278
                mkdir($outputPath);
279
            }
280
            rename("{$sourceOutputPath}/index.html", "$outputPath/index.blade.php");
281
            $contents = file_get_contents("$outputPath/index.blade.php");
282
            //
283
            $contents = str_replace('href="css/style.css"', 'href="/docs/css/style.css"', $contents);
284
            $contents = str_replace('src="js/all.js"', 'src="/docs/js/all.js"', $contents);
285
            $contents = str_replace('src="images/', 'src="/docs/images/', $contents);
286
            $contents = preg_replace('#href="http://.+?/docs/collection.json"#', 'href="{{ route("apidoc", ["format" => ".json"]) }}"', $contents);
287
            file_put_contents("$outputPath/index.blade.php", $contents);
288
        }
289
    }
290
291
    /**
292
     * @param string $sourceOutputPath
293
     * @param string $outputPath
294
     * @param bool $isStatic
295
     */
296
    protected function writeHtmlDocs(string $sourceOutputPath, string $outputPath, bool $isStatic): void
297
    {
298
        $this->output->info('Generating API HTML code');
299
300
        $this->documentarian->generate($sourceOutputPath);
301
302
        // Move assets to public folder
303
        $this->copyAssetsFromSourceFolderToPublicFolder($sourceOutputPath, $isStatic);
304
305
        $this->moveOutputFromSourceFolderToTargetFolder($sourceOutputPath, $outputPath, $isStatic);
306
307
        $this->output->info("Wrote HTML documentation to: {$outputPath}");
308
    }
309
}
310