Skip to content

Commit 831a5af

Browse files
committed
Add Navigation Menu to compose up
Signed-off-by: Joana Hrotko <[email protected]>
1 parent 3950460 commit 831a5af

File tree

19 files changed

+589
-47
lines changed

19 files changed

+589
-47
lines changed

Diff for: Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ RUN --mount=type=bind,target=. \
101101
FROM build-base AS test
102102
ARG CGO_ENABLED=0
103103
ARG BUILD_TAGS
104+
ENV COMPOSE_MENU=FALSE
104105
RUN --mount=type=bind,target=. \
105106
--mount=type=cache,target=/root/.cache \
106107
--mount=type=cache,target=/go/pkg/mod \

Diff for: Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
PKG := github.com/docker/compose/v2
16+
export COMPOSE_MENU = FALSE
1617
VERSION ?= $(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags)
1718

1819
GO_LDFLAGS ?= -w -X ${PKG}/internal.Version=${VERSION}

Diff for: cmd/compose/compose.go

+14
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const (
6565
ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
6666
// ComposeEnvFiles defines the env files to use if --env-file isn't used
6767
ComposeEnvFiles = "COMPOSE_ENV_FILES"
68+
// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
69+
ComposeMenu = "COMPOSE_MENU"
6870
)
6971

7072
type Backend interface {
@@ -620,3 +622,15 @@ var printerModes = []string{
620622
ui.ModePlain,
621623
ui.ModeQuiet,
622624
}
625+
626+
func SetUnchangedOption(name string, experimentalFlag bool) bool {
627+
var value bool
628+
// If the var is defined we use that value first
629+
if envVar, ok := os.LookupEnv(name); ok {
630+
value = utils.StringToBool(envVar)
631+
} else {
632+
// if not, we try to get it from experimental feature flag
633+
value = experimentalFlag
634+
}
635+
return value
636+
}

Diff for: cmd/compose/up.go

+33-24
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,22 @@ type composeOptions struct {
4242

4343
type upOptions struct {
4444
*composeOptions
45-
Detach bool
46-
noStart bool
47-
noDeps bool
48-
cascadeStop bool
49-
exitCodeFrom string
50-
noColor bool
51-
noPrefix bool
52-
attachDependencies bool
53-
attach []string
54-
noAttach []string
55-
timestamp bool
56-
wait bool
57-
waitTimeout int
58-
watch bool
45+
Detach bool
46+
noStart bool
47+
noDeps bool
48+
cascadeStop bool
49+
exitCodeFrom string
50+
noColor bool
51+
noPrefix bool
52+
attachDependencies bool
53+
attach []string
54+
noAttach []string
55+
timestamp bool
56+
wait bool
57+
waitTimeout int
58+
watch bool
59+
navigationMenu bool
60+
navigationMenuChanged bool
5961
}
6062

6163
func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) {
@@ -87,6 +89,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
8789
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
8890
create.pullChanged = cmd.Flags().Changed("pull")
8991
create.timeChanged = cmd.Flags().Changed("timeout")
92+
up.navigationMenuChanged = cmd.Flags().Changed("menu")
9093
return validateFlags(&up, &create)
9194
}),
9295
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
@@ -128,6 +131,8 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
128131
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
129132
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy")
130133
flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
134+
flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached (Experimental). Incompatible with --detach.")
135+
flags.MarkHidden("menu") //nolint:errcheck
131136

