wpry parses WordPress plugin and theme headers.
It ships as:
- a Go library for reading metadata from files and
fs.FS - a CLI that emits JSON for a file, directory, or zip archive
- Parse WordPress plugin headers from PHP files.
- Parse WordPress theme headers from
style.css. - Parse unzipped plugin and theme directories.
- Parse zip archives containing a plugin or theme.
- Normalize CR and CRLF line endings like WordPress.
- Apply best-effort encoding fallback before header parsing.
Build the CLI from source:
go build ./cmd/wpryOr run it without installing:
go run ./cmd/wpry <path>wpry [flags] <path>Flags:
-parallel <n>: maximum workers for filesystem parsing.0or less usesGOMAXPROCS.-timeout <d>: overall parse timeout. Default1m.
<path> may point to:
- a plugin PHP file
- a theme stylesheet
- an unzipped plugin directory
- an unzipped theme directory
- a zip archive containing either layout
When a path could parse as both a plugin and a theme, the CLI returns the first successful result. That selection is intentionally nondeterministic.
Parse a plugin file:
go run ./cmd/wpry testdata/plugin-full.php{"path":"testdata/plugin-full.php","plugin":{"name":"Full Plugin","uri":"https://example.com/full-plugin","description":"A fully specified plugin.","version":"2.0.0","requires_wp":"6.0","requires_php":"8.0","author":"Full Author","author_uri":"https://example.com/author","license":"GPL-2.0-or-later","license_uri":"https://www.gnu.org/licenses/gpl-2.0.html","update_uri":"https://example.com/update","text_domain":"full-plugin","domain_path":"/lang","requires_plugins":"woocommerce, akismet","network":"true"}}Parse a theme file:
go run ./cmd/wpry testdata/theme-full.css{"path":"testdata/theme-full.css","theme":{"name":"Full Theme","uri":"https://example.com/full-theme","author":"Full Author","author_uri":"https://example.com/author","description":"A fully specified theme.","version":"3.0.0","requires_wp":"6.0","tested_up_to":"6.5","requires_php":"8.0","license":"GPL-2.0-or-later","license_uri":"https://www.gnu.org/licenses/gpl-2.0.html","text_domain":"full-theme","domain_path":"/lang","tags":"custom-background, custom-logo","template":"twentytwentyfive"}}Parse a directory:
go run ./cmd/wpry /path/to/wp-content/plugins/woocommerce
go run ./cmd/wpry /path/to/wp-content/themes/twentytwentynineParse a zip archive:
go run ./cmd/wpry /path/to/plugin.zip
go run ./cmd/wpry /path/to/theme.zipSuccessful output is exactly one JSON object on stdout.
Plugin result:
{
"path": "plugin.php",
"plugin": {
"name": "Full Plugin",
"version": "2.0.0"
}
}Theme result:
{
"path": "style.css",
"theme": {
"name": "Full Theme",
"version": "3.0.0"
}
}Error result:
{
"error": "no header found"
}Exit codes:
0: success1: parse, I/O, zip, or timeout failure2: invalid CLI usage
package main
import (
"fmt"
"os"
"github.com/typisttech/wpry"
)
func main() {
f, err := os.Open("plugin.php")
if err != nil {
panic(err)
}
defer f.Close()
p, err := wpry.ParsePlugin(f)
if err != nil {
panic(err)
}
fmt.Println(p.Name)
}Available entrypoints:
wpry.ParsePlugin(io.Reader)wpry.ParseTheme(io.Reader)wpry.ParsePluginFS(context.Context, fs.FS, ...ParseOption)wpry.ParseThemeFS(context.Context, fs.FS)
- Only the first 8 KiB of each file is scanned for headers.
- CR is normalized to LF before header matching.
- UTF-8 BOM is stripped.
- If input is not valid UTF-8, decoding falls back to BOM-detected UTF-16/UTF-32, then Windows-1252, then ISO-8859-1.
- Header matching is intentionally aligned with WordPress header parsing semantics.
Run tests:
go test ./...Run only library tests:
go test .Run only CLI script tests:
go test ./cmd/wpryRefresh CLI golden files only when output intentionally changed:
WPRY_UPDATE_SCRIPTS=1 go test ./cmd/wpryRun lint and formatting:
golangci-lint run
golangci-lint fmtDevelopment notes:
- CLI script tests live in
cmd/wpry/testdata/script/*.txt. - Zip testscript scenarios shell out to
zip. - Preserve fixture line endings in
testdata/; some fixtures intentionally use CR or CRLF. .golangci.ymlenforces strict dependency rules. Tests use stdlib assertions orgithub.com/google/go-cmp/cmp.