From 73654e5984858d6728d568ca5e6b0954a9b2f90e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 21 Aug 2024 11:08:57 +0800 Subject: [PATCH] Add dependency map generator and related docs --- .github/workflows/vitepress-deploy.yml | 2 + docs/.vitepress/sidebar.en.ts | 1 + docs/.vitepress/sidebar.zh.ts | 1 + docs/deps-map-ext.md | 0 docs/deps-map-lib.md | 0 docs/en/guide/deps-map.md | 26 +++ docs/en/guide/extensions.md | 12 +- docs/zh/guide/deps-map.md | 22 +++ docs/zh/guide/extension-notes.md | 2 +- docs/zh/guide/extensions.md | 9 +- src/SPC/ConsoleApplication.php | 4 + .../command/dev/GenerateExtDepDocsCommand.php | 166 +++++++++++++++++ .../command/dev/GenerateLibDepDocsCommand.php | 172 ++++++++++++++++++ 13 files changed, 398 insertions(+), 19 deletions(-) create mode 100644 docs/deps-map-ext.md create mode 100644 docs/deps-map-lib.md create mode 100644 docs/en/guide/deps-map.md create mode 100644 docs/zh/guide/deps-map.md create mode 100644 src/SPC/command/dev/GenerateExtDepDocsCommand.php create mode 100644 src/SPC/command/dev/GenerateLibDepDocsCommand.php diff --git a/.github/workflows/vitepress-deploy.yml b/.github/workflows/vitepress-deploy.yml index f5bc0fd1..c2192461 100644 --- a/.github/workflows/vitepress-deploy.yml +++ b/.github/workflows/vitepress-deploy.yml @@ -58,6 +58,8 @@ jobs: - name: "Generate Extension Support List" run: | bin/spc dev:gen-ext-docs > docs/extensions.md + bin/spc dev:gen-ext-dep-docs > docs/deps-map-ext.md + bin/spc dev:gen-lib-dep-docs > docs/deps-map-lib.md - name: Build run: yarn docs:build diff --git a/docs/.vitepress/sidebar.en.ts b/docs/.vitepress/sidebar.en.ts index fa90241a..b9c80aae 100644 --- a/docs/.vitepress/sidebar.en.ts +++ b/docs/.vitepress/sidebar.en.ts @@ -10,6 +10,7 @@ export default { {text: 'Extension Notes', link: '/en/guide/extension-notes'}, {text: 'Command Generator', link: '/en/guide/cli-generator'}, {text: 'Environment Variables', link: '/en/guide/env-vars', collapsed: true,}, + {text: 'Dependency Table', link: '/en/guide/deps-map'}, ] }, { diff --git a/docs/.vitepress/sidebar.zh.ts b/docs/.vitepress/sidebar.zh.ts index c5932461..a17ac0f0 100644 --- a/docs/.vitepress/sidebar.zh.ts +++ b/docs/.vitepress/sidebar.zh.ts @@ -10,6 +10,7 @@ export default { {text: '扩展注意事项', link: '/zh/guide/extension-notes'}, {text: '编译命令生成器', link: '/zh/guide/cli-generator'}, {text: '环境变量列表', link: '/zh/guide/env-vars'}, + {text: '依赖关系图表', link: '/zh/guide/deps-map'}, ] }, { diff --git a/docs/deps-map-ext.md b/docs/deps-map-ext.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/deps-map-lib.md b/docs/deps-map-lib.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/en/guide/deps-map.md b/docs/en/guide/deps-map.md new file mode 100644 index 00000000..79100041 --- /dev/null +++ b/docs/en/guide/deps-map.md @@ -0,0 +1,26 @@ +--- +outline: 'deep' +--- + +# Dependency Table + +When compiling PHP, each extension and library has dependencies, which may be required or optional. +You can choose whether to include these optional dependencies. + +For example, when compiling the `gd` extension under Linux, +the `zlib,libpng` libraries and the `zlib` extension are forced to be compiled, +while the `libavif,libwebp,libjpeg,freetype` libraries are optional libraries and will not be compiled by default +unless specified by the `--with-libs=avif,webp,jpeg,freetype` option. + +- For optional extensions (optional features of extensions), you need to specify them manually at compile time, for example, to enable igbinary support for Redis: `bin/spc build redis,igbinary`. +- For optional libraries, you need to compile and specify them through the `--with-libs=XXX` option. +- If you want to enable all optional extensions, you can use `bin/spc build redis --with-suggested-exts`. +- If you want to enable all optional libraries, you can use `--with-suggested-libs`. + +## Extension Dependency Table + + + +## Library Dependency Table + + \ No newline at end of file diff --git a/docs/en/guide/extensions.md b/docs/en/guide/extensions.md index bfecfcfa..14ec91d2 100644 --- a/docs/en/guide/extensions.md +++ b/docs/en/guide/extensions.md @@ -14,14 +14,6 @@ Some extensions or libraries that the extension depends on will have some option For example, the gd library optionally supports libwebp, freetype, etc. If you only use `bin/spc build gd --build-cli` they will not be included (static-php-cli defaults to the minimum dependency principle). -You can use `--with-libs=` to add these libraries when compiling. -When the dependent libraries of this compilation include them, gd will automatically use them to enable these features. -(For example: `bin/spc build gd --with-libs=libwebp,freetype --build-cli`) - -Alternatively you can use `--with-suggested-exts` and `--with-suggested-libs` to enable all optional dependencies of these extensions and libraries. -(For example: `bin/spc build gd --with-suggested-libs --build-cli`) - -If you don't know whether an extension has optional features, -you can check the [spc configuration file](https://github.com/crazywhalecc/static-php-cli/tree/main/config) -or use the command `bin/spc dev:extensions` (library dependency is `lib-suggests`, extension dependency is `ext-suggests`). +For more information about optional libraries, see [Extensions, Library Dependency Map](./deps-map). +For optional libraries, you can also select an extension from the [Command Generator](./cli-generator) and then select optional libraries. ::: diff --git a/docs/zh/guide/deps-map.md b/docs/zh/guide/deps-map.md new file mode 100644 index 00000000..91ff57fd --- /dev/null +++ b/docs/zh/guide/deps-map.md @@ -0,0 +1,22 @@ +--- +outline: 'deep' +--- + +# 依赖关系图表 + +在编译 PHP 时,每个扩展、库都有依赖关系,这些依赖关系可能是必需的,也可能是可选的。在编译 PHP 时,可以选择是否包含这些可选的依赖关系。 + +例如,在 Linux 下编译 `gd` 扩展时,会强制编译 `zlib,libpng` 库和 `zlib` 扩展,而 `libavif,libwebp,libjpeg,freetype` 库都是可选的库,默认不会编译,除非通过 `--with-libs=avif,webp,jpeg,freetype` 选项指定。 + +- 对于可选扩展(扩展的可选特性),需手动在编译时指定,例如启用 Redis 的 igbinary 支持:`bin/spc build redis,igbinary`。 +- 对于可选库,需通过 `--with-libs=XXX` 选项编译指定。 +- 如果想启用所有的可选扩展,可以使用 `bin/spc build redis --with-suggested-exts` 参数。 +- 如果想启用所有的可选库,可以使用 `--with-suggested-libs` 参数。 + +## 扩展的依赖图 + + + +## 库的依赖表 + + \ No newline at end of file diff --git a/docs/zh/guide/extension-notes.md b/docs/zh/guide/extension-notes.md index 7a464dbb..4fc475e3 100644 --- a/docs/zh/guide/extension-notes.md +++ b/docs/zh/guide/extension-notes.md @@ -132,7 +132,7 @@ event 扩展在 macOS 系统下编译后暂无法使用 `openpty` 特性。相 parallel 扩展只支持 PHP 8.0 及以上版本,并只支持 ZTS 构建(`--enable-zts`)。 -# spx +## spx 1. [SPX 扩展](https://github.com/NoiseByNorthwest/php-spx) 只支持非线程模式。 2. SPX 目前不支持 Windows,且官方仓库也不支持静态编译,static-php-cli 使用了 [修改版本](https://github.com/static-php/php-spx)。 diff --git a/docs/zh/guide/extensions.md b/docs/zh/guide/extensions.md index 342eb4ab..46a561dd 100644 --- a/docs/zh/guide/extensions.md +++ b/docs/zh/guide/extensions.md @@ -13,12 +13,5 @@ 有些扩展或扩展依赖的库会有一些可选的特性,例如 gd 库可选支持 libwebp、freetype 等。 如果你只使用 `bin/spc build gd --build-cli` 是不会包含它们(static-php-cli 默认为最小依赖原则)。 -你可以在编译时使用 `--with-libs=` 加入这些库,当本次编译的依赖库中包含它们,gd 会自动依赖它们启用这些特性。 -(如:`bin/spc build gd --with-libs=libwebp,freetype --build-cli`) - -或者你也可以使用 `--with-suggested-exts` 和 `--with-suggested-libs` 启用这些扩展和库所有可选的依赖。 -(如:`bin/spc build gd --with-suggested-libs --build-cli`) - -如果你不知道某个扩展是否有可选特性,可以通过查看 [spc 配置文件](https://github.com/crazywhalecc/static-php-cli/tree/main/config) -或使用命令 `bin/spc dev:extensions` 查看(库依赖为 `lib-suggests`,扩展依赖为 `ext-suggests`)。 +有关编译可选库,请参考 [扩展、库的依赖关系图表](./deps-map)。对于可选的库,你也可以从 [编译命令生成器](./cli-generator) 中选择扩展后展开选择可选库。 ::: diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index 6e8f573d..64048cf6 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -9,7 +9,9 @@ use SPC\command\BuildLibsCommand; use SPC\command\DeleteDownloadCommand; use SPC\command\dev\AllExtCommand; use SPC\command\dev\ExtVerCommand; +use SPC\command\dev\GenerateExtDepDocsCommand; use SPC\command\dev\GenerateExtDocCommand; +use SPC\command\dev\GenerateLibDepDocsCommand; use SPC\command\dev\LibVerCommand; use SPC\command\dev\PackLibCommand; use SPC\command\dev\PhpVerCommand; @@ -55,6 +57,8 @@ final class ConsoleApplication extends Application new ExtVerCommand(), new SortConfigCommand(), new GenerateExtDocCommand(), + new GenerateExtDepDocsCommand(), + new GenerateLibDepDocsCommand(), new PackLibCommand(), ] ); diff --git a/src/SPC/command/dev/GenerateExtDepDocsCommand.php b/src/SPC/command/dev/GenerateExtDepDocsCommand.php new file mode 100644 index 00000000..afa6c9ff --- /dev/null +++ b/src/SPC/command/dev/GenerateExtDepDocsCommand.php @@ -0,0 +1,166 @@ + $ext) { + $line_linux = [ + "{$ext_name}", + implode('
', $ext['ext-depends-linux'] ?? $ext['ext-depends-unix'] ?? $ext['ext-depends'] ?? []), + implode('
', $ext['ext-suggests-linux'] ?? $ext['ext-suggests-unix'] ?? $ext['ext-suggests'] ?? []), + implode('
', $ext['lib-depends-linux'] ?? $ext['lib-depends-unix'] ?? $ext['lib-depends'] ?? []), + implode('
', $ext['lib-suggests-linux'] ?? $ext['lib-suggests-unix'] ?? $ext['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_linux, $line_linux); + if ($this->isSupported($ext, 'Linux') && !$this->isEmptyLine($line_linux)) { + $md_lines_linux[] = $line_linux; + } + $line_macos = [ + "{$ext_name}", + implode('
', $ext['ext-depends-macos'] ?? $ext['ext-depends-unix'] ?? $ext['ext-depends'] ?? []), + implode('
', $ext['ext-suggests-macos'] ?? $ext['ext-suggests-unix'] ?? $ext['ext-suggests'] ?? []), + implode('
', $ext['lib-depends-macos'] ?? $ext['lib-depends-unix'] ?? $ext['lib-depends'] ?? []), + implode('
', $ext['lib-suggests-macos'] ?? $ext['lib-suggests-unix'] ?? $ext['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_macos, $line_macos); + if ($this->isSupported($ext, 'macOS') && !$this->isEmptyLine($line_macos)) { + $md_lines_macos[] = $line_macos; + } + $line_windows = [ + "{$ext_name}", + implode('
', $ext['ext-depends-windows'] ?? $ext['ext-depends-win'] ?? $ext['ext-depends'] ?? []), + implode('
', $ext['ext-suggests-windows'] ?? $ext['ext-suggests-win'] ?? $ext['ext-suggests'] ?? []), + implode('
', $ext['lib-depends-windows'] ?? $ext['lib-depends-win'] ?? $ext['lib-depends'] ?? []), + implode('
', $ext['lib-suggests-windows'] ?? $ext['lib-suggests-win'] ?? $ext['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_windows, $line_windows); + if ($this->isSupported($ext, 'Windows') && !$this->isEmptyLine($line_windows)) { + $md_lines_windows[] = $line_windows; + } + $line_freebsd = [ + "{$ext_name}", + implode('
', $ext['ext-depends-freebsd'] ?? $ext['ext-depends-bsd'] ?? $ext['ext-depends-unix'] ?? $ext['ext-depends'] ?? []), + implode('
', $ext['ext-suggests-freebsd'] ?? $ext['ext-suggests-bsd'] ?? $ext['ext-suggests-unix'] ?? $ext['ext-suggests'] ?? []), + implode('
', $ext['lib-depends-freebsd'] ?? $ext['lib-depends-bsd'] ?? $ext['lib-depends-unix'] ?? $ext['lib-depends'] ?? []), + implode('
', $ext['lib-suggests-freebsd'] ?? $ext['lib-suggests-bsd'] ?? $ext['lib-suggests-unix'] ?? $ext['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_freebsd, $line_freebsd); + if ($this->isSupported($ext, 'BSD') && !$this->isEmptyLine($line_freebsd)) { + $md_lines_freebsd[] = $line_freebsd; + } + } + + // Generate markdown + if (!empty($md_lines_linux)) { + $content .= "### Linux\n\n"; + $content .= '| '; + $pads = ['Extension Name', 'Required Extensions', 'Suggested Extensions', 'Required Libraries', 'Suggested Libraries']; + // 生成首行 + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_linux[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + // 生成第二行表格分割符 | --- | --- | --- | --- | --- | + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_linux[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_linux as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_linux[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + if (!empty($md_lines_macos)) { + $content .= "\n\n### macOS\n\n"; + $content .= '| '; + $pads = ['Extension Name', 'Required Extensions', 'Suggested Extensions', 'Required Libraries', 'Suggested Libraries']; + // 生成首行 + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_macos[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + // 生成第二行表格分割符 | --- | --- | --- | --- | --- | + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_macos[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_macos as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_macos[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + if (!empty($md_lines_windows)) { + $content .= "\n\n### Windows\n\n"; + $content .= '| '; + $pads = ['Extension Name', 'Required Extensions', 'Suggested Extensions', 'Required Libraries', 'Suggested Libraries']; + // 生成首行 + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_windows[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + // 生成第二行表格分割符 | --- | --- | --- | --- | --- | + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_windows[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_windows as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_windows[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + if (!empty($md_lines_freebsd)) { + $content .= "\n\n### FreeBSD\n\n"; + $content .= '| '; + $pads = ['Extension Name', 'Required Extensions', 'Suggested Extensions', 'Required Libraries', 'Suggested Libraries']; + // 生成首行 + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_freebsd[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + // 生成第二行表格分割符 | --- | --- | --- | --- | --- | + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_freebsd[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_freebsd as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_freebsd[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + + $this->output->writeln($content); + return static::SUCCESS; + } + + private function applyMaxLen(array &$max, array $lines): void + { + foreach ($max as $k => $v) { + $max[$k] = max($v, strlen($lines[$k])); + } + } + + private function isSupported(array $ext, string $os): bool + { + return !in_array($ext['support'][$os] ?? 'yes', ['no', 'wip']); + } + + private function isEmptyLine(array $line): bool + { + return $line[1] === '' && $line[2] === '' && $line[3] === '' && $line[4] === ''; + } +} diff --git a/src/SPC/command/dev/GenerateLibDepDocsCommand.php b/src/SPC/command/dev/GenerateLibDepDocsCommand.php new file mode 100644 index 00000000..feee99af --- /dev/null +++ b/src/SPC/command/dev/GenerateLibDepDocsCommand.php @@ -0,0 +1,172 @@ +support_lib_list[$os] = []; + $classes = FileSystem::getClassesPsr4( + FileSystem::convertPath(ROOT_DIR . '/src/SPC/builder/' . $os . '/library'), + 'SPC\builder\\' . $os . '\library' + ); + foreach ($classes as $class) { + if (defined($class . '::NAME') && $class::NAME !== 'unknown' && Config::getLib($class::NAME) !== null) { + $this->support_lib_list[$os][$class::NAME] = $class; + } + } + } + + // Get lib.json + $libs = json_decode(FileSystem::readFile(ROOT_DIR . '/config/lib.json'), true); + ConfigValidator::validateLibs($libs); + + // Markdown table needs format, we need to calculate the max length of each column + $content = ''; + + // Calculate table column max length + $max_linux = [0, 20, 19]; + $max_macos = [0, 20, 19]; + $max_windows = [0, 20, 19]; + $max_freebsd = [0, 20, 19]; + + $md_lines_linux = []; + $md_lines_macos = []; + $md_lines_windows = []; + $md_lines_freebsd = []; + + foreach ($libs as $lib_name => $lib) { + $line_linux = [ + "{$lib_name}", + implode('
', $lib['lib-depends-linux'] ?? $lib['lib-depends-unix'] ?? $lib['lib-depends'] ?? []), + implode('
', $lib['lib-suggests-linux'] ?? $lib['lib-suggests-unix'] ?? $lib['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_linux, $line_linux); + if ($this->isSupported($lib_name, 'linux') && !$this->isEmptyLine($line_linux)) { + $md_lines_linux[] = $line_linux; + } + $line_macos = [ + "{$lib_name}", + implode('
', $lib['lib-depends-macos'] ?? $lib['lib-depends-unix'] ?? $lib['lib-depends'] ?? []), + implode('
', $lib['lib-suggests-macos'] ?? $lib['lib-suggests-unix'] ?? $lib['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_macos, $line_macos); + if ($this->isSupported($lib_name, 'macos') && !$this->isEmptyLine($line_macos)) { + $md_lines_macos[] = $line_macos; + } + $line_windows = [ + "{$lib_name}", + implode('
', $lib['lib-depends-windows'] ?? $lib['lib-depends-win'] ?? $lib['lib-depends'] ?? []), + implode('
', $lib['lib-suggests-windows'] ?? $lib['lib-suggests-win'] ?? $lib['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_windows, $line_windows); + if ($this->isSupported($lib_name, 'windows') && !$this->isEmptyLine($line_windows)) { + $md_lines_windows[] = $line_windows; + } + $line_freebsd = [ + "{$lib_name}", + implode('
', $lib['lib-depends-freebsd'] ?? $lib['lib-depends-bsd'] ?? $lib['lib-depends-unix'] ?? $lib['lib-depends'] ?? []), + implode('
', $lib['lib-suggests-freebsd'] ?? $lib['lib-suggests-bsd'] ?? $lib['lib-suggests-unix'] ?? $lib['lib-suggests'] ?? []), + ]; + $this->applyMaxLen($max_freebsd, $line_freebsd); + if ($this->isSupported($lib_name, 'freebsd') && !$this->isEmptyLine($line_freebsd)) { + $md_lines_freebsd[] = $line_freebsd; + } + } + + // Generate markdown + if (!empty($md_lines_linux)) { + $content .= "### Linux\n\n"; + $content .= '| '; + $pads = ['Library Name', 'Required Libraries', 'Suggested Libraries']; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_linux[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_linux[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_linux as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_linux[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + + if (!empty($md_lines_macos)) { + $content .= "### macOS\n\n"; + $content .= '| '; + $pads = ['Library Name', 'Required Libraries', 'Suggested Libraries']; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_macos[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_macos[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_macos as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_macos[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + + if (!empty($md_lines_windows)) { + $content .= "### Windows\n\n"; + $content .= '| '; + $pads = ['Library Name', 'Required Libraries', 'Suggested Libraries']; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_windows[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_windows[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_windows as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_windows[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + + if (!empty($md_lines_freebsd)) { + $content .= "### FreeBSD\n\n"; + $content .= '| '; + $pads = ['Library Name', 'Required Libraries', 'Suggested Libraries']; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad($pad, $max_freebsd[$i]), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + $content .= '| '; + $content .= implode(' | ', array_map(fn ($i, $pad) => str_pad('', $max_freebsd[$i], '-'), array_keys($pads), $pads)); + $content .= ' |' . PHP_EOL; + foreach ($md_lines_freebsd as $line) { + $content .= '| ' . implode(' | ', array_map(fn ($i, $pad) => str_pad($line[$i], $max_freebsd[$i]), array_keys($line), $line)) . ' |' . PHP_EOL; + } + } + + $this->output->writeln($content); + return static::SUCCESS; + } + + private function applyMaxLen(array &$max, array $lines): void + { + foreach ($max as $k => $v) { + $max[$k] = max($v, strlen($lines[$k])); + } + } + + private function isSupported(string $ext_name, string $os): bool + { + if (!in_array($os, ['linux', 'macos', 'freebsd', 'windows'])) { + throw new \InvalidArgumentException('Invalid os: ' . $os); + } + return isset($this->support_lib_list[$os][$ext_name]); + } + + private function isEmptyLine(array $line): bool + { + return $line[1] === '' && $line[2] === ''; + } +}