Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
11 / 11
CRAP
100.00% covered (success)
100.00%
1 / 1
GeneratesDocumentationSearchIndexFile
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
11 / 11
15
100.00% covered (success)
100.00%
1 / 1
 run
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 generate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 generatePageObject
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getSourceFileSlugs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getObject
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getJson
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 save
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSearchContentForDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDestinationForSlug
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3namespace Hyde\Framework\Actions;
4
5use Hyde\Framework\Contracts\ActionContract;
6use Hyde\Framework\Hyde;
7use Hyde\Framework\Models\DocumentationPage;
8use Hyde\Framework\Models\Parsers\DocumentationPageParser;
9use Hyde\Framework\Services\CollectionService;
10use Illuminate\Support\Collection;
11use Illuminate\Support\Str;
12
13/**
14 * 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 \Tests\Feature\Actions\GeneratesDocumentationSearchIndexFileTest
20 */
21class GeneratesDocumentationSearchIndexFile implements ActionContract
22{
23    public Collection $searchIndex;
24    public static string $filePath = '_site/docs/search.json';
25
26    public static function run(): void
27    {
28        (new static())->execute();
29    }
30
31    public function __construct()
32    {
33        $this->searchIndex = new Collection();
34
35        static::$filePath = '_site/'.config('docs.output_directory', 'docs').'/search.json';
36    }
37
38    public function execute(): void
39    {
40        $this->generate();
41        $this->save();
42    }
43
44    public function generate(): self
45    {
46        foreach ($this->getSourceFileSlugs() as $page) {
47            $this->searchIndex->push(
48                $this->generatePageObject($page)
49            );
50        }
51
52        return $this;
53    }
54
55    public function generatePageObject(string $slug): object
56    {
57        $page = (new DocumentationPageParser($slug))->get();
58
59        return (object) [
60            'slug' => $page->slug,
61            'title' => trim($page->findTitleForDocument()),
62            'content' => trim($this->getSearchContentForDocument($page)),
63            'destination' => $this->getDestinationForSlug($page->slug),
64        ];
65    }
66
67    public function getSourceFileSlugs(): array
68    {
69        return CollectionService::getDocumentationPageList();
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(): self
83    {
84        file_put_contents(Hyde::path(static::$filePath), $this->getJson());
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    public function getSearchContentForDocument(DocumentationPage $document): 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($document->body));
117    }
118
119    public function getDestinationForSlug(string $slug): string
120    {
121        if ($slug === 'index' && config('hyde.pretty_urls', false)) {
122            $slug = '';
123        }
124
125        return (config('hyde.pretty_urls', false) === true)
126            ? $slug : $slug.'.html';
127    }
128}