Repository: ryangjchandler/orbit (php)

Issue:
Add a new `MarkdownJson` driver
This driver would work the same as the normal Markdown driver, but instead of using YAML as the front-matter, it would use JSON instead (YAML with brackets, lol).

Pull request description:
feature: MarkdownJson driver
Closes #36.

Patch:
diff --git a/README.md b/README.md
index 4496f27..a7b6d07 100644
--- a/README.md
+++ b/README.md
@@ -133,6 +133,31 @@ Out of the box, Orbit provides the following drivers:
 * `md` -> `Orbit\Drivers\Markdown`
 * `json` => `Orbit\Drivers\Json`
 * `yaml` => `Orbit\Drivers\Yaml`
+* `md_json` => `Orbit\Drivers\MarkdownJson`
+
+### `md`
+
+This is a Markdown that is capable of parsing Markdown files, as well as YAML front-matter.
+
+When Orbit loads files using this driver, it will map each front-matter key into a column in your models `schema`.
+
+By default, the Markdown driver will also add a `TEXT content` column to your schema. This is used to store the Markdown body from the file.
+
+> 💡 If you wish to customise the name of the `content` column, you can use the `Markdown::contentColumn()` method and provide a new column name. This is applied to all models that use the `Markdown` driver.
+
+### `json`
+
+This is a JSON driver that is capable of parsing `.json` files. Each key in the file is mapped to a column in your schema.
+
+### `yaml`
+
+This is a YAML driver that is capable of parsing `.yml` drivers. Each key in the file is mapped to a column in your schema.
+
+### `md_json`
+
+This driver is very similar to the `Markdown` / `md` driver, but it supports JSON-based front-matter as opposed to the default YAML format.
+
+> 💡 If you wish to customise the name of the `content` column, you can use the `MarkdownJson::contentColumn()` method and provide a new column name. This is applied to all models that use the `MarkdownJson` driver.
 
 ### Registering drivers
 
@@ -144,7 +169,7 @@ You can register custom Orbit drivers by using the `Orbit::extend` method. You s
 })
 ```
 
-All drivers must implement the `Orbit\Contracts\Driver` contract. This enforces drivers to have at least 4 methods:
+All drivers must implement the `Orbit\Contracts\Driver` contract, or extend the `Orbit\Drivers\FileDriver` class. This enforces drivers to have at least 4 methods:
 
 ```php
 interface Driver
