Passed
Push — master ( 788a27...9cd729 )
by Caen
03:03 queued 12s
created

DocumentationSearchService::execute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Hyde\Framework\Services;
4
5
use Hyde\Framework\Concerns\InteractsWithDirectories;
6
use Hyde\Framework\Hyde;
7
use Hyde\Framework\Models\Pages\DocumentationPage;
8
use Illuminate\Support\Collection;
9
use Illuminate\Support\Str;
10
use JetBrains\PhpStorm\ArrayShape;
11
12
/**
13
 * @internal Generate a JSON file that can be used as a search index for documentation pages.
14
 *
15
 * @see \Hyde\Framework\Testing\Feature\Services\DocumentationSearchServiceTest
16
 */
17
final class DocumentationSearchService
18
{
19
    use InteractsWithDirectories;
20
21
    public Collection $searchIndex;
22
    public static string $filePath = '_site/docs/search.json';
23
24
    public static function generate(): self
25
    {
26
        return (new self())->execute();
27
    }
28
29
    public static function generateSearchPage(): string
30
    {
31
        $outputDirectory = Hyde::getSiteOutputPath(DocumentationPage::getOutputDirectory());
32
        self::needsDirectory(($outputDirectory));
33
34
        file_put_contents(
35
            "$outputDirectory/search.html",
36
            view('hyde::pages.documentation-search')->render()
37
        );
38
39
        return $outputDirectory;
40
    }
41
42
    public function __construct()
43
    {
44
        $this->searchIndex = new Collection();
45
        self::$filePath = Hyde::pathToRelative(Hyde::getSiteOutputPath(
46
            DocumentationPage::getOutputDirectory().'/search.json'
47
        ));
48
    }
49
50
    public function execute(): self
51
    {
52
        return $this->run()->save();
53
    }
54
55
    public function run(): self
56
    {
57
        /** @var DocumentationPage $page */
58
        foreach (DocumentationPage::all() as $page) {
59
            if (! in_array($page->identifier, config('docs.exclude_from_search', []))) {
60
                $this->searchIndex->push(
61
                    $this->generatePageEntry($page)
0 ignored issues
show
Bug introduced by
$this->generatePageEntry($page) of type array<string,string> is incompatible with the type Illuminate\Support\TValue expected by parameter $values of Illuminate\Support\Collection::push(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

61
                    /** @scrutinizer ignore-type */ $this->generatePageEntry($page)
Loading history...
62
                );
63
            }
64
        }
65
66
        return $this;
67
    }
68
69
    #[ArrayShape(['slug' => 'string', 'title' => 'string', 'content' => 'string', 'destination' => 'string'])]
70
    public function generatePageEntry(DocumentationPage $page): array
71
    {
72
        return [
73
            'slug' => basename($page->identifier),
74
            'title' => $page->title,
75
            'content' => trim($this->getSearchContentForDocument($page)),
76
            'destination' => $this->getDestinationForSlug(basename($page->identifier)),
77
        ];
78
    }
79
80
    protected function save(): self
81
    {
82
        $this->needsDirectory(Hyde::path(str_replace('/search.json', '', self::$filePath)));
83
84
        file_put_contents(Hyde::path(self::$filePath), $this->searchIndex->toJson());
85
86
        return $this;
87
    }
88
89
    /**
90
     * There are a few ways we could go about this. The goal is to allow the user
91
     * to run a free-text search to find relevant documentation pages.
92
     *
93
     * The easiest way to do this is by adding the Markdown body to the search index.
94
     * But this is of course not ideal as it may take an incredible amount of space
95
     * for large documentation sites. The Hyde docs weight around 80kb of JSON.
96
     *
97
     * Another option is to assemble all the headings in a document and use that
98
     * for the search basis. A truncated version of the body could also be included.
99
     *
100
     * A third option which might be the most space efficient (besides from just
101
     * adding titles, which doesn't offer much help to the user since it is just
102
     * a filterable sidebar at that point), would be to search for keywords
103
     * in the document. This would however add complexity as well as extra
104
     * computing time.
105
     *
106
     * Benchmarks: (for official Hyde docs)
107
     *
108
     * Returning $document->body as is: 500ms
109
     * Returning $document->body as Str::markdown(): 920ms + 10ms for regex
110
     */
111
    protected function getSearchContentForDocument(DocumentationPage $page): string
112
    {
113
        // This is compiles the Markdown body into HTML, and then strips out all
114
        // HTML tags to get a plain text version of the body. This takes a long
115
        // site, but is the simplest implementation I've found so far.
116
        return preg_replace('/<(.|\n)*?>/', ' ', Str::markdown($page->markdown));
117
    }
118
119
    public function getDestinationForSlug(string $slug): string
120
    {
121
        if (config('site.pretty_urls', false) === true) {
122
            return $slug !== 'index' ? $slug : '';
123
        }
124
125
        return $slug.'.html';
126
    }
127
}
128