132137
return upCmd
133138
}
@@ -161,7 +166,7 @@ func runUp(
161166
ctx context.Context,
162167
dockerCli command.Cli,
163168
backend api.Service,
164-
_ *experimental.State,
169+
experimentals *experimental.State,
165170
createOptions createOptions,
166171
upOptions upOptions,
167172
buildOptions buildOptions,
@@ -181,6 +186,9 @@ func runUp(
181186
if err != nil {
182187
return err
183188
}
189+
if !upOptions.navigationMenuChanged {
190+
upOptions.navigationMenu = SetUnchangedOption(ComposeMenu, experimentals.NavBar())
191+
}
184192

185193
var build *api.BuildOptions
186194
if !createOptions.noBuild {
@@ -253,15 +261,16 @@ func runUp(
253261
return backend.Up(ctx, project, api.UpOptions{
254262
Create: create,
255263
Start: api.StartOptions{
256-
Project: project,
257-
Attach: consumer,
258-
AttachTo: attach,
259-
ExitCodeFrom: upOptions.exitCodeFrom,
260-
CascadeStop: upOptions.cascadeStop,
261-
Wait: upOptions.wait,
262-
WaitTimeout: timeout,
263-
Watch: upOptions.watch,
264-
Services: services,
264+
Project: project,
265+
Attach: consumer,
266+
AttachTo: attach,
267+
ExitCodeFrom: upOptions.exitCodeFrom,
268+
CascadeStop: upOptions.cascadeStop,
269+
Wait: upOptions.wait,
270+
WaitTimeout: timeout,
271+
Watch: upOptions.watch,
272+
Services: services,
273+
NavigationMenu: upOptions.navigationMenu,
265274
},
266275
})
267276
}

Diff for: cmd/formatter/ansi.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright 2024 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package formatter
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/acarl005/stripansi"
23+
)
24+
25+
func ansi(code string) string {
26+
return fmt.Sprintf("\033%s", code)
27+
}
28+
func SaveCursor() {
29+
fmt.Print(ansi("7"))
30+
}
31+
func RestoreCursor() {
32+
fmt.Print(ansi("8"))
33+
}
34+
func HideCursor() {
35+
fmt.Print(ansi("[?25l"))
36+
}
37+
func ShowCursor() {
38+
fmt.Print(ansi("[?25h"))
39+
}
40+
func MoveCursor(y, x int) {
41+
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
42+
}
43+
func MoveCursorX(pos int) {
44+
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
45+
}
46+
func ClearLine() {
47+
// Does not move cursor from its current position
48+
fmt.Print(ansi("[2K"))
49+
}
50+
func MoveCursorUp(lines int) {
51+
// Does not add new lines
52+
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
53+
}
54+
func MoveCursorDown(lines int) {
55+
// Does not add new lines
56+
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
57+
}
58+
func NewLine() {
59+
// Like \n
60+
fmt.Print("\012")
61+
}
62+
func lenAnsi(s string) int {
63+
// len has into consideration ansi codes, if we want
64+
// the len of the actual len(string) we need to strip
65+
// all ansi codes
66+
return len(stripansi.Strip(s))
67+
}

Diff for: cmd/formatter/colors.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ var names = []string{
3535
"white",
3636
}
3737

38+
const (
39+
BOLD = "1"
40+
FAINT = "2"
41+
ITALIC = "3"
42+
UNDERLINE = "4"
43+
)
44+
45+
const (
46+
RESET = "0"
47+
CYAN = "36"
48+
)
49+
3850
const (
3951
// Never use ANSI codes
4052
Never = "never"
@@ -72,12 +84,17 @@ var monochrome = func(s string) string {
7284
return s
7385
}
7486

75-
func ansiColor(code, s string) string {
76-
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
87+
func ansiColor(code, s string, formatOpts ...string) string {
88+
return fmt.Sprintf("%s%s%s", ansiColorCode(code, formatOpts...), s, ansiColorCode("0"))
7789
}
7890

79-
func ansi(code string) string {
80-
return fmt.Sprintf("\033[%sm", code)
91+
// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
92+
func ansiColorCode(code string, formatOpts ...string) string {
93+
res := "\033["
94+
for _, c := range formatOpts {
95+
res = fmt.Sprintf("%s%s;", res, c)
96+
}
97+
return fmt.Sprintf("%s%sm", res, code)
8198
}
8299

83100
func makeColorFunc(code string) colorFunc {

Diff for: cmd/formatter/logs.go

+17-7
Original file line numberDiff line numberDiff line change
@@ -102,19 +102,29 @@ func (l *logConsumer) Err(container, message string) {
102102
l.write(l.stderr, container, message)
103103
}
104104

105+
var navColor = makeColorFunc("90")
106+
105107
func (l *logConsumer) write(w io.Writer, container, message string) {
106108
if l.ctx.Err() != nil {
107109
return
108110
}
109-
p := l.getPresenter(container)
110-
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
111-
for _, line := range strings.Split(message, "\n") {
112-
if l.timestamp {
113-
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
114-
} else {
115-
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
111+
printFn := func() {
112+
p := l.getPresenter(container)
113+
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
114+
for _, line := range strings.Split(message, "\n") {
115+
ClearLine()
116+
if l.timestamp {
117+
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
118+
} else {
119+
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
120+
}
116121
}
117122
}
123+
if KeyboardManager != nil {
124+
KeyboardManager.PrintKeyboardInfo(printFn)
125+
} else {
126+
printFn()
127+
}
118128
}
119129

120130
func (l *logConsumer) Status(container, msg string) {

0 commit comments

Comments
 (0)