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  }