├── .php-cs-fixer.php
├── .phpunit.cache
└── test-results
├── LICENSE.md
├── README.md
├── composer.json
├── composer.lock
├── config
└── enlighten.php
├── database
└── migrations
│ ├── 2020_09_20_000000_create_enlighten_runs_table.php
│ ├── 2020_09_21_000000_create_enlighten_example_groups_table.php
│ ├── 2020_09_22_000000_create_enlighten_examples_table.php
│ ├── 2020_09_23_000000_create_enlighten_example_requests_table.php
│ ├── 2020_09_24_000000_create_enlighten_exceptions_table.php
│ ├── 2020_10_18_000000_create_enlighten_example_snippets_table.php
│ └── 2020_10_23_000000_create_enlighten_example_queries_table.php
├── dist
├── css
│ └── app.css
└── js
│ ├── app.js
│ ├── build.js
│ └── prism.js
├── docs
└── areas.md
├── preview.png
├── rector.php
├── resources
├── css
│ ├── app.css
│ ├── code-snippets.css
│ └── prism.css
├── lang
│ ├── en
│ │ └── messages.php
│ └── es
│ │ └── messages.php
└── views
│ ├── area
│ ├── endpoints.blade.php
│ ├── features.blade.php
│ └── modules.blade.php
│ ├── components
│ ├── app-layout.blade.php
│ ├── area-module-panel.blade.php
│ ├── breadcrumbs.blade.php
│ ├── content-table.blade.php
│ ├── dynamic-tabs-menu.blade.php
│ ├── dynamic-tabs.blade.php
│ ├── edit-button.blade.php
│ ├── example-breadcrumbs.blade.php
│ ├── example-snippets.blade.php
│ ├── example-tabs.blade.php
│ ├── exception-info.blade.php
│ ├── expansible-section.blade.php
│ ├── group-breadcrumbs.blade.php
│ ├── html-response.blade.php
│ ├── iframe.blade.php
│ ├── info-panel.blade.php
│ ├── json-response.blade.php
│ ├── key-value.blade.php
│ ├── panel-title.blade.php
│ ├── pre.blade.php
│ ├── queries-info.blade.php
│ ├── request-info.blade.php
│ ├── request-input-table.blade.php
│ ├── response-info.blade.php
│ ├── response-preview.blade.php
│ ├── route-parameters-table.blade.php
│ ├── runs-table.blade.php
│ ├── scroll-to-top.blade.php
│ ├── search-box-static.blade.php
│ ├── search-box.blade.php
│ ├── stats-badge.blade.php
│ ├── status-badge.blade.php
│ └── svg-logo.blade.php
│ ├── example
│ └── show.blade.php
│ ├── group
│ └── show.blade.php
│ ├── intro.blade.php
│ ├── layout
│ └── main.blade.php
│ ├── run
│ └── index.blade.php
│ └── search
│ └── results.blade.php
└── src
├── CodeExamples
├── BaseCodeResultFormat.php
├── CodeExampleCreator.php
├── CodeInspector.php
├── CodeResultExporter.php
├── CodeResultFormat.php
├── CodeResultTransformer.php
├── HtmlResultFormat.php
└── PlainCodeResultFormat.php
├── Console
├── Commands
│ ├── ExportDocumentationCommand.php
│ ├── FreshCommand.php
│ ├── GenerateDocumentationCommand.php
│ ├── InstallCommand.php
│ ├── MigrateCommand.php
│ └── stubs
│ │ ├── BaseTestCase.php.stub
│ │ └── EnlightenTestCase.php.stub
├── ContentRequest.php
└── DocumentationExporter.php
├── Contracts
├── Example.php
├── ExampleBuilder.php
├── ExampleGroupBuilder.php
├── Run.php
├── RunBuilder.php
└── VersionControl.php
├── Drivers
├── BaseExampleBuilder.php
├── BaseExampleGroupBuilder.php
├── DatabaseExampleBuilder.php
├── DatabaseExampleGroupBuilder.php
└── DatabaseRunBuilder.php
├── Enlighten.php
├── ExampleCreator.php
├── ExampleProfile.php
├── ExceptionInfo.php
├── Exceptions
├── InvalidDriverException.php
└── LaravelNotPresent.php
├── Facades
├── Settings.php
└── VersionControl.php
├── Http
├── Controllers
│ ├── ListRunsController.php
│ ├── SearchController.php
│ ├── ShowAreaController.php
│ ├── ShowExampleController.php
│ ├── ShowExampleGroupController.php
│ └── WelcomeController.php
└── routes
│ ├── api.php
│ └── web.php
├── HttpExamples
├── HttpExampleCreator.php
├── HttpExampleCreatorMiddleware.php
├── RequestInfo.php
├── RequestInspector.php
├── ResponseInfo.php
├── ResponseInspector.php
├── RouteInfo.php
├── RouteInspector.php
└── SessionInspector.php
├── Models
├── Area.php
├── Concerns
│ ├── GetStats.php
│ ├── GetsStatsFromGroups.php
│ └── ReadsDynamicAttributes.php
├── Endpoint.php
├── Example.php
├── ExampleException.php
├── ExampleGroup.php
├── ExampleQuery.php
├── ExampleRequest.php
├── ExampleSnippet.php
├── Module.php
├── ModuleCollection.php
├── ReplacesValues.php
├── Run.php
├── Statable.php
├── Status.php
├── Statusable.php
└── Wrappable.php
├── Providers
├── EnlightenServiceProvider.php
├── RegistersConsoleConfiguration.php
├── RegistersDatabaseConnection.php
└── RegistersViewComponents.php
├── Section.php
├── Settings.php
├── Tests
├── EnlightenSetup.php
├── ExceptionRecorder.php
└── bootstrap.php
├── Utils
├── Annotations.php
├── FileLink.php
├── Git.php
└── JsonFormatter.php
├── View
└── Components
│ ├── AppLayoutComponent.php
│ ├── BreadcrumbsComponent.php
│ ├── CodeExampleComponent.php
│ ├── DynamicTabsComponent.php
│ ├── EditButtonComponent.php
│ ├── ExampleBreadcrumbs.php
│ ├── ExampleTabsComponent.php
│ ├── ExceptionInfoComponent.php
│ ├── GroupBreadcrumbs.php
│ ├── HtmlResponseComponent.php
│ ├── KeyValueComponent.php
│ ├── RepresentsStatusAsColor.php
│ ├── RequestInfoComponent.php
│ ├── RequestInputTableComponent.php
│ ├── ResponseInfoComponent.php
│ ├── RouteParametersTableComponent.php
│ ├── SearchBoxComponent.php
│ ├── SearchBoxStaticComponent.php
│ ├── StatsBadgeComponent.php
│ └── StatusBadgeComponent.php
└── helpers.php
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | in(__DIR__.'/config')
8 | ->in(__DIR__.'/database')
9 | ->in(__DIR__.'/src')
10 | ->in(__DIR__.'/tests')
11 | ;
12 |
13 | return (new Config)
14 | ->setRiskyAllowed(true)
15 | ->setRules([
16 | '@PSR2' => true,
17 | 'array_syntax' => ['syntax' => 'short'],
18 | 'ordered_imports' => ['sort_algorithm' => 'alpha'],
19 | 'single_quote' => true,
20 | 'visibility_required' => false,
21 | ])
22 | ->setFinder($finder);
23 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Styde.net
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "styde/enlighten",
3 | "description": "Enlighten your APIs with auto-generated documentation",
4 | "type": "library",
5 | "require": {
6 | "php": "^8.2",
7 | "laravel/framework": "^11.0",
8 | "ext-json": "*",
9 | "guzzlehttp/guzzle": "^7.2"
10 | },
11 | "require-dev": {
12 | "phpunit/phpunit": "^11.0.1",
13 | "orchestra/testbench": "^9.0",
14 | "friendsofphp/php-cs-fixer": "^3.55",
15 | "rector/rector": "^1.0"
16 | },
17 | "license": "MIT",
18 | "authors": [
19 | {
20 | "name": "Duilio Palacios",
21 | "email": "duilio@styde.net"
22 | },
23 | {
24 | "name": "Jeffer Ochoa",
25 | "email": "jeffer.8a@gmail.com"
26 | }
27 | ],
28 | "autoload": {
29 | "files": ["src/helpers.php"],
30 | "psr-4": {
31 | "Styde\\Enlighten\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "Tests\\": "tests/"
37 | }
38 | },
39 | "scripts": {
40 | "test": "vendor/bin/phpunit",
41 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes"
42 | },
43 | "minimum-stability": "dev",
44 | "prefer-stable": true,
45 | "extra": {
46 | "laravel": {
47 | "providers": [
48 | "Styde\\Enlighten\\Providers\\EnlightenServiceProvider"
49 | ]
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/config/enlighten.php:
--------------------------------------------------------------------------------
1 | true,
6 |
7 | 'driver' => 'database',
8 |
9 | // Add values to this array to hide certain sections
10 | // from your views. For all valid sections check
11 | // the constants in \Styde\Enlighten\Section.
12 | 'hide' => [
13 | // 'queries',
14 | // 'html',
15 | // 'blade',
16 | // 'route_parameters',
17 | // 'request_input',
18 | // 'request_headers',
19 | // 'response_headers',
20 | // 'session',
21 | // 'exception',
22 | ],
23 |
24 | // Default directory to export the documentation.
25 | 'docs_base_dir' => 'public/docs',
26 | // Default base URL for exported the documentation.
27 | 'docs_base_url' => '/docs',
28 |
29 | // Display / hide quick access links to open your IDE from the UI
30 | 'developer_mode' => true,
31 | 'editor' => 'phpstorm', // phpstorm, vscode or sublime
32 |
33 | 'tests' => [
34 | // Add regular expressions to skip certain test classes and test methods.
35 | // i.e. Tests\Unit\* will ignore all the tests in the Tests\Unit\ suite,
36 | // validates_* will ignore all the tests that start with "validates_".
37 | 'ignore' => [],
38 | ],
39 |
40 | // Use the arrays below to hide or obfuscate parameters
41 | // from the HTTP requests including headers, input,
42 | // query parameters, session data, and others.
43 | 'request' => [
44 | 'headers' => [
45 | 'hide' => [],
46 | 'overwrite' => [],
47 | ],
48 | 'query' => [
49 | 'hide' => [],
50 | 'overwrite' => [],
51 | ],
52 | 'input' => [
53 | 'hide' => [],
54 | 'overwrite' => [],
55 | ],
56 | ],
57 |
58 | 'response' => [
59 | 'headers' => [
60 | 'hide' => [],
61 | 'overwrite' => [],
62 | ],
63 | 'body' => [
64 | 'hide' => [],
65 | 'overwrite' => [],
66 | ]
67 | ],
68 |
69 | 'session' => [
70 | 'hide' => [],
71 | 'overwrite' => [],
72 | ],
73 |
74 | // Configure a default view for the panel. Options: features, modules and endpoints.
75 | 'area_view' => 'features',
76 |
77 | // Customise the name and view template of each area that will be shown in the panel.
78 | // By default, each area slug will represent a "test suite" in the tests directory.
79 | // Each area can have a different view style, ex: features, modules or endpoints.
80 | 'areas' => [
81 | [
82 | 'slug' => 'api',
83 | 'name' => 'API',
84 | 'view' => 'endpoints',
85 | ],
86 | [
87 | 'slug' => 'feature',
88 | 'name' => 'Features',
89 | 'view' => 'modules',
90 | ],
91 | [
92 | 'slug' => 'unit',
93 | 'name' => 'Unit',
94 | 'view' => 'features',
95 | ],
96 | ],
97 |
98 | // If you want to use "modules" or "endpoints" as the area view,
99 | // you will need to configure the modules adding their names
100 | // and patterns to match the test classes and/or routes.
101 | 'modules' => [
102 | [
103 | 'name' => 'Users',
104 | 'classes' => ['*User*'],
105 | 'routes' => ['users/*'],
106 | ],
107 | [
108 | 'name' => 'Other Modules',
109 | 'classes' => ['*'],
110 | ],
111 | ]
112 | ];
113 |
--------------------------------------------------------------------------------
/database/migrations/2020_09_20_000000_create_enlighten_runs_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_runs')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_runs', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->string('branch');
24 | $table->string('head');
25 | $table->boolean('modified');
26 |
27 | $table->unique(['head', 'modified']);
28 |
29 | $table->timestamps();
30 | });
31 | }
32 |
33 | /**
34 | * Reverse the migrations.
35 | *
36 | * @return void
37 | */
38 | public function down()
39 | {
40 | Schema::connection('enlighten')->dropIfExists('enlighten_runs');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/database/migrations/2020_09_21_000000_create_enlighten_example_groups_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_example_groups')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_example_groups', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->foreignId('run_id')
24 | ->references('id')
25 | ->on('enlighten_runs')
26 | ->cascadeOnDelete();
27 |
28 | $table->string('class_name');
29 |
30 | $table->unique(['run_id', 'class_name']);
31 |
32 | $table->string('title');
33 | $table->string('slug');
34 |
35 | $table->text('description')->nullable();
36 | $table->string('area');
37 |
38 | $table->unsignedInteger('order_num')->nullable();
39 |
40 | $table->timestamps();
41 | });
42 | }
43 |
44 | /**
45 | * Reverse the migrations.
46 | *
47 | * @return void
48 | */
49 | public function down()
50 | {
51 | Schema::connection('enlighten')->dropIfExists('enlighten_example_groups');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/database/migrations/2020_09_22_000000_create_enlighten_examples_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_examples')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_examples', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->foreignId('group_id')
24 | ->references('id')
25 | ->on('enlighten_example_groups')
26 | ->cascadeOnDelete();
27 |
28 | $table->string('method_name');
29 | $table->string('data_name')->nullable();
30 | $table->json('provided_data')->nullable();
31 |
32 | $table->string('slug');
33 |
34 | $table->unique(['group_id', 'method_name', 'data_name']);
35 | $table->unique(['group_id', 'slug', 'data_name']);
36 |
37 | $table->integer('line')->nullable();
38 | $table->string('title');
39 | $table->text('description')->nullable();
40 |
41 | $table->string('test_status')->nullable();
42 | $table->string('status')->nullable();
43 |
44 | $table->unsignedInteger('order_num')->nullable();
45 |
46 | $table->timestamps();
47 | });
48 | }
49 |
50 | /**
51 | * Reverse the migrations.
52 | *
53 | * @return void
54 | */
55 | public function down()
56 | {
57 | Schema::connection('enlighten')->dropIfExists('enlighten_examples');
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/database/migrations/2020_09_23_000000_create_enlighten_example_requests_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_example_requests')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_example_requests', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->foreignId('example_id')
24 | ->references('id')
25 | ->on('enlighten_examples')
26 | ->cascadeOnDelete();
27 |
28 | $table->json('request_headers');
29 | $table->string('request_method');
30 | $table->string('request_path');
31 | $table->json('request_query_parameters');
32 | $table->json('request_input');
33 | $table->json('request_files');
34 | $table->string('route')->nullable();
35 | $table->json('route_parameters')->nullable();
36 | $table->char('response_status', 3)->nullable();
37 | $table->boolean('follows_redirect')->default(false);
38 | $table->json('response_headers')->nullable();
39 | $table->longText('response_body')->nullable();
40 | $table->text('response_template')->nullable();
41 |
42 | $table->text('session_data')->nullable();
43 |
44 | $table->timestamps();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | *
51 | * @return void
52 | */
53 | public function down()
54 | {
55 | Schema::connection('enlighten')->dropIfExists('enlighten_example_requests');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/database/migrations/2020_09_24_000000_create_enlighten_exceptions_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_exceptions')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_exceptions', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->foreignId('example_id')
24 | ->unique()
25 | ->references('id')
26 | ->on('enlighten_examples')
27 | ->cascadeOnDelete();
28 |
29 | $table->string('code');
30 | $table->string('class_name');
31 | $table->longText('message');
32 | $table->string('file');
33 | $table->unsignedSmallInteger('line');
34 | $table->longText('trace');
35 | $table->longText('extra');
36 |
37 | $table->timestamps();
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | *
44 | * @return void
45 | */
46 | public function down()
47 | {
48 | Schema::connection('enlighten')->dropIfExists('enlighten_exceptions');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/database/migrations/2020_10_18_000000_create_enlighten_example_snippets_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_example_snippets')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_example_snippets', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->string('key')->unique()->nullable();
24 |
25 | $table->foreignId('example_id')
26 | ->references('id')
27 | ->on('enlighten_examples')
28 | ->cascadeOnDelete();
29 |
30 | $table->longText('code');
31 |
32 | $table->longText('result')->nullable();
33 |
34 | $table->timestamps();
35 | });
36 | }
37 |
38 | /**
39 | * Reverse the migrations.
40 | *
41 | * @return void
42 | */
43 | public function down()
44 | {
45 | Schema::connection('enlighten')->dropIfExists('enlighten_example_snippets');
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/database/migrations/2020_10_23_000000_create_enlighten_example_queries_table.php:
--------------------------------------------------------------------------------
1 | hasTable('enlighten_example_queries')) {
17 | return;
18 | }
19 |
20 | Schema::connection('enlighten')->create('enlighten_example_queries', function (Blueprint $table) {
21 | $table->id();
22 |
23 | $table->foreignId('example_id')
24 | ->references('id')
25 | ->on('enlighten_examples')
26 | ->cascadeOnDelete();
27 |
28 | $table->text('sql');
29 |
30 | $table->longText('bindings');
31 |
32 | $table->string('time');
33 |
34 | $table->foreignId('request_id')
35 | ->nullable()
36 | ->references('id')
37 | ->on('enlighten_example_requests');
38 |
39 | $table->foreignId('snippet_id')
40 | ->nullable()
41 | ->references('id')
42 | ->on('enlighten_example_snippets');
43 |
44 | $table->timestamps();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | *
51 | * @return void
52 | */
53 | public function down()
54 | {
55 | Schema::connection('enlighten')->dropIfExists('enlighten_example_queries');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/docs/areas.md:
--------------------------------------------------------------------------------
1 | # Areas
2 |
3 | Each area in Enlighten represents a **test suite**.
4 |
5 | We use the generic, less technical, term of "Areas" because one of the objectives of Enlighten
6 | is generating the documentation for your end users, clients or even the QA department.
7 |
8 | By default, each area is the second segment of your test class FQN in slug format, i.e.:
9 |
10 | `Tests\Feature\CreateUserTest` -> `feature`
11 |
12 | `Tests\Unit\UserTest` -> `unit`
13 |
14 | ## Customise the displayed areas (optional):
15 |
16 | If you wish to customise the areas that are displayed in the Enlighten panel, add the `areas` key in your Enlighten config, as an associative or simple array:
17 |
18 | ```php
19 | // config/enlighten.php
20 | return [
21 | //...
22 |
23 | 'areas' => ['feature', 'unit'],
24 |
25 | // or:
26 |
27 | 'areas' => ['api' => 'API', 'feature' => 'Feature'],
28 |
29 | //...
30 | ];
31 | ```
32 |
33 | Otherwise, leave that config option commented and Enlighten will show all the available areas.
34 |
35 | This is a display option, therefore it will not ignore tests. If you wish to ignore tests please refer to our readme file.
36 |
37 | ## Advanced configuration (optional)
38 |
39 | You can also create your own custom area resolver if for any reason your areas / test suites are not the second segment of your test classes.
40 |
41 | This is an advanced option, that won't be necessary in most cases.
42 |
43 | For example if your area is represented by the forth segment of your test classes instead of the second one, you can add the following logic to a boot method of a Service Provider in your app:
44 |
45 | ```php
46 | if (config('enlighten.enabled')) {
47 | \Styde\Enlighten\Facades\Settings::setCustomAreaResolver(function ($className) {
48 | return explode('\\', $className)[3];
49 | });
50 | }
51 | ```
52 |
53 | Now `Enlighten::getAreaSlug('Modules\Field\Tests\Feature\FieldGroupTest')` will return 'feature' instead of 'field'.
54 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StydeNet/enlighten/815e0ead1dc1bf79ca79c04525bcc7577c030262/preview.png
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | withPaths([
11 | __DIR__ . '/src',
12 | __DIR__ . '/tests',
13 | ])
14 | // uncomment to reach your current PHP version
15 | ->withPhpSets(php82: true)
16 | ->withRules([
17 | AddVoidReturnTypeWhereNoReturnRector::class,
18 | ])
19 | ->withSkip([
20 | ClosureToarrowFunctionRector::class => [
21 | __DIR__ . '/tests',
22 | ],
23 | ]);
24 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
1 | /* purgecss start ignore */
2 | @import "tailwindcss/base";
3 | @import "tailwindcss/components";
4 | @import "./code-snippets.css";
5 | /* purgecss end ignore */
6 |
7 | @import "tailwindcss/utilities";
8 |
9 | /* purgecss start ignore */
10 | @import "./prism.css";
11 |
12 | .code-toolbar {
13 | @apply h-full;
14 | }
15 |
16 | @media (min-width: 640px) {
17 | .md\:columns-2 { columns: 2 }
18 | }
19 |
20 | [x-cloak] {
21 | display: none;
22 | }
23 |
24 | pre.sf-dump {
25 | background-color: transparent !important;
26 | }
27 | .sf-dump {
28 | @apply h-full;
29 | }
30 |
31 | .intro-page-content {
32 | @apply px-4 pb-8 w-full mx-auto block;
33 | }
34 |
35 | .intro-page-content p:nth-child(2) {
36 | @apply flex;
37 | }
38 | .intro-page-content p:nth-child(2)>* {
39 | margin-right: 0.5rem;
40 | }
41 | /* purgecss end ignore */
42 |
--------------------------------------------------------------------------------
/resources/css/code-snippets.css:
--------------------------------------------------------------------------------
1 | .enlighten-symbol,
2 | .enlighten-int,
3 | .enlighten-float,
4 | .enlighten-string,
5 | .enlighten-class,
6 | .enlighten-property,
7 | .enlighten-bool,
8 | .enlighten-null {
9 | font-size: 0.9rem;
10 | }
11 |
12 | .enlighten-symbol {
13 | @apply text-white;
14 | }
15 | .enlighten-int {
16 | @apply text-white;
17 | }
18 | .enlighten-float {
19 | @apply text-white;
20 | }
21 | .enlighten-string {
22 | color: #a6e22e;
23 | }
24 | .enlighten-class {
25 | @apply text-white;
26 | }
27 | .enlighten-property {
28 | color: #a6e22e;
29 | }
30 | .enlighten-bool {
31 | @apply text-green-500;
32 | }
33 | .enlighten-null {
34 | color: #48beb4;
35 | }
36 |
--------------------------------------------------------------------------------
/resources/lang/en/messages.php:
--------------------------------------------------------------------------------
1 | 'All Areas',
5 | 'all_endpoints' => 'All Endpoints',
6 | 'branch_commit' => 'Branch / Commit',
7 | 'dashboard' => 'Dashboard',
8 | 'date' => 'Date',
9 | 'features' => 'Features',
10 | 'input' => 'Input',
11 | 'output' => 'Output',
12 | 'pattern' => 'Pattern',
13 | 'requirement' => 'Requirement',
14 | 'response' => 'Response',
15 | 'route_parameter' => 'Route Parameter',
16 | 'session_data' => 'Session Data',
17 | 'snippet' => 'Snippet',
18 | 'stats' => 'Stats',
19 | 'test_queries' => 'Test Queries',
20 | 'request_queries' => 'Request Queries',
21 | 'snippet_queries' => 'Snippet Queries',
22 | 'there_are_no_examples_to_show' => 'There are no examples to show.',
23 | 'time' => 'Time',
24 | 'value' => 'Value',
25 | 'view' => 'View'
26 | ];
27 |
--------------------------------------------------------------------------------
/resources/lang/es/messages.php:
--------------------------------------------------------------------------------
1 | 'Todas las áreas',
5 | 'all_endpoints' => 'Todos las URLs',
6 | 'branch_commit' => 'Rama / Confirmación',
7 | 'dashboard' => 'Tablero',
8 | 'date' => 'Fecha',
9 | 'features' => 'Funcionalidades',
10 | 'input' => 'Entrada',
11 | 'output' => 'Salida',
12 | 'pattern' => 'Patrón',
13 | 'requirement' => 'Requerimiento',
14 | 'response' => 'Respuesta',
15 | 'route_parameter' => 'Parámetro de ruta',
16 | 'session_data' => 'Datos de sesión',
17 | 'snippet' => 'Snippet',
18 | 'stats' => 'Estadísticas',
19 | 'request_queries' => 'Consultas en peticiones',
20 | 'test_queries' => 'Consultas en la prueba',
21 | 'snippet_queries' => 'Consultas en Snippets',
22 | 'there_are_no_examples_to_show' => 'No hay ejemplos para mostrar.',
23 | 'time' => 'Hora',
24 | 'value' => 'Valor',
25 | 'view' => 'Vista'
26 | ];
27 |
--------------------------------------------------------------------------------
/resources/views/area/features.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $area->name }}
3 |
4 | @foreach($groups as $group)
5 | @if($group->description)
6 | {{ $group->description }}
7 | @endif
8 |
9 |
60 | @endforeach
61 |
62 |
--------------------------------------------------------------------------------
/resources/views/area/modules.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ $area->name }}
3 |
4 |
5 |
6 | @forelse($modules as $module)
7 |
8 | @empty
9 |
10 | {{ __('enlighten::messages.there_are_no_examples_to_show') }}
11 |
12 | @endforelse
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/views/components/area-module-panel.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $module->name }}
4 |
5 |
6 |
16 |
17 |
--------------------------------------------------------------------------------
/resources/views/components/breadcrumbs.blade.php:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/resources/views/components/content-table.blade.php:
--------------------------------------------------------------------------------
1 | @props(['examples'])
2 |
3 |
4 |
{{ __('enlighten::messages.features') }}
5 |
19 |
20 |
--------------------------------------------------------------------------------
/resources/views/components/dynamic-tabs-menu.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if($tabs_collection->count() > 1)
3 |
4 | @foreach($tabs_collection as $name => $title)
5 | @if(isset($$name) && $$name instanceof $htmlable && !$$name->isEmpty())
6 |
14 | @endif
15 | @endforeach
16 |
17 | @endif
18 |
19 |
20 | @foreach($tabs_collection as $name => $title)
21 | @if(isset($$name) && $$name instanceof $htmlable && !$$name->isEmpty())
22 |
23 | {!! $$name !!}
24 |
25 | @endif
26 | @endforeach
27 |
28 |
29 |
--------------------------------------------------------------------------------
/resources/views/components/dynamic-tabs.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if($tabs_collection->count() > 1)
3 |
4 |
5 | @foreach($tabs_collection as $name => $title)
6 | @if(isset($$name) && $$name instanceof $htmlable && !$$name->isEmpty())
7 |
12 | @endif
13 | @endforeach
14 |
15 |
16 | @endif
17 |
18 |
19 | @foreach($tabs_collection as $name => $title)
20 | @if(isset($$name) && $$name instanceof $htmlable && !$$name->isEmpty())
21 |
22 | {!! $$name !!}
23 |
24 | @endif
25 | @endforeach
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/views/components/edit-button.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/views/components/example-breadcrumbs.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/example-snippets.blade.php:
--------------------------------------------------------------------------------
1 | @foreach($snippets as $snippet)
2 |
3 |
4 | {{ __('enlighten::messages.snippet') }}
5 |
6 |
7 |
8 | {{ __('enlighten::messages.output') }}
9 |
11 | {!! $snippet->result_code !!}
12 |
13 |
14 |
15 | @endforeach
16 |
--------------------------------------------------------------------------------
/resources/views/components/example-tabs.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if($showException)
3 |
4 |
5 |
6 | @endif
7 | @if($showQueries)
8 |
9 |
10 |
11 | @endif
12 | @if($showRequests)
13 |
14 |
15 | @foreach($requestTabs as $tab)
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | @if($tab->showSession)
26 |
27 | {{ __('enlighten::messages.session_data') }}
28 |
29 |
30 | @endif
31 |
32 |
33 | @if($tab->showPreviewOnly)
34 |
35 | @else
36 |
37 | @endif
38 |
39 |
40 |
41 | @endforeach
42 |
43 |
44 | @endif
45 |
46 |
--------------------------------------------------------------------------------
/resources/views/components/exception-info.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ $exception->class_name }}: {{ $exception->message }}
5 |
6 |
7 |
8 |
9 | @if($exception->extra)
10 |
11 |
12 | @endif
13 |
14 | @foreach($trace as $data)
15 |
16 | @if(!empty($title))
17 |
{{ $title }}
19 | @endif
20 |
21 |
22 | @if($data['file'] && $data['line'])
23 |
24 | {{ $data['file'] }} : {{ $data['line'] }} |
25 |
26 | @endif
27 |
28 |
29 | {{ $data['function'] }}
30 | @if(!empty($data['args']))
31 |
32 |
33 |
34 |
35 | @endif
36 | |
37 |
38 | @if(!empty($data['args']))
39 |
40 |
41 |
46 | |
47 |
48 | @endif
49 |
50 |
51 |
52 | @unless($loop->last)
53 |
54 | @endunless
55 | @endforeach
56 |
57 |
--------------------------------------------------------------------------------
/resources/views/components/expansible-section.blade.php:
--------------------------------------------------------------------------------
1 | @props(['collapsed' => true])
2 |
3 |
13 |
19 |
20 | {{ $slot }}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/resources/views/components/group-breadcrumbs.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/components/html-response.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @if ($showHtml)
7 |
8 |
9 |
10 | @endif
11 | @if ($showTemplate)
12 |
13 |
14 |
15 | @endif
16 |
17 |
18 |
--------------------------------------------------------------------------------
/resources/views/components/iframe.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/views/components/info-panel.blade.php:
--------------------------------------------------------------------------------
1 | merge(['class' => 'bg-gray-800 rounded-md overflow-hidden']) }}>
2 |
3 | {{ $title }}
4 |
5 | {!! $slot !!}
6 |
7 |
--------------------------------------------------------------------------------
/resources/views/components/json-response.blade.php:
--------------------------------------------------------------------------------
1 | {{ enlighten_json_prettify($json) }}
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/key-value.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if(!empty($title))
3 |
{{ $title }}
4 | @endif
5 |
6 |
7 | @foreach($items as $key => $value)
8 |
9 | {{ $key }}: |
10 | {!! $value !!} |
12 |
13 | @endforeach
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/views/components/panel-title.blade.php:
--------------------------------------------------------------------------------
1 | {{ $slot }}
3 |
--------------------------------------------------------------------------------
/resources/views/components/pre.blade.php:
--------------------------------------------------------------------------------
1 | @props(['language', 'code'])
2 |
3 |
--------------------------------------------------------------------------------
/resources/views/components/queries-info.blade.php:
--------------------------------------------------------------------------------
1 | @props(['example'])
2 |
3 | @php
4 | $queryGroups = $example->queries->groupBy('request_id');
5 | @endphp
6 |
7 | @foreach($queryGroups as $group)
8 |
11 |
24 | @if($group->first()->context === 'request')
25 |
26 |
27 | @foreach($group as $query)
28 |
29 | {{ __('enlighten::messages.time') }}: {{ $query->time }}
30 |
31 | @if($query->bindings)
32 |
33 | @endif
34 |
35 | @endforeach
36 |
37 |
42 |
43 | @else
44 |
45 |
46 | @foreach($group as $query)
47 |
48 | {{ __('enlighten::messages.time') }}: {{ $query->time }}
49 |
50 | @if($query->bindings)
51 |
52 | @endif
53 |
54 | @endforeach
55 |
56 |
57 | @endif
58 |
59 | @endforeach
60 |
--------------------------------------------------------------------------------
/resources/views/components/request-info.blade.php:
--------------------------------------------------------------------------------
1 |
2 | Request
3 |
4 |
5 |
6 | @if($showRouteParameters)
7 |
8 | @endif
9 |
10 | @if($showInput)
11 |
12 | @endif
13 |
14 | @if($showHeaders)
15 |
16 | @endif
17 |
18 |
--------------------------------------------------------------------------------
/resources/views/components/request-input-table.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ __('enlighten::messages.input') }} |
5 | {{ __('enlighten::messages.value') }} |
6 |
7 |
8 |
9 | @foreach($input as $name => $value)
10 |
11 | {{ $name }} |
12 |
13 | @if(is_bool($value))
14 | {{ $value ? 'true' : 'false' }}
15 | @else
16 | {{ $value }}
17 | @endif
18 | |
19 |
20 | @endforeach
21 |
22 |
23 |
--------------------------------------------------------------------------------
/resources/views/components/response-info.blade.php:
--------------------------------------------------------------------------------
1 |
2 | {{ __('enlighten::messages.response') }}
3 |
4 | {{ $status }}
5 | {{ $request->response_type }}
6 |
7 |
8 | @if($showHeaders)
9 |
10 | @endif
11 |
12 |
--------------------------------------------------------------------------------
/resources/views/components/response-preview.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if($request->response_type === 'JSON')
3 |
4 | @elseif($request->response_type === 'HTML')
5 |
6 | @endif
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/route-parameters-table.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ __('enlighten::messages.route_parameter') }} |
5 | {{ __('enlighten::messages.pattern') }} |
6 | {{ __('enlighten::messages.requirement') }} |
7 |
8 |
9 |
10 | @foreach($parameters as $parameter)
11 |
12 | {{ $parameter['name'] }} |
13 | {{ $parameter['pattern'] }} |
14 | {{ $parameter['requirement'] }} |
15 |
16 | @endforeach
17 |
18 |
19 |
--------------------------------------------------------------------------------
/resources/views/components/runs-table.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ __('enlighten::messages.branch_commit') }} |
5 | {{ __('enlighten::messages.date') }} |
6 | {{ __('enlighten::messages.stats') }} |
7 |
8 |
9 |
10 | @foreach ($runs as $run)
11 |
12 |
13 | {{ $run->branch }}
14 | @if ($run->modified)*@endif
15 | {{ $run->head }}
16 | |
17 | {{ $run->created_at->toDateTimeString() }} |
18 |
19 |
20 | |
21 |
22 | {{ __('enlighten::messages.view') }}
23 | |
24 |
25 | @endforeach
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/views/components/scroll-to-top.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/resources/views/components/search-box-static.blade.php:
--------------------------------------------------------------------------------
1 |
35 |
--------------------------------------------------------------------------------
/resources/views/components/search-box.blade.php:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/resources/views/components/stats-badge.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if ($color == 'success')
3 | {{ $total }}
4 | @else
5 | {{ $positive }} / {{ $total }}
6 | @endif
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/components/status-badge.blade.php:
--------------------------------------------------------------------------------
1 |
2 | @if ($color === 'success')
3 |
4 | @elseif ($color === 'failure')
5 |
6 | @else
7 |
8 | @endif
9 |
10 |
--------------------------------------------------------------------------------
/resources/views/components/svg-logo.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/resources/views/example/show.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ $example->title }}
11 |
12 |
13 |
14 |
15 | @if($example->description)
16 | {{ $example->description }}
17 | @endif
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/views/group/show.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ $title }}
8 |
9 | @if($group->description)
10 | {{ $group->description }}
11 | @endif
12 |
13 |
61 |
62 |
--------------------------------------------------------------------------------
/resources/views/intro.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {!! $content !!}
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/resources/views/layout/main.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 | Laravel Enlighten
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ $top ?? '' }}
18 | {{ $title ?? 'Dashboard' }}
19 | {{ $slot }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/resources/views/run/index.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/resources/views/search/results.blade.php:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/CodeExamples/BaseCodeResultFormat.php:
--------------------------------------------------------------------------------
1 | ', $code, '']);
10 | }
11 |
12 | public function indentation($level): string
13 | {
14 | return str_repeat(' ', $level * 4);
15 | }
16 |
17 | public function space(): string
18 | {
19 | return ' ';
20 | }
21 |
22 | public function line(): string
23 | {
24 | return PHP_EOL;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/CodeExamples/CodeExampleCreator.php:
--------------------------------------------------------------------------------
1 | exampleCreator->getCurrentExample();
19 |
20 | if (is_null($testExample)) {
21 | return $callback();
22 | }
23 |
24 | $testExample->addSnippet($key, $this->codeInspector->getCodeFrom($callback));
25 |
26 | try {
27 | $result = call_user_func($callback);
28 |
29 | $testExample->setSnippetResult(CodeResultTransformer::export($result));
30 |
31 | return $result;
32 | } catch (Throwable $throwable) {
33 | $this->exampleCreator->captureException($throwable);
34 |
35 | throw $throwable;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/CodeExamples/CodeInspector.php:
--------------------------------------------------------------------------------
1 | getFileName());
15 |
16 | return collect(explode(PHP_EOL, $code))
17 | ->slice(
18 | $reflection->getStartLine(),
19 | $reflection->getEndLine() - $reflection->getStartLine() - 1
20 | )
21 | ->pipe(fn ($collection) => $this->removeExternalIndentation($collection))
22 | ->pipe(fn ($collection) => $this->removeReturnKeyword($collection))
23 | ->implode("\n");
24 | }
25 |
26 | /**
27 | * Remove the indentation outside the scope of the current code block.
28 | */
29 | private function removeExternalIndentation(Collection $lines)
30 | {
31 | $leadingSpacesInFirstLine = $this->numberOfLeadingSpaces($lines->first());
32 |
33 | return $lines->transform(fn ($line) => preg_replace("/^( {{$leadingSpacesInFirstLine}})/", '', (string) $line));
34 | }
35 |
36 | private function numberOfLeadingSpaces(string $str)
37 | {
38 | preg_match('/^( +)/', $str, $matches);
39 |
40 | return strlen($matches[1]);
41 | }
42 |
43 | /**
44 | * Remove the return keyword in the first or in the last line of the code block.
45 | */
46 | private function removeReturnKeyword(Collection $lines)
47 | {
48 | if (str_starts_with((string) $lines->first(), 'return ')) {
49 | return $lines->prepend(substr((string) $lines->shift(), 7));
50 | }
51 |
52 | if (str_starts_with((string) $lines->last(), 'return ')) {
53 | return $lines->add(substr((string) $lines->pop(), 7));
54 | }
55 |
56 | return $lines;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/CodeExamples/CodeResultExporter.php:
--------------------------------------------------------------------------------
1 | currentLevel = 1;
18 |
19 | return $this->format->block(
20 | $this->exportIndentation()
21 | . $this->exportValue($snippet)
22 | );
23 | }
24 |
25 | private function exportValue($value): string
26 | {
27 | if (isset($value[ExampleSnippet::CLASS_NAME])) {
28 | return $this->exportObject($value);
29 | }
30 | return match (gettype($value)) {
31 | 'array' => $this->exportArray($value),
32 | 'integer' => $this->format->integer($value),
33 | 'double', 'float' => $this->format->float($value),
34 | 'string' => $this->format->string($value),
35 | 'boolean' => $this->format->bool($value ? 'true' : 'false'),
36 | 'NULL', 'null' => $this->format->null(),
37 | default => '',
38 | };
39 | }
40 |
41 | private function exportArray($items): string
42 | {
43 | $result = $this->format->symbol('[').$this->format->line();
44 |
45 | if ($this->isAssoc($items)) {
46 | $result .= $this->exportAssocArrayItems($items);
47 | } else {
48 | $result .= $this->exportArrayItems($items);
49 | }
50 |
51 | $result .= $this->exportIndentation()
52 | . $this->format->symbol(']');
53 |
54 | return $result;
55 | }
56 |
57 | public function isAssoc(array $array): bool
58 | {
59 | return array_keys($array) !== range(0, count($array) - 1);
60 | }
61 |
62 | private function exportAssocArrayItems($items): string
63 | {
64 | $result = '';
65 |
66 | $this->currentLevel += 1;
67 |
68 | foreach ($items as $key => $value) {
69 | $result .= $this->exportIndentation()
70 | . $this->exportValue($key)
71 | . $this->format->space()
72 | . $this->format->symbol('=>')
73 | . $this->format->space()
74 | . $this->exportValue($value)
75 | . $this->format->symbol(',')
76 | . $this->format->line();
77 | }
78 |
79 | $this->currentLevel -= 1;
80 |
81 | return $result;
82 | }
83 |
84 | private function exportArrayItems($items): string
85 | {
86 | $result = '';
87 |
88 | $this->currentLevel += 1;
89 |
90 | foreach ($items as $item) {
91 | $result .= $this->exportIndentation()
92 | . $this->exportValue($item)
93 | . $this->format->symbol(',')
94 | . $this->format->line();
95 | }
96 |
97 | $this->currentLevel -= 1;
98 |
99 | return $result;
100 | }
101 |
102 | private function exportObject($snippet): string
103 | {
104 | $className = $snippet[ExampleSnippet::CLASS_NAME];
105 | $attributes = $snippet[ExampleSnippet::ATTRIBUTES] ?? [];
106 |
107 | $result = $this->format->className($className)
108 | . $this->format->space()
109 | . $this->format->symbol('{')
110 | . $this->format->line();
111 |
112 | $this->currentLevel += 1;
113 |
114 | foreach ($attributes as $property => $value) {
115 | $result .= $this->format->indentation($this->currentLevel)
116 | . $this->format->propertyName($property)
117 | . $this->format->symbol(':')
118 | . $this->format->space()
119 | . $this->exportValue($value)
120 | . $this->format->symbol(',')
121 | . $this->format->line();
122 | }
123 |
124 | $this->currentLevel -= 1;
125 |
126 | $result .= $this->format->indentation($this->currentLevel)
127 | .$this->format->symbol('}');
128 |
129 | return $result;
130 | }
131 |
132 | private function exportIndentation(): string
133 | {
134 | return $this->format->indentation($this->currentLevel);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/CodeExamples/CodeResultFormat.php:
--------------------------------------------------------------------------------
1 | transformInArray($result);
19 | }
20 |
21 | public static function exportProvidedData(array $data): array
22 | {
23 | return static::export($data);
24 | }
25 |
26 | private function transformInArray($result, int $currentLevel = 0)
27 | {
28 | if ($result instanceof Closure) {
29 | return $this->exportFunction($result);
30 | }
31 |
32 | if (is_object($result)) {
33 | return $this->exportObject($result, $currentLevel);
34 | }
35 |
36 | if (! is_array($result)) {
37 | return $result;
38 | }
39 |
40 | return array_map(fn ($item) => $this->transformInArray($item, $currentLevel), $result);
41 | }
42 |
43 | private function exportFunction($result): array
44 | {
45 | $functionReflection = new ReflectionFunction($result);
46 |
47 | return [
48 | ExampleSnippet::FUNCTION => ExampleSnippet::ANONYMOUS_FUNCTION,
49 | ExampleSnippet::PARAMETERS => $this->exportParameters($functionReflection->getParameters()),
50 | ExampleSnippet::RETURN_TYPE => $functionReflection->hasReturnType() ? $functionReflection->getReturnType()->getName(): null,
51 | ];
52 | }
53 |
54 | private function exportParameters(array $parameters): array
55 | {
56 | return collect($parameters)
57 | ->map(fn (ReflectionParameter $parameter) => [
58 | ExampleSnippet::TYPE => $parameter->hasType() ? $parameter->getType()->getName() : null,
59 | ExampleSnippet::PARAMETER => $parameter->getName(),
60 | ExampleSnippet::OPTIONAL => $parameter->isOptional(),
61 | ExampleSnippet::DEFAULT => $parameter->isOptional() ? $parameter->getDefaultValue() : null,
62 | ])
63 | ->all();
64 | }
65 |
66 | private function exportObject(object $result, int $currentLevel): array
67 | {
68 | return [
69 | ExampleSnippet::CLASS_NAME => $result::class,
70 | ExampleSnippet::ATTRIBUTES => $this->exportAttributes($result, $currentLevel),
71 | ];
72 | }
73 |
74 | private function exportAttributes(object $result, int $currentLevel)
75 | {
76 | if ($currentLevel >= static::$maxNestedLevel) {
77 | return null;
78 | }
79 |
80 | return $this->transformInArray($this->getObjectAttributes($result), $currentLevel + 1);
81 | }
82 |
83 | private function getObjectAttributes(object $object)
84 | {
85 | if ($object instanceof Enumerable) {
86 | return ['items' => $object->all()];
87 | }
88 |
89 | if ($object instanceof Arrayable) {
90 | return $object->toArray();
91 | }
92 |
93 | return get_object_vars($object);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/CodeExamples/HtmlResultFormat.php:
--------------------------------------------------------------------------------
1 | {$symbol}";
10 | }
11 |
12 | public function integer(int $value): string
13 | {
14 | return "{$value}";
15 | }
16 |
17 | public function float($value): string
18 | {
19 | return "{$value}";
20 | }
21 |
22 | public function string($value): string
23 | {
24 | return sprintf('"%s"', $value);
25 | }
26 |
27 | public function className($className): string
28 | {
29 | return "{$className}";
30 | }
31 |
32 | public function propertyName(string $property)
33 | {
34 | return "{$property}";
35 | }
36 |
37 | public function bool($value): string
38 | {
39 | return "{$value}";
40 | }
41 |
42 | public function null(): string
43 | {
44 | return 'null';
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/CodeExamples/PlainCodeResultFormat.php:
--------------------------------------------------------------------------------
1 | '.$code.'');
12 | }
13 |
14 | public function symbol(string $symbol): string
15 | {
16 | return $symbol;
17 | }
18 |
19 | public function integer(int $value): string
20 | {
21 | return $value;
22 | }
23 |
24 | public function float(float $value): string
25 | {
26 | return $value;
27 | }
28 |
29 | public function string(string $value): string
30 | {
31 | return sprintf('"%s"', $value);
32 | }
33 |
34 | public function bool(string $value): string
35 | {
36 | return strtoupper($value);
37 | }
38 |
39 | public function null(): string
40 | {
41 | return 'NULL';
42 | }
43 |
44 | public function className(string $name): string
45 | {
46 | return $name;
47 | }
48 |
49 | public function propertyName(string $name): string
50 | {
51 | return $name;
52 | }
53 |
54 | public function indentation($level): string
55 | {
56 | return '';
57 | }
58 |
59 | public function space(): string
60 | {
61 | return ' ';
62 | }
63 |
64 | public function line(): string
65 | {
66 | return ' ';
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Console/Commands/ExportDocumentationCommand.php:
--------------------------------------------------------------------------------
1 | getLatestRuns();
26 |
27 | if ($runs->isEmpty()) {
28 | $this->line('');
29 | $this->warn('There are no runs available. Please setup `Enlighten` and run the tests first.');
30 | return;
31 | }
32 |
33 | $selectedRun = $runs->firstWhere('signature', $this->choice(
34 | "Please select the run you'd like to export",
35 | $runs->pluck('signature')->all(),
36 | $runs->first()->signature
37 | ));
38 |
39 | $baseDir = $this->ask('In which directory would you like to export the documentation?', config('enlighten.docs_base_dir'));
40 |
41 | $baseUrl = $this->normalizeBaseUrl($this->ask("What's the base URL for this documentation going to be?", config('enlighten.docs_base_url')));
42 |
43 | $this->warn("Exporting the documentation for `{$selectedRun->signature}`...\n");
44 |
45 | $this->exporter->export($selectedRun, $baseDir, $baseUrl);
46 |
47 | $this->info("`{$selectedRun->signature}` run exported!");
48 | }
49 |
50 | protected function getLatestRuns(): Collection
51 | {
52 | return Run::query()
53 | ->latest()
54 | ->take(5)
55 | ->get();
56 | }
57 |
58 | private function normalizeBaseUrl($url): string
59 | {
60 | if (Str::startsWith($url, ['http://', 'https://'])) {
61 | return $url;
62 | }
63 |
64 | return '/'.ltrim((string) $url, '/');
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Console/Commands/FreshCommand.php:
--------------------------------------------------------------------------------
1 | call('db:wipe', [
17 | '--database' => 'enlighten',
18 | '--force' => $this->option('force'),
19 | ]);
20 |
21 | $this->call('enlighten:migrate', [
22 | '--force' => $this->option('force'),
23 | ]);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Console/Commands/GenerateDocumentationCommand.php:
--------------------------------------------------------------------------------
1 | ignoreValidationErrors();
23 | }
24 |
25 | public function handle(): void
26 | {
27 | $runBuilder = app(RunBuilder::class);
28 |
29 | $runBuilder->reset();
30 | $runBuilder->save();
31 |
32 | $this->addCustomBootstrapToGlobalArguments();
33 |
34 | $this->runTests();
35 |
36 | $run = $runBuilder->getRun();
37 |
38 | if ($run->isEmpty()) {
39 | $this->printMissingSetupWarning();
40 | } else {
41 | $this->printFailedExamples($run);
42 | $this->printDocumentationLink($run);
43 | }
44 | }
45 |
46 | private function addCustomBootstrapToGlobalArguments(): void
47 | {
48 | $_SERVER['argv'] = array_merge(
49 | array_slice($_SERVER['argv'], 0, 2),
50 | ['--bootstrap=vendor/styde/enlighten/src/Tests/bootstrap.php'],
51 | array_slice($_SERVER['argv'], 2)
52 | );
53 | }
54 |
55 | private function runTests(): void
56 | {
57 | $this->call('test', [
58 | '--parallel' => $this->option('parallel'),
59 | '--recreate-databases' => $this->option('recreate-databases')
60 | ]);
61 | }
62 |
63 | private function printMissingSetupWarning(): void
64 | {
65 | $this->output->newLine();
66 | $this->alert('The documentation was not generated');
67 | $this->output->newLine();
68 | $this->error('Did you forget to call `$this->setUpEnlighten();` in your tests?');
69 | $this->warn('Learn more: https://github.com/StydeNet/enlighten#installation');
70 | }
71 |
72 | private function printFailedExamples(RunContract $run): void
73 | {
74 | $failedExamples = $run->getFailedExamples();
75 |
76 | if ($failedExamples->isNotEmpty()) {
77 | $this->printFailedExamplesHeader($failedExamples);
78 | $this->printFailedExampleItems($failedExamples);
79 | }
80 | }
81 |
82 | private function printFailedExamplesHeader($examples): void
83 | {
84 | $this->output->newLine();
85 | $this->error(sprintf(
86 | '⚠️ %s %s failed:',
87 | $examples->count(),
88 | Str::plural('test', $examples->count())
89 | ));
90 | }
91 |
92 | private function printFailedExampleItems($examples): void
93 | {
94 | $examples->each(function ($example) {
95 | $this->output->newLine();
96 | $this->line("❌ {$example->getTitle()}:");
97 | $this->warn($example->getUrl());
98 | });
99 | }
100 |
101 | private function printDocumentationLink(RunContract $run): void
102 | {
103 | $this->output->newLine();
104 | $this->line('⚡ Check your documentation at:');
105 | $this->info($run->url());
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Console/Commands/InstallCommand.php:
--------------------------------------------------------------------------------
1 | publishBuildAndConfigFiles();
17 |
18 | $this->output->newLine();
19 |
20 | if ($this->setupEnlightenInTestCase()) {
21 | $this->info('Installation complete!');
22 | } else {
23 | $this->error('The installer has detected changes in your TestCase class.');
24 | $this->error('Please setup Enlighten manually with the link below:');
25 | $this->error('https://github.com/StydeNet/enlighten#manual-setup');
26 | }
27 |
28 | $this->output->newLine();
29 | $this->warn('Please remember to create and setup the database for Enlighten and to change the APP_URL env variable if necessary.');
30 | $this->output->newLine();
31 | $this->info("After running `php artisan enlighten`, you'll find your documentation by visiting: ".url('/enlighten'));
32 | }
33 |
34 | private function publishBuildAndConfigFiles(): void
35 | {
36 | $this->call('vendor:publish', ['--tag' => 'enlighten']);
37 | }
38 |
39 | private function setupEnlightenInTestCase(): bool
40 | {
41 | $appTestCase = File::get(base_path('tests/TestCase.php'));
42 | $baseTestCase = File::get(__DIR__.'/stubs/BaseTestCase.php.stub');
43 |
44 | if ($appTestCase != $baseTestCase) {
45 | return false;
46 | }
47 |
48 | $enlightenTestCase = File::get(__DIR__ . '/stubs/EnlightenTestCase.php.stub');
49 | File::put(base_path('tests/TestCase.php'), $enlightenTestCase);
50 |
51 | return true;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Console/Commands/MigrateCommand.php:
--------------------------------------------------------------------------------
1 | call('migrate', [
18 | '--database' => 'enlighten',
19 | '--realpath' => true,
20 | '--path' => __DIR__ . '/../../../database/migrations',
21 | '--force' => $this->option('force'),
22 | '--pretend' => $this->option('pretend'),
23 | ]);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Console/Commands/stubs/BaseTestCase.php.stub:
--------------------------------------------------------------------------------
1 | setUpEnlighten();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Console/ContentRequest.php:
--------------------------------------------------------------------------------
1 | httpKernel->handle(Request::createFromBase($symfonyRequest));
21 |
22 | return $response->getContent();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Console/DocumentationExporter.php:
--------------------------------------------------------------------------------
1 | baseDir = rtrim($baseDir, '/');
28 | $this->staticBaseUrl = rtrim($staticBaseUrl, '/');
29 | $this->originalBaseUrl = $run->base_url;
30 |
31 | $this->createDirectory('/');
32 |
33 | $this->exportAssets();
34 |
35 | $this->exportRunWithAreas($run);
36 |
37 | $run->groups->each(function ($group) {
38 | $this->exportGroupWithExamples($group);
39 | });
40 |
41 | $this->exportSearchJson($run);
42 | }
43 |
44 | private function exportAssets(): void
45 | {
46 | $this->filesystem->deleteDirectory("{$this->baseDir}/assets");
47 |
48 | $this->filesystem->copyDirectory(__DIR__.'/../../dist', "{$this->baseDir}/assets");
49 | }
50 |
51 | private function exportRunWithAreas(Run $run): void
52 | {
53 | $this->createFile('index.html', $this->withContentFrom($run->url));
54 |
55 | $this->createDirectory('/areas');
56 |
57 | $run->areas->each(function ($area) use ($run) {
58 | $this->exportArea($run, $area);
59 | });
60 | }
61 |
62 | private function exportArea(Run $run, Area $area): void
63 | {
64 | $this->createFile("areas/{$area->slug}.html", $this->withContentFrom($run->areaUrl($area->slug)));
65 | }
66 |
67 | private function exportGroupWithExamples(ExampleGroup $group): void
68 | {
69 | $this->createFile("{$group->slug}.html", $this->withContentFrom($group->url));
70 |
71 | $this->createDirectory($group->slug);
72 |
73 | $group->examples->each(function (Example $example) use ($group) {
74 | $this->exportExample($example->setRelation('group', $group));
75 | });
76 | }
77 |
78 | private function exportExample(Example $example): void
79 | {
80 | $this->createFile(
81 | "{$example->group->slug}/{$example->slug}.html",
82 | $this->withContentFrom($example->url)
83 | );
84 | }
85 |
86 | private function exportSearchJson(Run $run): void
87 | {
88 | $this->createFile(
89 | 'search.json',
90 | json_encode(['items' => $this->getSearchItems($run)], JSON_THROW_ON_ERROR)
91 | );
92 | }
93 |
94 | private function getSearchItems(Run $run)
95 | {
96 | return $run->groups
97 | ->load('examples')
98 | ->flatMap(fn ($group) => $group->examples->map(fn ($example) => [
99 | 'section' => "{$group->area_title} / {$group->title}",
100 | 'title' => $example->title,
101 | 'url' => $this->getStaticUrl($example->url),
102 | ]))
103 | ->sortBy('title')
104 | ->values();
105 | }
106 |
107 | private function createDirectory($path): void
108 | {
109 | if ($this->filesystem->isDirectory("{$this->baseDir}/$path")) {
110 | return;
111 | }
112 |
113 | $this->filesystem->makeDirectory("{$this->baseDir}/$path", 0755);
114 | }
115 |
116 | private function createFile(string $filename, string $contents): void
117 | {
118 | $this->filesystem->put("{$this->baseDir}/{$filename}", $contents);
119 | }
120 |
121 | private function withContentFrom(string $url): string
122 | {
123 | return $this->replaceUrls($this->request->getContent($url));
124 | }
125 |
126 | private function replaceUrls(string $contents)
127 | {
128 | // Search json path
129 | $contents = preg_replace('@fetch\((.*?)search.json\'\)@', "fetch('{$this->staticBaseUrl}/search.json')", $contents);
130 |
131 | // Assets paths
132 | $contents = str_replace('/vendor/enlighten/', "{$this->staticBaseUrl}/assets/", (string) $contents);
133 |
134 | // Internal links
135 | return preg_replace_callback(
136 | '@'.$this->originalBaseUrl.'([^"]+)?@',
137 | fn ($matches) => $this->getStaticUrl($matches[0]),
138 | $contents
139 | );
140 | }
141 |
142 | private function getStaticUrl(string $originalUrl): string
143 | {
144 | $result = str_replace($this->originalBaseUrl, $this->staticBaseUrl, $originalUrl);
145 |
146 | if ($result === $this->staticBaseUrl) {
147 | return $result;
148 | }
149 |
150 | return "{$result}.html";
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Contracts/Example.php:
--------------------------------------------------------------------------------
1 | title = $title;
62 |
63 | return $this;
64 | }
65 |
66 | public function setMethodName(string $methodName): ExampleBuilder
67 | {
68 | $this->methodName = $methodName;
69 |
70 | return $this;
71 | }
72 |
73 | public function setProvidedData(array $data = null): ExampleBuilder
74 | {
75 | $this->providedData = $data;
76 |
77 | return $this;
78 | }
79 |
80 | public function setDataName($name = null): ExampleBuilder
81 | {
82 | $this->dataName = $name;
83 |
84 | return $this;
85 | }
86 |
87 | public function setOrderNum(int $order_num): ExampleBuilder
88 | {
89 | $this->order_num = $order_num;
90 |
91 | return $this;
92 | }
93 |
94 | public function setDescription(?string $description): ExampleBuilder
95 | {
96 | $this->description = $description;
97 |
98 | return $this;
99 | }
100 |
101 | public function setSlug(string $slug): ExampleBuilder
102 | {
103 | $this->slug = $slug;
104 |
105 | return $this;
106 | }
107 |
108 | public function setStatus(string $testStatus, string $status): void
109 | {
110 | $this->testStatus = $testStatus;
111 | $this->status = $status;
112 | }
113 |
114 | public function setLine(int $line): ExampleBuilder
115 | {
116 | $this->line = $line;
117 |
118 | return $this;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Drivers/BaseExampleGroupBuilder.php:
--------------------------------------------------------------------------------
1 | area = $area;
42 |
43 | return $this;
44 | }
45 |
46 | public function setClassName(string $className): ExampleGroupBuilder
47 | {
48 | $this->className = $className;
49 |
50 | return $this;
51 | }
52 |
53 | public function is(string $name): bool
54 | {
55 | return $this->className === $name;
56 | }
57 |
58 | public function setOrderNum(int $orderNum): ExampleGroupBuilder
59 | {
60 | $this->orderNum = $orderNum;
61 |
62 | return $this;
63 | }
64 |
65 | public function setDescription(?string $description): ExampleGroupBuilder
66 | {
67 | $this->description = $description;
68 |
69 | return $this;
70 | }
71 |
72 | public function setSlug(string $slug): ExampleGroupBuilder
73 | {
74 | $this->slug = $slug;
75 |
76 | return $this;
77 | }
78 |
79 | public function setTitle(string $title): ExampleGroupBuilder
80 | {
81 | $this->title = $title;
82 |
83 | return $this;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Drivers/DatabaseExampleBuilder.php:
--------------------------------------------------------------------------------
1 | currentRequests = new Collection;
35 | }
36 |
37 | public function addRequest(RequestInfo $request): void
38 | {
39 | $this->save();
40 |
41 | $this->currentRequests->push($this->example->requests()->create([
42 | 'example_id' => $this->example->id,
43 | 'request_headers' => $request->getHeaders(),
44 | 'request_method' => $request->getMethod(),
45 | 'request_path' => $request->getPath(),
46 | 'request_query_parameters' => $request->getQueryParameters(),
47 | 'request_input' => $request->getInput(),
48 | 'request_files' => $request->getFiles(),
49 | ]));
50 | }
51 |
52 | public function setResponse(ResponseInfo $response, bool $followsRedirect, RouteInfo $routeInfo, array $session): void
53 | {
54 | $this->save();
55 |
56 | $this->currentRequests->pop()->update([
57 | // Route
58 | 'route' => $routeInfo->getUri(),
59 | 'route_parameters' => $routeInfo->getParameters(),
60 | // Response
61 | 'response_status' => $response->getStatusCode(),
62 | 'follows_redirect' => $followsRedirect,
63 | 'response_headers' => $response->getHeaders(),
64 | 'response_body' => $response->getContent(),
65 | 'response_template' => $response->getTemplate(),
66 | // Session
67 | 'session_data' => $session,
68 | ]);
69 | }
70 |
71 | public function setException(ExceptionInfo $exception): void
72 | {
73 | $this->example->exception->fill([
74 | 'class_name' => $exception->getClassName(),
75 | 'code' => $exception->getCode(),
76 | 'message' => $exception->getMessage(),
77 | 'file' => $exception->getFile(),
78 | 'line' => $exception->getLine(),
79 | 'trace' => $exception->getTrace(),
80 | 'extra' => $exception->getData(),
81 | ])->save();
82 | }
83 |
84 | public function addQuery(QueryExecuted $queryExecuted): void
85 | {
86 | $this->save();
87 |
88 | $this->example->queries()->create([
89 | 'sql' => $queryExecuted->sql,
90 | 'bindings' => $queryExecuted->bindings,
91 | 'time' => $queryExecuted->time,
92 | 'request_id' => optional($this->currentRequests->last())->id,
93 | 'snippet_id' => optional($this->currentSnippet)->id,
94 | ]);
95 | }
96 |
97 | public function addSnippet($key, string $code): void
98 | {
99 | $this->save();
100 |
101 | $this->currentSnippet = $this->example->snippets()->create([
102 | 'key' => $key,
103 | 'code' => $code,
104 | ]);
105 | }
106 |
107 | public function setSnippetResult($result): void
108 | {
109 | $this->currentSnippet->update(['result' => $result]);
110 |
111 | $this->currentSnippet = null;
112 | }
113 |
114 | public function build(): ExampleContract
115 | {
116 | $this->save();
117 |
118 | $this->example->update([
119 | 'test_status' => $this->testStatus,
120 | 'status' => $this->status,
121 | ]);
122 |
123 | return $this->example;
124 | }
125 |
126 | private function save(): void
127 | {
128 | if ($this->example != null) {
129 | return;
130 | }
131 |
132 | $group = $this->exampleGroupBuilder->save();
133 |
134 | $this->example = Example::create([
135 | 'group_id' => $group->id,
136 | 'method_name' => $this->methodName,
137 | 'slug' => $this->slug,
138 | 'title' => $this->title,
139 | 'data_name' => $this->dataName,
140 | 'provided_data' => $this->providedData,
141 | 'description' => $this->description,
142 | 'order_num' => $this->order_num,
143 | 'line' => $this->line,
144 | 'test_status' => Status::UNKNOWN,
145 | 'status' => Status::UNKNOWN,
146 | ]);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Drivers/DatabaseExampleGroupBuilder.php:
--------------------------------------------------------------------------------
1 | exampleGroup !== null) {
22 | return $this->exampleGroup;
23 | }
24 |
25 | $run = $this->runBuilder->save();
26 |
27 | $this->exampleGroup = ExampleGroup::updateOrCreate([
28 | 'run_id' => $run->id,
29 | 'class_name' => $this->className,
30 | 'title' => $this->title,
31 | 'description' => $this->description,
32 | 'area' => $this->area,
33 | 'slug' => $this->slug,
34 | 'order_num' => $this->orderNum,
35 | ]);
36 |
37 | return $this->exampleGroup;
38 | }
39 |
40 | public function newExample(): ExampleBuilder
41 | {
42 | return new DatabaseExampleBuilder($this);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Drivers/DatabaseRunBuilder.php:
--------------------------------------------------------------------------------
1 | initRun();
31 |
32 | $this->run->groups()->delete();
33 | }
34 |
35 | public function save(): RunContract
36 | {
37 | $this->initRun();
38 |
39 | $this->run->save();
40 |
41 | return $this->run;
42 | }
43 |
44 | public function getRun(): RunContract
45 | {
46 | $this->initRun();
47 |
48 | return $this->run->fresh();
49 | }
50 |
51 | protected function initRun()
52 | {
53 | if ($this->run !== null) {
54 | return;
55 | }
56 |
57 | $this->run = Run::firstOrNew([
58 | 'branch' => VersionControl::currentBranch(),
59 | 'head' => VersionControl::head(),
60 | 'modified' => VersionControl::modified(),
61 | ]);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Enlighten.php:
--------------------------------------------------------------------------------
1 | createSnippet($callback, $key);
44 | } catch (BindingResolutionException) {
45 | throw new LaravelNotPresent;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/ExampleProfile.php:
--------------------------------------------------------------------------------
1 | ignore = $config['ignore'];
18 | }
19 |
20 | public function shouldIgnore(string $className, string $methodName, ?array $options): bool
21 | {
22 | // If the test has been explicitly ignored via the
23 | // annotation options we need to ignore the test.
24 | if (Arr::get($options, 'ignore', false)) {
25 | return true;
26 | }
27 |
28 | // If the test has been explicitly included via the
29 | // annotation options we need to include the test.
30 | if (Arr::get($options, 'include', false)) {
31 | return false;
32 | }
33 |
34 | // Otherwise check the patterns we've got from the
35 | // config to check if the test should be ignored.
36 | if (Str::is($this->ignore, $className)) {
37 | return true;
38 | }
39 |
40 | return Str::is($this->ignore, $methodName);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/ExceptionInfo.php:
--------------------------------------------------------------------------------
1 | exception::class;
22 | }
23 |
24 | public function getCode(): int
25 | {
26 | return $this->exception->getCode();
27 | }
28 |
29 | public function getMessage(): string
30 | {
31 | return $this->exception->getMessage();
32 | }
33 |
34 | public function getFile(): string
35 | {
36 | return $this->exception->getFile();
37 | }
38 |
39 | public function getLine(): int
40 | {
41 | return $this->exception->getLine();
42 | }
43 |
44 | public function getTrace(): array
45 | {
46 | return $this->exception->getTrace();
47 | }
48 |
49 | public function getData(): array
50 | {
51 | if ($this->exception instanceof ValidationException) {
52 | return [
53 | 'errors' => $this->exception->errors(),
54 | ];
55 | }
56 |
57 | return [];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidDriverException.php:
--------------------------------------------------------------------------------
1 | with('stats')->latest()->get();
12 |
13 | if ($runs->isEmpty()) {
14 | return redirect(route('enlighten.intro'));
15 | }
16 |
17 | return view('enlighten::run.index', ['runs' => $runs]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Http/Controllers/SearchController.php:
--------------------------------------------------------------------------------
1 | getExamples($run, $request->query('search'));
14 |
15 | return view('enlighten::search.results', [
16 | 'examples' => $examples,
17 | 'run' => $run
18 | ]);
19 | }
20 |
21 | private function getExamples($run, $search)
22 | {
23 | return Example::query()
24 | ->with('group')
25 | ->whereHas('group.run', function ($q) use ($run) {
26 | $q->where('id', $run->id);
27 | })
28 | ->where('title', 'like', "%$search%")
29 | ->limit(5)
30 | ->get();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ShowAreaController.php:
--------------------------------------------------------------------------------
1 | getArea($run, $areaSlug);
20 |
21 | $action = $area->view;
22 |
23 | if (! in_array($action, ['features', 'modules', 'endpoints'])) {
24 | $action = 'features';
25 | }
26 |
27 | return $this->$action($run, $area);
28 | }
29 |
30 | private function modules(Run $run, Area $area)
31 | {
32 | return view('enlighten::area.modules', [
33 | 'area' => $area,
34 | 'modules' => $this->wrapByModule($this->getGroups($run, $area)->load('stats')),
35 | ]);
36 | }
37 |
38 | private function features(Run $run, Area $area)
39 | {
40 | $groups = $this->getGroups($run, $area)
41 | ->load([
42 | 'examples' => function ($q) {
43 | $q->withCount('queries');
44 | },
45 | 'examples.group',
46 | 'examples.requests',
47 | 'examples.exception'
48 | ]);
49 |
50 | return view('enlighten::area.features', [
51 | 'area' => $area,
52 | 'showQueries' => Settings::show(Section::QUERIES),
53 | 'groups' => $groups,
54 | ]);
55 | }
56 |
57 | private function endpoints(Run $run, Area $area)
58 | {
59 | $requests = ExampleRequest::query()
60 | ->select('id', 'example_id', 'request_method', 'request_path')
61 | ->addSelect('route', 'response_status', 'response_headers')
62 | ->with([
63 | 'example:id,group_id,title,slug,status,order_num',
64 | 'example.group:id,slug,run_id',
65 | ])
66 | ->when($area->isNotDefault(), function ($q) use ($area) {
67 | $q->whereHas('example.group', function ($q) use ($area) {
68 | $q->where('area', $area->slug);
69 | });
70 | })
71 | ->whereHas('example.group.run', function ($q) use ($run) {
72 | $q->where('id', $run->id);
73 | })
74 | ->where('follows_redirect', false)
75 | ->get();
76 |
77 | $endpoints = $requests
78 | ->groupBy('signature')
79 | ->map(fn ($requests) => new Endpoint(
80 | $requests->first()->request_method,
81 | $requests->first()->route_or_path,
82 | $requests->unique(fn ($response) => $response->signature.$response->example->slug)->sortBy('example.order')
83 | ))
84 | ->sortBy('method_index');
85 |
86 | return view('enlighten::area.endpoints', [
87 | 'area' => $area,
88 | 'modules' => $this->wrapByModule($endpoints),
89 | ]);
90 | }
91 |
92 | private function getArea(Run $run, string $areaSlug = null): Area
93 | {
94 | if (empty($areaSlug)) {
95 | return $this->defaultArea();
96 | }
97 |
98 | return $run->areas->firstWhere('slug', $areaSlug) ?: $this->defaultArea();
99 | }
100 |
101 | private function defaultArea(): Area
102 | {
103 | return new Area('', trans('enlighten::messages.all_areas'), config('enlighten.area_view', 'features'));
104 | }
105 |
106 | private function getGroups(Run $run, Area $area): Collection
107 | {
108 | // We always want to get the collection with all the groups
109 | // because we use them to build the menu. So by filtering
110 | // at a collection level we're actually saving a query.
111 | return $run->groups
112 | ->when($area->isNotDefault(), fn ($collection) => $collection->where('area', $area->slug))
113 | ->sortBy('order');
114 | }
115 |
116 | private function wrapByModule(Collection $groups): ModuleCollection
117 | {
118 | return Module::all()->wrapGroups($groups)->whereHasGroups();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ShowExampleController.php:
--------------------------------------------------------------------------------
1 | findGroup($groupSlug);
14 |
15 | return view('enlighten::example.show', [
16 | 'example' => $this->getExampleWithRelations($group, $exampleSlug)
17 | ]);
18 | }
19 |
20 | private function getExampleWithRelations(Model $group, string $exampleSlug)
21 | {
22 | return Example::query()
23 | ->with('requests', 'requests.queries', 'snippets', 'exception', 'queries')
24 | ->where([
25 | 'group_id' => $group->id,
26 | 'slug' => $exampleSlug,
27 | ])
28 | ->firstOrFail()
29 | ->setRelation('group', $group);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ShowExampleGroupController.php:
--------------------------------------------------------------------------------
1 | findGroup($groupSlug);
14 |
15 | $examples = $group->examples()
16 | ->with(['group', 'requests', 'exception'])
17 | ->withCount('queries')
18 | ->get();
19 |
20 | return view('enlighten::group.show', [
21 | 'group' => $group,
22 | 'title' => $group->title,
23 | 'examples' => $examples,
24 | 'showQueries' => Settings::show(Section::QUERIES),
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Http/Controllers/WelcomeController.php:
--------------------------------------------------------------------------------
1 | $this->getIntroContent()
14 | ]);
15 | }
16 |
17 | private function getIntroContent()
18 | {
19 | if (file_exists(base_path('ENLIGHTEN.md'))) {
20 | return $this->parseMarkdownFile(base_path('ENLIGHTEN.md'));
21 | }
22 |
23 | return $this->fixImagesPath($this->parseMarkdownFile(__DIR__ . '/../../../README.md'));
24 | }
25 |
26 | private function parseMarkdownFile(string $filePath): HtmlString
27 | {
28 | return Markdown::parse(file_get_contents($filePath));
29 | }
30 |
31 | private function fixImagesPath(string $content)
32 | {
33 | $baseImagePath = asset('vendor/enlighten/img') . '/';
34 |
35 | return str_replace('
middleware('web')->group(function () {
13 | Route::get('intro', WelcomeController::class)
14 | ->name('enlighten.intro');
15 |
16 | Route::get('/', ListRunsController::class)
17 | ->name('enlighten.run.index');
18 |
19 | Route::get('run/{run}/areas/{area?}', ShowAreaController::class)
20 | ->name('enlighten.area.show');
21 |
22 | Route::get('run/{run}/{group:slug}', ShowExampleGroupController::class)
23 | ->name('enlighten.group.show');
24 |
25 | Route::get('run/{run}/{group:slug}/{example:slug}', ShowExampleController::class)
26 | ->name('enlighten.method.show');
27 | });
28 |
29 | Route::prefix('enlighten/api')
30 | ->middleware(SubstituteBindings::class)
31 | ->group(function () {
32 | Route::get('run/{run}/search', SearchController::class)->name('enlighten.api.search');
33 | });
34 |
--------------------------------------------------------------------------------
/src/HttpExamples/HttpExampleCreator.php:
--------------------------------------------------------------------------------
1 | exampleCreator->getCurrentExample();
36 |
37 | if (is_null($testExample)) {
38 | return;
39 | }
40 |
41 | $testExample->addRequest(
42 | $this->requestInspector->getDataFrom($request)
43 | );
44 | }
45 |
46 | public function saveHttpResponseData(Request $request, Response $response): void
47 | {
48 | $testExample = $this->exampleCreator->getCurrentExample();
49 |
50 | if (is_null($testExample)) {
51 | return;
52 | }
53 |
54 | $testExample->setResponse(
55 | $this->responseInspector->getDataFrom($this->normalizeResponse($response)),
56 | static::$followsRedirect,
57 | $this->routeInspector->getInfoFrom($request->route()),
58 | $this->sessionInspector->getData()
59 | );
60 | }
61 |
62 | private function normalizeResponse(Response $response)
63 | {
64 | if ($response instanceof TestResponse) {
65 | $response = $response->baseResponse;
66 | }
67 |
68 | return $response;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/HttpExamples/HttpExampleCreatorMiddleware.php:
--------------------------------------------------------------------------------
1 | httpExampleCreator->createHttpExample($request);
25 |
26 | $response = $next($request);
27 |
28 | $this->httpExampleCreator->saveHttpResponseData($request, $response);
29 |
30 | return $response;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/HttpExamples/RequestInfo.php:
--------------------------------------------------------------------------------
1 | method;
14 | }
15 |
16 | public function getPath(): string
17 | {
18 | return $this->path;
19 | }
20 |
21 | public function getHeaders(): array
22 | {
23 | return $this->headers;
24 | }
25 |
26 | public function getQueryParameters(): array
27 | {
28 | return $this->queryParameters;
29 | }
30 |
31 | public function getInput(): array
32 | {
33 | return $this->input;
34 | }
35 |
36 | public function getFiles(): array
37 | {
38 | return $this->files;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/HttpExamples/RequestInspector.php:
--------------------------------------------------------------------------------
1 | method(),
14 | $request->path(),
15 | $request->headers->all(),
16 | $request->query(),
17 | $request->post(),
18 | $this->getFilesInfo($request->allFiles()),
19 | );
20 | }
21 |
22 | public function getFilesInfo(array $files): array
23 | {
24 | return collect($files)
25 | ->map(fn (UploadedFile $file) => [
26 | 'name' => $file->getClientOriginalName(),
27 | 'type' => $file->getMimeType(),
28 | 'size' => intdiv($file->getSize(), 1024),
29 | ])
30 | ->all();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/HttpExamples/ResponseInfo.php:
--------------------------------------------------------------------------------
1 | statusCode;
14 | }
15 |
16 | public function getHeaders(): array
17 | {
18 | return $this->headers;
19 | }
20 |
21 | public function getContent(): string
22 | {
23 | return $this->content;
24 | }
25 |
26 | public function getTemplate(): ?string
27 | {
28 | return $this->template;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/HttpExamples/ResponseInspector.php:
--------------------------------------------------------------------------------
1 | getStatusCode(),
15 | $response->headers->all(),
16 | $response->getContent(),
17 | $this->getTemplate($response)
18 | );
19 | }
20 |
21 | protected function getTemplate(Response $response): ?string
22 | {
23 | if (isset($response->original) && $response->original instanceof View) {
24 | return File::get($response->original->getPath());
25 | }
26 |
27 | return null;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/HttpExamples/RouteInfo.php:
--------------------------------------------------------------------------------
1 | uri = null;
21 | $this->parameters = null;
22 | } else {
23 | $this->uri = $uri;
24 | $this->parameters = $parameters;
25 | }
26 | }
27 |
28 | public function getUri(): ?string
29 | {
30 | return $this->uri;
31 | }
32 |
33 | public function getParameters(): ?array
34 | {
35 | return $this->parameters;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/HttpExamples/RouteInspector.php:
--------------------------------------------------------------------------------
1 | uri(), $this->getParameters($route));
16 | }
17 |
18 | /**
19 | * Get all the route parameters as keys and the parameter-where conditions as values.
20 | *
21 | * @return array
22 | */
23 | protected function getParameters(Route $route): array
24 | {
25 | return collect($route->parameterNames())
26 | ->mapWithKeys(fn ($parameter) => [$parameter => '*'])
27 | ->merge(
28 | array_intersect_key($route->wheres, $route->originalParameters())
29 | )
30 | ->map(fn ($pattern, $name) => [
31 | 'name' => $name,
32 | 'pattern' => $pattern,
33 | 'optional' => $this->isParameterOptional($route, $name),
34 | ])
35 | ->values()
36 | ->all();
37 | }
38 |
39 | protected function isParameterOptional(Route $route, $parameter): bool
40 | {
41 | return (bool) preg_match("/{{$parameter}\?}/", $route->uri());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/HttpExamples/SessionInspector.php:
--------------------------------------------------------------------------------
1 | session->all();
16 |
17 | // Wrap the errors array in a collection so it can be
18 | // exported by calling the toArray method since the
19 | // error bags implement the Arrayable interface.
20 | if (! empty($session['errors'])) {
21 | $session['errors'] = collect($session['errors']->getBags());
22 | }
23 |
24 | return collect($session)->toArray();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Models/Area.php:
--------------------------------------------------------------------------------
1 | map(fn ($data) => new static(
21 | $data['slug'],
22 | $data['name'] ?? null,
23 | $data['view'] ?? $defaultView,
24 | ));
25 | }
26 |
27 | public static function get($areas): Collection
28 | {
29 | return collect($areas)
30 | ->map(function ($slug) {
31 | $config = static::getConfigFor($slug);
32 |
33 | return new static(
34 | $slug,
35 | $config['name'] ?? null,
36 | $config['view'] ?? config('enlighten.area_view')
37 | );
38 | })
39 | ->sortBy('name')
40 | ->values();
41 | }
42 |
43 | public static function getConfigFor(string $areaSlug): array
44 | {
45 | return collect(config('enlighten.areas'))->firstWhere('slug', $areaSlug) ?: [];
46 | }
47 |
48 | public function __construct(string $slug, string $name = null, $view = 'features')
49 | {
50 | $this->setAttributes([
51 | 'name' => $name ?: ucfirst(str_replace('-', ' ', $slug)),
52 | 'slug' => $slug,
53 | 'view' => $view,
54 | ]);
55 | }
56 |
57 | public function isNotDefault(): bool
58 | {
59 | return ! empty($this->slug);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Models/Concerns/GetStats.php:
--------------------------------------------------------------------------------
1 | stats
14 | ->where('status', Status::SUCCESS)
15 | ->sum('count');
16 | }
17 |
18 | public function getTestsCount(): int
19 | {
20 | return $this->stats->sum('count');
21 | }
22 |
23 | // Statusable
24 | public function getStatus(): string
25 | {
26 | if ($this->getPassingTestsCount() === $this->getTestsCount()) {
27 | return Status::SUCCESS;
28 | }
29 |
30 | if ($this->stats->firstWhere('status', Status::FAILURE)) {
31 | return Status::FAILURE;
32 | }
33 |
34 | return Status::WARNING;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Models/Concerns/GetsStatsFromGroups.php:
--------------------------------------------------------------------------------
1 | groups->pluck('passing_tests_count')->sum();
12 | }
13 |
14 | public function getTestsCount(): int
15 | {
16 | return $this->groups->pluck('tests_count')->sum();
17 | }
18 |
19 | public function getStatus(): string
20 | {
21 | if ($this->getPassingTestsCount() === $this->getTestsCount()) {
22 | return Status::SUCCESS;
23 | }
24 |
25 | if ($this->groups->firstWhere('status', Status::FAILURE)) {
26 | return Status::FAILURE;
27 | }
28 |
29 | return Status::WARNING;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Models/Concerns/ReadsDynamicAttributes.php:
--------------------------------------------------------------------------------
1 | attributes = $attributes;
17 | }
18 |
19 | public function __isset($name)
20 | {
21 | if (method_exists($this, 'get'.Str::studly($name))) {
22 | return true;
23 | }
24 |
25 | return array_key_exists($name, $this->attributes);
26 | }
27 |
28 | public function __get($name)
29 | {
30 | if (method_exists($this, $method = 'get'.Str::studly($name))) {
31 | return $this->$method();
32 | }
33 |
34 | return $this->attributes[$name] ?? null;
35 | }
36 |
37 | public function toArray(): array
38 | {
39 | return $this->attributes;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Models/Endpoint.php:
--------------------------------------------------------------------------------
1 | setAttributes([
17 | 'method' => $method,
18 | 'route' => $route,
19 | 'requests' => $requests ?: collect(),
20 | ]);
21 | }
22 |
23 | public function matches(Module $module): bool
24 | {
25 | return Str::is($module->routes, $this->route);
26 | }
27 |
28 | public function getSignature()
29 | {
30 | return "{$this->method} {$this->route}";
31 | }
32 |
33 | public function getTitle()
34 | {
35 | return $this->getMainRequest()->example->group->title;
36 | }
37 |
38 | public function getMainRequest()
39 | {
40 | return $this->requests->first();
41 | }
42 |
43 | public function getAdditionalRequests()
44 | {
45 | return $this->requests->slice(1);
46 | }
47 |
48 | public function getMethodIndex()
49 | {
50 | return array_search($this->method, ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
51 | }
52 |
53 | public function getStats()
54 | {
55 | return $this->requests
56 | ->groupBy('example.status')
57 | ->map(fn ($endpoints, $status) => [
58 | 'status' => $status,
59 | 'count' => count($endpoints),
60 | ]);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Models/Example.php:
--------------------------------------------------------------------------------
1 | 'array',
19 | 'count' => 'int',
20 | 'order_num' => 'int',
21 | ];
22 |
23 | // Relationships
24 |
25 | public function group()
26 | {
27 | return $this->belongsTo(ExampleGroup::class);
28 | }
29 |
30 | public function requests()
31 | {
32 | return $this->hasMany(ExampleRequest::class);
33 | }
34 |
35 | public function exception()
36 | {
37 | return $this->hasOne(ExampleException::class)->withDefault();
38 | }
39 |
40 | public function queries()
41 | {
42 | return $this->hasMany(ExampleQuery::class);
43 | }
44 |
45 | public function snippets()
46 | {
47 | return $this->hasMany(ExampleSnippet::class);
48 | }
49 |
50 | // Accessors
51 |
52 | public function getTitleAttribute($title)
53 | {
54 | if (is_null($this->data_name)) {
55 | return $title;
56 | }
57 |
58 | if (is_numeric($this->data_name)) {
59 | return sprintf('%s (dataset #%s)', $title, $this->data_name);
60 | }
61 |
62 | return sprintf('%s (%s)', $title, $this->data_name);
63 | }
64 |
65 | public function getSignatureAttribute()
66 | {
67 | return $this->group->class_name.'::'.$this->method_name;
68 | }
69 |
70 | public function getHasExceptionAttribute()
71 | {
72 | return $this->exception->exists;
73 | }
74 |
75 | public function getFileLinkAttribute()
76 | {
77 | return FileLink::get(str_replace('\\', '/', (string) $this->group->class_name).'.php', $this->line);
78 | }
79 |
80 | public function getIsHttpAttribute()
81 | {
82 | return $this->requests->isNotEmpty();
83 | }
84 |
85 | public function getUrlAttribute()
86 | {
87 | return route('enlighten.method.show', [
88 | $this->group->run_id,
89 | $this->group->slug,
90 | $this->slug,
91 | ]);
92 | }
93 |
94 | public function getOrphanQueriesAttribute()
95 | {
96 | return $this->queries->where('request_id', null);
97 | }
98 |
99 | public function getOrderAttribute()
100 | {
101 | return [$this->order_num, $this->id];
102 | }
103 |
104 | // Contract
105 |
106 | public function getSignature(): string
107 | {
108 | return $this->signature;
109 | }
110 |
111 | public function getTitle(): string
112 | {
113 | return "{$this->group->title} - {$this->title}";
114 | }
115 |
116 | public function getStatus(): string
117 | {
118 | return $this->attributes['status'] ?? Status::UNKNOWN;
119 | }
120 |
121 | public function getUrl(): string
122 | {
123 | return $this->url;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Models/ExampleException.php:
--------------------------------------------------------------------------------
1 | 'int',
18 | 'trace' => 'array',
19 | 'extra' => 'array',
20 | ];
21 |
22 | public function getFileLinkAttribute()
23 | {
24 | if (empty($this->file)) {
25 | return '';
26 | }
27 |
28 | return FileLink::get($this->file);
29 | }
30 |
31 | public function getValidationErrorsAttribute()
32 | {
33 | return $this->extra['errors'] ?? [];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Models/ExampleGroup.php:
--------------------------------------------------------------------------------
1 | 'int',
22 | ];
23 |
24 | // Relationships
25 | public function run()
26 | {
27 | return $this->belongsTo(Run::class);
28 | }
29 |
30 | public function examples()
31 | {
32 | return $this->hasMany(Example::class, 'group_id')
33 | ->orderBy('order_num')
34 | ->orderBy('id');
35 | }
36 |
37 | public function stats()
38 | {
39 | return $this->hasMany(Example::class, 'group_id', 'id')
40 | ->selectRaw('DISTINCT(status), COUNT(id) as count, group_id')
41 | ->groupBy('status', 'group_id');
42 | }
43 |
44 | // Helpers
45 | public function matches(Module $module): bool
46 | {
47 | return Str::is($module->classes, $this->class_name);
48 | }
49 |
50 | // Scopes
51 | public function scopeFilterByArea($query, Area $area) : Builder
52 | {
53 | return $query->where('area', $area->slug);
54 | }
55 |
56 | // Accessors
57 |
58 | public function getAreaTitleAttribute()
59 | {
60 | return config('enlighten.areas.'.$this->area) ?: ucwords((string) $this->area);
61 | }
62 |
63 | public function getPassingTestsCountAttribute()
64 | {
65 | return $this->getPassingTestsCount();
66 | }
67 |
68 | public function getTestsCountAttribute()
69 | {
70 | return $this->getTestsCount();
71 | }
72 |
73 | public function getStatusAttribute(): string
74 | {
75 | return $this->getStatus();
76 | }
77 |
78 | public function getUrlAttribute()
79 | {
80 | return route('enlighten.group.show', [
81 | 'run' => $this->run_id,
82 | 'group' => $this->slug,
83 | ]);
84 | }
85 |
86 | public function getOrderAttribute()
87 | {
88 | return [$this->order_num, $this->title];
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Models/ExampleQuery.php:
--------------------------------------------------------------------------------
1 | 'array',
17 | 'request_id' => 'int',
18 | 'snippet_id' => 'int',
19 | ];
20 |
21 | public function request()
22 | {
23 | return $this->belongsTo(ExampleRequest::class);
24 | }
25 |
26 | public function snippet()
27 | {
28 | return $this->belongsTo(ExampleSnippet::class);
29 | }
30 |
31 | // Accessors
32 |
33 | public function getContextAttribute()
34 | {
35 | if ($this->request_id !== null) {
36 | return 'request';
37 | }
38 |
39 | if ($this->snippet_id !== null) {
40 | return 'snippet';
41 | }
42 |
43 | return 'test';
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Models/ExampleSnippet.php:
--------------------------------------------------------------------------------
1 | 'array'
30 | ];
31 |
32 | public function getResultCodeAttribute()
33 | {
34 | return app(CodeResultExporter::class)->export($this->result);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Models/Module.php:
--------------------------------------------------------------------------------
1 | map(fn ($item) => new static($item['name'], $item['classes'] ?? [], $item['routes'] ?? []));
17 | }
18 |
19 | public function __construct(string $name, array $classes = [], array $routes = [])
20 | {
21 | $this->setAttributes([
22 | 'name' => $name,
23 | 'classes' => $classes,
24 | 'routes' => $routes,
25 | ]);
26 | }
27 |
28 | public function addGroups(Collection $groups): self
29 | {
30 | $this->attributes['groups'] = $groups;
31 |
32 | return $this;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Models/ModuleCollection.php:
--------------------------------------------------------------------------------
1 | firstWhere('name', $name);
12 | }
13 |
14 | public function wrapGroups(Collection $groups) : self
15 | {
16 | return $this
17 | ->each(function ($module) use (&$groups) {
18 | [$matches, $groups] = $groups->partition(fn (Wrappable $group) => $group->matches($module));
19 |
20 | $module->addGroups($matches);
21 | })
22 | ->wrapRemainingGroups($groups);
23 | }
24 |
25 | private function wrapRemainingGroups(Collection $groups): self
26 | {
27 | if ($groups->isEmpty()) {
28 | return $this;
29 | }
30 |
31 | $module = new Module(config('enlighten.default_module', 'Other Modules'));
32 |
33 | return $this->add($module->addGroups($groups));
34 | }
35 |
36 | public function whereHasGroups(): self
37 | {
38 | return $this->filter(fn ($module) => $module->groups->isNotEmpty());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Models/ReplacesValues.php:
--------------------------------------------------------------------------------
1 | decodeValues($originalValues);
17 |
18 | if (! is_array($decodedValues)) {
19 | return $originalValues;
20 | }
21 |
22 | return collect($decodedValues)
23 | ->merge(array_intersect_key($config['overwrite'] ?? [], $decodedValues))
24 | ->diffKeys(array_flip($config['hide'] ?? []))
25 | ->all();
26 | }
27 |
28 | private function decodeValues($originalValues)
29 | {
30 | if (! is_string($originalValues)) {
31 | return $originalValues;
32 | }
33 |
34 | return json_decode($originalValues, JSON_OBJECT_AS_ARRAY);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Models/Run.php:
--------------------------------------------------------------------------------
1 | hasMany(ExampleGroup::class);
25 | }
26 |
27 | public function examples()
28 | {
29 | return $this->hasManyThrough(Example::class, ExampleGroup::class, 'run_id', 'group_id');
30 | }
31 |
32 | public function findGroup(string $slug)
33 | {
34 | return $this->groups()
35 | ->where('slug', $slug)
36 | ->firstOrFail();
37 | }
38 |
39 | public function stats()
40 | {
41 | return $this->hasManyThrough(Example::class, ExampleGroup::class, 'run_id', 'group_id')
42 | ->selectRaw('
43 | DISTINCT(status),
44 | COUNT(enlighten_examples.id) as count
45 | ')
46 | ->groupBy('status', 'run_id');
47 | }
48 |
49 | // Run Contract
50 |
51 | public function isEmpty(): bool
52 | {
53 | return $this->groups()->count() == 0;
54 | }
55 |
56 | public function getFailedExamples(): SupportCollection
57 | {
58 | return $this->examples()->where('status', '!=', Status::SUCCESS)->get();
59 | }
60 |
61 | public function url(): string
62 | {
63 | return $this->getUrlAttribute();
64 | }
65 |
66 | // Accessors
67 |
68 | public function getAreasAttribute()
69 | {
70 | $areas = $this->groups->pluck('area')->unique();
71 |
72 | return Area::get($areas);
73 | }
74 |
75 | public function getSignatureAttribute($value)
76 | {
77 | if ($this->modified) {
78 | return "{$this->branch} * {$this->head}";
79 | }
80 |
81 | return "{$this->branch} {$this->head}";
82 | }
83 |
84 | public function getUrlAttribute()
85 | {
86 | return route('enlighten.area.show', $this);
87 | }
88 |
89 | public function getBaseUrlAttribute()
90 | {
91 | return url("enlighten/run/{$this->id}");
92 | }
93 |
94 | public function areaUrl(string $area)
95 | {
96 | return route('enlighten.area.show', [$this, $area]);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Models/Statable.php:
--------------------------------------------------------------------------------
1 | app->environment('production') && ! $this->app->runningInConsole()) {
33 | return;
34 | }
35 |
36 | $this->mergeConfigFrom($this->packageRoot('config/enlighten.php'), 'enlighten');
37 |
38 | $this->registerDatabaseConnection($this->app['config']);
39 |
40 | $this->loadRoutesFrom($this->packageRoot('src/Http/routes/api.php'));
41 |
42 | if ($this->app[Settings::class]->dashboardEnabled() || $this->app->runningInConsole()) {
43 | $this->loadRoutesFrom($this->packageRoot('src/Http/routes/web.php'));
44 | $this->loadViewsFrom($this->packageRoot('resources/views'), 'enlighten');
45 | $this->loadTranslationsFrom($this->packageRoot('resources/lang'), 'enlighten');
46 | $this->registerViewComponents();
47 | }
48 |
49 | if ($this->app->runningInConsole()) {
50 | $this->registerMiddleware();
51 |
52 | $this->registerPublishing();
53 |
54 | $this->registerCommands();
55 | }
56 | }
57 |
58 | public function register(): void
59 | {
60 | $this->registerSettings();
61 | $this->registerRunBuilder();
62 | $this->registerExampleCreator();
63 | $this->registerVersionControlSystem();
64 | $this->registerHttpExampleCreator();
65 | $this->registerCodeResultFormat();
66 | }
67 |
68 | private function registerMiddleware(): void
69 | {
70 | $this->app[HttpKernel::class]->pushMiddleware(HttpExampleCreatorMiddleware::class);
71 | }
72 |
73 | private function registerSettings(): void
74 | {
75 | $this->app->singleton(Settings::class, fn () => new Settings);
76 | }
77 |
78 | private function registerRunBuilder(): void
79 | {
80 | $this->app->singleton(RunBuilder::class, fn ($app) => $this->getDriver($app));
81 | }
82 |
83 | private function getDriver($app)
84 | {
85 | return match ($app['config']->get('enlighten.driver', 'database')) {
86 | 'database' => new DatabaseRunBuilder,
87 | 'api' => new ApiRunBuilder,
88 | default => throw new InvalidDriverException,
89 | };
90 | }
91 |
92 | private function registerExampleCreator(): void
93 | {
94 | $this->app->singleton(ExampleCreator::class, function ($app) {
95 | $annotations = new Annotations;
96 |
97 | $annotations->addCast('enlighten', function ($value) {
98 | $options = json_decode($value, JSON_OBJECT_AS_ARRAY);
99 | return array_merge(['include' => true], $options ?: []);
100 | });
101 |
102 | return new ExampleCreator(
103 | $app[RunBuilder::class],
104 | $annotations,
105 | $app[Settings::class],
106 | new ExampleProfile($app['config']->get('enlighten.tests')),
107 | );
108 | });
109 | }
110 |
111 | private function registerVersionControlSystem(): void
112 | {
113 | $this->app->singleton(VersionControl::class, Git::class);
114 | }
115 |
116 | private function registerHttpExampleCreator(): void
117 | {
118 | $this->app->singleton(HttpExampleCreator::class, fn ($app) => new HttpExampleCreator(
119 | $app[ExampleCreator::class],
120 | new RequestInspector,
121 | new RouteInspector,
122 | new ResponseInspector,
123 | new SessionInspector($app['session.store']),
124 | ));
125 | }
126 |
127 | private function registerCodeResultFormat(): void
128 | {
129 | $this->app->singleton(CodeResultFormat::class, HtmlResultFormat::class);
130 | }
131 |
132 | private function packageRoot(string $path): string
133 | {
134 | return __DIR__.'/../../'.$path;
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Providers/RegistersConsoleConfiguration.php:
--------------------------------------------------------------------------------
1 | app->singleton(MigrateCommand::class, fn ($app) => new MigrateCommand($app['migrator'], $app['events']));
20 |
21 | $this->app->singleton(ExportDocumentationCommand::class, fn ($app) => new ExportDocumentationCommand(
22 | new DocumentationExporter(
23 | $app[Filesystem::class],
24 | new ContentRequest($app[HttpKernel::class]),
25 | )
26 | ));
27 |
28 | $this->commands([
29 | InstallCommand::class,
30 | FreshCommand::class,
31 | MigrateCommand::class,
32 | GenerateDocumentationCommand::class,
33 | ExportDocumentationCommand::class
34 | ]);
35 | }
36 |
37 | private function registerPublishing(): void
38 | {
39 | $this->publishes([
40 | $this->packageRoot('config') => base_path('config'),
41 | ], ['enlighten', 'enlighten-config']);
42 |
43 | $this->publishes([
44 | $this->packageRoot('dist') => public_path('vendor/enlighten'),
45 | $this->packageRoot('/preview.png') => public_path('vendor/enlighten/img/preview.png'),
46 | ], ['enlighten', 'enlighten-build']);
47 |
48 | $this->publishes([
49 | $this->packageRoot('resources/views') => resource_path('views/vendor/enlighten'),
50 | ], 'enlighten-views');
51 |
52 | $this->publishes([
53 | $this->packageRoot('resources/lang') => resource_path('lang/vendor/enlighten'),
54 | ], 'enlighten-translations');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Providers/RegistersDatabaseConnection.php:
--------------------------------------------------------------------------------
1 | has('database.connections.enlighten')) {
13 | return;
14 | }
15 |
16 | $connection = $config->get('database.connections.'.$config->get('database.default'));
17 |
18 | $config->set('database.connections.enlighten', array_merge($connection, [
19 | 'database' => $this->guessDatabaseName($connection),
20 | ]));
21 | }
22 |
23 | protected function guessDatabaseName(array $connection)
24 | {
25 | if ($connection['driver'] === 'sqlite') {
26 | return $connection['database'];
27 | }
28 |
29 | $result = $connection['database'];
30 |
31 | if (Str::endsWith($result, '_tests')) {
32 | $result = Str::substr($result, 0, -6);
33 | } elseif (Str::endsWith($result, '_test')) {
34 | $result = Str::substr($result, 0, -5);
35 | }
36 |
37 | return "{$result}_enlighten";
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Providers/RegistersViewComponents.php:
--------------------------------------------------------------------------------
1 | loadViewComponentsAs('enlighten', [
30 | 'status-badge' => StatusBadgeComponent::class,
31 | 'response-info' => ResponseInfoComponent::class,
32 | 'request-info' => RequestInfoComponent::class,
33 | 'stats-badge' => StatsBadgeComponent::class,
34 | 'html-response' => HtmlResponseComponent::class,
35 | 'key-value' => KeyValueComponent::class,
36 | 'app-layout' => AppLayoutComponent::class,
37 | 'route-parameters-table' => RouteParametersTableComponent::class,
38 | 'request-input-table' => RequestInputTableComponent::class,
39 | 'dynamic-tabs' => DynamicTabsComponent::class,
40 | 'exception-info' => ExceptionInfoComponent::class,
41 | 'edit-button' => EditButtonComponent::class,
42 | 'breadcrumbs' => BreadcrumbsComponent::class,
43 | 'search-box-static' => SearchBoxStaticComponent::class,
44 | 'search-box' => SearchBoxComponent::class,
45 | // Group
46 | 'code-example' => CodeExampleComponent::class,
47 | 'content-table' => 'enlighten::components.content-table',
48 | 'response-preview' => 'enlighten::components.response-preview',
49 | // Layout components
50 | 'info-panel' => 'enlighten::components.info-panel',
51 | 'scroll-to-top' => 'enlighten::components.scroll-to-top',
52 | 'pre' => 'enlighten::components.pre',
53 | 'main-layout' => 'enlighten::layout.main',
54 | 'area-module-panel' => 'enlighten::components.area-module-panel',
55 | 'queries-info' => 'enlighten::components.queries-info',
56 | 'iframe' => 'enlighten::components.iframe',
57 | 'widget' => 'enlighten::components.widget',
58 | 'expansible-section' => 'enlighten::components.expansible-section',
59 | 'svg-logo' => 'enlighten::components.svg-logo',
60 | 'runs-table' => 'enlighten::components.runs-table',
61 | 'panel-title' => 'enlighten::components.panel-title',
62 | 'example-snippets' => 'enlighten::components.example-snippets',
63 | 'example-tabs' => ExampleTabsComponent::class,
64 | 'example-breadcrumbs' => ExampleBreadcrumbs::class,
65 | 'group-breadcrumbs' => GroupBreadcrumbs::class
66 | ]);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Section.php:
--------------------------------------------------------------------------------
1 | hide($sectionName);
39 | }
40 |
41 | public function setCustomAreaResolver(Closure $callback): self
42 | {
43 | $this->customAreaResolver = $callback;
44 |
45 | return $this;
46 | }
47 |
48 | public function getAreaSlug(string $className): string
49 | {
50 | if ($this->customAreaResolver != null) {
51 | return Str::slug(call_user_func($this->customAreaResolver, $className));
52 | }
53 |
54 | return Str::slug(collect(explode('\\', $className))[1]);
55 | }
56 |
57 | public function setCustomTitleGenerator(Closure $callback): self
58 | {
59 | $this->customTitleGenerator = $callback;
60 |
61 | return $this;
62 | }
63 |
64 | public function generateTitle(string $type, string $classOrMethodName): string
65 | {
66 | if ($this->customTitleGenerator) {
67 | return call_user_func($this->customTitleGenerator, $type, $classOrMethodName);
68 | } elseif ($type == 'class') {
69 | return $this->generateDefaultTitleFromClassName($classOrMethodName);
70 | } else {
71 | return $this->generateDefaultTitleFromMethodName($classOrMethodName);
72 | }
73 | }
74 |
75 | protected function generateDefaultTitleFromMethodName($methodName): string
76 | {
77 | $result = Str::of($methodName);
78 |
79 | if ($result->startsWith('test')) {
80 | $result = $result->substr(4);
81 | }
82 |
83 | return $result
84 | ->replaceMatches('@([A-Z])|_@', ' $1')
85 | ->lower()
86 | ->trim()
87 | ->ucfirst()
88 | ->__toString();
89 | }
90 |
91 | protected function generateDefaultTitleFromClassName($className): string
92 | {
93 | $result = Str::of(class_basename($className));
94 |
95 | if ($result->endsWith('Test')) {
96 | $result = $result->substr(0, -4);
97 | }
98 |
99 | return $result
100 | ->replaceMatches('@([A-Z])@', ' $1')
101 | ->trim()
102 | ->__toString();
103 | }
104 |
105 | public function setCustomSlugGenerator(Closure $callback): self
106 | {
107 | $this->customSlugGenerator = $callback;
108 |
109 | return $this;
110 | }
111 |
112 | public function generateSlugFromClassName($className): string
113 | {
114 | if ($this->customSlugGenerator) {
115 | return call_user_func($this->customSlugGenerator, $className, 'class');
116 | }
117 |
118 | $result = Str::of($className);
119 |
120 | if ($result->startsWith('Tests\\')) {
121 | $result = $result->substr(6);
122 | }
123 |
124 | if ($result->endsWith('Test')) {
125 | $result = $result->substr(0, -4);
126 | }
127 |
128 | return $result
129 | ->replaceMatches('@([A-Z])@', '-$1')
130 | ->ltrim('-')
131 | ->slug()
132 | ->__toString();
133 | }
134 |
135 | public function generateSlugFromMethodName($methodName): string
136 | {
137 | if ($this->customSlugGenerator) {
138 | return call_user_func($this->customSlugGenerator, $methodName, 'method');
139 | }
140 |
141 | $result = Str::of($methodName);
142 |
143 | if ($result->startsWith('test') || $result->startsWith('Test')) {
144 | $result = $result->substr(4);
145 | }
146 |
147 | return $result
148 | ->replaceMatches('@([A-Z])@', '-$1')
149 | ->ltrim('-')
150 | ->slug()
151 | ->__toString();
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/Tests/EnlightenSetup.php:
--------------------------------------------------------------------------------
1 | app)) {
22 | throw new LaravelNotPresent;
23 | }
24 |
25 | if (Enlighten::isDocumenting()) {
26 | $this->afterApplicationCreated(function () {
27 | $this->makeExample();
28 |
29 | $this->captureExceptions();
30 |
31 | $this->captureQueries();
32 | });
33 |
34 | $this->beforeApplicationDestroyed(function () {
35 | $this->stopCapturingQueries();
36 |
37 | $this->saveExampleStatus();
38 |
39 | $this->restoreBackupOriginalExceptionHandler();
40 | });
41 | }
42 | }
43 |
44 | private function makeExample(): void
45 | {
46 | $this->app->make(ExampleCreator::class)->makeExample(
47 | $this::class,
48 | $this->name(),
49 | $this->providedData(),
50 | $this->dataName()
51 | );
52 | }
53 |
54 | private function captureQueries(): void
55 | {
56 | DB::listen(function ($query) {
57 | if (! $this->captureQueries) {
58 | return;
59 | }
60 |
61 | if ($query->connectionName === 'enlighten') {
62 | return;
63 | }
64 |
65 | $this->app->make(ExampleCreator::class)->addQuery($query);
66 | });
67 | }
68 |
69 | private function stopCapturingQueries(): void
70 | {
71 | $this->captureQueries = false;
72 | }
73 |
74 | private function captureExceptions(): void
75 | {
76 | if ($this->exceptionRecorder === null) {
77 | $this->backupOriginalExceptionHandler = $this->app->make(ExceptionHandler::class);
78 | $this->exceptionRecorder = new ExceptionRecorder($this->backupOriginalExceptionHandler);
79 | }
80 |
81 | $this->app->instance(ExceptionHandler::class, $this->exceptionRecorder);
82 | }
83 |
84 | private function restoreBackupOriginalExceptionHandler(): void
85 | {
86 | $this->app->instance(ExceptionHandler::class, $this->backupOriginalExceptionHandler);
87 | }
88 |
89 | /**
90 | * Only handle the given exceptions via the exception handler.
91 | *
92 | * @return $this
93 | */
94 | protected function withoutExceptionHandling(array $except = []): self
95 | {
96 | if (Enlighten::isDocumenting()) {
97 | $this->captureExceptions();
98 |
99 | $this->exceptionRecorder->forceThrow($except);
100 |
101 | return $this;
102 | } else {
103 | return parent::withoutExceptionHandling($except);
104 | }
105 | }
106 |
107 | /**
108 | * Restore exception handling.
109 | *
110 | * @return $this
111 | */
112 | protected function withExceptionHandling(): self
113 | {
114 | if (Enlighten::isDocumenting()) {
115 | $this->captureExceptions();
116 |
117 | $this->exceptionRecorder->forwardToOriginal();
118 |
119 | return $this;
120 | } else {
121 | return parent::withExceptionHandling();
122 | }
123 | }
124 |
125 | protected function saveExampleStatus(): void
126 | {
127 | $exampleCreator = $this->app->make(ExampleCreator::class);
128 |
129 | $exampleCreator->setStatus($this->getStatusAsText());
130 | $exampleCreator->build();
131 | }
132 |
133 | private function getStatusAsText(): string
134 | {
135 | return $this->status()->asString();
136 | }
137 |
138 | /**
139 | * Follow a redirect chain until a non-redirect is received.
140 | *
141 | * @param \Illuminate\Http\Response $response
142 | * @return \Illuminate\Http\Response|\Illuminate\Testing\TestResponse
143 | */
144 | protected function followRedirects($response)
145 | {
146 | return HttpExampleCreator::followingRedirect(fn () => parent::followRedirects($response));
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Tests/ExceptionRecorder.php:
--------------------------------------------------------------------------------
1 | forwardToOriginalHandler = true;
27 | $this->except = [];
28 | }
29 |
30 | public function forceThrow(array $except = []): void
31 | {
32 | $this->forwardToOriginalHandler = false;
33 | $this->except = $except;
34 | }
35 |
36 | public function report(Throwable $e): void
37 | {
38 | app(ExampleCreator::class)->captureException($e);
39 |
40 | if ($this->forwardToOriginalHandler) {
41 | $this->originalHandler->report($e);
42 | }
43 | }
44 |
45 | public function shouldReport(Throwable $e)
46 | {
47 | if ($this->forwardToOriginalHandler) {
48 | return $this->originalHandler->shouldReport($e);
49 | }
50 |
51 | return false;
52 | }
53 |
54 | public function render($request, Throwable $e)
55 | {
56 | if ($this->forwardToOriginalHandler) {
57 | return $this->originalHandler->render($request, $e);
58 | }
59 |
60 | foreach ($this->except as $class) {
61 | if ($e instanceof $class) {
62 | return $this->originalHandler->render($request, $e);
63 | }
64 | }
65 |
66 | if ($e instanceof NotFoundHttpException) {
67 | throw new NotFoundHttpException(
68 | "{$request->method()} {$request->url()}",
69 | null,
70 | $e->getCode()
71 | );
72 | }
73 |
74 | throw $e;
75 | }
76 |
77 | public function renderForConsole($output, Throwable $e): void
78 | {
79 | if ($this->forwardToOriginalHandler) {
80 | $this->originalHandler->renderForConsole($output, $e);
81 | return;
82 | }
83 |
84 | (new ConsoleApplication)->renderThrowable($e, $output);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | casts[$key] = $callback;
17 | }
18 |
19 | public function getFromClass($class): Collection
20 | {
21 | $reflectionClass = new ReflectionClass($class);
22 |
23 | return $this->fromDocComment($reflectionClass->getDocComment());
24 | }
25 |
26 | public function getFromMethod($class, $method): Collection
27 | {
28 | $reflectionMethod = new ReflectionMethod($class, $method);
29 |
30 | return $this->fromDocComment($reflectionMethod->getDocComment());
31 | }
32 |
33 | protected function fromDocComment($docComment)
34 | {
35 | return Collection::make(explode(PHP_EOL, trim((string) $docComment, '/*')))
36 | ->map(fn ($line) => ltrim(rtrim((string) $line, ' .'), '* '))
37 | ->pipe(fn ($collection) => Collection::make(static::chunkByAnnotation($collection)))
38 | ->map(fn ($value, $name) => static::applyCast($name, trim((string) $value)));
39 | }
40 |
41 | protected function chunkByAnnotation(Collection $lines)
42 | {
43 | $result = [];
44 |
45 | foreach ($lines as $line) {
46 | if (preg_match("#^@(\w+)(.*?)?$#", (string) $line, $matches)) {
47 | $currentAnnotation = $matches[1];
48 | $result[$currentAnnotation] = $matches[2] ?? '';
49 | continue;
50 | }
51 |
52 | if (isset($currentAnnotation)) {
53 | $result[$currentAnnotation] .= PHP_EOL.$line;
54 | }
55 | }
56 |
57 | return $result;
58 | }
59 |
60 | protected function applyCast($name, $value)
61 | {
62 | if (empty($this->casts[$name])) {
63 | return $value;
64 | }
65 |
66 | return call_user_func($this->casts[$name], $value);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Utils/FileLink.php:
--------------------------------------------------------------------------------
1 | 'phpstorm://open?file={path}&line={line}',
11 | 'sublime' => 'subl://open?url=file://{path}&line={line}',
12 | 'vscode' => 'vscode://file/{path}:{line}',
13 | ];
14 |
15 | public static $template;
16 |
17 | public static function get(string $path, ?int $line = 1)
18 | {
19 | if (static::$template == null) {
20 | static::$template = Arr::get(
21 | static::$editors,
22 | config('enlighten.editor', 'phpstorm'),
23 | Arr::first(static::$editors)
24 | );
25 | }
26 |
27 | return str_replace(['{path}', '{line}'], [urlencode(base_path($path)), $line], (string) static::$template);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Utils/Git.php:
--------------------------------------------------------------------------------
1 | activeRun = $request->route('run') ?: Run::latest()->firstOrNew();
18 | }
19 |
20 | public function render()
21 | {
22 | return view('enlighten::components.app-layout', [
23 | 'showDashboardLink' => ! app()->runningInConsole(),
24 | 'useStaticSearch' => app()->runningInConsole(),
25 | ]);
26 | }
27 |
28 | public function tabs()
29 | {
30 | return $this->activeRun->areas->map(fn ($area) => [
31 | 'slug' => $area->slug,
32 | 'title' => $area->name,
33 | 'active' => $area->slug === request()->route('area'),
34 | 'panels' => $this->panels($area)
35 | ]);
36 | }
37 |
38 | public function panels(Area $area)
39 | {
40 | return Module::all()
41 | ->wrapGroups(
42 | $this->activeRun->groups->where('area', $area->slug)
43 | )->filter(fn ($panel) => $panel->groups->isNotEmpty());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/View/Components/BreadcrumbsComponent.php:
--------------------------------------------------------------------------------
1 | segments = $segments;
14 | }
15 |
16 | public function render()
17 | {
18 | return view('enlighten::components.breadcrumbs');
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/View/Components/CodeExampleComponent.php:
--------------------------------------------------------------------------------
1 | example = $example;
20 | }
21 |
22 | public function render()
23 | {
24 | return view('enlighten::group._code-example', [
25 | 'developer_mode' => config('enlighten.developer-mode'),
26 | 'failed' => $this->example->getStatus() !== Status::SUCCESS,
27 | ]);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/View/Components/DynamicTabsComponent.php:
--------------------------------------------------------------------------------
1 | tabs = $this->normalizeTabs($tabs);
19 | $this->type = $type;
20 | $this->htmlable = class_exists(ComponentSlot::class) ? ComponentSlot::class : HtmlString::class;
21 | }
22 |
23 | public function render()
24 | {
25 | if ($this->type === 'menu') {
26 | $view = 'enlighten::components.dynamic-tabs-menu';
27 | } else {
28 | $view = 'enlighten::components.dynamic-tabs';
29 | }
30 |
31 | return view($view, [
32 | 'tabs_collection' => $this->tabs
33 | ]);
34 | }
35 |
36 | private function normalizeTabs(array $tabs): Collection
37 | {
38 | return collect($tabs)->mapWithKeys(function ($value, $key) {
39 | if (is_numeric($key)) {
40 | return [strtolower($value) => $value];
41 | }
42 |
43 | return [$key => $value];
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/View/Components/EditButtonComponent.php:
--------------------------------------------------------------------------------
1 | file)
17 | && ! app()->runningInConsole();
18 | }
19 |
20 | public function render()
21 | {
22 | return view('enlighten::components.edit-button', [
23 | 'file' => $this->file
24 | ]);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/View/Components/ExampleBreadcrumbs.php:
--------------------------------------------------------------------------------
1 | example = $example;
16 | }
17 |
18 | public function render()
19 | {
20 | return view('enlighten::components.example-breadcrumbs', [
21 | 'segments' => $this->getBreadcrumbSegments()
22 | ]);
23 | }
24 |
25 | private function getBreadcrumbSegments(): array
26 | {
27 | return [
28 | route('enlighten.area.show', ['run' => $this->example->group->run_id, 'area' => $this->example->group->area]) => ucwords((string) $this->example->group->area),
29 | $this->example->group->url => $this->example->group->title
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/View/Components/ExampleTabsComponent.php:
--------------------------------------------------------------------------------
1 | example = $example;
19 | }
20 |
21 | public function render()
22 | {
23 | return view('enlighten::components.example-tabs', [
24 | 'showRequests' => $this->example->requests->isNotEmpty(),
25 | 'requestTabs' => $this->getRequestTabs(),
26 | 'showQueries' => $this->shouldShowQueries(),
27 | 'showException' => $this->showException(),
28 | ]);
29 | }
30 | private function showException()
31 | {
32 | if (Settings::hide(Section::EXCEPTION)) {
33 | return false;
34 | }
35 |
36 | return $this->example->has_exception;
37 | }
38 |
39 | private function shouldShowQueries(): bool
40 | {
41 | if (Settings::hide(Section::QUERIES)) {
42 | return false;
43 | }
44 |
45 | return $this->example->queries->isNotEmpty();
46 | }
47 |
48 | private function getRequestTabs()
49 | {
50 | return $this->example->requests->map(fn ($request, $key) => $this->newRequestTab($request, $key + 1));
51 | }
52 |
53 | private function newRequestTab(ExampleRequest $request, int $requestNumber): object
54 | {
55 | return (object) [
56 | 'request' => $request,
57 | 'key' => $request->hash,
58 | 'title' => "Request #{$requestNumber}",
59 | 'showSession' => $this->shouldShowSessionTab($request),
60 | 'showPreviewOnly' => $this->shouldOnlyShowPreview($request),
61 | ];
62 | }
63 |
64 | private function shouldShowSessionTab(ExampleRequest $request): bool
65 | {
66 | return Settings::show(Section::SESSION) && !empty($request->session_data);
67 | }
68 |
69 | private function shouldOnlyShowPreview(ExampleRequest $request): bool
70 | {
71 | return $this->example->has_exception && $request->response_type === 'JSON';
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/View/Components/ExceptionInfoComponent.php:
--------------------------------------------------------------------------------
1 | exception->trace)) {
17 | return collect();
18 | }
19 |
20 | return collect($this->exception->trace)
21 | ->map(fn ($data) => [
22 | 'file' => $data['file'] ?? '',
23 | 'line' => $data['line'] ?? '',
24 | 'function' => $this->getFunctionSignature($data),
25 | 'args' => $data['args'] ?? [],
26 | ]);
27 | }
28 |
29 | private function getFunctionSignature(array $data): string
30 | {
31 | if (empty($data['class'])) {
32 | return $data['function'];
33 | }
34 |
35 | return $data['class'].$data['type'].$data['function'];
36 | }
37 |
38 | public function render()
39 | {
40 | if ($this->trace()->isEmpty()) {
41 | return;
42 | }
43 |
44 | return view('enlighten::components.exception-info', [
45 | 'trace' => $this->trace(),
46 | 'exception' => $this->exception
47 | ]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/View/Components/GroupBreadcrumbs.php:
--------------------------------------------------------------------------------
1 | exampleGroup = $group;
15 | }
16 |
17 | public function render()
18 | {
19 | return view('enlighten::components.group-breadcrumbs', [
20 | 'segments' => $this->getBreadcrumbsSegments()
21 | ]);
22 | }
23 |
24 | private function getBreadcrumbsSegments(): array
25 | {
26 | return [
27 | route('enlighten.area.show', [
28 | 'run' => $this->exampleGroup->run_id,
29 | 'area' => $this->exampleGroup->area
30 | ]) => ucwords((string) $this->exampleGroup->area),
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/View/Components/HtmlResponseComponent.php:
--------------------------------------------------------------------------------
1 | request = $request;
20 | }
21 |
22 | public function render()
23 | {
24 | return view('enlighten::components.html-response', [
25 | 'showHtml' => Settings::show(Section::HTML),
26 | 'showTemplate' => $this->showTemplate(),
27 | ]);
28 | }
29 |
30 | private function showTemplate(): bool
31 | {
32 | if (Settings::hide(Section::BLADE)) {
33 | return false;
34 | }
35 |
36 | return ! empty($this->request->response_template);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/View/Components/KeyValueComponent.php:
--------------------------------------------------------------------------------
1 | items = $this->normalizeItems($items);
23 |
24 | $this->title = $title;
25 | }
26 |
27 | private function normalizeItems(array $items): array
28 | {
29 | return array_map(fn ($value) => is_array($value) ? implode('
', $value) : $value, $items);
30 | }
31 |
32 | public function render()
33 | {
34 | return view('enlighten::components.key-value');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/View/Components/RepresentsStatusAsColor.php:
--------------------------------------------------------------------------------
1 | 'green',
14 | 'warning' => 'yellow',
15 | 'failure' => 'red'
16 | ])->get($model->getStatus(), 'yellow');
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/View/Components/RequestInfoComponent.php:
--------------------------------------------------------------------------------
1 | $this->routeInfo($this->request),
20 | 'request' => $this->request,
21 | 'request_input' => $this->normalizeRequestInput(),
22 | 'showRouteParameters' => $this->showRouteParameters(),
23 | 'showInput' => $this->showInput(),
24 | 'showHeaders' => $this->showHeaders(),
25 | ]);
26 | }
27 |
28 | private function showRouteParameters()
29 | {
30 | if (Settings::hide(Section::ROUTE_PARAMETERS)) {
31 | return false;
32 | }
33 |
34 | return ! empty($this->request->route_parameters);
35 | }
36 |
37 | private function showInput()
38 | {
39 | if (Settings::hide(Section::REQUEST_INPUT)) {
40 | return false;
41 | }
42 |
43 | return ! empty($this->request->request_input);
44 | }
45 |
46 | private function showHeaders()
47 | {
48 | if (Settings::hide(Section::REQUEST_HEADERS)) {
49 | return false;
50 | }
51 |
52 | return ! empty($this->request->request_headers);
53 | }
54 |
55 | private function routeInfo(ExampleRequest $request): array
56 | {
57 | return [
58 | 'Method' => $this->request->request_method,
59 | 'Route' => $this->request->route,
60 | 'Example' => $this->request->request_path . ($this->request->request_query_parameters ? '?' . http_build_query($this->request->request_query_parameters) : ''),
61 | ];
62 | }
63 |
64 | private function normalizeRequestInput(): array
65 | {
66 | return collect($this->request['request_input'])
67 | ->map(fn ($value) => is_array($value) ? enlighten_json_prettify($value) : $value)->toArray();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/View/Components/RequestInputTableComponent.php:
--------------------------------------------------------------------------------
1 | input = $this->normalizeInput($input);
18 | }
19 |
20 | private function normalizeInput(array $input): array
21 | {
22 | return array_map(fn ($value) => is_array($value) ? implode(': ', $value) : $value, $input);
23 | }
24 |
25 | public function render()
26 | {
27 | return view('enlighten::components.request-input-table');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/View/Components/ResponseInfoComponent.php:
--------------------------------------------------------------------------------
1 | request = $request;
20 | }
21 |
22 | public function render()
23 | {
24 | return view('enlighten::components.response-info', [
25 | 'request' => $this->request,
26 | 'color' => $this->request->getStatus(),
27 | 'status' => $this->request->response_status ?? 'UNKNOWN',
28 | 'showHeaders' => $this->showHeaders(),
29 | ]);
30 | }
31 |
32 | private function showHeaders()
33 | {
34 | if (Settings::hide(Section::RESPONSE_HEADERS)) {
35 | return false;
36 | }
37 |
38 | return ! empty($this->request->response_headers);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/View/Components/RouteParametersTableComponent.php:
--------------------------------------------------------------------------------
1 | parameters = $this->normalizeParameters($parameters);
18 | }
19 |
20 | public function normalizeParameters(array $parameters): array
21 | {
22 | return array_map(function ($parameter) {
23 | $parameter['requirement'] = $parameter['optional'] ? 'Optional' : 'Required';
24 | return $parameter;
25 | }, $parameters);
26 | }
27 |
28 | public function render()
29 | {
30 | return view('enlighten::components.route-parameters-table');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/View/Components/SearchBoxComponent.php:
--------------------------------------------------------------------------------
1 | activeRun = $run;
15 | }
16 |
17 | public function render()
18 | {
19 | return view('enlighten::components.search-box', [
20 | 'searchUrl' => route('enlighten.api.search', ['run' => $this->activeRun])
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/View/Components/SearchBoxStaticComponent.php:
--------------------------------------------------------------------------------
1 | $this->model->getPassingTestsCount(),
18 | 'total' => $this->model->getTestsCount(),
19 | 'color' => $this->model->getStatus(),
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/View/Components/StatusBadgeComponent.php:
--------------------------------------------------------------------------------
1 | model = $model;
23 | $this->size = $size;
24 | }
25 |
26 | public function render()
27 | {
28 | return view('enlighten::components.status-badge', [
29 | 'color' => $this->model->getStatus(),
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 |