diff --git a/packages/framework/src/HydeServiceProvider.php b/packages/framework/src/HydeServiceProvider.php index 0a4c751588b..c190b79816b 100644 --- a/packages/framework/src/HydeServiceProvider.php +++ b/packages/framework/src/HydeServiceProvider.php @@ -88,6 +88,8 @@ function () { Commands\HydePackageDiscoverCommand::class, ]); + + $this->registerModuleServiceProviders(); } /** @@ -136,4 +138,14 @@ protected function storeCompiledSiteIn(string $directory): void { StaticPageBuilder::$outputPath = $directory; } + + /** + * Register module service providers. + * + * @todo Make modules configurable. + */ + protected function registerModuleServiceProviders(): void + { + $this->app->register(Modules\DataCollections\DataCollectionServiceProvider::class); + } } diff --git a/packages/framework/src/Modules/DataCollections/DataCollection.php b/packages/framework/src/Modules/DataCollections/DataCollection.php new file mode 100644 index 00000000000..0a9e96604b0 --- /dev/null +++ b/packages/framework/src/Modules/DataCollections/DataCollection.php @@ -0,0 +1,68 @@ +timeStart = microtime(true); + $this->key = $key; + + parent::__construct(); + } + + public function getCollection(): DataCollection + { + $this->parseTimeInMs = round((microtime(true) - $this->timeStart) * 1000, 2); + unset($this->timeStart); + + return $this; + } + + public function getMarkdownFiles(): array + { + return glob(Hyde::path( + static::$sourceDirectory.'/'.$this->key.'/*'.MarkdownDocument::$fileExtension + )); + } + + /** + * Get a collection of Markdown documents in the _data/<$key> directory. + * Each Markdown file will be parsed into a MarkdownDocument with front matter. + * + * @param string $key for a subdirectory of the _data directory + * @return DataCollection<\Hyde\Framework\Models\MarkdownDocument> + */ + public static function markdown(string $key): DataCollection + { + $collection = new DataCollection($key); + foreach ($collection->getMarkdownFiles() as $file) { + if (! str_starts_with(basename($file), '_')) { + $collection->push( + (new MarkdownFileService($file))->get() + ); + } + } + + return $collection->getCollection(); + } +} diff --git a/packages/framework/src/Modules/DataCollections/DataCollectionServiceProvider.php b/packages/framework/src/Modules/DataCollections/DataCollectionServiceProvider.php new file mode 100644 index 00000000000..513ebeac396 --- /dev/null +++ b/packages/framework/src/Modules/DataCollections/DataCollectionServiceProvider.php @@ -0,0 +1,29 @@ +alias( + 'MarkdownCollection', Facades\MarkdownCollection::class + ); + } + + public function boot() + { + // Create the _data directory if it doesn't exist + if (! is_dir(Hyde::path('_data'))) { + mkdir(Hyde::path('_data')); + } + } +} diff --git a/packages/framework/src/Modules/DataCollections/Facades/MarkdownCollection.php b/packages/framework/src/Modules/DataCollections/Facades/MarkdownCollection.php new file mode 100644 index 00000000000..dd7478a5e5a --- /dev/null +++ b/packages/framework/src/Modules/DataCollections/Facades/MarkdownCollection.php @@ -0,0 +1,16 @@ +assertInstanceOf(DataCollection::class, $class); + $this->assertInstanceOf(Collection::class, $class); + } + + public function test_constructor_sets_key() + { + $class = new DataCollection('foo'); + $this->assertEquals('foo', $class->key); + } + + public function test_key_is_required() + { + $this->expectException(\ArgumentCountError::class); + new DataCollection(); + } + + public function test_get_collection_method_returns_the_collection_instance() + { + $class = new DataCollection('foo'); + $this->assertSame($class, $class->getCollection()); + } + + public function test_get_collection_method_sets_parse_time_in_ms() + { + $class = new DataCollection('foo'); + $class->getCollection(); + $this->assertIsFloat($class->parseTimeInMs); + } + + public function test_get_markdown_files_method_returns_empty_array_if_the_specified_directory_does_not_exist() + { + $class = new DataCollection('foo'); + $this->assertIsArray($class->getMarkdownFiles()); + $this->assertEmpty($class->getMarkdownFiles()); + } + + public function test_get_markdown_files_method_returns_empty_array_if_no_files_are_found_in_specified_directory() + { + mkdir(Hyde::path('_data/foo')); + $class = new DataCollection('foo'); + $this->assertIsArray($class->getMarkdownFiles()); + $this->assertEmpty($class->getMarkdownFiles()); + rmdir(Hyde::path('_data/foo')); + } + + public function test_get_markdown_files_method_returns_an_array_of_markdown_files_in_the_specified_directory() + { + mkdir(Hyde::path('_data/foo')); + touch(Hyde::path('_data/foo/foo.md')); + touch(Hyde::path('_data/foo/bar.md')); + + $this->assertEquals([ + Hyde::path('_data/foo/bar.md'), + Hyde::path('_data/foo/foo.md'), + ], (new DataCollection('foo'))->getMarkdownFiles()); + + File::deleteDirectory(Hyde::path('_data/foo')); + } + + public function test_get_markdown_files_method_does_not_include_files_in_subdirectories() + { + mkdir(Hyde::path('_data/foo')); + mkdir(Hyde::path('_data/foo/bar')); + touch(Hyde::path('_data/foo/foo.md')); + touch(Hyde::path('_data/foo/bar/bar.md')); + $this->assertEquals([ + Hyde::path('_data/foo/foo.md'), + ], (new DataCollection('foo'))->getMarkdownFiles()); + File::deleteDirectory(Hyde::path('_data/foo')); + } + + public function test_get_markdown_files_method_does_not_include_files_with_extensions_other_than_md() + { + mkdir(Hyde::path('_data/foo')); + touch(Hyde::path('_data/foo/foo.md')); + touch(Hyde::path('_data/foo/bar.txt')); + $this->assertEquals([ + Hyde::path('_data/foo/foo.md'), + ], (new DataCollection('foo'))->getMarkdownFiles()); + File::deleteDirectory(Hyde::path('_data/foo')); + } + + public function test_static_markdown_helper_returns_new_data_collection_instance() + { + $this->assertInstanceOf(DataCollection::class, DataCollection::markdown('foo')); + } + + public function test_static_markdown_helper_discovers_and_parses_markdown_files_in_the_specified_directory() + { + mkdir(Hyde::path('_data/foo')); + touch(Hyde::path('_data/foo/foo.md')); + touch(Hyde::path('_data/foo/bar.md')); + + $collection = DataCollection::markdown('foo'); + + $this->assertContainsOnlyInstancesOf(MarkdownDocument::class, $collection); + + File::deleteDirectory(Hyde::path('_data/foo')); + } + + public function test_static_markdown_helper_ignores_files_starting_with_an_underscore() + { + mkdir(Hyde::path('_data/foo')); + touch(Hyde::path('_data/foo/foo.md')); + touch(Hyde::path('_data/foo/_bar.md')); + $this->assertCount(1, DataCollection::markdown('foo')); + File::deleteDirectory(Hyde::path('_data/foo')); + } + + public function test_markdown_facade_returns_same_result_as_static_markdown_helper() + { + $expected = DataCollection::markdown('foo'); + $actual = MarkdownCollection::get('foo'); + unset($expected->parseTimeInMs); + unset($actual->parseTimeInMs); + $this->assertEquals($expected, $actual); + } + + // Test DataCollectionServiceProvider registers the facade as an alias + public function test_data_collection_service_provider_registers_the_facade_as_an_alias() + { + $this->assertArrayHasKey('MarkdownCollection', AliasLoader::getInstance()->getAliases()); + $this->assertContains(MarkdownCollection::class, AliasLoader::getInstance()->getAliases()); + } + + public function test_data_collection_service_provider_creates_the__data_directory_if_it_does_not_exist() + { + File::deleteDirectory(Hyde::path('_data')); + $this->assertFileDoesNotExist(Hyde::path('_data')); + + (new DataCollectionServiceProvider($this->app))->boot(); + + $this->assertFileExists(Hyde::path('_data')); + } + + public function test_class_has_static_source_directory_property() + { + $this->assertEquals('_data', DataCollection::$sourceDirectory); + } + + public function test_source_directory_can_be_changed() + { + DataCollection::$sourceDirectory = 'foo'; + mkdir(Hyde::path('foo/bar'), recursive: true); + touch(Hyde::path('foo/bar/foo.md')); + $this->assertEquals([ + Hyde::path('foo/bar/foo.md'), + ], (new DataCollection('bar'))->getMarkdownFiles()); + File::deleteDirectory(Hyde::path('foo')); + } +}