Skip to content

Commit 0292358

Browse files
rohanKanojiapraveenkumar
authored andcommitted
feat : generate manpages for cli during crc setup (#4181)
+ crc should generate manpages for all sub commands and place them in `~/.local/share/man/man1` directory + These man pages would only be generated once, crc would skip generation if it detects man pages already present in target directory + These generated man pages would be cleaned up during `crc cleanup` Signed-off-by: Rohan Kumar <[email protected]>
1 parent 6d72284 commit 0292358

36 files changed

+10028
-9
lines changed

cmd/crc/cmd/root.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/crc-org/crc/v2/pkg/crc/manpages"
12+
13+
"github.com/spf13/cobra/doc"
14+
1115
cmdBundle "github.com/crc-org/crc/v2/cmd/crc/cmd/bundle"
1216
cmdConfig "github.com/crc-org/crc/v2/cmd/crc/cmd/config"
1317
crcConfig "github.com/crc-org/crc/v2/pkg/crc/config"
@@ -64,7 +68,6 @@ func init() {
6468
logging.Warn(err.Error())
6569
logging.Warn("Error during segment client initialization, telemetry will be unavailable in this session")
6670
}
67-
6871
// subcommands
6972
rootCmd.AddCommand(cmdConfig.GetConfigCmd(config))
7073
rootCmd.AddCommand(cmdBundle.GetBundleCmd(config))
@@ -103,6 +106,10 @@ const (
103106
func Execute() {
104107
attachMiddleware([]string{}, rootCmd)
105108

109+
if err := manpages.GenerateManPages(crcManPageGenerator, constants.CrcManPageDir); err != nil {
110+
logging.Warn("Error generating man-pages")
111+
}
112+
106113
if err := rootCmd.ExecuteContext(telemetry.NewContext(context.Background())); err != nil {
107114
runPostrun()
108115
_, _ = fmt.Fprintln(os.Stderr, err.Error())
@@ -186,3 +193,7 @@ func attachMiddleware(names []string, cmd *cobra.Command) {
186193
cmd.RunE = executeWithLogging(fullCmd, src)
187194
}
188195
}
196+
197+
func crcManPageGenerator(targetDir string) error {
198+
return doc.GenManTree(rootCmd, manpages.CrcManPageHeader, targetDir)
199+
}

cmd/crc/cmd/root_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestCrcManPageGenerator_WhenInvoked_GeneratesManPagesForAllCrcSubCommands(t *testing.T) {
11+
// Given
12+
dir := t.TempDir()
13+
14+
// When
15+
err := crcManPageGenerator(dir)
16+
17+
// Then
18+
assert.NoError(t, err)
19+
files, readErr := os.ReadDir(dir)
20+
assert.NoError(t, readErr)
21+
var manPagesFiles []string
22+
for _, manPage := range files {
23+
manPagesFiles = append(manPagesFiles, manPage.Name())
24+
}
25+
assert.ElementsMatch(t, []string{
26+
"crc-bundle-generate.1",
27+
"crc-bundle.1",
28+
"crc-cleanup.1",
29+
"crc-config-get.1",
30+
"crc-config-set.1",
31+
"crc-config-unset.1",
32+
"crc-config-view.1",
33+
"crc-config.1",
34+
"crc-console.1",
35+
"crc-delete.1",
36+
"crc-ip.1",
37+
"crc-oc-env.1",
38+
"crc-podman-env.1",
39+
"crc-setup.1",
40+
"crc-start.1",
41+
"crc-status.1",
42+
"crc-stop.1",
43+
"crc-version.1",
44+
"crc.1",
45+
}, manPagesFiles)
46+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ require (
8181
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
8282
github.com/containers/ocicrypt v1.2.0 // indirect
8383
github.com/containers/storage v1.55.1 // indirect
84+
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
8485
github.com/creack/pty v1.1.18 // indirect
8586
github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect
8687
github.com/cucumber/messages/go/v21 v21.0.1 // indirect
@@ -165,6 +166,7 @@ require (
165166
github.com/qdm12/dns/v2 v2.0.0-rc6 // indirect
166167
github.com/qdm12/gosettings v0.4.1 // indirect
167168
github.com/rivo/uniseg v0.4.7 // indirect
169+
github.com/russross/blackfriday/v2 v2.1.0 // indirect
168170
github.com/sagikazarmark/locafero v0.4.0 // indirect
169171
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
170172
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ github.com/containers/storage v1.55.1/go.mod h1:28cB81IDk+y7ok60Of6u52RbCeBRucbF
5959
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
6060
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
6161
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
62+
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
6263
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
6364
github.com/crc-org/admin-helper v0.5.4 h1:Wq6wp6514MipPHHYdoL2VUyhUL9qh26wR1I3qPaVxf4=
6465
github.com/crc-org/admin-helper v0.5.4/go.mod h1:sFkqIILzKrt62CH1bJn5PSBFSdhaCyMdz6BG37N3TBE=
@@ -350,6 +351,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
350351
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
351352
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
352353
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
354+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
353355
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
354356
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
355357
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=

pkg/crc/constants/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func GetDefaultBundle(preset crcpreset.Preset) string {
104104

105105
var (
106106
CrcBaseDir = filepath.Join(GetHomeDir(), ".crc")
107+
CrcManPageDir = filepath.Join(GetHomeDir(), ".local", "share", "man")
107108
CrcBinDir = filepath.Join(CrcBaseDir, "bin")
108109
CrcOcBinDir = filepath.Join(CrcBinDir, "oc")
109110
CrcPodmanBinDir = filepath.Join(CrcBinDir, "podman")

pkg/crc/manpages/manpages_unix.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package manpages
5+
6+
import (
7+
"compress/gzip"
8+
"fmt"
9+
"io"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
14+
"github.com/spf13/cobra/doc"
15+
16+
"github.com/crc-org/crc/v2/pkg/crc/logging"
17+
)
18+
19+
var (
20+
rootCrcManPage = "crc.1.gz"
21+
osEnvGetter = os.Getenv
22+
osEnvSetter = os.Setenv
23+
ManPathEnvironmentVariable = "MANPATH"
24+
CrcManPageHeader = &doc.GenManHeader{
25+
Title: "CRC",
26+
Section: "1",
27+
}
28+
)
29+
30+
// GenerateManPages generates manual pages for user commands and places them
31+
// in the specified target directory. It performs the following steps:
32+
//
33+
// 1. Checks if the man pages should be generated based on the target folder.
34+
// 2. Creates the necessary directory structure if it does not exist.
35+
// 3. Generates man pages in a temporary directory.
36+
// 4. Compresses the generated man pages and moves them to the target folder.
37+
// 5. Updates the MANPATH environment variable to include the target directory.
38+
// 6. Cleans up the temporary directory.
39+
//
40+
// manPageGenerator: Function that generates man pages in the specified directory.
41+
// targetDir: Directory where the generated man pages should be placed.
42+
//
43+
// Returns an error if any step in the process fails.
44+
func GenerateManPages(manPageGenerator func(targetDir string) error, targetDir string) error {
45+
manUserCommandTargetFolder := filepath.Join(targetDir, "man1")
46+
if !manPagesAlreadyGenerated(manUserCommandTargetFolder) {
47+
if _, err := os.Stat(manUserCommandTargetFolder); os.IsNotExist(err) {
48+
err = os.MkdirAll(manUserCommandTargetFolder, 0755)
49+
if err != nil {
50+
logging.Errorf("error in creating dir for man pages: %s", err.Error())
51+
}
52+
}
53+
temporaryManPagesDir, err := generateManPagesInTemporaryDirectory(manPageGenerator)
54+
if err != nil {
55+
return err
56+
}
57+
err = compressManPages(temporaryManPagesDir, manUserCommandTargetFolder)
58+
if err != nil {
59+
return fmt.Errorf("error in compressing man pages: %s", err.Error())
60+
}
61+
err = appendToManPathEnvironmentVariable(targetDir)
62+
if err != nil {
63+
return fmt.Errorf("error updating MANPATH environment variable: %s", err.Error())
64+
}
65+
err = os.RemoveAll(temporaryManPagesDir)
66+
if err != nil {
67+
return fmt.Errorf("error removing temporary man pages directory: %s", err.Error())
68+
}
69+
}
70+
return nil
71+
}
72+
73+
func appendToManPathEnvironmentVariable(folder string) error {
74+
manPath := osEnvGetter(ManPathEnvironmentVariable)
75+
if !manPathAlreadyContains(folder, manPath) {
76+
if manPath == "" {
77+
manPath = folder
78+
} else {
79+
manPath = fmt.Sprintf("%s%c%s", manPath, os.PathListSeparator, folder)
80+
}
81+
err := osEnvSetter(ManPathEnvironmentVariable, manPath)
82+
if err != nil {
83+
return err
84+
}
85+
}
86+
87+
return nil
88+
}
89+
90+
func manPathAlreadyContains(manPathEnvVarValue string, folder string) bool {
91+
manDirs := strings.Split(manPathEnvVarValue, string(os.PathListSeparator))
92+
for _, manDir := range manDirs {
93+
if manDir == folder {
94+
return true
95+
}
96+
}
97+
return false
98+
}
99+
100+
func removeFromManPathEnvironmentVariable(manPathEnvVarValue string, folder string) error {
101+
manDirs := strings.Split(manPathEnvVarValue, string(os.PathListSeparator))
102+
var updatedManPathEnvVarValues []string
103+
for _, manDir := range manDirs {
104+
if manDir != folder {
105+
updatedManPathEnvVarValues = append(updatedManPathEnvVarValues, manDir)
106+
}
107+
}
108+
return osEnvSetter(ManPathEnvironmentVariable, strings.Join(updatedManPathEnvVarValues, string(os.PathListSeparator)))
109+
}
110+
111+
func generateManPagesInTemporaryDirectory(manPageGenerator func(targetDir string) error) (string, error) {
112+
tempDir, err := os.MkdirTemp("", "crc-manpages")
113+
if err != nil {
114+
return "", err
115+
}
116+
manPagesGenerationErr := manPageGenerator(tempDir)
117+
if manPagesGenerationErr != nil {
118+
return "", manPagesGenerationErr
119+
}
120+
logging.Debugf("Successfully generated manpages in %s", tempDir)
121+
return tempDir, nil
122+
}
123+
124+
func compressManPages(manPagesSourceFolder string, manPagesTargetFolder string) error {
125+
return filepath.Walk(manPagesSourceFolder, func(path string, info os.FileInfo, err error) error {
126+
if err != nil {
127+
return err
128+
}
129+
130+
if info.IsDir() {
131+
return nil
132+
}
133+
134+
srcFile, err := os.Open(path)
135+
if err != nil {
136+
return err
137+
}
138+
defer srcFile.Close()
139+
140+
compressedFilePath := filepath.Join(manPagesTargetFolder, info.Name()+".gz")
141+
compressedFile, err := os.Create(compressedFilePath)
142+
if err != nil {
143+
return err
144+
}
145+
defer compressedFile.Close()
146+
147+
gzipWriter := gzip.NewWriter(compressedFile)
148+
defer gzipWriter.Close()
149+
150+
_, err = io.Copy(gzipWriter, srcFile)
151+
if err != nil {
152+
return err
153+
}
154+
return nil
155+
})
156+
}
157+
158+
func manPagesAlreadyGenerated(manPagesTargetFolder string) bool {
159+
rootCrcManPageFilePath := filepath.Join(manPagesTargetFolder, rootCrcManPage)
160+
if _, err := os.Stat(rootCrcManPageFilePath); os.IsNotExist(err) {
161+
return false
162+
}
163+
return true
164+
}
165+
166+
func RemoveCrcManPages(manPageDir string) error {
167+
manUserCommandTargetFolder := filepath.Join(manPageDir, "man1")
168+
err := filepath.Walk(manUserCommandTargetFolder, func(path string, info os.FileInfo, err error) error {
169+
if err != nil {
170+
return err
171+
}
172+
if !info.IsDir() && filepath.Base(path)[:len("crc")] == "crc" {
173+
err = os.Remove(path)
174+
if err != nil {
175+
return err
176+
}
177+
}
178+
return nil
179+
})
180+
if err != nil {
181+
return err
182+
}
183+
return removeFromManPathEnvironmentVariable(osEnvGetter(ManPathEnvironmentVariable), manPageDir)
184+
}

0 commit comments

Comments
 (0)