🔧 输出解析
把 composer 各子命令的原始文本/JSON 输出解析成结构化 Go 数据——包信息、过时包、安全审计、搜索结果、依赖树、安装/更新/移除统计、self-update、平台需求、资金、诊断、配置、版本号、包名列表。
Composer 大量子命令支持 --format=json,但原始 JSON 形态各异(有的是对象、有的是数组、有的把包名当 key)。本模块把这些异构输出统一归一化为强类型结构体,并对若干只有文本输出的命令(如 diagnose、fund、self-update、about)做了正则/缩进解析,让你无需手写解析逻辑。
何时使用
- 📊 仪表盘 / CI 报告:把
install/update/outdated/audit的输出转成数字统计。 - 🌳 依赖可视化:
ParseDependencyTreeOutput把composer show --tree的树形文本转成可递归遍历的[]DependencyNode。 - 🔍 检索:
ParseComposerSearchJSON把搜索结果转成结构化条目列表。 - 🧪 单测里直接喂入固定字符串断言解析结果,不依赖真实
composer调用。 - 🩺 自检:
ParseDiagnoseOutputAsChecks把diagnose的多行[OK]/[WARNING]/[ERROR]拆成逐项检查。
与结构化方法的关系
解析函数 vs 结构化方法
本页函数都是包级函数(Parse*、Extract*),输入是 string、输出是结构体。它们与 audit、packages、version 等页面里的 *Composer 方法(如 GetAuditInfo、GetOutdatedInfo、GetVersionInfo)一一对应——后者只是「先 c.Run(...) 拿到输出,再调本页的 Parse*」的语法糖。因此你既可以走高级方法,也可以自己拿输出后手动解析。
方法签名
| 函数 | 签名 | 说明 |
|---|---|---|
| 📦 ParseComposerShowJSON | func ParseComposerShowJSON(output string) (*PackageInfo, error) | 解析 show --format=json,等价 ParsePackageInfo |
| 🕰️ ParseComposerOutdatedJSON | func ParseComposerOutdatedJSON(output string) (*OutdatedResult, error) | 解析 outdated --format=json,等价 ParseOutdatedResult |
| 🛡️ ParseComposerAuditJSON | func ParseComposerAuditJSON(output string) (*AuditInfoResult, error) | 解析 audit --format=json,等价 ParseAuditInfoResult |
| 🔎 ParseComposerSearchJSON | func ParseComposerSearchJSON(output string) (*SearchResult, error) | 解析 search --format=json,等价 ParseSearchResult |
| 🌳 ParseDependencyTreeOutput | func ParseDependencyTreeOutput(output string) ([]DependencyNode, error) | 解析 show --tree 的文本输出为依赖树 |
| 🌳 ParseDependencyTreeJSON | func ParseDependencyTreeJSON(output string) ([]DependencyNode, error) | 解析 show --tree --format=json 的依赖树 |
| ⬇️ ParseInstallOutput | func ParseInstallOutput(output string) *InstallResult | 解析 install 输出的安装/更新/移除计数与警告 |
| 🔄 ParseUpdateOutput | func ParseUpdateOutput(output string) *UpdateResult | 解析 update 输出的更新计数与警告 |
| ➕ ParseRequireOutput | func ParseRequireOutput(output string, packageName string) *RequireResult | 解析 require 输出,提取安装版本 |
| ➖ ParseRemoveOutput | func ParseRemoveOutput(output string, packageName string) *RemoveResult | 解析 remove 输出与警告 |
| ⬆️ ParseSelfUpdateOutput | func ParseSelfUpdateOutput(output string) (oldVersion, newVersion string, err error) | 解析 self-update 输出的旧/新版本 |
| ✅ ParseCheckPlatformReqsOutput | func ParseCheckPlatformReqsOutput(output string) ([]PlatformRequirement, error) | 解析 check-platform-reqs 文本输出 |
| 💰 ParseFundOutput | func ParseFundOutput(output string) []FundInfo | 解析 fund 文本输出为资金条目列表 |
| 🩺 ParseDiagnoseOutputAsChecks | func ParseDiagnoseOutputAsChecks(output string) []DiagnoseCheck | 解析 diagnose 输出为检查项列表 |
| ⚙️ ParseConfigOutput | func ParseConfigOutput(output string) (string, error) | 解析 config 单值输出(去空白) |
| 🔢 ExtractVersionFromOutput | func ExtractVersionFromOutput(output string) (string, bool) | 从任意文本中正则提取 X.Y.Z 版本号 |
| 📛 ExtractPackageNamesFromOutput | func ExtractPackageNamesFromOutput(output string) []string | 从任意文本中正则提取 vendor/package 包名(去重) |
结构化返回类型
PackageInfo / DependencyNode
ParseComposerShowJSON 返回 *PackageInfo(字段见 packages);ParseDependencyTreeOutput / ParseDependencyTreeJSON 返回 []DependencyNode:
type DependencyNode struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Children []DependencyNode `json:"children,omitempty"`
}| 字段 | 类型 | 说明 |
|---|---|---|
Name | string | 包名,如 symfony/console |
Version | string | 该节点版本(可空) |
Children | []DependencyNode | 递归子依赖 |
InstallResult / UpdateResult / RequireResult / RemoveResult
type InstallResult struct {
PackagesInstalled int `json:"packages_installed"`
PackagesUpdated int `json:"packages_updated"`
PackagesRemoved int `json:"packages_removed"`
Output string `json:"output"`
Warnings []string `json:"warnings,omitempty"`
}
type UpdateResult struct {
PackagesUpdated int `json:"packages_updated"`
Output string `json:"output"`
Warnings []string `json:"warnings,omitempty"`
}
type RequireResult struct {
PackageName string `json:"package_name"`
Version string `json:"version,omitempty"`
Output string `json:"output"`
Warnings []string `json:"warnings,omitempty"`
}
type RemoveResult struct {
PackageName string `json:"package_name"`
Output string `json:"output"`
Warnings []string `json:"warnings,omitempty"`
}Output 字段保留原始命令输出,便于调试;Warnings 收集所有含 Warning 的行。
PlatformRequirement / FundInfo / DiagnoseCheck
type PlatformRequirement struct {
Package string `json:"package"`
Version string `json:"version,omitempty"`
Status string `json:"status"` // "ok" / "missing" / "mismatch"
Required string `json:"required,omitempty"`
}
type FundInfo struct {
Package string `json:"package"`
Type string `json:"type,omitempty"` // "github" / "patreon" / "tidelift" 等
URL string `json:"url,omitempty"`
}
type DiagnoseCheck struct {
Name string `json:"name"`
Status string `json:"status"` // "ok" / "warning" / "error" / "info"
Detail string `json:"detail,omitempty"`
}示例
解析 outdated 的 JSON 输出
package main
import (
"fmt"
"log"
"github.com/scagogogo/composer-skills/pkg/composer"
)
func main() {
comp, err := composer.New(composer.DefaultOptions())
if err != nil {
log.Fatal(err)
}
// 拿到 outdated 的 JSON 原始输出
output, err := comp.Run("outdated", "--format", "json")
if err != nil && output == "" {
// 没有过时包时 composer 可能返回非零退出码,但输出仍含有效 JSON
log.Fatalf("运行 outdated 失败: %v", err)
}
// 用解析函数把 JSON 转成结构体
result, err := composer.ParseComposerOutdatedJSON(output)
if err != nil {
log.Fatalf("解析过时包失败: %v", err)
}
for _, pkg := range result.Installed {
fmt.Printf("%s: 已装 %s -> 可升级 %s (%s)\n",
pkg.Name, pkg.Installed, pkg.Latest, pkg.LatestStatus)
}
fmt.Printf("过时包总数: %d\n", result.Count)
}也可以用高级方法
上面用解析函数直接解析字符串。如果你只想「跑命令拿结果」,用 comp.GetOutdatedInfo() 即可,它内部就是 c.Run("outdated", "--format", "json") + ParseOutdatedResult。
把 install 输出转成统计
output := `Loading composer repositories with package information
Updating dependencies
Package operations: 3 installs, 0 updates, 0 removals
- Installing symfony/console (v5.4.0)
Warning: ...`
result := composer.ParseInstallOutput(output)
fmt.Printf("安装 %d 个,更新 %d 个,移除 %d 个\n",
result.PackagesInstalled, result.PackagesUpdated, result.PackagesRemoved)
// 安装 3 个,更新 0 个,移除 0 个
fmt.Printf("警告数: %d\n", len(result.Warnings))解析依赖树文本输出
tree := `symfony/console v5.4.0
├── psr/log ~1.0
│ └── ...
└── symfony/polyfill-ctype v1.23.0`
nodes, err := composer.ParseDependencyTreeOutput(tree)
if err != nil {
log.Fatal(err)
}
for _, root := range nodes {
fmt.Printf("根: %s %s,子依赖 %d 个\n", root.Name, root.Version, len(root.Children))
}文本 vs JSON
ParseDependencyTreeOutput 解析 composer show --tree 的缩进文本(按 4 空格缩进确定层级);ParseDependencyTreeJSON 解析 --tree --format=json。两者返回相同的 []DependencyNode,按你的命令调用方式选用。
解析 self-update 输出的新旧版本
output := "Upgrading to 2.6.6 (from 2.6.5)..."
oldVer, newVer, err := composer.ParseSelfUpdateOutput(output)
if err != nil {
log.Fatal(err)
}
fmt.Printf("从 %s 升级到 %s\n", oldVer, newVer)
// 从 2.6.5 升级到 2.6.6ParseSelfUpdateOutput 支持三种格式:Upgrading to X (from Y)、You are already using composer version X、Successfully updated to X。
从任意输出提取版本号与包名
text := "Detected symfony/console at version 5.4.0 in vendor/bin"
if ver, ok := composer.ExtractVersionFromOutput(text); ok {
fmt.Println("版本:", ver) // 版本: 5.4.0
}
for _, name := range composer.ExtractPackageNamesFromOutput(text) {
fmt.Println("包名:", name) // 包名: symfony/console
}ExtractVersionFromOutput 用正则 (\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?) 匹配语义化版本号(含预发布后缀)。ExtractPackageNamesFromOutput 匹配 vendor/package 形态并去重。
解析 diagnose 为逐项检查
output := `[OK] Checking composer version
[OK] Checking platform settings
[WARNING] Checking git settings
[ERROR] Checking http connectivity`
checks := composer.ParseDiagnoseOutputAsChecks(output)
for _, c := range checks {
fmt.Printf("[%s] %s\n", c.Status, c.Name)
}
// [ok] Checking composer version
// [ok] Checking platform settings
// [warning] Checking git settings
// [error] Checking http connectivity进阶
两个 FundInfo 不要混淆
ParseFundOutput 返回的 FundInfo(result_types.go,含 Package / Type / URL)来自 fund 文本输出的逐行解析;而 fund 页里的 FundingInfo(fund.go,含 URLs 切片)来自 --format=json。两者字段不同,按输出形态选用。
错误退出码也能解析
composer outdated / audit / validate 在发现问题时常返回非零退出码,但 stdout 仍含有效 JSON。对应的高级方法(GetOutdatedInfo 等)已处理这种情况并继续解析;如果你直接用本页 Parse* 函数,请确保传入的 output 是实际 stdout 内容,而非 err.Error()。
DependencyNode 文本解析的缩进规则
ParseDependencyTreeOutput 把行首的 、|、`、- 都视为缩进字符,每 4 个缩进字符算一层。因此它对 Composer 默认的树形渲染(├── / └── 前缀)兼容良好,但若你自定义了输出格式可能需要改用 JSON 解析。