github.com/mithrandie/csvq@v1.18.1/lib/query/string_formatter.go (about)

     1  package query
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math"
     7  	"reflect"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/mithrandie/csvq/lib/option"
    13  	"github.com/mithrandie/csvq/lib/value"
    14  )
    15  
    16  const EOF = -1
    17  
    18  type StringFormatter struct {
    19  	format    []rune
    20  	formatPos int
    21  	offset    int
    22  	values    []value.Primary
    23  
    24  	buf bytes.Buffer
    25  
    26  	err error
    27  }
    28  
    29  func NewStringFormatter() *StringFormatter {
    30  	return &StringFormatter{}
    31  }
    32  
    33  func (f *StringFormatter) Format(format string, values []value.Primary) (string, error) {
    34  	f.format = []rune(format)
    35  	f.values = values
    36  	f.formatPos = 0
    37  	f.buf.Reset()
    38  
    39  	var placeholder bytes.Buffer
    40  	placeholderOrder := 0
    41  
    42  	for {
    43  		ch := f.next()
    44  		if ch == EOF {
    45  			break
    46  		}
    47  
    48  		if ch != '%' {
    49  			f.buf.WriteRune(ch)
    50  			continue
    51  		}
    52  
    53  		ch = f.next()
    54  		if ch == '%' {
    55  			f.buf.WriteRune(ch)
    56  			continue
    57  		}
    58  
    59  		if len(values) <= placeholderOrder {
    60  			return "", NewFormatStringLengthNotMatchError()
    61  		}
    62  
    63  		placeholder.Reset()
    64  		placeholder.WriteRune('%')
    65  
    66  		var flag rune = -1
    67  		width := -1
    68  		precision := -1
    69  
    70  		if f.isFlag(ch) {
    71  			placeholder.WriteRune(ch)
    72  			flag = ch
    73  			ch = f.next()
    74  		}
    75  
    76  		if f.isDecimal(ch) {
    77  			f.offset = 1
    78  			f.scanDecimal()
    79  			placeholder.WriteString(f.literal())
    80  			width = f.integer()
    81  			ch = f.next()
    82  		}
    83  
    84  		if ch == '.' {
    85  			ch = f.next()
    86  			if f.isDecimal(ch) {
    87  				f.offset = 1
    88  				f.scanDecimal()
    89  				precision = f.integer()
    90  				ch = f.next()
    91  			} else {
    92  				precision = 0
    93  			}
    94  		}
    95  
    96  		switch ch {
    97  		case 's', 'q', 'i', 'T':
    98  			placeholder.WriteRune('s')
    99  		default:
   100  			if -1 < precision {
   101  				placeholder.WriteRune('.')
   102  				placeholder.WriteString(strconv.Itoa(precision))
   103  			}
   104  
   105  			placeholder.WriteRune(ch)
   106  		}
   107  
   108  		var s string
   109  
   110  		switch ch {
   111  		case 'b', 'o', 'd', 'x', 'X':
   112  			p := value.ToInteger(values[placeholderOrder])
   113  			if !value.IsNull(p) {
   114  				val := p.(*value.Integer).Raw()
   115  				s = fmt.Sprintf(placeholder.String(), val)
   116  			} else if -1 < width {
   117  				s = strings.Repeat(" ", width)
   118  			}
   119  		case 'e', 'E', 'f':
   120  			p := value.ToFloat(values[placeholderOrder])
   121  			if !value.IsNull(p) {
   122  				val := p.(*value.Float).Raw()
   123  				if -1 < precision {
   124  					s = fmt.Sprintf(placeholder.String(), val)
   125  				} else {
   126  					sign := f.numericSign(flag, val < 0)
   127  					val := math.Abs(val)
   128  					s = strconv.FormatFloat(val, byte(ch), -1, 64)
   129  					switch flag {
   130  					case '0':
   131  						if -1 < width {
   132  							padLen := width - len(s) - len(sign)
   133  							if padLen < 0 {
   134  								padLen = 0
   135  							}
   136  							s = sign + strings.Repeat("0", padLen) + s
   137  						} else {
   138  							s = sign + s
   139  						}
   140  					default:
   141  						s = sign + s
   142  						if -1 < width {
   143  							padLen := width - len(s)
   144  							if padLen < 0 {
   145  								padLen = 0
   146  							}
   147  							if flag == '-' {
   148  								s = s + strings.Repeat(" ", padLen)
   149  							} else {
   150  								s = strings.Repeat(" ", padLen) + s
   151  							}
   152  						}
   153  					}
   154  				}
   155  			} else if -1 < width {
   156  				s = strings.Repeat(" ", width)
   157  			}
   158  		case 's', 'q', 'i':
   159  			switch values[placeholderOrder].(type) {
   160  			case *value.String:
   161  				s = values[placeholderOrder].(*value.String).Raw()
   162  			case *value.Datetime:
   163  				s = values[placeholderOrder].(*value.Datetime).Format(time.RFC3339Nano)
   164  			default:
   165  				s = values[placeholderOrder].String()
   166  			}
   167  
   168  			if -1 < precision {
   169  				s = s[:precision]
   170  			}
   171  
   172  			switch ch {
   173  			case 'q':
   174  				s = option.QuoteString(s)
   175  			case 'i':
   176  				s = option.QuoteIdentifier(s)
   177  			}
   178  
   179  			s = fmt.Sprintf(placeholder.String(), s)
   180  		case 'T':
   181  			rv := reflect.ValueOf(values[placeholderOrder]).Elem().Interface()
   182  			s = reflect.TypeOf(rv).Name()
   183  			if -1 < precision {
   184  				s = s[:precision]
   185  			}
   186  
   187  			s = fmt.Sprintf(placeholder.String(), s)
   188  		case EOF:
   189  			return "", NewFormatUnexpectedTerminationError()
   190  		default:
   191  			return "", NewUnknownFormatPlaceholderError(ch)
   192  		}
   193  
   194  		f.buf.WriteString(s)
   195  
   196  		placeholderOrder++
   197  	}
   198  
   199  	if placeholderOrder < len(values) {
   200  		return "", NewFormatStringLengthNotMatchError()
   201  	}
   202  
   203  	return f.buf.String(), nil
   204  }
   205  
   206  func (f *StringFormatter) runes() []rune {
   207  	return f.format[(f.formatPos - f.offset):f.formatPos]
   208  }
   209  
   210  func (f *StringFormatter) literal() string {
   211  	return string(f.runes())
   212  }
   213  
   214  func (f *StringFormatter) integer() int {
   215  	i, _ := strconv.Atoi(string(f.runes()))
   216  	return i
   217  }
   218  
   219  func (f *StringFormatter) peek() rune {
   220  	if len(f.format) <= f.formatPos {
   221  		return EOF
   222  	}
   223  	return f.format[f.formatPos]
   224  }
   225  
   226  func (f *StringFormatter) next() rune {
   227  	ch := f.peek()
   228  	if ch == EOF {
   229  		return ch
   230  	}
   231  
   232  	f.formatPos++
   233  	f.offset++
   234  	return ch
   235  }
   236  
   237  func (f *StringFormatter) isFlag(ch rune) bool {
   238  	switch ch {
   239  	case '+', '-', ' ', '0':
   240  		return true
   241  	default:
   242  		return false
   243  	}
   244  }
   245  
   246  func (f *StringFormatter) isDecimal(ch rune) bool {
   247  	return '0' <= ch && ch <= '9'
   248  }
   249  
   250  func (f *StringFormatter) scanDecimal() {
   251  	for f.isDecimal(f.peek()) {
   252  		f.next()
   253  	}
   254  }
   255  
   256  func (f *StringFormatter) numericSign(flag rune, minus bool) string {
   257  	if minus {
   258  		return "-"
   259  	}
   260  	switch flag {
   261  	case '+', ' ':
   262  		return string(flag)
   263  	}
   264  	return ""
   265  }