Skip to content

Commit 3a99a4d

Browse files
authored
Merge pull request juju#18496 from hpidcock/intern-cmd
juju#18496 In an effort to improve commands and their documentation in Juju, interning juju/cmd will allow the project to have critical documentation generation in tree. ## QA steps Build and test some juju commands.
2 parents 05f7b3b + e6198bd commit 3a99a4d

33 files changed

+6428
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ require (
2525
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f
2626
github.com/canonical/go-dqlite v1.21.0
2727
github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a
28-
github.com/canonical/sqlair v0.0.0-20240417091145-13970327005b
2928
github.com/canonical/pebble v1.17.0
29+
github.com/canonical/sqlair v0.0.0-20240417091145-13970327005b
3030
github.com/chzyer/readline v1.5.1
3131
github.com/coreos/go-systemd/v22 v22.5.0
3232
github.com/docker/distribution v2.8.3+incompatible

internal/cmd/aliasfile.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2015 Canonical Ltd.
2+
// Licensed under the LGPLv3, see LICENSE file for details.
3+
4+
package cmd
5+
6+
import (
7+
"io/ioutil"
8+
"strings"
9+
)
10+
11+
// ParseAliasFile will read the specified file and convert
12+
// the content to a map of names to the command line arguments
13+
// they relate to. The function will always return a valid map, even
14+
// if it is empty.
15+
func ParseAliasFile(aliasFilename string) map[string][]string {
16+
result := map[string][]string{}
17+
if aliasFilename == "" {
18+
return result
19+
}
20+
21+
content, err := ioutil.ReadFile(aliasFilename)
22+
if err != nil {
23+
logger.Tracef("unable to read alias file %q: %s", aliasFilename, err)
24+
return result
25+
}
26+
27+
lines := strings.Split(string(content), "\n")
28+
for i, line := range lines {
29+
line = strings.TrimSpace(line)
30+
if line == "" || strings.HasPrefix(line, "#") {
31+
// skip blank lines and comments
32+
continue
33+
}
34+
parts := strings.SplitN(line, "=", 2)
35+
if len(parts) != 2 {
36+
logger.Warningf("line %d bad in alias file: %s", i+1, line)
37+
continue
38+
}
39+
name, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
40+
if name == "" {
41+
logger.Warningf("line %d missing alias name in alias file: %s", i+1, line)
42+
continue
43+
}
44+
if value == "" {
45+
logger.Warningf("line %d missing alias value in alias file: %s", i+1, line)
46+
continue
47+
}
48+
49+
logger.Tracef("setting alias %q=%q", name, value)
50+
result[name] = strings.Fields(value)
51+
}
52+
return result
53+
}

internal/cmd/aliasfile_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2015 Canonical Ltd.
2+
// Licensed under the LGPLv3, see LICENSE file for details.
3+
4+
package cmd_test
5+
6+
import (
7+
_ "fmt"
8+
"io/ioutil"
9+
"path/filepath"
10+
11+
"github.com/juju/testing"
12+
gc "gopkg.in/check.v1"
13+
14+
"github.com/juju/juju/internal/cmd"
15+
)
16+
17+
type ParseAliasFileSuite struct {
18+
testing.LoggingSuite
19+
}
20+
21+
var _ = gc.Suite(&ParseAliasFileSuite{})
22+
23+
func (*ParseAliasFileSuite) TestMissing(c *gc.C) {
24+
dir := c.MkDir()
25+
filename := filepath.Join(dir, "missing")
26+
aliases := cmd.ParseAliasFile(filename)
27+
c.Assert(aliases, gc.NotNil)
28+
c.Assert(aliases, gc.HasLen, 0)
29+
}
30+
31+
func (*ParseAliasFileSuite) TestParse(c *gc.C) {
32+
dir := c.MkDir()
33+
filename := filepath.Join(dir, "missing")
34+
content := `
35+
# comments skipped, as are the blank lines, such as the line
36+
# at the start of this file
37+
foo = trailing-space
38+
repeat = first
39+
flags = flags --with flag
40+
41+
# if the same alias name is used more than once, last one wins
42+
repeat = second
43+
44+
# badly formated values are logged, but skipped
45+
no equals sign
46+
=
47+
key =
48+
= value
49+
`
50+
err := ioutil.WriteFile(filename, []byte(content), 0644)
51+
c.Assert(err, gc.IsNil)
52+
aliases := cmd.ParseAliasFile(filename)
53+
c.Assert(aliases, gc.DeepEquals, map[string][]string{
54+
"foo": {"trailing-space"},
55+
"repeat": {"second"},
56+
"flags": {"flags", "--with", "flag"},
57+
})
58+
}

internal/cmd/args.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2013 Canonical Ltd.
2+
// Licensed under the LGPLv3, see LICENSE file for details.
3+
4+
package cmd
5+
6+
import (
7+
"strings"
8+
9+
"github.com/juju/gnuflag"
10+
)
11+
12+
// StringsValue implements gnuflag.Value for a comma separated list of
13+
// strings. This allows flags to be created where the target is []string, and
14+
// the caller is after comma separated values.
15+
type StringsValue []string
16+
17+
var _ gnuflag.Value = (*StringsValue)(nil)
18+
19+
// NewStringsValue is used to create the type passed into the gnuflag.FlagSet Var function.
20+
// f.Var(cmd.NewStringsValue(defaultValue, &someMember), "name", "help")
21+
func NewStringsValue(defaultValue []string, target *[]string) *StringsValue {
22+
value := (*StringsValue)(target)
23+
*value = defaultValue
24+
return value
25+
}
26+
27+
// Implements gnuflag.Value Set.
28+
func (v *StringsValue) Set(s string) error {
29+
*v = strings.Split(s, ",")
30+
return nil
31+
}
32+
33+
// Implements gnuflag.Value String.
34+
func (v *StringsValue) String() string {
35+
return strings.Join(*v, ",")
36+
}
37+
38+
// AppendStringsValue implements gnuflag.Value for a value that can be set
39+
// multiple times, and it appends each value to the slice.
40+
type AppendStringsValue []string
41+
42+
var _ gnuflag.Value = (*AppendStringsValue)(nil)
43+
44+
// NewAppendStringsValue is used to create the type passed into the gnuflag.FlagSet Var function.
45+
// f.Var(cmd.NewAppendStringsValue(&someMember), "name", "help")
46+
func NewAppendStringsValue(target *[]string) *AppendStringsValue {
47+
return (*AppendStringsValue)(target)
48+
}
49+
50+
// Implements gnuflag.Value Set.
51+
func (v *AppendStringsValue) Set(s string) error {
52+
*v = append(*v, s)
53+
return nil
54+
}
55+
56+
// Implements gnuflag.Value String.
57+
func (v *AppendStringsValue) String() string {
58+
return strings.Join(*v, ",")
59+
}

internal/cmd/args_test.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright 2013 Canonical Ltd.
2+
// Licensed under the LGPLv3, see LICENSE file for details.
3+
4+
package cmd_test
5+
6+
import (
7+
"fmt"
8+
"io/ioutil"
9+
10+
"github.com/juju/gnuflag"
11+
"github.com/juju/testing"
12+
gc "gopkg.in/check.v1"
13+
14+
"github.com/juju/juju/internal/cmd"
15+
)
16+
17+
type ArgsSuite struct {
18+
testing.LoggingSuite
19+
}
20+
21+
var _ = gc.Suite(&ArgsSuite{})
22+
23+
func (*ArgsSuite) TestFlagsUsage(c *gc.C) {
24+
for i, test := range []struct {
25+
message string
26+
defaultValue []string
27+
args []string
28+
expectedValue []string
29+
}{{
30+
message: "nil default and no arg",
31+
}, {
32+
message: "default value and not set by args",
33+
defaultValue: []string{"foo", "bar"},
34+
expectedValue: []string{"foo", "bar"},
35+
}, {
36+
message: "no value set by args",
37+
args: []string{"--value", "foo,bar"},
38+
expectedValue: []string{"foo", "bar"},
39+
}, {
40+
message: "default value and set by args",
41+
defaultValue: []string{"omg"},
42+
args: []string{"--value", "foo,bar"},
43+
expectedValue: []string{"foo", "bar"},
44+
}} {
45+
c.Log(fmt.Sprintf("%v: %s", i, test.message))
46+
f := gnuflag.NewFlagSet("test", gnuflag.ContinueOnError)
47+
f.SetOutput(ioutil.Discard)
48+
var value []string
49+
f.Var(cmd.NewStringsValue(test.defaultValue, &value), "value", "help")
50+
err := f.Parse(false, test.args)
51+
c.Check(err, gc.IsNil)
52+
c.Check(value, gc.DeepEquals, test.expectedValue)
53+
}
54+
}
55+
56+
func (*ArgsSuite) TestNewStringsValue(c *gc.C) {
57+
for i, test := range []struct {
58+
message string
59+
defaultValue []string
60+
}{{
61+
message: "null default",
62+
}, {
63+
message: "empty default",
64+
defaultValue: []string{},
65+
}, {
66+
message: "single value",
67+
defaultValue: []string{"foo"},
68+
}, {
69+
message: "multiple values",
70+
defaultValue: []string{"foo", "bar", "baz"},
71+
}} {
72+
c.Log(fmt.Sprintf("%v: %s", i, test.message))
73+
var underlyingValue []string
74+
_ = cmd.NewStringsValue(test.defaultValue, &underlyingValue)
75+
c.Assert(underlyingValue, gc.DeepEquals, test.defaultValue)
76+
}
77+
}
78+
79+
func (*ArgsSuite) TestSet(c *gc.C) {
80+
for i, test := range []struct {
81+
message string
82+
arg string
83+
expected []string
84+
}{{
85+
message: "empty",
86+
expected: []string{""},
87+
}, {
88+
message: "just whitespace",
89+
arg: " ",
90+
expected: []string{" "},
91+
}, {
92+
message: "whitespace and comma",
93+
arg: " , ",
94+
expected: []string{" ", " "},
95+
}, {
96+
message: "single value",
97+
arg: "foo",
98+
expected: []string{"foo"},
99+
}, {
100+
message: "single value with comma",
101+
arg: "foo,",
102+
expected: []string{"foo", ""},
103+
}, {
104+
message: "single value with whitespace",
105+
arg: " foo ",
106+
expected: []string{" foo "},
107+
}, {
108+
message: "multiple values",
109+
arg: "foo,bar,baz",
110+
expected: []string{"foo", "bar", "baz"},
111+
}, {
112+
message: "multiple values with spaces",
113+
arg: "foo, bar, baz",
114+
expected: []string{"foo", " bar", " baz"},
115+
}} {
116+
c.Log(fmt.Sprintf("%v: %s", i, test.message))
117+
var result []string
118+
value := cmd.NewStringsValue(nil, &result)
119+
error := value.Set(test.arg)
120+
c.Check(error, gc.IsNil)
121+
c.Check(result, gc.DeepEquals, test.expected)
122+
}
123+
}
124+
125+
func (*ArgsSuite) TestString(c *gc.C) {
126+
for i, test := range []struct {
127+
message string
128+
target []string
129+
expected string
130+
}{{
131+
message: "null",
132+
expected: "",
133+
}, {
134+
message: "empty",
135+
target: []string{},
136+
expected: "",
137+
}, {
138+
message: "single value",
139+
target: []string{"foo"},
140+
expected: "foo",
141+
}, {
142+
message: "multiple values",
143+
target: []string{"foo", "bar", "baz"},
144+
expected: "foo,bar,baz",
145+
}} {
146+
c.Log(fmt.Sprintf("%v: %s", i, test.message))
147+
var temp []string
148+
value := cmd.NewStringsValue(test.target, &temp)
149+
c.Assert(value.String(), gc.Equals, test.expected)
150+
}
151+
}
152+
153+
func (*ArgsSuite) TestAppendStringsUsage(c *gc.C) {
154+
for i, test := range []struct {
155+
message string
156+
args []string
157+
expectedValue []string
158+
}{{
159+
message: "no args",
160+
}, {
161+
message: "value set by args",
162+
args: []string{"--value", "foo", "--value=bar"},
163+
expectedValue: []string{"foo", "bar"},
164+
}} {
165+
c.Log(fmt.Sprintf("%v: %s", i, test.message))
166+
f := gnuflag.NewFlagSet("test", gnuflag.ContinueOnError)
167+
f.SetOutput(ioutil.Discard)
168+
var value []string
169+
f.Var(cmd.NewAppendStringsValue(&value), "value", "help")
170+
err := f.Parse(false, test.args)
171+
c.Check(err, gc.IsNil)
172+
c.Check(value, gc.DeepEquals, test.expectedValue)
173+
}
174+
}

0 commit comments

Comments
 (0)