Passed
Push — master ( 47cd92...6a0eb4 )
by Caen
03:28 queued 12s
created

getSourceFileSlugs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
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
12
/**
13
 * Generate a JSON file that can be used as a search index for documentation pages.
14
 *
15
 * @todo Convert into Service, and add more strategies, such as slug-only (no file parsing)
16
 *        search which while dumber, would be much faster to compile and take way less space.
17
 * @todo Refactor to use custom site output paths
18
 *
19
 * @see \Hyde\Framework\Testing\Feature\Actions\GeneratesDocumentationSearchIndexFileTest
20
 *
21
 * @phpstan-consistent-constructor
22
 */
23
class GeneratesDocumentationSearchIndexFile implements ActionContract
24
{
25
    use InteractsWithDirectories;
26
27
    public Collection $searchIndex;
28
    public static string $filePath = '_site/docs/search.json';
29
30
    public static function run(): void
31
    {
32
        (new static())->execute();
33
    }
34
35
    public function __construct()
36
    {
37
        $this->searchIndex = new Collection();
38
39
        static::$filePath = '_site/'.config('docs.output_directory', 'docs').'/search.json';
40
    }
41
42
    public function execute(): void
43
    {
44
        $this->generate();
45
        $this->save();
46
    }
47
48
    public function generate(): static
49
    {
50
        /** @var DocumentationPage $page */
51
        foreach (DocumentationPage::all() as $page) {
52
            if (! in_array($page->slug, config('docs.exclude_from_search', []))) {
53
                $this->searchIndex->push(
54
                    $this->generatePageObject($page)
55
                );
56
            }
57
        }
58
59
        return $this;
60
    }
61
62
    public function generatePageObject(DocumentationPage $page): object
63
    {
64
        return (object) [
65
            'slug' => $page->slug,
66
            'title' => trim($page->findTitleForDocument()),
67
            'content' => trim($this->getSearchContentForDocument($page)),
68
            'destination' => $this->getDestinationForSlug($page->slug),
69
        ];
70
    }
71
72
    public function getObject(): object
73
    {
74
        return (object) $this->searchIndex;
75
    }
76
77
    public function getJson(): string
78
    {
79
        return json_encode($this->getObject());
80
    }
81
82
    public function save(): static
83
    {
84
        $this->needsDirectory(Hyde::path(str_replace('/search.json', '', static::$filePath)));
85
86
        file_put_contents(Hyde::path(static::$filePath), $this->getJson());
87
88
        return $this;
89
    }
90
91
    /**
92
     * There are a few ways we could go about this. The goal is to allow the user
93
     * to run a free-text search to find relevant documentation pages.
94
     *
95
     * The easiest way to do this is by adding the Markdown body to the search index.
96
     * But this is of course not ideal as it may take an incredible amount of space
97
     * for large documentation sites. The Hyde docs weight around 80kb of JSON.
98
     *
99
     * Another option is to assemble all the headings in a document and use that
100
     * for the search basis. A truncated version of the body could also be included.
101
     *
102
     * A third option which might be the most space efficient (besides from just
103
     * adding titles, which doesn't offer much help to the user since it is just
104
     * a filterable sidebar at that point), would be to search for keywords
105
     * in the document. This would however add complexity as well as extra
106
     * computing time.
107
     *
108
     * Benchmarks: (for official Hyde docs)
109
     *
110
     * Returning $document->body as is: 500ms
111
     * Returning $document->body as Str::markdown(): 920ms + 10ms for regex
112
     */
113
    public function getSearchContentForDocument(DocumentationPage $document): string
114
    {
115
        // This is compiles the Markdown body into HTML, and then strips out all
116
        // HTML tags to get a plain text version of the body. This takes a long
117
        // site, but is the simplest implementation I've found so far.
118
        return preg_replace('/<(.|\n)*?>/', ' ', Str::markdown($document->body));
119
    }
120
121
    public function getDestinationForSlug(string $slug): string
122
    {
123
        if ($slug === 'index' && config('site.pretty_urls', false)) {
124
            $slug = '';
125
        }
126
127
        return (config('site.pretty_urls', false) === true)
128
            ? $slug : $slug.'.html';
129
    }
130
}
131