@@ -159,13 +184,21 @@ interface Driver
 }
 ```
 
-Here is what each of the methods are for:
+Here is what each method is used for:
 
 * `shouldRestoreCache` - used to determine if the file cache should be updated. 
 * `save` - used to persist model changes to a file on disk, or create a new file.
 * `delete` - used to delete an existing file on disk
 * `all` - used to retrieve all records from disk and cache.
 
+### Extending `FileDriver`
+
+Extending the `Orbit\Drivers\FileDriver` class is useful when you want some of the heavy lifting done for you. To work with this base class, you should do the following:
+
+1. Implement an `extension(): string` method that returns the file extension as a string, i.e. `return 'md'` for `Markdown`.
+2. Implement a `dumpContent(Model $model): string` method. This method should return the updated content for the file.
+3. Implement a `parseContent(SplFileInfo $file): array` method. This method should return an array of `key => value` pairs, where each `key` is a column in the `schema`.
+
 ### Changing drivers
 
 If you wish to use a different driver for one of your models, you can add a `public static $driver` property to your model and set the value to the name of a driver.
diff --git a/config/orbit.php b/config/orbit.php
index 211d345..6227bef 100644
--- a/config/orbit.php
+++ b/config/orbit.php
@@ -8,6 +8,7 @@ return [
         'md' => \Orbit\Drivers\Markdown::class,
         'json' => \Orbit\Drivers\Json::class,
         'yaml' => \Orbit\Drivers\Yaml::class,
+        'md_json' => \Orbit\Drivers\MarkdownJson::class,
     ],
 
     'paths' => [
diff --git a/src/Drivers/MarkdownJson.php b/src/Drivers/MarkdownJson.php
new file mode 100644
index 0000000..0bfcd68
--- /dev/null
+++ b/src/Drivers/MarkdownJson.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Orbit\Drivers;
+
+use SplFileInfo;
+use Illuminate\Database\Eloquent\Model;
+use Spatie\YamlFrontMatter\YamlFrontMatter;
+
+class MarkdownJson extends Markdown
+{
+    protected function dumpContent(Model $model): string
+    {
+        $matter = array_filter($model->attributesToArray(), function ($value, $key) {
+            return $key !== 'content' && $value !== null;
+        }, ARRAY_FILTER_USE_BOTH);
+
+        $json = json_encode($matter, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+
+        return implode(PHP_EOL, [
+            '---',
+            rtrim($json, PHP_EOL),
+            '---',
+            $model->getAttribute('content'),
+        ]);
+    }
+
+    protected function parseContent(SplFileInfo $file): array
+    {
+        $content = file_get_contents($file->getPathname());
+
+        $pattern = '/^[\s\r\n]?---[\s\r\n]?$/sm';
+
+        $parts = preg_split($pattern, PHP_EOL . ltrim($content));
+
+        $matter = count($parts) < 3 ? [] : json_decode(trim($parts[1]), true);
+
+        $body = implode(PHP_EOL.'---'.PHP_EOL, array_slice($parts, 2));
+
+        return array_merge(
+            $matter,
+            ['content' => trim($body)]
+        );
+    }
+}


Test patch:
diff --git a/tests/AdvancedOrbitalTest.php b/tests/AdvancedOrbitalTest.php
index b5f0e38..1d9aa80 100644
--- a/tests/AdvancedOrbitalTest.php
+++ b/tests/AdvancedOrbitalTest.php
@@ -4,6 +4,7 @@ namespace Orbit\Tests;
 
 use Orbit\Tests\Fixtures\CustomKey;
 use Orbit\Tests\Fixtures\JsonModel;
+use Orbit\Tests\Fixtures\MarkdownJsonModel;
 use Orbit\Tests\Fixtures\YamlModel;
 
 class AdvancedOrbitalTest extends TestCase
@@ -57,4 +58,13 @@ class AdvancedOrbitalTest extends TestCase
 
         $this->assertFileExists(__DIR__.'/content/yaml_models/'.$yaml->getKey().'.yml');
     }
+
+    public function test_it_can_use_markdown_json_driver()
+    {
+        $md = MarkdownJsonModel::create([
+            'name' => 'Ryan',
+        ]);
+
+        $this->assertFileExists(__DIR__.'/content/markdown_json_models/'.$md->getKey().'.md');
+    }
 }
diff --git a/tests/BasicOrbitalTest.php b/tests/BasicOrbitalTest.php
index 9a82a6f..8895613 100644
--- a/tests/BasicOrbitalTest.php
+++ b/tests/BasicOrbitalTest.php
@@ -5,7 +5,7 @@ namespace Orbit\Tests;
 use Orbit\Tests\Fixtures\Post;
 use Orbit\Tests\Fixtures\Emptiness;
 
-class OrbitalTest extends TestCase
+class BasicOrbitalTest extends TestCase
 {
     public function tearDown(): void
     {
diff --git a/tests/Fixtures/MarkdownJsonModel.php b/tests/Fixtures/MarkdownJsonModel.php
new file mode 100644
index 0000000..399f412
--- /dev/null
+++ b/tests/Fixtures/MarkdownJsonModel.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Orbit\Tests\Fixtures;
+
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Schema\Blueprint;
+use Orbit\Concerns\Orbital;
+
+class MarkdownJsonModel extends Model
+{
+    use Orbital;
+
+    protected $guarded = [];
+
+    protected static $driver = 'md_json';
+
+    public static function schema(Blueprint $table)
+    {
+        $table->string('name');
+    }
+
+    public function getKeyName()
+    {
+        return 'name';
+    }
+
+    public function getIncrementing()
+    {
+        return false;
+    }
+}