github.com/jxskiss/gopkg@v0.17.3/strutil/format.go (about)

     1  package strutil
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"unsafe"
     8  
     9  	"github.com/jxskiss/gopkg/reflectx"
    10  )
    11  
    12  // Format mimics subset features of the python string.format function.
    13  //
    14  // It formats the string using given keyword arguments and positional arguments.
    15  // kwArgs can be a map[string]interface{}, map[string]string or a struct
    16  // or a pointer to a struct.
    17  //
    18  // If bracket is needed in string it can be created by escaping (two brackets).
    19  //
    20  // All standard formatting options from fmt work. To specify them, add colon
    21  // after key name or position number and specify fmt package compatible formatting
    22  // options. The percent sign is optional. For example:
    23  //
    24  //   // returns "3.14, 3.1416"
    25  //   Format("{pi:%.2f}, {pi:.4f}", map[string]interface{}{"pi": math.Pi})
    26  //
    27  // If a replacement is not found in kwArgs and posArgs, the placeholder will be
    28  // output as the same in the given format.
    29  func Format(format string, kwArgs interface{}, posArgs ...interface{}) string {
    30  	var (
    31  		defaultFormat = []rune("%v")
    32  
    33  		// newFormat holds the new format string
    34  		newFormat     = make([]rune, 0, len(format))
    35  		newFormatArgs []interface{}
    36  
    37  		prevChar      rune
    38  		currentName   = make([]rune, 0, 10)
    39  		currentFormat = make([]rune, 0, 10)
    40  
    41  		inWing      bool
    42  		inWingParam bool
    43  
    44  		isAutoNumber   bool
    45  		isManualNumber bool
    46  		argIndex       int
    47  
    48  		kwGetter = getKeywordArgFunc(kwArgs)
    49  	)
    50  
    51  	for i, char := range format {
    52  		if i > 0 {
    53  			prevChar = rune(format[i-1])
    54  		}
    55  		switch char {
    56  		case '{':
    57  			if inWing && prevChar == '{' {
    58  				inWing = false
    59  				newFormat = append(newFormat, char)
    60  				break
    61  			}
    62  			inWing = true
    63  		case '}':
    64  			if !inWing {
    65  				if prevChar == '}' {
    66  					newFormat = append(newFormat, char)
    67  				}
    68  				break
    69  			}
    70  			isInvalid := false
    71  
    72  			// find the argument
    73  			name := string(currentName)
    74  			if name == "" {
    75  				if isManualNumber || argIndex > len(posArgs) {
    76  					isInvalid = true
    77  				} else {
    78  					arg := posArgs[argIndex]
    79  					newFormatArgs = append(newFormatArgs, arg)
    80  					argIndex++
    81  					isAutoNumber = true
    82  				}
    83  			} else if IsASCIIDigit(name) {
    84  				argNum, _ := strconv.ParseInt(name, 10, 64)
    85  				if isAutoNumber || int(argNum) >= len(posArgs) {
    86  					isInvalid = true
    87  				} else {
    88  					arg := posArgs[argNum]
    89  					newFormatArgs = append(newFormatArgs, arg)
    90  					isManualNumber = true
    91  				}
    92  			} else {
    93  				arg, ok := kwGetter(name)
    94  				if !ok {
    95  					isInvalid = true
    96  				} else {
    97  					newFormatArgs = append(newFormatArgs, arg)
    98  				}
    99  			}
   100  			if isInvalid {
   101  				newFormat = append(newFormat, '{')
   102  				newFormat = append(newFormat, currentName...)
   103  				if len(currentFormat) > 0 {
   104  					newFormat = append(newFormat, ':')
   105  					newFormat = append(newFormat, '%')
   106  					newFormat = append(newFormat, currentFormat...)
   107  				}
   108  				newFormat = append(newFormat, '}')
   109  			} else {
   110  				if len(currentFormat) > 0 {
   111  					newFormat = append(newFormat, currentFormat...)
   112  				} else {
   113  					newFormat = append(newFormat, defaultFormat...)
   114  				}
   115  			}
   116  			currentName = currentName[:0]
   117  			currentFormat = currentFormat[:0]
   118  
   119  			inWing = false
   120  			inWingParam = false
   121  		case ':':
   122  			if inWing {
   123  				inWingParam = true
   124  			}
   125  		default:
   126  			if inWing {
   127  				if inWingParam {
   128  					if prevChar == ':' && char != '%' {
   129  						currentFormat = append(currentFormat, '%')
   130  					}
   131  					currentFormat = append(currentFormat, char)
   132  				} else {
   133  					currentName = append(currentName, char)
   134  				}
   135  			} else {
   136  				newFormat = append(newFormat, char)
   137  			}
   138  		}
   139  	}
   140  
   141  	return fmt.Sprintf(string(newFormat), newFormatArgs...)
   142  }
   143  
   144  var strInterfaceMapTyp = reflect.TypeOf(map[string]interface{}(nil))
   145  
   146  func isStringInterfaceMap(typ reflect.Type) bool {
   147  	return typ.Kind() == reflect.Map &&
   148  		typ.Key().Kind() == reflect.String &&
   149  		typ.Elem() == strInterfaceMapTyp.Elem()
   150  }
   151  
   152  func castStringInterfaceMap(v interface{}) map[string]interface{} {
   153  	eface := reflectx.EfaceOf(&v)
   154  	strMap := *(*map[string]interface{})(unsafe.Pointer(&eface.Word))
   155  	return strMap
   156  }
   157  
   158  func getKeywordArgFunc(kwArgs interface{}) func(key string) (interface{}, bool) {
   159  	if kwArgs == nil {
   160  		return func(string) (interface{}, bool) { return nil, false }
   161  	}
   162  	kwTyp := reflect.TypeOf(kwArgs)
   163  	if isStringInterfaceMap(kwTyp) {
   164  		kwMap := castStringInterfaceMap(kwArgs)
   165  		return func(key string) (interface{}, bool) {
   166  			val, ok := kwMap[key]
   167  			return val, ok
   168  		}
   169  	}
   170  	if kwTyp.Kind() == reflect.Map && kwTyp.Key().Kind() == reflect.String {
   171  		kwValue := reflect.ValueOf(kwArgs)
   172  		return func(key string) (interface{}, bool) {
   173  			val := kwValue.MapIndex(reflect.ValueOf(key))
   174  			if val.IsValid() {
   175  				return val.Interface(), true
   176  			}
   177  			return nil, false
   178  		}
   179  	}
   180  	value := reflect.Indirect(reflect.ValueOf(kwArgs))
   181  	if value.Kind() == reflect.Struct {
   182  		return func(field string) (interface{}, bool) {
   183  			x := value.FieldByName(field)
   184  			if x.IsValid() {
   185  				return x, true
   186  			}
   187  			return nil, false
   188  		}
   189  	}
   190  	return func(string) (interface{}, bool) { return nil, false }
   191  }