github.com/mithrandie/csvq@v1.18.1/lib/doc/writer.go (about)

     1  package doc
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"strings"
     7  
     8  	"github.com/mithrandie/csvq/lib/option"
     9  	"github.com/mithrandie/go-text/color"
    10  )
    11  
    12  const DefaultPadding = 1
    13  
    14  type Writer struct {
    15  	Flags   *option.Flags
    16  	Palette *color.Palette
    17  
    18  	MaxWidth    int
    19  	Padding     int
    20  	Indent      int
    21  	IndentWidth int
    22  
    23  	Title1       string
    24  	Title1Effect string
    25  	Title2       string
    26  	Title2Effect string
    27  
    28  	buf bytes.Buffer
    29  
    30  	subBlock  int
    31  	lineWidth int
    32  	Column    int
    33  }
    34  
    35  func NewWriter(screenWidth int, flags *option.Flags, palette *color.Palette) *Writer {
    36  	return &Writer{
    37  		Flags:       flags,
    38  		Palette:     palette,
    39  		MaxWidth:    screenWidth,
    40  		Indent:      0,
    41  		IndentWidth: 4,
    42  		Padding:     DefaultPadding,
    43  		lineWidth:   0,
    44  		Column:      0,
    45  		subBlock:    0,
    46  	}
    47  }
    48  
    49  func (w *Writer) Clear() {
    50  	w.Title1 = ""
    51  	w.Title1Effect = ""
    52  	w.Title2 = ""
    53  	w.Title2Effect = ""
    54  	w.lineWidth = 0
    55  	w.Column = 0
    56  	w.subBlock = 0
    57  	w.buf.Reset()
    58  }
    59  
    60  func (w *Writer) WriteColorWithoutLineBreak(s string, effect string) {
    61  	w.write(s, effect, true)
    62  }
    63  
    64  func (w *Writer) WriteColor(s string, effect string) {
    65  	w.write(s, effect, false)
    66  }
    67  
    68  func (w *Writer) write(s string, effect string, withoutLineBreak bool) {
    69  	startOfLine := w.Column < 1
    70  
    71  	if startOfLine {
    72  		width := w.LeadingSpacesWidth() + w.subBlock
    73  		w.writeToBuf(strings.Repeat(" ", width))
    74  		w.Column = width
    75  	}
    76  
    77  	if !withoutLineBreak && !startOfLine && !w.FitInLine(s) {
    78  		w.NewLine()
    79  		w.write(s, effect, withoutLineBreak)
    80  	} else {
    81  		if w.Palette == nil {
    82  			w.writeToBuf(s)
    83  		} else {
    84  			w.writeToBuf(w.Palette.Render(effect, s))
    85  		}
    86  		w.Column = w.Column + option.TextWidth(s, w.Flags)
    87  	}
    88  }
    89  
    90  func (w *Writer) writeToBuf(s string) {
    91  	w.buf.WriteString(s)
    92  }
    93  
    94  func (w *Writer) LeadingSpacesWidth() int {
    95  	return w.Padding + (w.Indent * w.IndentWidth)
    96  }
    97  
    98  func (w *Writer) FitInLine(s string) bool {
    99  	if w.MaxWidth-(w.Padding*2)-1 < w.Column+option.TextWidth(s, w.Flags) {
   100  		return false
   101  	}
   102  	return true
   103  }
   104  
   105  func (w *Writer) WriteWithoutLineBreak(s string) {
   106  	w.WriteColorWithoutLineBreak(s, option.NoEffect)
   107  }
   108  
   109  func (w *Writer) Write(s string) {
   110  	w.WriteColor(s, option.NoEffect)
   111  }
   112  
   113  func (w *Writer) WriteWithAutoLineBreak(s string) {
   114  	w.writeWithAutoLineBreak(s, false, true)
   115  }
   116  
   117  func (w *Writer) WriteWithAutoLineBreakWithContinueMark(s string) {
   118  	w.writeWithAutoLineBreak(s, true, false)
   119  }
   120  
   121  func (w *Writer) writeWithAutoLineBreak(s string, useContinueMark bool, useBlock bool) {
   122  	continueMark := ""
   123  	if useContinueMark {
   124  		continueMark = "\\"
   125  	}
   126  
   127  	scanner := bufio.NewScanner(strings.NewReader(s))
   128  	firstLine := true
   129  	blockQuote := false
   130  	preformatted := false
   131  	for scanner.Scan() {
   132  		if blockQuote {
   133  			w.EndBlock()
   134  			blockQuote = false
   135  		}
   136  
   137  		line := scanner.Text()
   138  		if useBlock && option.TrimSpace(line) == "```" {
   139  			preformatted = !preformatted
   140  			continue
   141  		} else {
   142  			if firstLine {
   143  				firstLine = false
   144  			} else {
   145  				w.NewLine()
   146  			}
   147  		}
   148  
   149  		if preformatted {
   150  			w.Write(line)
   151  			continue
   152  		}
   153  
   154  		wscanner := bufio.NewScanner(strings.NewReader(line))
   155  		wscanner.Split(bufio.ScanWords)
   156  		lineHead := true
   157  
   158  		for wscanner.Scan() {
   159  			word := wscanner.Text()
   160  			if lineHead {
   161  				if useBlock && blockQuote == false && word == ">" {
   162  					blockQuote = true
   163  					w.BeginBlock()
   164  					continue
   165  				}
   166  
   167  				lineHead = false
   168  			} else {
   169  				if !w.FitInLine(" " + word + continueMark) {
   170  					w.Write(continueMark)
   171  					w.NewLine()
   172  				} else {
   173  					word = " " + word
   174  				}
   175  			}
   176  
   177  			w.Write(word)
   178  		}
   179  	}
   180  
   181  	if blockQuote {
   182  		w.EndBlock()
   183  	}
   184  }
   185  
   186  func (w *Writer) WriteSpaces(l int) {
   187  	w.Write(strings.Repeat(" ", l))
   188  }
   189  
   190  func (w *Writer) NewLine() {
   191  	w.buf.WriteRune('\n')
   192  	if w.lineWidth < w.Column {
   193  		w.lineWidth = w.Column
   194  	}
   195  	w.Column = 0
   196  }
   197  
   198  func (w *Writer) BeginBlock() {
   199  	w.Indent++
   200  }
   201  
   202  func (w *Writer) EndBlock() {
   203  	w.Indent--
   204  }
   205  
   206  func (w *Writer) BeginSubBlock() {
   207  	w.subBlock = w.Column - w.LeadingSpacesWidth()
   208  }
   209  
   210  func (w *Writer) EndSubBlock() {
   211  	w.subBlock = 0
   212  }
   213  
   214  func (w *Writer) ClearBlock() {
   215  	w.Indent = 0
   216  }
   217  
   218  func (w *Writer) String() string {
   219  	var header bytes.Buffer
   220  	if 0 < len(w.Title1) || 0 < len(w.Title2) {
   221  		tw := option.TextWidth(w.Title1, w.Flags) + option.TextWidth(w.Title2, w.Flags)
   222  		if 0 < len(w.Title1) && 0 < len(w.Title2) {
   223  			tw++
   224  		}
   225  
   226  		hlLen := tw + 2
   227  		if hlLen < w.lineWidth+1 {
   228  			hlLen = w.lineWidth + 1
   229  		}
   230  		if hlLen < w.Column+1 {
   231  			hlLen = w.Column + 1
   232  		}
   233  		if w.MaxWidth < hlLen {
   234  			hlLen = w.MaxWidth
   235  		}
   236  
   237  		if tw < hlLen {
   238  			header.Write(bytes.Repeat([]byte(" "), (hlLen-tw)/2))
   239  		}
   240  		if 0 < len(w.Title1) {
   241  			if w.Palette == nil {
   242  				header.WriteString(w.Title1)
   243  			} else {
   244  				header.WriteString(w.Palette.Render(w.Title1Effect, w.Title1))
   245  			}
   246  		}
   247  		if 0 < len(w.Title2) {
   248  			header.WriteRune(' ')
   249  			if w.Palette == nil {
   250  				header.WriteString(w.Title2)
   251  			} else {
   252  				header.WriteString(w.Palette.Render(w.Title2Effect, w.Title2))
   253  			}
   254  		}
   255  		header.WriteRune('\n')
   256  		header.Write(bytes.Repeat([]byte("-"), hlLen))
   257  		header.WriteRune('\n')
   258  	}
   259  
   260  	return header.String() + w.buf.String()
   261  }