github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/internal/util/man/man.go (about) 1 // Copyright 2019 The kpt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package man contains libraries for rendering package documentation as man 16 // pages. 17 package man 18 19 import ( 20 "io" 21 "os" 22 "os/exec" 23 "path/filepath" 24 "strings" 25 26 "github.com/GoogleContainerTools/kpt/internal/pkg" 27 v1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 28 "github.com/cpuguy83/go-md2man/v2/md2man" 29 "sigs.k8s.io/kustomize/kyaml/errors" 30 "sigs.k8s.io/kustomize/kyaml/filesys" 31 ) 32 33 // Command displays local package documentation as man pages. 34 // The location of the man page is read from the Kptfile packageMetadata. 35 // If no man page is specified, and error is returned. 36 // 37 // Man page format should be the format supported by the 38 // github.com/cpuguy83/go-md2man/md2man library 39 type Command struct { 40 // Path is the path to a local package 41 Path string 42 43 // ManExecCommand is the exec command to run for displaying the man pages. 44 ManExecCommand string 45 46 // StdOut is the StdOut value 47 StdOut io.Writer 48 } 49 50 const ManFilename = "README.md" 51 52 // Run runs the command. 53 func (m Command) Run() error { 54 _, err := exec.LookPath(m.GetExecCmd()) 55 if err != nil { 56 return errors.Errorf(m.GetExecCmd() + " not installed") 57 } 58 59 // lookup the path to the man page 60 k, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, m.Path) 61 if err != nil { 62 return err 63 } 64 if k.Info == nil { 65 k.Info = &v1.PackageInfo{} 66 } 67 68 if k.Info.Man == "" { 69 _, err := os.Stat(filepath.Join(m.Path, ManFilename)) 70 if err != nil { 71 return errors.Errorf("no manual entry for %q", m.Path) 72 } 73 k.Info.Man = ManFilename 74 } 75 76 // Convert from separator to slash and back. 77 // This ensures all separators are compatible with the local OS. 78 p := filepath.FromSlash(filepath.ToSlash(k.Info.Man)) 79 80 // verify the man page is in the package 81 apPkg, err := filepath.Abs(m.Path) 82 if err != nil { 83 return err 84 } 85 apMan, err := filepath.Abs(filepath.Join(m.Path, p)) 86 if err != nil { 87 return err 88 } 89 if !strings.HasPrefix(apMan, apPkg) { 90 return errors.Errorf("invalid manual location for %q", m.Path) 91 } 92 93 // write the formatted manual to a tmp file so it can be displayed 94 f, err := os.CreateTemp("", "kpt-man") 95 if err != nil { 96 return err 97 } 98 defer os.Remove(f.Name()) 99 b, err := os.ReadFile(apMan) 100 if err != nil { 101 return err 102 } 103 err = os.WriteFile(f.Name(), md2man.Render(b), 0600) 104 if err != nil { 105 return err 106 } 107 108 // setup the man command 109 manCmd := exec.Command(m.GetExecCmd(), f.Name()) 110 manCmd.Stderr = os.Stderr 111 manCmd.Stdin = os.Stdin 112 manCmd.Stdout = m.GetStdOut() 113 manCmd.Env = os.Environ() 114 return manCmd.Run() 115 } 116 117 // GetExecCmd returns the command that will be executed to display the 118 // man pages. 119 func (m Command) GetExecCmd() string { 120 if m.ManExecCommand == "" { 121 return "man" 122 } 123 return m.ManExecCommand 124 } 125 126 // GetStdOut returns the io.Writer that will be used as the man stdout 127 func (m Command) GetStdOut() io.Writer { 128 if m.StdOut == nil { 129 return os.Stdout 130 } 131 return m.StdOut 132 }