Passed
Push — master ( 7773d7...9d8725 )
by Caen
03:24 queued 12s
created

generatePageObject()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Hyde\Framework\Actions;
4
5
use Hyde\Framework\Concerns\InteractsWithDirectories;
6
use Hyde\Framework\Contracts\ActionContract;
7
use Hyde\Framework\Hyde;
8
use Hyde\Framework\Models\Pages\DocumentationPage;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Str;
11
use JetBrains\PhpStorm\ArrayShape;
12
13
/**
14
 * @internal Generate a JSON file that can be used as a search index for documentation pages.
15
 *
16
 * @todo Convert into Service, and add more strategies, such as slug-only (no file parsing)
17
 *        search which while dumber, would be much faster to compile and take way less space.
18
 *
19
 * @see \Hyde\Framework\Testing\Feature\Actions\GeneratesDocumentationSearchIndexFileTest
20
 */
21
final class GeneratesDocumentationSearchIndexFile implements ActionContract
22
{
23
    use InteractsWithDirectories;
24
25
    public Collection $searchIndex;
26
    public static string $filePath = '_site/docs/search.json';
27
28
    public static function run(): self
29
    {
30
        return (new self())->execute();
31
    }
32
33
    public function __construct()
34
    {
35
        $this->searchIndex = new Collection();
36
        static::$filePath = Hyde::pathToRelative(Hyde::getSiteOutputPath(
37
            DocumentationPage::getOutputDirectory().'/search.json')
38
        );
39
    }
40
41
    public function execute(): self
42
    {
43
        $this->generate();
44
        $this->save();
45
46
        return $this;
47
    }
48
49
    public function generate(): self
50
    {
51
        /** @var DocumentationPage $page */
52
        foreach (DocumentationPage::all() as $page) {
53
            if (! in_array($page->identifier, config('docs.exclude_from_search', []))) {
54
                $this->searchIndex->push(
55
                    $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

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