GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 0b78e2...0e0cd9 )
by Anton
04:35 queued 11s
created

title()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/* (c) Anton Medvedev <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Deployer\Documentation;
12
13
use RecursiveDirectoryIterator;
14
use RecursiveIteratorIterator;
15
use RecursiveRegexIterator;
16
use RegexIterator;
17
18
use function Deployer\Support\str_contains as str_contains;
19
20
class DocGen
21
{
22
    /**
23
     * @var string
24
     */
25
    public $root;
26
    /**
27
     * @var DocRecipe[]
28
     */
29
    public $recipes = [];
30
31
    public function __construct(string $root)
32
    {
33
        $this->root = str_replace(DIRECTORY_SEPARATOR, '/', realpath($root));
34
    }
35
36
    public function parse(string $source): void
37
    {
38
        $directory = new RecursiveDirectoryIterator($source);
39
        $iterator = new RegexIterator(new RecursiveIteratorIterator($directory), '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
40
        foreach ($iterator as [$path]) {
41
            $realPath = str_replace(DIRECTORY_SEPARATOR, '/', realpath($path));
42
            $recipePath = str_replace($this->root . '/', '', $realPath);
43
            $recipeName = preg_replace('/\.php$/i', '', basename($recipePath));
44
            $recipe = new DocRecipe($recipeName, $recipePath);
45
            $recipe->parse(file_get_contents($path));
46
            $this->recipes[$recipePath] = $recipe;
47
        }
48
    }
49
50
    public function gen(string $destination): ?string
51
    {
52
        foreach ($this->recipes as $recipe) {
53
            // $find will try to return DocConfig for a given config $name.
54
            $findConfig = function (string $name) use ($recipe): ?DocConfig {
55
                if (array_key_exists($name, $recipe->config)) {
56
                    return $recipe->config[$name];
57
                }
58
                foreach ($recipe->require as $r) {
59
                    if (array_key_exists($r, $this->recipes)) {
60
                        if (array_key_exists($name, $this->recipes[$r]->config)) {
61
                            return $this->recipes[$r]->config[$name];
62
                        }
63
                    }
64
                }
65
                foreach ($this->recipes as $r) {
66
                    if (array_key_exists($name, $r->config)) {
67
                        return $r->config[$name];
68
                    }
69
                }
70
                return null;
71
            };
72
            $findConfigOverride = function (DocRecipe $recipe, string $name) use (&$findConfigOverride): ?DocConfig {
73
                foreach ($recipe->require as $r) {
74
                    if (array_key_exists($r, $this->recipes)) {
75
                        if (array_key_exists($name, $this->recipes[$r]->config)) {
76
                            return $this->recipes[$r]->config[$name];
77
                        }
78
                    }
79
                }
80
                foreach ($recipe->require as $r) {
81
                    if (array_key_exists($r, $this->recipes)) {
82
                        return $findConfigOverride($this->recipes[$r], $name);
83
                    }
84
                }
85
                return null;
86
            };
87
            // Replace all {{name}} with link to correct config declaration.
88
            $replaceLinks = function (string $comment) use ($findConfig): string {
89
                $output = '';
90
                $code = false;
91
                foreach (explode("\n", $comment) as $i => $line) {
92
                    if (str_starts_with($line, '```') || str_starts_with($line, '~~~')) {
93
                        $code = !$code;
0 ignored issues
show
introduced by
The condition $code is always false.
Loading history...
94
                    }
95
                    if ($code) {
96
                        $output .= $line;
97
                        $output .= "\n";
98
                        continue;
99
                    }
100
                    $output .= preg_replace_callback('#(\{\{(?<name>[\w_:\-/]+)\}\})#', function ($m) use ($findConfig) {
101
                        $name = $m['name'];
102
                        $config = $findConfig($name);
103
                        if ($config !== null) {
104
                            $md = php_to_md($config->recipePath);
105
                            $anchor = anchor($name);
106
                            return "[$name](/docs/$md#$anchor)";
107
                        }
108
                        return "{{" . $name . "}}";
109
                    }, $line);
110
                    $output .= "\n";
111
                }
112
                return $output;
113
            };
114
            $findTask = function (string $name, bool $searchOtherRecipes = true) use ($recipe): ?DocTask {
115
                if (array_key_exists($name, $recipe->tasks)) {
116
                    return $recipe->tasks[$name];
117
                }
118
                foreach ($recipe->require as $r) {
119
                    if (array_key_exists($r, $this->recipes)) {
120
                        if (array_key_exists($name, $this->recipes[$r]->tasks)) {
121
                            return $this->recipes[$r]->tasks[$name];
122
                        }
123
                    }
124
                }
125
                if ($searchOtherRecipes) {
126
                    foreach ($this->recipes as $r) {
127
                        if (array_key_exists($name, $r->tasks)) {
128
                            return $r->tasks[$name];
129
                        }
130
                    }
131
                }
132
                return null;
133
            };
134
135
            $title = join(' ', array_map('ucfirst', explode('_', $recipe->recipeName))) . ' Recipe';
136
            $config = '';
137
            $tasks = '';
138
            $intro = <<<MD
139
                ```php
140
                require '$recipe->recipePath';
141
                ```
142
143
                [Source](/$recipe->recipePath)
144
145
146
                MD;
147
            if (is_framework_recipe($recipe)) {
148
                $brandName = framework_brand_name($recipe->recipeName);
149
                $typeOfProject = preg_match('/^symfony/i', $recipe->recipeName) ? 'Application' : 'Project';
150
                $title = "How to Deploy a $brandName $typeOfProject";
151
152
                $intro .= <<<MARKDOWN
153
                    Deployer is a free and open source deployment tool written in PHP. 
154
                    It helps you to deploy your $brandName application to a server. 
155
                    It is very easy to use and has a lot of features. 
156
157
                    Three main features of Deployer are:
158
                    - **Provisioning** - provision your server for you.
159
                    - **Zero downtime deployment** - deploy your application without a downtime.
160
                    - **Rollbacks** - rollback your application to a previous version, if something goes wrong.
161
162
                    Additionally, Deployer has a lot of other features, like:
163
                    - **Easy to use** - Deployer is very easy to use. It has a simple and intuitive syntax.
164
                    - **Fast** - Deployer is very fast. It uses parallel connections to deploy your application.
165
                    - **Secure** - Deployer uses SSH to connect to your server.
166
                    - **Supports all major PHP frameworks** - Deployer supports all major PHP frameworks.
167
168
                    You can read more about Deployer in [Getting Started](/docs/getting-started.md).
169
170
171
                    MARKDOWN;
172
173
                $map = function (DocTask $task, $ident = '') use (&$map, $findTask, &$intro): void {
174
                    foreach ($task->group as $taskName) {
175
                        $t = $findTask($taskName);
176
                        if ($t !== null) {
177
                            $intro .= "$ident* {$t->mdLink()} – $t->desc\n";
178
                            if ($t->group !== null) {
179
                                $map($t, $ident . '  ');
180
                            }
181
                        }
182
                    }
183
                };
184
                $deployTask = $findTask('deploy');
185
                if ($deployTask !== null) {
186
                    $intro .= "The [deploy](#deploy) task of **$brandName** consists of:\n";
187
                    $map($deployTask);
188
                }
189
190
                $intro .= "\n\n";
191
192
                $artifactBuildTask = $findTask('artifact:build', false);
193
                $artifactDeployTask = $findTask('artifact:deploy', false);
194
                if ($artifactDeployTask !== null && $artifactBuildTask !== null) {
195
                    $intro .= "In addition the **$brandName** recipe contains an artifact deployment.\n";
196
                    $intro .= <<<MD
197
                        This is a two step process where you first execute
198
199
                        ```php
200
                        bin/dep artifact:build [options] [localhost]
201
                        ```
202
203
                        to build an artifact, which then is deployed on a server with
204
205
                        ```php
206
                        bin/dep artifact:deploy [host]
207
                        ```
208
209
                        The `localhost` to build the artifact on has to be declared local, so either add
210
                        ```php
211
                        localhost()
212
                            ->set('local', true);
213
                        ```
214
                        to your deploy.php or
215
                        ```yaml
216
                        hosts:
217
                            localhost:
218
                                local: true
219
                        ```
220
                        to your deploy yaml.
221
222
                        The [artifact:build](#artifact:build) command of **$brandName** consists of: 
223
                        MD;
224
                    $map($artifactBuildTask);
225
226
                    $intro .= "\n\n The [artifact:deploy](#artifact:deploy) command of **$brandName** consists of:\n";
227
228
                    $map($artifactDeployTask);
229
230
                    $intro .= "\n\n";
231
                }
232
            }
233
            if (count($recipe->require) > 0) {
234
                if (is_framework_recipe($recipe)) {
235
                    $link = recipe_to_md_link($recipe->require[0]);
236
                    $intro .= "The $recipe->recipeName recipe is based on the $link recipe.\n";
237
                } else {
238
                    $intro .= "* Requires\n";
239
                    foreach ($recipe->require as $r) {
240
                        $link = recipe_to_md_link($r);
241
                        $intro .= "  * {$link}\n";
242
                    }
243
                }
244
            }
245
            if (!empty($recipe->comment)) {
246
                $intro .= "\n$recipe->comment\n";
247
            }
248
            if (count($recipe->config) > 0) {
249
                $config .= "## Configuration\n";
250
                foreach ($recipe->config as $c) {
251
                    $config .= "### {$c->name}\n";
252
                    $config .= "[Source](https://github.com/deployphp/deployer/blob/master/{$c->recipePath}#L{$c->lineNumber})\n\n";
253
                    $o = $findConfigOverride($recipe, $c->name);
254
                    if ($o !== null) {
255
                        $md = php_to_md($o->recipePath);
256
                        $anchor = anchor($c->name);
257
                        $config .= "Overrides [{$c->name}](/docs/$md#$anchor) from `$o->recipePath`.\n\n";
258
                    }
259
                    $config .= $replaceLinks($c->comment);
260
                    $config .= "\n";
261
                    if (
262
                        !empty($c->defaultValue)
263
                        && $c->defaultValue !== "''"
264
                        && $c->defaultValue !== '[]'
265
                    ) {
266
                        $config .= "```php title=\"Default value\"\n";
267
                        $config .= $c->defaultValue;
268
                        $config .= "\n";
269
                        $config .= "```\n";
270
                    }
271
                    $config .= "\n\n";
272
                }
273
            }
274
            if (count($recipe->tasks) > 0) {
275
                $tasks .= "## Tasks\n\n";
276
                foreach ($recipe->tasks as $t) {
277
                    $anchorTag = '{#' . anchor($t->name) . '}';
278
                    $name = title($t->name);
279
                    $tasks .= "### $name $anchorTag\n";
280
                    $tasks .= "[Source](https://github.com/deployphp/deployer/blob/master/{$t->recipePath}#L{$t->lineNumber})\n\n";
281
                    $tasks .= add_tailing_dot($t->desc) . "\n\n";
282
                    $tasks .= $replaceLinks($t->comment);
283
                    if (is_array($t->group)) {
284
                        $tasks .= "\n\n";
285
                        $tasks .= "This task is group task which contains next tasks:\n";
286
                        foreach ($t->group as $taskName) {
287
                            $t = $findTask($taskName);
288
                            if ($t !== null) {
289
                                $tasks .= "* {$t->mdLink()}\n";
290
                            } else {
291
                                $tasks .= "* `$taskName`\n";
292
                            }
293
                        }
294
                    }
295
                    $tasks .= "\n\n";
296
                }
297
            }
298
299
            $output = <<<MD
300
                <!-- DO NOT EDIT THIS FILE! -->
301
                <!-- Instead edit $recipe->recipePath -->
302
                <!-- Then run bin/docgen -->
303
304
                # $title
305
306
                $intro
307
                $config
308
                $tasks
309
                MD;
310
311
            $filePath = "$destination/" . php_to_md($recipe->recipePath);
312
            if (!file_exists(dirname($filePath))) {
313
                mkdir(dirname($filePath), 0o755, true);
314
            }
315
            $output = remove_text_emoji($output);
316
            file_put_contents($filePath, $output);
317
        }
318
        $this->generateRecipesIndex($destination);
319
        $this->generateContribIndex($destination);
320
        return null;
321
    }
322
323
    public function generateRecipesIndex(string $destination)
324
    {
325
        $index = "# All Recipes\n\n";
326
        $list = [];
327
        foreach ($this->recipes as $recipe) {
328
            if (preg_match('/^recipe\/[^\/]+\.php$/', $recipe->recipePath)) {
329
                $name = framework_brand_name($recipe->recipeName);
330
                $list[] = "* [$name Recipe](/docs/recipe/{$recipe->recipeName}.md)";
331
            }
332
        }
333
        sort($list);
334
        $index .= implode("\n", $list);
335
        file_put_contents("$destination/recipe/README.md", $index);
336
    }
337
338
    public function generateContribIndex(string $destination)
339
    {
340
        $index = "# All Contrib Recipes\n\n";
341
        $list = [];
342
        foreach ($this->recipes as $recipe) {
343
            if (preg_match('/^contrib\/[^\/]+\.php$/', $recipe->recipePath)) {
344
                $name = ucfirst($recipe->recipeName);
345
                $list[] = "* [$name Recipe](/docs/contrib/$recipe->recipeName.md)";
346
            }
347
        }
348
        sort($list);
349
        $index .= implode("\n", $list);
350
        file_put_contents("$destination/contrib/README.md", $index);
351
    }
352
}
353
354
function trim_comment(string $line): string
355
{
356
    return preg_replace('#^(/\*\*?\s?|\s\*\s?|//\s?)#', '', $line);
357
}
358
359
function indent(string $text): string
360
{
361
    return implode("\n", array_map(function ($line) {
362
        return "  " . $line;
363
    }, explode("\n", $text)));
364
}
365
366
function php_to_md(string $file): string
367
{
368
    return preg_replace('#\.php$#', '.md', $file);
369
}
370
371
function title(string $s): string
372
{
373
    return str_replace(':', '\\:', $s);
374
}
375
376
function anchor(string $s): string
377
{
378
    return strtolower(str_replace(':', '-', $s));
379
}
380
381
function remove_text_emoji(string $text): string
382
{
383
    return preg_replace('/:(bowtie|smile|laughing|blush|smiley|relaxed|smirk|heart_eyes|kissing_heart|kissing_closed_eyes|flushed|relieved|satisfied|grin|wink|stuck_out_tongue_winking_eye|stuck_out_tongue_closed_eyes|grinning|kissing|kissing_smiling_eyes|stuck_out_tongue|sleeping|worried|frowning|anguished|open_mouth|grimacing|confused|hushed|expressionless|unamused|sweat_smile|sweat|disappointed_relieved|weary|pensive|disappointed|confounded|fearful|cold_sweat|persevere|cry|sob|joy|astonished|scream|neckbeard|tired_face|angry|rage|triumph|sleepy|yum|mask|sunglasses|dizzy_face|imp|smiling_imp|neutral_face|no_mouth|innocent|alien|yellow_heart|blue_heart|purple_heart|heart|green_heart|broken_heart|heartbeat|heartpulse|two_hearts|revolving_hearts|cupid|sparkling_heart|sparkles|star|star2|dizzy|boom|collision|anger|exclamation|question|grey_exclamation|grey_question|zzz|dash|sweat_drops|notes|musical_note|fire|hankey|poop|shit|\+1|thumbsup|\-1|thumbsdown|ok_hand|punch|facepunch|fist|v|wave|hand|raised_hand|open_hands|point_up|point_down|point_left|point_right|raised_hands|pray|point_up_2|clap|muscle|metal|fu|walking|runner|running|couple|family|two_men_holding_hands|two_women_holding_hands|dancer|dancers|ok_woman|no_good|information_desk_person|raising_hand|bride_with_veil|person_with_pouting_face|person_frowning|bow|couplekiss|couple_with_heart|massage|haircut|nail_care|boy|girl|woman|man|baby|older_woman|older_man|person_with_blond_hair|man_with_gua_pi_mao|man_with_turban|construction_worker|cop|angel|princess|smiley_cat|smile_cat|heart_eyes_cat|kissing_cat|smirk_cat|scream_cat|crying_cat_face|joy_cat|pouting_cat|japanese_ogre|japanese_goblin|see_no_evil|hear_no_evil|speak_no_evil|guardsman|skull|feet|lips|kiss|droplet|ear|eyes|nose|tongue|love_letter|bust_in_silhouette|busts_in_silhouette|speech_balloon|thought_balloon|feelsgood|finnadie|goberserk|godmode|hurtrealbad|rage1|rage2|rage3|rage4|suspect|trollface|sunny|umbrella|cloud|snowflake|snowman|zap|cyclone|foggy|ocean|cat|dog|mouse|hamster|rabbit|wolf|frog|tiger|koala|bear|pig|pig_nose|cow|boar|monkey_face|monkey|horse|racehorse|camel|sheep|elephant|panda_face|snake|bird|baby_chick|hatched_chick|hatching_chick|chicken|penguin|turtle|bug|honeybee|ant|beetle|snail|octopus|tropical_fish|fish|whale|whale2|dolphin|cow2|ram|rat|water_buffalo|tiger2|rabbit2|dragon|goat|rooster|dog2|pig2|mouse2|ox|dragon_face|blowfish|crocodile|dromedary_camel|leopard|cat2|poodle|paw_prints|bouquet|cherry_blossom|tulip|four_leaf_clover|rose|sunflower|hibiscus|maple_leaf|leaves|fallen_leaf|herb|mushroom|cactus|palm_tree|evergreen_tree|deciduous_tree|chestnut|seedling|blossom|ear_of_rice|shell|globe_with_meridians|sun_with_face|full_moon_with_face|new_moon_with_face|new_moon|waxing_crescent_moon|first_quarter_moon|waxing_gibbous_moon|full_moon|waning_gibbous_moon|last_quarter_moon|waning_crescent_moon|last_quarter_moon_with_face|first_quarter_moon_with_face|moon|earth_africa|earth_americas|earth_asia|volcano|milky_way|partly_sunny|octocat|squirrel|bamboo|gift_heart|dolls|school_satchel|mortar_board|flags|fireworks|sparkler|wind_chime|rice_scene|jack_o_lantern|ghost|santa|christmas_tree|gift|bell|no_bell|tanabata_tree|tada|confetti_ball|balloon|crystal_ball|cd|dvd|floppy_disk|camera|video_camera|movie_camera|computer|tv|iphone|phone|telephone|telephone_receiver|pager|fax|minidisc|vhs|sound|speaker|mute|loudspeaker|mega|hourglass|hourglass_flowing_sand|alarm_clock|watch|radio|satellite|loop|mag|mag_right|unlock|lock|lock_with_ink_pen|closed_lock_with_key|key|bulb|flashlight|high_brightness|low_brightness|electric_plug|battery|calling|email|mailbox|postbox|bath|bathtub|shower|toilet|wrench|nut_and_bolt|hammer|seat|moneybag|yen|dollar|pound|euro|credit_card|money_with_wings|e-mail|inbox_tray|outbox_tray|envelope|incoming_envelope|postal_horn|mailbox_closed|mailbox_with_mail|mailbox_with_no_mail|door|smoking|bomb|gun|hocho|pill|syringe|page_facing_up|page_with_curl|bookmark_tabs|bar_chart|chart_with_upwards_trend|chart_with_downwards_trend|scroll|clipboard|calendar|date|card_index|file_folder|open_file_folder|scissors|pushpin|paperclip|black_nib|pencil2|straight_ruler|triangular_ruler|closed_book|green_book|blue_book|orange_book|notebook|notebook_with_decorative_cover|ledger|books|bookmark|name_badge|microscope|telescope|newspaper|football|basketball|soccer|baseball|tennis|8ball|rugby_football|bowling|golf|mountain_bicyclist|bicyclist|horse_racing|snowboarder|swimmer|surfer|ski|spades|hearts|clubs|diamonds|gem|ring|trophy|musical_score|musical_keyboard|violin|space_invader|video_game|black_joker|flower_playing_cards|game_die|dart|mahjong|clapper|memo|pencil|book|art|microphone|headphones|trumpet|saxophone|guitar|shoe|sandal|high_heel|lipstick|boot|shirt|tshirt|necktie|womans_clothes|dress|running_shirt_with_sash|jeans|kimono|bikini|ribbon|tophat|crown|womans_hat|mans_shoe|closed_umbrella|briefcase|handbag|pouch|purse|eyeglasses|fishing_pole_and_fish|coffee|tea|sake|baby_bottle|beer|beers|cocktail|tropical_drink|wine_glass|fork_and_knife|pizza|hamburger|fries|poultry_leg|meat_on_bone|spaghetti|curry|fried_shrimp|bento|sushi|fish_cake|rice_ball|rice_cracker|rice|ramen|stew|oden|dango|egg|bread|doughnut|custard|icecream|ice_cream|shaved_ice|birthday|cake|cookie|chocolate_bar|candy|lollipop|honey_pot|apple|green_apple|tangerine|lemon|cherries|grapes|watermelon|strawberry|peach|melon|banana|pear|pineapple|sweet_potato|eggplant|tomato|corn|house|house_with_garden|school|office|post_office|hospital|bank|convenience_store|love_hotel|hotel|wedding|church|department_store|european_post_office|city_sunrise|city_sunset|japanese_castle|european_castle|tent|factory|tokyo_tower|japan|mount_fuji|sunrise_over_mountains|sunrise|stars|statue_of_liberty|bridge_at_night|carousel_horse|rainbow|ferris_wheel|fountain|roller_coaster|ship|speedboat|boat|sailboat|rowboat|anchor|rocket|airplane|helicopter|steam_locomotive|tram|mountain_railway|bike|aerial_tramway|suspension_railway|mountain_cableway|tractor|blue_car|oncoming_automobile|car|red_car|taxi|oncoming_taxi|articulated_lorry|bus|oncoming_bus|rotating_light|police_car|oncoming_police_car|fire_engine|ambulance|minibus|truck|train|station|train2|bullettrain_front|bullettrain_side|light_rail|monorail|railway_car|trolleybus|ticket|fuelpump|vertical_traffic_light|traffic_light|warning|construction|beginner|atm|slot_machine|busstop|barber|hotsprings|checkered_flag|crossed_flags|izakaya_lantern|moyai|circus_tent|performing_arts|round_pushpin|triangular_flag_on_post|jp|kr|cn|us|fr|es|it|ru|gb|uk|de|one|two|three|four|five|six|seven|eight|nine|keycap_ten|1234|zero|hash|symbols|arrow_backward|arrow_down|arrow_forward|arrow_left|capital_abcd|abcd|abc|arrow_lower_left|arrow_lower_right|arrow_right|arrow_up|arrow_upper_left|arrow_upper_right|arrow_double_down|arrow_double_up|arrow_down_small|arrow_heading_down|arrow_heading_up|leftwards_arrow_with_hook|arrow_right_hook|left_right_arrow|arrow_up_down|arrow_up_small|arrows_clockwise|arrows_counterclockwise|rewind|fast_forward|information_source|ok|twisted_rightwards_arrows|repeat|repeat_one|new|top|up|cool|free|ng|cinema|koko|signal_strength|u5272|u5408|u55b6|u6307|u6708|u6709|u6e80|u7121|u7533|u7a7a|u7981|sa|restroom|mens|womens|baby_symbol|no_smoking|parking|wheelchair|metro|baggage_claim|accept|wc|potable_water|put_litter_in_its_place|secret|congratulations|m|passport_control|left_luggage|customs|ideograph_advantage|cl|sos|id|no_entry_sign|underage|no_mobile_phones|do_not_litter|non-potable_water|no_bicycles|no_pedestrians|children_crossing|no_entry|eight_spoked_asterisk|eight_pointed_black_star|heart_decoration|vs|vibration_mode|mobile_phone_off|chart|currency_exchange|aries|taurus|gemini|cancer|leo|virgo|libra|scorpius|sagittarius|capricorn|aquarius|pisces|ophiuchus|six_pointed_star|negative_squared_cross_mark|a|b|ab|o2|diamond_shape_with_a_dot_inside|recycle|end|on|soon|clock1|clock130|clock10|clock1030|clock11|clock1130|clock12|clock1230|clock2|clock230|clock3|clock330|clock4|clock430|clock5|clock530|clock6|clock630|clock7|clock730|clock8|clock830|clock9|clock930|heavy_dollar_sign|copyright|registered|tm|x|heavy_exclamation_mark|bangbang|interrobang|o|heavy_multiplication_x|heavy_plus_sign|heavy_minus_sign|heavy_division_sign|white_flower|100|heavy_check_mark|ballot_box_with_check|radio_button|link|curly_loop|wavy_dash|part_alternation_mark|trident|black_square|white_square|white_check_mark|black_square_button|white_square_button|black_circle|white_circle|red_circle|large_blue_circle|large_blue_diamond|large_orange_diamond|small_blue_diamond|small_orange_diamond|small_red_triangle|small_red_triangle_down|shipit):/i', ':&#8203;\1:', $text);
384
}
385
386
function add_tailing_dot(string $sentence): string
387
{
388
    if (empty($sentence)) {
389
        return $sentence;
390
    }
391
    if (str_ends_with($sentence, '.')) {
392
        return $sentence;
393
    }
394
    return $sentence . '.';
395
}
396
397
function recipe_to_md_link(string $recipe): string
398
{
399
    $md = php_to_md($recipe);
400
    $basename = basename($recipe, '.php');
401
    return "[$basename](/docs/$md)";
402
}
403
404
function is_framework_recipe(DocRecipe $recipe): bool
405
{
406
    return preg_match('/recipe\/[\w_\d]+\.php$/', $recipe->recipePath) &&
407
    !in_array($recipe->recipeName, ['common', 'composer', 'provision'], true);
408
}
409
410
function framework_brand_name(string $brandName): string
411
{
412
    $brandName = preg_replace('/(\w+)(\d)/', '$1 $2', $brandName);
413
    $brandName = preg_replace('/typo 3/', 'TYPO3', $brandName);
414
    $brandName = preg_replace('/yii/', 'Yii2', $brandName);
415
    $brandName = preg_replace('/wordpress/', 'WordPress', $brandName);
416
    $brandName = preg_replace('/_/', ' ', $brandName);
417
    $brandName = preg_replace('/framework/', 'Framework', $brandName);
418
    return ucfirst($brandName);
419
}
420