Skip to content

Commit a4d901c

Browse files
authored
devpkg: make FlakeInstallable.Outputs a string (#1599)
Change `ParseInstallable.Outputs` from a `[]string` to a `string` and move the splitting logic to a `SplitOutputs` method. This makes installables directly comparable with `==` and usable as map keys, which is something we currently do a bunch using unparsed installable strings.
1 parent 439f2c4 commit a4d901c

File tree

2 files changed

+45
-64
lines changed

2 files changed

+45
-64
lines changed

internal/devpkg/flakeref.go

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package devpkg
33
import (
44
"net/url"
55
"path"
6+
"slices"
67
"strings"
78

89
"go.jetpack.io/devbox/internal/redact"
@@ -407,6 +408,16 @@ func buildQueryString(keyval ...string) string {
407408
return query.Encode()
408409
}
409410

411+
// Special values for FlakeInstallable.Outputs.
412+
const (
413+
// DefaultOutputs specifies that the package-defined default outputs
414+
// should be installed.
415+
DefaultOutputs = ""
416+
417+
// AllOutputs specifies that all package outputs should be installed.
418+
AllOutputs = "*"
419+
)
420+
410421
// FlakeInstallable is a Nix command line argument that specifies how to install
411422
// a flake. It can be a plain flake reference, or a flake reference with an
412423
// attribute path and/or output specification.
@@ -429,9 +440,9 @@ func buildQueryString(keyval ...string) string {
429440
type FlakeInstallable struct {
430441
Ref FlakeRef
431442
AttrPath string
432-
Outputs []string
433443

434-
raw string
444+
raw string
445+
Outputs string
435446
}
436447

437448
// ParseFlakeInstallable parses a flake installable. The string s must contain a
@@ -445,11 +456,8 @@ func ParseFlakeInstallable(raw string) (FlakeInstallable, error) {
445456
// The output spec must be parsed and removed first, otherwise it will
446457
// be parsed as part of the flakeref's URL fragment.
447458
install := FlakeInstallable{raw: raw}
448-
before, after := splitOutputSpec(raw)
449-
if after != "" {
450-
install.Outputs = strings.Split(after, ",")
451-
}
452-
raw = before
459+
raw, install.Outputs = splitOutputSpec(raw)
460+
install.Outputs = strings.Join(install.SplitOutputs(), ",") // clean the outputs
453461

454462
// Interpret installables with path-style flakerefs as URLs to extract
455463
// the attribute path (fragment). This means that path-style flakerefs
@@ -468,20 +476,29 @@ func ParseFlakeInstallable(raw string) (FlakeInstallable, error) {
468476
return install, nil
469477
}
470478

471-
// AllOutputs returns true if the installable specifies all outputs with the
472-
// "^*" syntax.
473-
func (f FlakeInstallable) AllOutputs() bool {
474-
for _, out := range f.Outputs {
479+
// SplitOutputs splits and sorts the comma-separated list of outputs. It skips
480+
// any empty outputs. If one or more of the outputs is a "*", then the result
481+
// will be a slice with a single "*" element.
482+
func (f FlakeInstallable) SplitOutputs() []string {
483+
if f.Outputs == "" {
484+
return []string{}
485+
}
486+
487+
split := strings.Split(f.Outputs, ",")
488+
i := 0
489+
for _, out := range split {
490+
// A wildcard takes priority over any other outputs.
475491
if out == "*" {
476-
return true
492+
return []string{"*"}
493+
}
494+
if out != "" {
495+
split[i] = out
496+
i++
477497
}
478498
}
479-
return false
480-
}
481-
482-
// DefaultOutputs returns true if the installable does not specify any outputs.
483-
func (f FlakeInstallable) DefaultOutputs() bool {
484-
return len(f.Outputs) == 0
499+
split = split[:i]
500+
slices.Sort(split)
501+
return split
485502
}
486503

487504
// String returns the raw installable string as given to ParseFlakeInstallable.

internal/devpkg/flakeref_test.go

Lines changed: 10 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -237,22 +237,22 @@ func TestParseFlakeInstallable(t *testing.T) {
237237

238238
".": {Ref: FlakeRef{Type: "path", Path: "."}},
239239
".#app": {AttrPath: "app", Ref: FlakeRef{Type: "path", Path: "."}},
240-
".#app^out": {AttrPath: "app", Outputs: []string{"out"}, Ref: FlakeRef{Type: "path", Path: "."}},
241-
".#app^out,lib": {AttrPath: "app", Outputs: []string{"out", "lib"}, Ref: FlakeRef{Type: "path", Path: "."}},
242-
".#app^*": {AttrPath: "app", Outputs: []string{"*"}, Ref: FlakeRef{Type: "path", Path: "."}},
243-
".^*": {Outputs: []string{"*"}, Ref: FlakeRef{Type: "path", Path: "."}},
240+
".#app^out": {AttrPath: "app", Outputs: "out", Ref: FlakeRef{Type: "path", Path: "."}},
241+
".#app^out,lib": {AttrPath: "app", Outputs: "lib,out", Ref: FlakeRef{Type: "path", Path: "."}},
242+
".#app^*": {AttrPath: "app", Outputs: "*", Ref: FlakeRef{Type: "path", Path: "."}},
243+
".^*": {Outputs: "*", Ref: FlakeRef{Type: "path", Path: "."}},
244244

245245
"./flake": {Ref: FlakeRef{Type: "path", Path: "./flake"}},
246246
"./flake#app": {AttrPath: "app", Ref: FlakeRef{Type: "path", Path: "./flake"}},
247-
"./flake#app^out": {AttrPath: "app", Outputs: []string{"out"}, Ref: FlakeRef{Type: "path", Path: "./flake"}},
248-
"./flake#app^out,lib": {AttrPath: "app", Outputs: []string{"out", "lib"}, Ref: FlakeRef{Type: "path", Path: "./flake"}},
249-
"./flake^out": {Outputs: []string{"out"}, Ref: FlakeRef{Type: "path", Path: "./flake"}},
247+
"./flake#app^out": {AttrPath: "app", Outputs: "out", Ref: FlakeRef{Type: "path", Path: "./flake"}},
248+
"./flake#app^out,lib": {AttrPath: "app", Outputs: "lib,out", Ref: FlakeRef{Type: "path", Path: "./flake"}},
249+
"./flake^out": {Outputs: "out", Ref: FlakeRef{Type: "path", Path: "./flake"}},
250250

251251
"indirect": {Ref: FlakeRef{Type: "indirect", ID: "indirect"}},
252252
"nixpkgs#app": {AttrPath: "app", Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
253-
"nixpkgs#app^out": {AttrPath: "app", Outputs: []string{"out"}, Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
254-
"nixpkgs#app^out,lib": {AttrPath: "app", Outputs: []string{"out", "lib"}, Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
255-
"nixpkgs^out": {Outputs: []string{"out"}, Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
253+
"nixpkgs#app^out": {AttrPath: "app", Outputs: "out", Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
254+
"nixpkgs#app^out,lib": {AttrPath: "app", Outputs: "lib,out", Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
255+
"nixpkgs^out": {Outputs: "out", Ref: FlakeRef{Type: "indirect", ID: "nixpkgs"}},
256256

257257
"%23#app": {AttrPath: "app", Ref: FlakeRef{Type: "indirect", ID: "#"}},
258258
"./%23#app": {AttrPath: "app", Ref: FlakeRef{Type: "path", Path: "./#"}},
@@ -280,42 +280,6 @@ func TestParseFlakeInstallable(t *testing.T) {
280280
}
281281
}
282282

283-
func TestFlakeInstallableDefaultOutputs(t *testing.T) {
284-
install := FlakeInstallable{Outputs: nil}
285-
if !install.DefaultOutputs() {
286-
t.Errorf("DefaultOutputs() = false for nil outputs slice, want true")
287-
}
288-
289-
install = FlakeInstallable{Outputs: []string{}}
290-
if !install.DefaultOutputs() {
291-
t.Errorf("DefaultOutputs() = false for empty outputs slice, want true")
292-
}
293-
294-
install = FlakeInstallable{Outputs: []string{"out"}}
295-
if install.DefaultOutputs() {
296-
t.Errorf("DefaultOutputs() = true for %v, want false", install.Outputs)
297-
}
298-
}
299-
300-
func TestFlakeInstallableAllOutputs(t *testing.T) {
301-
install := FlakeInstallable{Outputs: []string{"*"}}
302-
if !install.AllOutputs() {
303-
t.Errorf("AllOutputs() = false for %v, want true", install.Outputs)
304-
}
305-
install = FlakeInstallable{Outputs: []string{"out", "*"}}
306-
if !install.AllOutputs() {
307-
t.Errorf("AllOutputs() = false for %v, want true", install.Outputs)
308-
}
309-
install = FlakeInstallable{Outputs: nil}
310-
if install.AllOutputs() {
311-
t.Errorf("AllOutputs() = true for nil outputs slice, want false")
312-
}
313-
install = FlakeInstallable{Outputs: []string{}}
314-
if install.AllOutputs() {
315-
t.Errorf("AllOutputs() = true for empty outputs slice, want false")
316-
}
317-
}
318-
319283
func TestBuildQueryString(t *testing.T) {
320284
defer func() {
321285
if r := recover(); r == nil {

0 commit comments

Comments
 (0)