github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/util/outputpager/page_output.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     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  // This file incorporates work covered by the following copyright and
    16  // permission notice:
    17  //
    18  // Copyright 2016 Attic Labs, Inc. All rights reserved.
    19  // Licensed under the Apache License, version 2.0:
    20  // http://www.apache.org/licenses/LICENSE-2.0
    21  
    22  package outputpager
    23  
    24  import (
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"os/exec"
    29  	"os/signal"
    30  	"sync"
    31  	"syscall"
    32  
    33  	flag "github.com/juju/gnuflag"
    34  	goisatty "github.com/mattn/go-isatty"
    35  
    36  	"github.com/dolthub/dolt/go/store/d"
    37  )
    38  
    39  var (
    40  	noPager bool
    41  	testing = false
    42  )
    43  
    44  type Pager struct {
    45  	Writer        io.Writer
    46  	stdin, stdout *os.File
    47  	mtx           *sync.Mutex
    48  	doneCh        chan struct{}
    49  }
    50  
    51  func Start() *Pager {
    52  	// `testing` is set to true only to test this function because when testing this function, stdout is not Terminal.
    53  	// otherwise, it must be always false.
    54  	if !testing {
    55  		if noPager || !IsStdoutTty() {
    56  			return &Pager{os.Stdout, nil, nil, nil, nil}
    57  		}
    58  	}
    59  
    60  	var lessPath string
    61  	var err error
    62  	var cmd *exec.Cmd
    63  
    64  	lessPath, err = exec.LookPath("less")
    65  	if err != nil {
    66  		lessPath, err = exec.LookPath("more")
    67  		d.Chk.NoError(err)
    68  		cmd = exec.Command(lessPath)
    69  	} else {
    70  		d.Chk.NoError(err)
    71  		// -F ... Quit if entire file fits on first screen.
    72  		// -S ... Chop (truncate) long lines rather than wrapping.
    73  		// -R ... Output "raw" control characters.
    74  		// -X ... Don't use termcap init/deinit strings.
    75  		cmd = exec.Command(lessPath, "-FSRX")
    76  	}
    77  
    78  	stdin, stdout, err := os.Pipe()
    79  	d.Chk.NoError(err)
    80  	cmd.Stdout = os.Stdout
    81  	cmd.Stderr = os.Stderr
    82  	cmd.Stdin = stdin
    83  	cmd.Start()
    84  
    85  	p := &Pager{stdout, stdin, stdout, &sync.Mutex{}, make(chan struct{})}
    86  
    87  	interruptChannel := make(chan os.Signal, 1)
    88  	signal.Notify(interruptChannel, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
    89  
    90  	go func() {
    91  		for {
    92  			select {
    93  			case _, ok := <-interruptChannel:
    94  				if ok {
    95  					p.closePipe()
    96  					p.doneCh <- struct{}{}
    97  				}
    98  			default:
    99  			}
   100  		}
   101  	}()
   102  
   103  	go func() {
   104  		err := cmd.Wait()
   105  		if err != nil {
   106  			fmt.Printf("error occurred during exit: %s ", err)
   107  		}
   108  		p.closePipe()
   109  		p.doneCh <- struct{}{}
   110  	}()
   111  	return p
   112  }
   113  
   114  func (p *Pager) Stop() {
   115  	if p.Writer != os.Stdout {
   116  		p.closePipe()
   117  		// Wait until less has fully exited, otherwise it might not have printed the terminal restore characters.
   118  		<-p.doneCh
   119  	}
   120  }
   121  
   122  func (p *Pager) closePipe() {
   123  	p.mtx.Lock()
   124  	defer p.mtx.Unlock()
   125  	if p.stdin != nil {
   126  		// Closing the pipe will cause any outstanding writes to stdout fail, and fail from now on.
   127  		p.stdin.Close()
   128  		p.stdout.Close()
   129  		p.stdin, p.stdout = nil, nil
   130  	}
   131  }
   132  
   133  func RegisterOutputpagerFlags(flags *flag.FlagSet) {
   134  	flags.BoolVar(&noPager, "no-pager", false, "suppress paging functionality")
   135  }
   136  
   137  func IsStdoutTty() bool {
   138  	return goisatty.IsTerminal(os.Stdout.Fd())
   139  }
   140  
   141  func SetTestingArg(s bool) {
   142  	testing = s
   143  }