🐞 fix: Linting cleanup
This commit is contained in:
Vendored
+2
-1
@@ -12,5 +12,6 @@
|
|||||||
"statusBar.debuggingForeground": "#F3FBFE",
|
"statusBar.debuggingForeground": "#F3FBFE",
|
||||||
"statusBar.noFolderBackground": "#053241",
|
"statusBar.noFolderBackground": "#053241",
|
||||||
"statusBar.noFolderForeground": "#F3FBFE"
|
"statusBar.noFolderForeground": "#F3FBFE"
|
||||||
}
|
},
|
||||||
|
"phpcs.standard": ".\\phpcs.xml",
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -19,6 +19,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "phpunit"
|
"test": "phpunit",
|
||||||
|
"lint": "phpcs --standard=phpcs.xml --report=full --report-file=phpcs-results.txt .",
|
||||||
|
"lint:fix": "phpcbf --standard=phpcs.xml ."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-23
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task Domain Model
|
* Task Domain Model
|
||||||
*
|
*
|
||||||
@@ -31,30 +30,30 @@ class Task {
|
|||||||
/**
|
/**
|
||||||
* Constructor for Task
|
* Constructor for Task
|
||||||
*
|
*
|
||||||
* @param string $id The task identifier
|
* @param string $id The task identifier.
|
||||||
* @param string $title The task title
|
* @param string $title The task title.
|
||||||
* @param string $projectId The project identifier
|
* @param string $projectId The project identifier.
|
||||||
* @param string $column The column the task belongs to
|
* @param string $column The column the task belongs to.
|
||||||
* @param int $order The order of the task
|
* @param int $order The order of the task.
|
||||||
* @param bool $completed Whether the task is completed
|
* @param bool $completed Whether the task is completed.
|
||||||
* @param string $priority The priority level of the task
|
* @param string $priority The priority level of the task.
|
||||||
* @param bool $isActive Whether the task is active
|
* @param bool $isActive Whether the task is active.
|
||||||
* @param string $body The task description
|
* @param string $body The task description.
|
||||||
* @param string $fileName The task file name
|
* @param string $fileName The task file name.
|
||||||
* @param array $meta Additional metadata (optional)
|
* @param array $meta Additional metadata (optional).
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $id,
|
public string $id,
|
||||||
public string $title,
|
public string $title,
|
||||||
public string $projectId,
|
public string $projectId,
|
||||||
public string $column,
|
public string $column,
|
||||||
public int $order,
|
public int $order,
|
||||||
public bool $completed,
|
public bool $completed,
|
||||||
public string $priority,
|
public string $priority,
|
||||||
public bool $isActive,
|
public bool $isActive,
|
||||||
public string $body,
|
public string $body,
|
||||||
public string $fileName,
|
public string $fileName,
|
||||||
public array $meta = []
|
public array $meta = array()
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,9 @@ class FrontMatterDocument {
|
|||||||
/**
|
/**
|
||||||
* Constructor for FrontMatterDocument
|
* Constructor for FrontMatterDocument
|
||||||
*
|
*
|
||||||
* @param array $meta The front matter metadata
|
* @param array $meta The front matter metadata.
|
||||||
* @param string $body The document body content
|
* @param string $body The document body content.
|
||||||
*/
|
*/
|
||||||
public function __construct( public array $meta, public string $body ) {
|
public function __construct( public array $meta, public string $body ) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Front Matter Parser
|
* Front Matter Parser
|
||||||
*
|
*
|
||||||
@@ -14,7 +13,7 @@
|
|||||||
* @link https://git.keithsolomon.net/keith/IronKanban
|
* @link https://git.keithsolomon.net/keith/IronKanban
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types=1 );
|
||||||
|
|
||||||
namespace IronKanban\Markdown;
|
namespace IronKanban\Markdown;
|
||||||
|
|
||||||
@@ -31,19 +30,17 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
* @license Unlicense https://unlicense.org
|
* @license Unlicense https://unlicense.org
|
||||||
* @link https://git.keithsolomon.net/keith/IronKanban
|
* @link https://git.keithsolomon.net/keith/IronKanban
|
||||||
*/
|
*/
|
||||||
class FrontMatterParser
|
class FrontMatterParser {
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* Parse a markdown file and extract front matter
|
* Parse a markdown file and extract front matter
|
||||||
*
|
*
|
||||||
* @param string $filePath Path to the markdown file
|
* @param string $filePath Path to the markdown file.
|
||||||
*
|
*
|
||||||
* @return FrontMatterDocument The parsed document
|
* @return FrontMatterDocument The parsed document.
|
||||||
*
|
*
|
||||||
* @throws RuntimeException If file cannot be read
|
* @throws RuntimeException If file cannot be read.
|
||||||
*/
|
*/
|
||||||
public function parseFile(string $filePath): FrontMatterDocument
|
public function parseFile( string $filePath ): FrontMatterDocument {
|
||||||
{
|
|
||||||
if (!is_file($filePath)) {
|
if (!is_file($filePath)) {
|
||||||
throw new RuntimeException("Markdown file not found: {$filePath}");
|
throw new RuntimeException("Markdown file not found: {$filePath}");
|
||||||
}
|
}
|
||||||
@@ -60,14 +57,13 @@ class FrontMatterParser
|
|||||||
/**
|
/**
|
||||||
* Parse a markdown string and extract front matter
|
* Parse a markdown string and extract front matter
|
||||||
*
|
*
|
||||||
* @param string $contents The markdown content to parse
|
* @param string $contents The markdown content to parse.
|
||||||
*
|
*
|
||||||
* @return FrontMatterDocument The parsed document
|
* @return FrontMatterDocument The parsed document.
|
||||||
*
|
*
|
||||||
* @throws RuntimeException If YAML parsing fails
|
* @throws RuntimeException If YAML parsing fails.
|
||||||
*/
|
*/
|
||||||
public function parseString(string $contents): FrontMatterDocument
|
public function parseString( string $contents ): FrontMatterDocument {
|
||||||
{
|
|
||||||
$contents = str_replace(["\r\n", "\r"], "\n", $contents);
|
$contents = str_replace(["\r\n", "\r"], "\n", $contents);
|
||||||
|
|
||||||
if (!str_starts_with($contents, "---\n")) {
|
if (!str_starts_with($contents, "---\n")) {
|
||||||
@@ -92,7 +88,7 @@ class FrontMatterParser
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$meta = Yaml::parse($yamlText);
|
$meta = Yaml::parse($yamlText);
|
||||||
} catch (ParseException $e) {
|
} catch ( ParseException $e ) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
'Failed to parse YAML front matter: ' . $e->getMessage(),
|
'Failed to parse YAML front matter: ' . $e->getMessage(),
|
||||||
0,
|
0,
|
||||||
@@ -110,12 +106,11 @@ class FrontMatterParser
|
|||||||
/**
|
/**
|
||||||
* Dump a document to markdown string with front matter
|
* Dump a document to markdown string with front matter
|
||||||
*
|
*
|
||||||
* @param FrontMatterDocument $document The document to dump
|
* @param FrontMatterDocument $document The document to dump.
|
||||||
*
|
*
|
||||||
* @return string The markdown content with front matter
|
* @return string The markdown content with front matter.
|
||||||
*/
|
*/
|
||||||
public function dump(FrontMatterDocument $document): string
|
public function dump( FrontMatterDocument $document ): string {
|
||||||
{
|
|
||||||
$yaml = Yaml::dump(
|
$yaml = Yaml::dump(
|
||||||
$document->meta,
|
$document->meta,
|
||||||
4,
|
4,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task Repository
|
* Task Repository
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File Lock
|
* File Lock
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID Generator
|
* ID Generator
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paths Helper
|
* Paths Helper
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task Mapper
|
* Task Mapper
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
// phpcs:disable
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IronKanban\Tests;
|
||||||
|
|
||||||
|
use IronKanban\Markdown\FrontMatterDocument;
|
||||||
|
use IronKanban\Markdown\FrontMatterParser;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class FrontMatterParserTest extends TestCase {
|
||||||
|
public function testParsesFrontMatterAndBody(): void {
|
||||||
|
$contents = <<<MD
|
||||||
|
---
|
||||||
|
id: task-123
|
||||||
|
title: Test Task
|
||||||
|
completed: false
|
||||||
|
tags:
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hello
|
||||||
|
|
||||||
|
This is the body.
|
||||||
|
MD;
|
||||||
|
|
||||||
|
$parser = new FrontMatterParser();
|
||||||
|
$document = $parser->parseString($contents);
|
||||||
|
|
||||||
|
$this->assertSame('task-123', $document->meta['id']);
|
||||||
|
$this->assertSame('Test Task', $document->meta['title']);
|
||||||
|
$this->assertFalse($document->meta['completed']);
|
||||||
|
$this->assertSame(['alpha', 'beta'], $document->meta['tags']);
|
||||||
|
$this->assertStringContainsString('# Hello', $document->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReturnsEmptyMetaWhenNoFrontMatterExists(): void {
|
||||||
|
$contents = <<<MD
|
||||||
|
# Plain Markdown
|
||||||
|
|
||||||
|
No front matter here.
|
||||||
|
MD;
|
||||||
|
|
||||||
|
$parser = new FrontMatterParser();
|
||||||
|
$document = $parser->parseString($contents);
|
||||||
|
|
||||||
|
$this->assertSame([], $document->meta);
|
||||||
|
$this->assertStringContainsString('# Plain Markdown', $document->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRoundTripsUnknownMetaKeys(): void {
|
||||||
|
$document = new FrontMatterDocument(
|
||||||
|
meta: [
|
||||||
|
'id' => 'task-456',
|
||||||
|
'type' => 'task',
|
||||||
|
'custom_key' => 'custom value',
|
||||||
|
'nested' => [
|
||||||
|
'one' => 'two',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
body: "Test body\n\nMore text."
|
||||||
|
);
|
||||||
|
|
||||||
|
$parser = new FrontMatterParser();
|
||||||
|
$dumped = $parser->dump($document);
|
||||||
|
$parsed = $parser->parseString($dumped);
|
||||||
|
|
||||||
|
$this->assertSame('custom value', $parsed->meta['custom_key']);
|
||||||
|
$this->assertSame(['one' => 'two'], $parsed->meta['nested']);
|
||||||
|
$this->assertSame("Test body\n\nMore text.", $parsed->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// phpcs:enable
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
// phpcs:disable
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IronKanban\Tests;
|
||||||
|
|
||||||
|
use IronKanban\Markdown\FrontMatterParser;
|
||||||
|
use IronKanban\Repository\TaskRepository;
|
||||||
|
use IronKanban\Support\IdGenerator;
|
||||||
|
use IronKanban\Support\Paths;
|
||||||
|
use IronKanban\Support\TaskMapper;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class TaskRepositoryTest extends TestCase {
|
||||||
|
private string $tempRoot;
|
||||||
|
|
||||||
|
protected function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->tempRoot = sys_get_temp_dir() . '/ironkanban-test-' . bin2hex(random_bytes(4));
|
||||||
|
mkdir($this->tempRoot, 0775, true);
|
||||||
|
mkdir($this->tempRoot . '/sample-project', 0775, true);
|
||||||
|
mkdir($this->tempRoot . '/sample-project/tasks', 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void {
|
||||||
|
$this->deleteDirectory($this->tempRoot);
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreateWritesTaskFileAndCanReadItBack(): void {
|
||||||
|
$repo = $this->makeRepository();
|
||||||
|
|
||||||
|
$task = $repo->create([
|
||||||
|
'projectId' => 'sample-project',
|
||||||
|
'title' => 'Build parser',
|
||||||
|
'body' => "Task body",
|
||||||
|
'column' => 'doing',
|
||||||
|
'priority' => 'high',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame('Build parser', $task->title);
|
||||||
|
$this->assertSame('doing', $task->column);
|
||||||
|
$this->assertSame('high', $task->priority);
|
||||||
|
$this->assertFileExists($this->tempRoot . '/sample-project/tasks/' . $task->fileName);
|
||||||
|
|
||||||
|
$loaded = $repo->get('sample-project', $task->id);
|
||||||
|
|
||||||
|
$this->assertSame($task->id, $loaded->id);
|
||||||
|
$this->assertSame('Build parser', $loaded->title);
|
||||||
|
$this->assertSame('Task body', $loaded->body);
|
||||||
|
$this->assertSame($task->fileName, $loaded->fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPreservesLegacyFilenameWhenReadingAndSaving(): void {
|
||||||
|
$contents = <<<MD
|
||||||
|
---
|
||||||
|
id: custom-task-id
|
||||||
|
type: task
|
||||||
|
title: Legacy Task
|
||||||
|
project_id: sample-project
|
||||||
|
section: Back Burner
|
||||||
|
priority: normal
|
||||||
|
completed: false
|
||||||
|
is_active: true
|
||||||
|
created: 2026-03-01T00:00:00+00:00
|
||||||
|
updated: 2026-03-01T00:00:00+00:00
|
||||||
|
legacy_key: keep-me
|
||||||
|
---
|
||||||
|
|
||||||
|
Legacy body
|
||||||
|
MD;
|
||||||
|
|
||||||
|
$legacyPath = $this->tempRoot . '/sample-project/tasks/task-legacy-import.md';
|
||||||
|
file_put_contents($legacyPath, $contents);
|
||||||
|
|
||||||
|
$repo = $this->makeRepository();
|
||||||
|
$task = $repo->get('sample-project', 'custom-task-id');
|
||||||
|
|
||||||
|
$this->assertSame('task-legacy-import.md', $task->fileName);
|
||||||
|
$this->assertSame('back-burner', $task->column);
|
||||||
|
$this->assertSame('keep-me', $task->meta['legacy_key']);
|
||||||
|
|
||||||
|
$task->title = 'Legacy Task Updated';
|
||||||
|
$repo->save($task);
|
||||||
|
|
||||||
|
$this->assertFileExists($legacyPath);
|
||||||
|
$this->assertStringContainsString('Legacy Task Updated', (string) file_get_contents($legacyPath));
|
||||||
|
$this->assertStringContainsString('legacy_key: keep-me', (string) file_get_contents($legacyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeleteRemovesUnderlyingFile(): void {
|
||||||
|
$repo = $this->makeRepository();
|
||||||
|
|
||||||
|
$task = $repo->create([
|
||||||
|
'projectId' => 'sample-project',
|
||||||
|
'title' => 'Delete me',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$filePath = $this->tempRoot . '/sample-project/tasks/' . $task->fileName;
|
||||||
|
|
||||||
|
$this->assertFileExists($filePath);
|
||||||
|
|
||||||
|
$repo->delete('sample-project', $task->id);
|
||||||
|
|
||||||
|
$this->assertFileDoesNotExist($filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeRepository(): TaskRepository {
|
||||||
|
return new TaskRepository(
|
||||||
|
new Paths($this->tempRoot),
|
||||||
|
new FrontMatterParser(),
|
||||||
|
new TaskMapper(),
|
||||||
|
new IdGenerator()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteDirectory(string $path): void {
|
||||||
|
if (!is_dir($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = scandir($path);
|
||||||
|
|
||||||
|
if ($items === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($item === '.' || $item === '..') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$itemPath = $path . DIRECTORY_SEPARATOR . $item;
|
||||||
|
|
||||||
|
if (is_dir($itemPath) && !is_link($itemPath)) {
|
||||||
|
$this->deleteDirectory($itemPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@unlink($itemPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@rmdir($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// phpcs:enable
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{"version":2,"defects":{"IronKanban\\Tests\\TaskRepositoryTest::testCreateWritesTaskFileAndCanReadItBack":7,"IronKanban\\Tests\\TaskRepositoryTest::testPreservesLegacyFilenameWhenReadingAndSaving":7,"IronKanban\\Tests\\TaskRepositoryTest::testDeleteRemovesUnderlyingFile":7},"times":{"IronKanban\\Tests\\FrontMatterParserTest::testParsesFrontMatterAndBody":0.702,"IronKanban\\Tests\\FrontMatterParserTest::testReturnsEmptyMetaWhenNoFrontMatterExists":0,"IronKanban\\Tests\\FrontMatterParserTest::testRoundTripsUnknownMetaKeys":0.081,"IronKanban\\Tests\\TaskRepositoryTest::testCreateWritesTaskFileAndCanReadItBack":0.879,"IronKanban\\Tests\\TaskRepositoryTest::testPreservesLegacyFilenameWhenReadingAndSaving":0.037,"IronKanban\\Tests\\TaskRepositoryTest::testDeleteRemovesUnderlyingFile":0.009}}
|
||||||
Reference in New Issue
Block a user