132 lines
4.4 KiB
PHP
132 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Help;
|
|
|
|
use Illuminate\Filesystem\Filesystem;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use League\CommonMark\Environment\Environment;
|
|
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
|
|
use League\CommonMark\Extension\Table\TableExtension;
|
|
use League\CommonMark\MarkdownConverter;
|
|
use RuntimeException;
|
|
use Symfony\Component\Finder\SplFileInfo;
|
|
use Symfony\Component\Yaml\Yaml;
|
|
|
|
class HelpSyncService
|
|
{
|
|
private MarkdownConverter $converter;
|
|
|
|
public function __construct(private readonly Filesystem $files)
|
|
{
|
|
$environment = new Environment;
|
|
$environment->addExtension(new CommonMarkCoreExtension);
|
|
$environment->addExtension(new TableExtension);
|
|
$this->converter = new MarkdownConverter($environment);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string, int>>
|
|
*/
|
|
public function sync(): array
|
|
{
|
|
$sourcePath = base_path(config('help.source_path'));
|
|
|
|
if (! $this->files->exists($sourcePath)) {
|
|
throw new RuntimeException('Help source directory not found: '.$sourcePath);
|
|
}
|
|
|
|
$articles = collect();
|
|
|
|
foreach (config('help.audiences', []) as $audience) {
|
|
$audiencePath = $sourcePath.DIRECTORY_SEPARATOR.$audience;
|
|
|
|
if (! $this->files->isDirectory($audiencePath)) {
|
|
continue;
|
|
}
|
|
|
|
$files = $this->files->allFiles($audiencePath);
|
|
|
|
/** @var SplFileInfo $file */
|
|
foreach ($files as $file) {
|
|
if ($file->getExtension() !== 'md') {
|
|
continue;
|
|
}
|
|
|
|
$parsed = $this->parseFile($file);
|
|
$articles->push($parsed);
|
|
}
|
|
}
|
|
|
|
$disk = config('help.disk');
|
|
$compiledPath = trim(config('help.compiled_path'), '/');
|
|
$written = [];
|
|
|
|
foreach ($articles->groupBy(fn ($article) => $article['audience'].'::'.$article['locale']) as $key => $group) {
|
|
[$audience, $locale] = explode('::', $key);
|
|
$path = sprintf('%s/%s/%s/articles.json', $compiledPath, $audience, $locale);
|
|
Storage::disk($disk)->put($path, $group->values()->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
Cache::forget($this->cacheKey($audience, $locale));
|
|
$written[$audience][$locale] = $group->count();
|
|
}
|
|
|
|
return $written;
|
|
}
|
|
|
|
private function parseFile(SplFileInfo $file): array
|
|
{
|
|
$contents = $this->files->get($file->getPathname());
|
|
|
|
if (! Str::startsWith($contents, "---\n")) {
|
|
throw new RuntimeException('Missing front matter in '.$file->getPathname());
|
|
}
|
|
|
|
$pattern = '/^---\s*\n(?P<yaml>.*?)-{3}\s*\n(?P<body>.*)$/s';
|
|
|
|
if (! preg_match($pattern, $contents, $matches)) {
|
|
throw new RuntimeException('Unable to parse front matter for '.$file->getPathname());
|
|
}
|
|
|
|
$frontMatter = Yaml::parse(trim($matches['yaml'] ?? '')) ?? [];
|
|
$frontMatter = array_map(static fn ($value) => $value ?? null, $frontMatter);
|
|
|
|
$this->validateFrontMatter($frontMatter, $file->getPathname());
|
|
|
|
$body = trim($matches['body'] ?? '');
|
|
$html = trim($this->converter->convert($body)->getContent());
|
|
$updatedAt = now()->setTimestamp($file->getMTime())->toISOString();
|
|
|
|
return array_merge($frontMatter, [
|
|
'body_markdown' => $body,
|
|
'body_html' => $html,
|
|
'source_path' => $file->getRelativePathname(),
|
|
'updated_at' => $updatedAt,
|
|
]);
|
|
}
|
|
|
|
private function validateFrontMatter(array $frontMatter, string $path): void
|
|
{
|
|
$required = config('help.required_front_matter', []);
|
|
|
|
foreach ($required as $key) {
|
|
if (! Arr::exists($frontMatter, $key)) {
|
|
throw new RuntimeException(sprintf('Missing front matter key "%s" in %s', $key, $path));
|
|
}
|
|
}
|
|
|
|
$audiences = config('help.audiences', []);
|
|
$audience = $frontMatter['audience'];
|
|
|
|
if (! in_array($audience, $audiences, true)) {
|
|
throw new RuntimeException(sprintf('Invalid audience "%s" in %s', $audience, $path));
|
|
}
|
|
}
|
|
|
|
private function cacheKey(string $audience, string $locale): string
|
|
{
|
|
return sprintf('help.%s.%s', $audience, $locale);
|
|
}
|
|
}
|