Skip to content

Commit 2112fcd

Browse files
pankgeorgclaude
andcommitted
feat: --short for jh package dependency and dependents
Prints a single line with the counts, e.g. JuMP v1.32.1 · General: 9 direct · 40 indirect · 49 total dependencies `--short` cannot be combined with `--json`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ef3d89b commit 2112fcd

5 files changed

Lines changed: 48 additions & 11 deletions

File tree

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ jh run setup
535535
- Package search (`jh package search`) and info (`jh package info`) both try REST API (`/api/v1/ui/packages/info`) first, then fall back to GraphQL (`FilteredPackages` / `FilteredPackagesCount` via `/v1/graphql`) on failure; `searchPackages` prints `warning: REST package search failed (...); falling back to GraphQL` to stderr when it falls back (`getPackageInfo` falls back silently)
536536
- `--registries` resolution for `jh package search` / `jh package info` / `jh search packages` goes through `resolveRegistries(server, csv)` in `registries.go` (empty = all registries; unknown name → `Registry not found: '<name>'`)
537537
- `jh package info` adds `details` (`packageDetails`) to each match: the listing's `downloads_timeseries` plus the stable `pkg.json` (`fetchPackageDocs`, one request per match): homepage, release date, contributors, `doctype`/`documenter_errored`, and dependency/dependent counts (unique UUIDs, direct wins). A failed `pkg.json` fetch prints a warning on stderr and leaves those fields empty. `restToInfo` also carries `owner`, `is_app`, `is_dyad` and `downloads_30d` (sum of the 30 daily counts), so `jh package search --json` gains them too; the GraphQL fallback has no download data and omits `downloads_30d`
538-
- Package text output: `jh package info` and `search --verbose` print a card per package (`printPackageCard`: header `name vX · registry · owner`, description, aligned fields, links including the JuliaHub page `https://<server>/ui/Packages/<registry>/<name>` from `packagePageURL`, install hint); `search` prints a table with STARS and DL/30 DAYS columns; `dependency` and `dependents` print `printPackageHeading`, the direct/indirect/total counts and tables, with standard libraries (no registry in pkg.json) shown as `stdlib`. `splitDependencies` collapses `deps` to one entry per UUID like `splitDependents`, for both text and `--json`
538+
- Package text output: `jh package info` and `search --verbose` print a card per package (`printPackageCard`: header `name vX · registry · owner`, description, aligned fields, links including the JuliaHub page `https://<server>/ui/Packages/<registry>/<name>` from `packagePageURL`, install hint); `search` prints a table with STARS and DL/30 DAYS columns; `dependency` and `dependents` print `printPackageHeading`, the direct/indirect/total counts and tables, with standard libraries (no registry in pkg.json) shown as `stdlib`. `splitDependencies` collapses `deps` to one entry per UUID like `splitDependents`, for both text and `--json`. `--short` on `dependency`/`dependents` prints only `printPackageSummary` (`name vX · registry: <countsSummary>`); it is mutually exclusive with `--json`
539539
- `jh package info --json` prints the exact matches in the same `{"results": [...], "total": N}` document (`writePackagesJSON`; no match → empty `results`). `jh package dependency --json` prints `dependenciesDoc` and `jh package dependents --json` prints `dependentsDoc` (counts always cover every dependent; `dependents` lists indirect ones only with `--indirect`). All go through `writeJSON`; errors from these three commands go to stderr
540540
- `jh package search` / `jh search packages --json` sets `PackageSearchParams.JSON`; both the REST and GraphQL paths then call `writePackagesJSON`, which prints `{"results": [...], "total": N}` (snake_case keys from `packageInfo`'s json tags, `tags` always an array) 2-space indented with a trailing newline — nothing else on stdout
541541
- **JSON output convention** (`jh search *`): `--json` prints only the document on stdout, 2-space indented, trailing newline — `{"results": <server array, untouched>, "truncated": bool}` for code/symbols/docs; warnings, the human-mode `note: results truncated (search stopped early)` line, and errors always go to stderr; exit code is 0/1 only

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,13 @@ go build -o jh .
171171
- Default: Shows up to 10 direct dependencies (NAME, REGISTRY, UUID, VERSIONS)
172172
- `jh package dependency --indirect` - Include both direct and indirect dependencies
173173
- `jh package dependency --registry General` - Specify registry to use
174+
- `jh package dependency --short` - Print one line: `JuMP v1.32.1 · General: 9 direct · 40 indirect · 49 total dependencies`
174175
- `jh package dependency --json` - Print only `{"name", "version", "registry", "dependencies": [...]}` on stdout (`--indirect` adds the indirect ones)
175176
- `jh package dependents <package-name>` - List packages that depend on a package (reverse dependencies)
176177
- Default: Prints direct/indirect/total counts and lists the direct dependents (NAME, REGISTRY, UUID)
177178
- `jh package dependents --indirect` - Also list indirect dependents
178179
- `jh package dependents --registry General` - Specify registry to use
180+
- `jh package dependents --short` - Print one line: `JuMP v1.32.1 · General: 335 direct · 94 indirect · 429 total dependents`
179181
- `jh package dependents --json` - Print only `{"name", "version", "registry", "direct", "indirect", "total", "dependents": [...]}` on stdout; the counts always cover every dependent, `dependents` lists the indirect ones only with `--indirect`
180182

181183
### Registry Management (`jh registry`)

‎main.go‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,7 @@ The command fetches dependency information from the package documentation
10391039
JSON endpoint. If a package exists in multiple registries, it uses the
10401040
first registry by default. You can specify a different registry using
10411041
the --registry flag.`,
1042-
Example: " jh package dependency DataFrames\n jh package dependency --indirect Plots\n jh package dependency --registry General CSV\n jh package dependency --json JuMP",
1042+
Example: " jh package dependency DataFrames\n jh package dependency --indirect Plots\n jh package dependency --registry General CSV\n jh package dependency --short JuMP\n jh package dependency --json JuMP",
10431043
Args: cobra.ExactArgs(1),
10441044
Run: func(cmd *cobra.Command, args []string) {
10451045
server, err := getServerFromFlagOrConfig(cmd)
@@ -1050,10 +1050,11 @@ the --registry flag.`,
10501050

10511051
packageName := args[0]
10521052
asJSON, _ := cmd.Flags().GetBool("json")
1053+
short, _ := cmd.Flags().GetBool("short")
10531054
registryName, _ := cmd.Flags().GetString("registry")
10541055
showIndirect, _ := cmd.Flags().GetBool("indirect")
10551056

1056-
if err := getPackageDependencies(server, packageName, registryName, showIndirect, asJSON); err != nil {
1057+
if err := getPackageDependencies(server, packageName, registryName, showIndirect, asJSON, short); err != nil {
10571058
fmt.Fprintf(os.Stderr, "Failed to get package dependencies: %v\n", err)
10581059
os.Exit(1)
10591060
}
@@ -1073,7 +1074,7 @@ The data comes from the same package documentation JSON endpoint as
10731074
jh package dependency, for the package's stable version. If a package
10741075
exists in multiple registries, it uses the first registry by default.
10751076
You can specify a different registry using the --registry flag.`,
1076-
Example: " jh package dependents JuMP\n jh package dependents --indirect DataFrames\n jh package dependents --registry General CSV\n jh package dependents --json JuMP",
1077+
Example: " jh package dependents JuMP\n jh package dependents --indirect DataFrames\n jh package dependents --registry General CSV\n jh package dependents --short JuMP\n jh package dependents --json JuMP",
10771078
Args: cobra.ExactArgs(1),
10781079
Run: func(cmd *cobra.Command, args []string) {
10791080
server, err := getServerFromFlagOrConfig(cmd)
@@ -1084,10 +1085,11 @@ You can specify a different registry using the --registry flag.`,
10841085

10851086
packageName := args[0]
10861087
asJSON, _ := cmd.Flags().GetBool("json")
1088+
short, _ := cmd.Flags().GetBool("short")
10871089
registryName, _ := cmd.Flags().GetString("registry")
10881090
showIndirect, _ := cmd.Flags().GetBool("indirect")
10891091

1090-
if err := getPackageDependents(server, packageName, registryName, showIndirect, asJSON); err != nil {
1092+
if err := getPackageDependents(server, packageName, registryName, showIndirect, asJSON, short); err != nil {
10911093
fmt.Fprintf(os.Stderr, "Failed to get package dependents: %v\n", err)
10921094
os.Exit(1)
10931095
}
@@ -2657,10 +2659,14 @@ func init() {
26572659
packageDependencyCmd.Flags().String("registry", "", "Specify registry name (uses first registry if not specified)")
26582660
packageDependencyCmd.Flags().Bool("indirect", false, "Include indirect dependencies")
26592661
packageDependencyCmd.Flags().Bool("json", false, "Print only a JSON document ({\"name\", \"version\", \"registry\", \"dependencies\": [...]}) on stdout")
2662+
packageDependencyCmd.Flags().Bool("short", false, "Print only one line: the direct, indirect and total dependencies counts")
2663+
packageDependencyCmd.MarkFlagsMutuallyExclusive("short", "json")
26602664
packageDependentsCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
26612665
packageDependentsCmd.Flags().String("registry", "", "Specify registry name (uses first registry if not specified)")
26622666
packageDependentsCmd.Flags().Bool("indirect", false, "Also list indirect dependents")
26632667
packageDependentsCmd.Flags().Bool("json", false, "Print only a JSON document ({\"name\", \"version\", \"registry\", \"direct\", \"indirect\", \"total\", \"dependents\": [...]}) on stdout")
2668+
packageDependentsCmd.Flags().Bool("short", false, "Print only one line: the direct, indirect and total dependents counts")
2669+
packageDependentsCmd.MarkFlagsMutuallyExclusive("short", "json")
26642670
registryListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
26652671
registryListCmd.Flags().Bool("verbose", false, "Show detailed registry information")
26662672
projectListCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")

‎packages.go‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ type dependenciesDoc struct {
10291029
Dependencies []PackageDependency `json:"dependencies"`
10301030
}
10311031

1032-
func getPackageDependencies(server string, packageName string, registryName string, showIndirect, asJSON bool) error {
1032+
func getPackageDependencies(server string, packageName string, registryName string, showIndirect, asJSON, short bool) error {
10331033
docsResp, targetRegistry, err := fetchPackageDocs(server, packageName, registryName)
10341034
if err != nil {
10351035
return err
@@ -1045,13 +1045,17 @@ func getPackageDependencies(server string, packageName string, registryName stri
10451045
return writeJSON(os.Stdout, dependenciesDoc{docsResp.Name, docsResp.Version, targetRegistry, deps})
10461046
}
10471047

1048+
summary := countsSummary(len(direct), len(indirect), "dependencies")
1049+
if short {
1050+
printPackageSummary(docsResp, targetRegistry, summary)
1051+
return nil
1052+
}
10481053
printPackageHeading(docsResp, targetRegistry)
10491054
if len(direct)+len(indirect) == 0 {
10501055
fmt.Println("No dependencies")
10511056
return nil
10521057
}
1053-
fmt.Printf("%s direct · %s indirect · %s total dependencies\n",
1054-
formatCount(len(direct)), formatCount(len(indirect)), formatCount(len(direct)+len(indirect)))
1058+
fmt.Println(summary)
10551059
if len(direct) > 0 {
10561060
fmt.Println()
10571061
printDependencyTable("Direct dependencies", direct)
@@ -1065,6 +1069,18 @@ func getPackageDependencies(server string, packageName string, registryName stri
10651069
return nil
10661070
}
10671071

1072+
// countsSummary renders "9 direct · 40 indirect · 49 total <noun>".
1073+
func countsSummary(direct, indirect int, noun string) string {
1074+
return fmt.Sprintf("%s direct · %s indirect · %s total %s",
1075+
formatCount(direct), formatCount(indirect), formatCount(direct+indirect), noun)
1076+
}
1077+
1078+
// printPackageSummary prints the one-line --short form:
1079+
// "<name> v<version> · <registry>: <summary>".
1080+
func printPackageSummary(docsResp *PackageDocsResponse, registry, summary string) {
1081+
fmt.Printf("%s %s %s\n", bold(docsResp.Name), dim("v"+docsResp.Version+" · "+registry+":"), summary)
1082+
}
1083+
10681084
// printPackageHeading prints "<name> v<version> · <registry>" and a blank line.
10691085
func printPackageHeading(docsResp *PackageDocsResponse, registry string) {
10701086
fmt.Printf("%s %s\n\n", bold(docsResp.Name), dim("v"+docsResp.Version+" · "+registry))
@@ -1188,7 +1204,7 @@ func newDependentsDoc(docsResp *PackageDocsResponse, registry string, direct, in
11881204
}
11891205
}
11901206

1191-
func getPackageDependents(server, packageName, registryName string, showIndirect, asJSON bool) error {
1207+
func getPackageDependents(server, packageName, registryName string, showIndirect, asJSON, short bool) error {
11921208
docsResp, targetRegistry, err := fetchPackageDocs(server, packageName, registryName)
11931209
if err != nil {
11941210
return err
@@ -1198,13 +1214,17 @@ func getPackageDependents(server, packageName, registryName string, showIndirect
11981214
if asJSON {
11991215
return writeJSON(os.Stdout, newDependentsDoc(docsResp, targetRegistry, direct, indirect, showIndirect))
12001216
}
1217+
summary := countsSummary(len(direct), len(indirect), "dependents")
1218+
if short {
1219+
printPackageSummary(docsResp, targetRegistry, summary)
1220+
return nil
1221+
}
12011222
printPackageHeading(docsResp, targetRegistry)
12021223
if len(direct)+len(indirect) == 0 {
12031224
fmt.Println("No dependents")
12041225
return nil
12051226
}
1206-
fmt.Printf("%s direct · %s indirect · %s total dependents\n",
1207-
formatCount(len(direct)), formatCount(len(indirect)), formatCount(len(direct)+len(indirect)))
1227+
fmt.Println(summary)
12081228
if len(direct) > 0 {
12091229
fmt.Println()
12101230
printDependentsTable("Direct dependents", direct)

‎packages_test.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,12 @@ func TestListTags(t *testing.T) {
384384
t.Errorf("listTags keeps at least one tag: %q", got)
385385
}
386386
}
387+
388+
func TestCountsSummary(t *testing.T) {
389+
if got := countsSummary(9, 40, "dependencies"); got != "9 direct · 40 indirect · 49 total dependencies" {
390+
t.Errorf("countsSummary = %q", got)
391+
}
392+
if got := countsSummary(1335, 0, "dependents"); got != "1,335 direct · 0 indirect · 1,335 total dependents" {
393+
t.Errorf("countsSummary = %q", got)
394+
}
395+
}

0 commit comments

Comments
 (0)