Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions benchmarks/MediumImporterBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace YiiPress\Benchmarks;

use PhpBench\Attributes\AfterMethods;
use PhpBench\Attributes\BeforeMethods;
use PhpBench\Attributes\Iterations;
use PhpBench\Attributes\Revs;
use PhpBench\Attributes\Warmup;
use YiiPress\Import\Medium\MediumContentImporter;

#[BeforeMethods('setUp')]
#[AfterMethods('tearDown')]
final class MediumImporterBench
{
private string $sourceDir;
private string $targetDir;
private MediumContentImporter $importer;

public function setUp(): void
{
$this->sourceDir = sys_get_temp_dir() . '/yiipress-medium-bench-source-' . uniqid();
$this->targetDir = sys_get_temp_dir() . '/yiipress-medium-bench-target-' . uniqid();
mkdir($this->sourceDir . '/posts', 0o755, true);
mkdir($this->targetDir, 0o755, true);

for ($i = 1; $i <= 100; $i++) {
file_put_contents(
$this->sourceDir . '/posts/post-' . $i . '.md',
"---\ntitle: Post $i\ndate: 2024-03-15\ntags: php, yii\n---\n\nBody $i.\n",
);
}

$this->importer = new MediumContentImporter();
}

public function tearDown(): void
{
$this->removeDir($this->sourceDir);
$this->removeDir($this->targetDir);
}

#[Revs(10)]
#[Iterations(3)]
#[Warmup(1)]
public function benchImportPosts(): void
{
$this->removeDir($this->targetDir);
mkdir($this->targetDir, 0o755, true);

$this->importer->import(['directory' => $this->sourceDir], $this->targetDir, 'blog');
}

private function removeDir(string $path): void
{
if (!is_dir($path)) {
return;
}

$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST,
);
foreach ($iterator as $item) {
if ($item->isDir()) {
rmdir($item->getPathname());
} else {
unlink($item->getPathname());
}
}

rmdir($path);
}
}
2 changes: 2 additions & 0 deletions config/common/di/importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use YiiPress\Console\ImportCommand;
use YiiPress\Import\Medium\MediumContentImporter;
use YiiPress\Import\Telegram\TelegramContentImporter;

$workingDirectory = getcwd() ?: dirname(__DIR__, 3);
Expand All @@ -12,6 +13,7 @@
'__construct()' => [
'rootPath' => $workingDirectory,
'importers' => [
'medium' => new MediumContentImporter(),
'telegram' => new TelegramContentImporter(),
],
],
Expand Down
21 changes: 20 additions & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Imports content from external sources into a YiiPress collection.

**Arguments:**

- `source` — source type to import from (required). Currently supported: `telegram`.
- `source` — source type to import from (required). Currently supported: `medium`, `telegram`.

**Common options:**

Expand Down Expand Up @@ -191,6 +191,25 @@ Supports both single-chat exports (`result.json` with `messages` array) and full
./yiipress import telegram --directory=./telegram-data --content-dir=content
```

### Medium Markdown import

Imports Markdown files from a Medium Markdown export directory. The importer scans `posts/` first when it exists, otherwise it scans the provided directory recursively.

**Importer options:**

- `--directory` — path to the Medium Markdown export directory (required). Absolute or relative to project root.

The importer reads `.md` files and converts YAML front matter plus body content into YiiPress entries. It preserves `title`, `date` / `published_at`, `canonical_url` / `url` as `origin`, `draft`, `published`, `tags`, and `categories`. When metadata is missing, the title is inferred from the first `# Heading` or filename, and the date can be inferred from filenames starting with `YYYY-MM-DD-`.

Duplicate output filenames get numeric suffixes so earlier files are not overwritten.

**Examples:**

```bash
./yiipress import medium --directory=/path/to/medium-markdown-export
./yiipress import medium --directory=./medium --collection=blog
```

### Adding custom importers

Importers implement `YiiPress\Import\ContentImporterInterface` and are registered via [Yii3 DI](https://yiisoft.github.io/docs/guide/concept/di-container.html) in `config/common/di/importer.php`. Each importer declares its own options via the `options()` method. See [Importing content](importing-content.md) for details.
Expand Down
12 changes: 12 additions & 0 deletions docs/importing-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ Imports messages from a Telegram Desktop channel export (JSON format).

See [commands.md](commands.md#yii-import) for usage details.

### MediumContentImporter

Imports Markdown files from a Medium Markdown export directory.

**Options:**

- `--directory` — Path to the Medium Markdown export directory (required)

The importer scans `posts/` first when present, otherwise the provided directory, and converts `.md` files into the selected YiiPress collection. It supports YAML front matter, preserves common metadata (`title`, date, origin URL, draft status, tags, and categories), infers missing titles/dates from headings and filenames, and avoids overwriting duplicate output filenames.

See [commands.md](commands.md#medium-markdown-import) for usage details.

## Writing a custom importer

Create a class implementing `ContentImporterInterface`. Each importer declares its own options — a file-based importer might need a `directory`, while an API-based importer might need `url` and `api-key`.
Expand Down
2 changes: 1 addition & 1 deletion roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,6 @@
- [ ] WordPress
- [ ] Jekyll
- [ ] Hugo
- [ ] Medium exported Markdown
- [x] Medium exported Markdown
- [ ] Ghost
- [x] Telegram export
Loading