More work on pager kitten
This commit is contained in:
parent
1aa4d7d24b
commit
b3e74de390
3 changed files with 96 additions and 24 deletions
|
|
@ -9,23 +9,41 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func read_input(input_file *os.File, input_file_name string, input_channel chan<- input_line_struct) {
|
||||
func wait_for_file_to_grow(file_name string, limit int64) (err error) {
|
||||
// TODO: Use the fsnotify package to avoid this poll
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
s, err := os.Stat(file_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Size() > limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func read_input(input_file *os.File, input_file_name string, input_channel chan<- input_line_struct, follow bool, count_carriage_returns bool) {
|
||||
const buf_capacity = 8192
|
||||
var buf_array [buf_capacity]byte
|
||||
output_buf := strings.Builder{}
|
||||
output_buf.Grow(buf_capacity)
|
||||
var err error
|
||||
var n int
|
||||
var total_read int64
|
||||
var num_carriage_returns int
|
||||
|
||||
defer func() {
|
||||
_ = input_file.Close()
|
||||
last := input_line_struct{line: output_buf.String(), err: err}
|
||||
last := input_line_struct{line: output_buf.String(), err: err, num_carriage_returns: num_carriage_returns}
|
||||
if errors.Is(err, io.EOF) {
|
||||
last.err = nil
|
||||
}
|
||||
|
|
@ -35,30 +53,73 @@ func read_input(input_file *os.File, input_file_name string, input_channel chan<
|
|||
close(input_channel)
|
||||
}()
|
||||
|
||||
process_chunk := func(chunk []byte) {
|
||||
for len(chunk) > 0 {
|
||||
idx := bytes.IndexByte(chunk, '\n')
|
||||
switch idx {
|
||||
case -1:
|
||||
_, _ = output_buf.Write(chunk)
|
||||
chunk = nil
|
||||
default:
|
||||
_, _ = output_buf.Write(chunk[idx:])
|
||||
chunk = chunk[idx+1:]
|
||||
input_channel <- input_line_struct{line: output_buf.String()}
|
||||
output_buf.Reset()
|
||||
output_buf.Grow(buf_capacity)
|
||||
var process_chunk func([]byte)
|
||||
|
||||
if count_carriage_returns {
|
||||
process_chunk = func(chunk []byte) {
|
||||
for _, ch := range chunk {
|
||||
switch ch {
|
||||
case '\r':
|
||||
num_carriage_returns += 1
|
||||
default:
|
||||
_ = output_buf.WriteByte(ch)
|
||||
case '\n':
|
||||
input_channel <- input_line_struct{line: output_buf.String(), num_carriage_returns: num_carriage_returns, is_a_complete_line: true}
|
||||
num_carriage_returns = 0
|
||||
output_buf.Reset()
|
||||
output_buf.Grow(buf_capacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
process_chunk = func(chunk []byte) {
|
||||
for len(chunk) > 0 {
|
||||
idx := bytes.IndexByte(chunk, '\n')
|
||||
switch idx {
|
||||
case -1:
|
||||
_, _ = output_buf.Write(chunk)
|
||||
chunk = nil
|
||||
default:
|
||||
_, _ = output_buf.Write(chunk[idx:])
|
||||
chunk = chunk[idx+1:]
|
||||
input_channel <- input_line_struct{line: output_buf.String(), is_a_complete_line: true}
|
||||
output_buf.Reset()
|
||||
output_buf.Grow(buf_capacity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
n, err = input_file.Read(buf_array[:])
|
||||
if n > 0 {
|
||||
process_chunk(buf_array[:n])
|
||||
for {
|
||||
for err != nil {
|
||||
n, err = input_file.Read(buf_array[:])
|
||||
if n > 0 {
|
||||
total_read += int64(n)
|
||||
process_chunk(buf_array[:n])
|
||||
}
|
||||
if err == unix.EAGAIN || err == unix.EINTR {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
if err == unix.EAGAIN || err == unix.EINTR {
|
||||
err = nil
|
||||
if !follow {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
input_file.Close()
|
||||
if err = wait_for_file_to_grow(input_file_name, total_read); err != nil {
|
||||
break
|
||||
}
|
||||
if input_file, err = os.Open(input_file_name); err != nil {
|
||||
break
|
||||
}
|
||||
var off int64
|
||||
if off, err = input_file.Seek(total_read, io.SeekStart); err != nil {
|
||||
break
|
||||
}
|
||||
if off != total_read {
|
||||
err = fmt.Errorf("Failed to seek in %s to: %d", input_file_name, off)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ var debugprintln = tty.DebugPrintln
|
|||
var _ = debugprintln
|
||||
|
||||
type input_line_struct struct {
|
||||
line string
|
||||
err error
|
||||
line string
|
||||
num_carriage_returns int
|
||||
is_a_complete_line bool
|
||||
err error
|
||||
}
|
||||
|
||||
type global_state_struct struct {
|
||||
|
|
@ -59,7 +61,11 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
|
|||
}
|
||||
global_state.input_file_name = args[0]
|
||||
}
|
||||
go read_input(input_file, global_state.input_file_name, input_channel)
|
||||
follow := global_state.opts.Follow
|
||||
if follow && global_state.input_file_name == "/dev/stdin" {
|
||||
follow = false
|
||||
}
|
||||
go read_input(input_file, global_state.input_file_name, input_channel, follow, global_state.opts.Role == "scrollback")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@
|
|||
default=pager
|
||||
choices=pager,scrollback
|
||||
The role the pager is used for. The default is a standard less like pager.
|
||||
|
||||
|
||||
--follow
|
||||
type=bool-set
|
||||
Follow changes in the specified file, automatically scrolling if currently on the last line.
|
||||
'''.format
|
||||
|
||||
help_text = '''\
|
||||
|
|
|
|||
Loading…
Reference in a new issue