github.com/boki/go-xmp@v1.0.1/xmp/units.go (about)

     1  // Copyright (c) 2017-2018 Alexander Eichhorn
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"): you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // 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, WITHOUT
    11  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    12  // License for the specific language governing permissions and limitations
    13  // under the License.
    14  
    15  // Duration
    16  // NullInt
    17  // SplitInt (1:2:3:4)
    18  // NullString
    19  // NullBool
    20  // NullFloat
    21  // NullFloat64
    22  
    23  package xmp
    24  
    25  import (
    26  	"bytes"
    27  	"encoding/json"
    28  	"errors"
    29  	"fmt"
    30  	"math"
    31  	"regexp"
    32  	"strconv"
    33  	"strings"
    34  	"time"
    35  	"unicode"
    36  )
    37  
    38  var EEmptyValue = errors.New("empty value")
    39  
    40  // Int
    41  type NullInt int
    42  
    43  func (i NullInt) Int() int {
    44  	return int(i)
    45  }
    46  
    47  func (i NullInt) Value() int {
    48  	return int(i)
    49  }
    50  
    51  func ParseNullInt(d string) (NullInt, error) {
    52  	switch d {
    53  	case "", "-", "--", "---", "NaN", "unknown":
    54  		return 0, EEmptyValue
    55  	default:
    56  		// parse integer
    57  		if i, err := strconv.ParseInt(d, 10, 64); err == nil {
    58  			return NullInt(i), nil
    59  		}
    60  		return 0, fmt.Errorf("xmp: parsing NullInt '%s': invalid syntax", d)
    61  	}
    62  }
    63  
    64  func (i NullInt) MarshalText() ([]byte, error) {
    65  	return []byte(strconv.Itoa(int(i))), nil
    66  }
    67  
    68  func (i *NullInt) UnmarshalText(data []byte) error {
    69  	v, err := ParseNullInt(string(data))
    70  	if err == EEmptyValue {
    71  		return nil
    72  	}
    73  	if err != nil {
    74  		return err
    75  	}
    76  	*i = v
    77  	return nil
    78  }
    79  
    80  func (i NullInt) MarshalJSON() ([]byte, error) {
    81  	return i.MarshalText()
    82  }
    83  
    84  func (i *NullInt) UnmarshalJSON(data []byte) error {
    85  	if len(data) == 0 {
    86  		return nil
    87  	}
    88  	if data[0] == '"' {
    89  		return i.UnmarshalText(bytes.Trim(data, "\""))
    90  	}
    91  	return i.UnmarshalText(data)
    92  }
    93  
    94  // 32bit Integer split byte-wise (06:13:14:61)
    95  type SplitInt uint32
    96  
    97  func (x SplitInt) Uint32() uint32 {
    98  	return uint32(x)
    99  }
   100  
   101  func (x SplitInt) Value() uint32 {
   102  	return uint32(x)
   103  }
   104  
   105  func (x *SplitInt) UnmarshalText(data []byte) error {
   106  	d := string(data)
   107  	switch d {
   108  	case "", "-", "--", "---", "NaN", "unknown":
   109  		return nil
   110  	default:
   111  		// parse integer
   112  		if i, err := strconv.ParseInt(d, 10, 32); err == nil {
   113  			*x = SplitInt(i)
   114  		} else if f := strings.Split(d, ":"); len(f) == 4 {
   115  			var u uint32
   116  			for i := 0; i < 4; i++ {
   117  				if v, err := strconv.ParseInt(f[i], 10, 8); err != nil {
   118  					return fmt.Errorf("xmp: parsing SplitInt '%s': invalid syntax", d)
   119  				} else {
   120  					u += uint32(v) << uint32((3-i)*8)
   121  				}
   122  			}
   123  			*x = SplitInt(u)
   124  		} else {
   125  			return fmt.Errorf("xmp: parsing SplitInt '%s': invalid syntax", d)
   126  		}
   127  	}
   128  	return nil
   129  }
   130  
   131  // String
   132  type NullString string
   133  
   134  func (s NullString) String() string {
   135  	return string(s)
   136  }
   137  
   138  func (s NullString) Value() string {
   139  	return string(s)
   140  }
   141  
   142  func ParseNullString(s string) (NullString, error) {
   143  	s = strings.TrimSpace(s)
   144  	switch s {
   145  	case "", "-", "--", "unknown":
   146  		return "", EEmptyValue
   147  	default:
   148  		return NullString(s), nil
   149  	}
   150  }
   151  
   152  func (s NullString) MarshalText() ([]byte, error) {
   153  	return []byte(s.String()), nil
   154  }
   155  
   156  func (s *NullString) UnmarshalText(data []byte) error {
   157  	// only overwrite when not set
   158  	if len(*s) > 0 {
   159  		return nil
   160  	}
   161  	// only overwrite when not empty
   162  	p, err := ParseNullString(string(data))
   163  	if err == EEmptyValue {
   164  		return nil
   165  	}
   166  	if err != nil {
   167  		return err
   168  	}
   169  	*s = p
   170  	return nil
   171  }
   172  
   173  // Bool
   174  type NullBool bool
   175  
   176  func (x NullBool) Value() bool {
   177  	return bool(x)
   178  }
   179  
   180  func ParseNullBool(d string) (NullBool, error) {
   181  	switch strings.ToLower(d) {
   182  	case "", "-", "--", "---":
   183  		return NullBool(false), EEmptyValue
   184  	case "true", "on", "yes", "1", "enabled":
   185  		return NullBool(true), nil
   186  	default:
   187  		return NullBool(false), nil
   188  	}
   189  }
   190  
   191  func (x NullBool) MarshalText() ([]byte, error) {
   192  	return []byte(strconv.FormatBool(bool(x))), nil
   193  }
   194  
   195  func (x *NullBool) UnmarshalText(data []byte) error {
   196  	v, err := ParseNullBool(string(data))
   197  	if err == EEmptyValue {
   198  		return nil
   199  	}
   200  	if err != nil {
   201  		return err
   202  	}
   203  	*x = v
   204  	return nil
   205  }
   206  
   207  func (x NullBool) MarshalJSON() ([]byte, error) {
   208  	return x.MarshalText()
   209  }
   210  
   211  func (x *NullBool) UnmarshalJSON(data []byte) error {
   212  	if len(data) == 0 {
   213  		return nil
   214  	}
   215  	if data[0] == '"' {
   216  		return x.UnmarshalText(bytes.Trim(data, "\""))
   217  	}
   218  	return x.UnmarshalText(data)
   219  }
   220  
   221  // Duration
   222  type Duration time.Duration
   223  
   224  func (d Duration) Duration() time.Duration {
   225  	return time.Duration(d)
   226  }
   227  
   228  func (d Duration) String() string {
   229  	return time.Duration(d).String()
   230  }
   231  
   232  func ParseDuration(d string) (Duration, error) {
   233  	// parse integer values as seconds
   234  	if i, err := strconv.ParseInt(d, 10, 64); err == nil {
   235  		return Duration(time.Duration(i) * time.Second), nil
   236  	}
   237  	// parse as duration string (note: no whitespace allowed)
   238  	if i, err := time.ParseDuration(d); err == nil {
   239  		return Duration(i), nil
   240  	}
   241  	// parse as duration string with whitespace removed
   242  	d = strings.Map(func(r rune) rune {
   243  		if unicode.IsSpace(r) {
   244  			return -1
   245  		}
   246  		return r
   247  	}, d)
   248  	if i, err := time.ParseDuration(d); err == nil {
   249  		return Duration(i), nil
   250  	}
   251  	return 0, fmt.Errorf("xmp: parsing duration '%s': invalid syntax", d)
   252  }
   253  
   254  func (d Duration) MarshalText() ([]byte, error) {
   255  	return []byte(time.Duration(d).String()), nil
   256  }
   257  
   258  func (d *Duration) UnmarshalText(data []byte) error {
   259  	i, err := ParseDuration(string(data))
   260  	if err != nil {
   261  		return err
   262  	}
   263  	*d = i
   264  	return nil
   265  }
   266  
   267  func (d *Duration) UnmarshalJSON(data []byte) error {
   268  	if len(data) == 0 {
   269  		return nil
   270  	}
   271  	if data[0] == '"' {
   272  		return d.UnmarshalText(bytes.Trim(data, "\""))
   273  	}
   274  	if i, err := strconv.ParseInt(string(data), 10, 64); err == nil {
   275  		*d = Duration(time.Duration(i) * time.Second)
   276  		return nil
   277  	}
   278  	return fmt.Errorf("xmp: parsing duration '%s': invalid syntax", string(data))
   279  }
   280  
   281  func (d Duration) Truncate(r time.Duration) Duration {
   282  	if d > 0 {
   283  		return Duration(math.Ceil(float64(d)/float64(r))) * Duration(r)
   284  	} else {
   285  		return Duration(math.Floor(float64(d)/float64(r))) * Duration(r)
   286  	}
   287  }
   288  
   289  func (d Duration) RoundToDays() int {
   290  	return int(d.Truncate(time.Hour*24) / Duration(time.Hour*24))
   291  }
   292  
   293  func (d Duration) RoundToHours() int64 {
   294  	return int64(d.Truncate(time.Hour) / Duration(time.Hour))
   295  }
   296  
   297  func (d Duration) RoundToMinutes() int64 {
   298  	return int64(d.Truncate(time.Minute) / Duration(time.Minute))
   299  }
   300  
   301  func (d Duration) RoundToSeconds() int64 {
   302  	return int64(d.Truncate(time.Second) / Duration(time.Second))
   303  }
   304  
   305  func (d Duration) RoundToMillisecond() int64 {
   306  	return int64(d.Truncate(time.Millisecond) / Duration(time.Millisecond))
   307  }
   308  
   309  // Float32
   310  type NullFloat float32
   311  
   312  func (f NullFloat) Float32() float32 {
   313  	return float32(f)
   314  }
   315  
   316  func (f NullFloat) Value() float64 {
   317  	return float64(f)
   318  }
   319  
   320  var cleanFloat = regexp.MustCompile("[^0-9e.+-]")
   321  
   322  func ParseNullFloat(d string) (NullFloat, error) {
   323  	switch strings.ToLower(d) {
   324  	case "", "-", "--", "---", "unknown":
   325  		return 0, EEmptyValue
   326  	case "nan":
   327  		return NullFloat(math.NaN()), nil
   328  	case "-inf":
   329  		return NullFloat(math.Inf(-1)), nil
   330  	case "+inf", "inf":
   331  		return NullFloat(math.Inf(1)), nil
   332  	default:
   333  		// parse float
   334  		c := cleanFloat.ReplaceAllString(d, "")
   335  		if i, err := strconv.ParseFloat(c, 32); err == nil {
   336  			return NullFloat(i), nil
   337  		}
   338  		return 0, fmt.Errorf("xmp: parsing NullFloat '%s': invalid syntax", d)
   339  	}
   340  }
   341  
   342  func (f NullFloat) MarshalText() ([]byte, error) {
   343  	return []byte(strconv.FormatFloat(f.Value(), 'f', -1, 32)), nil
   344  }
   345  
   346  func (f *NullFloat) UnmarshalText(data []byte) error {
   347  	v, err := ParseNullFloat(string(data))
   348  	if err == EEmptyValue {
   349  		return nil
   350  	}
   351  	if err != nil {
   352  		return err
   353  	}
   354  	*f = v
   355  	return nil
   356  }
   357  
   358  // JSON does not define NaN, +Inf and -Inf
   359  func (f NullFloat) MarshalJSON() ([]byte, error) {
   360  	switch {
   361  	case math.IsNaN(f.Value()):
   362  		return []byte("\"NaN\""), nil
   363  	case math.IsInf(f.Value(), -1):
   364  		return []byte("\"+Inf\""), nil
   365  	case math.IsInf(f.Value(), 1):
   366  		return []byte("\"-Inf\""), nil
   367  	default:
   368  		return json.Marshal(f.Value())
   369  	}
   370  }
   371  
   372  func (f *NullFloat) UnmarshalJSON(data []byte) error {
   373  	if len(data) == 0 {
   374  		return nil
   375  	}
   376  	if data[0] == '"' {
   377  		return f.UnmarshalText(bytes.Trim(data, "\""))
   378  	}
   379  	return f.UnmarshalText(data)
   380  }
   381  
   382  // Float64
   383  type NullFloat64 float64
   384  
   385  func (f NullFloat64) Float64() float64 {
   386  	return float64(f)
   387  }
   388  
   389  func (f NullFloat64) Value() float64 {
   390  	return float64(f)
   391  }
   392  
   393  func ParseNullFloat64(d string) (NullFloat64, error) {
   394  	switch strings.ToLower(d) {
   395  	case "", "-", "--", "---", "unknown":
   396  		return 0, EEmptyValue
   397  	case "nan":
   398  		return NullFloat64(math.NaN()), nil
   399  	case "-inf":
   400  		return NullFloat64(math.Inf(-1)), nil
   401  	case "+inf", "inf":
   402  		return NullFloat64(math.Inf(1)), nil
   403  	default:
   404  		// parse float
   405  		c := cleanFloat.ReplaceAllString(d, "")
   406  		if i, err := strconv.ParseFloat(c, 64); err == nil {
   407  			return NullFloat64(i), nil
   408  		}
   409  		return 0, fmt.Errorf("xmp: parsing NullFloat64 '%s': invalid syntax", d)
   410  	}
   411  }
   412  
   413  func (f NullFloat64) MarshalText() ([]byte, error) {
   414  	return []byte(strconv.FormatFloat(f.Value(), 'f', -1, 64)), nil
   415  }
   416  
   417  func (f *NullFloat64) UnmarshalText(data []byte) error {
   418  	v, err := ParseNullFloat64(string(data))
   419  	if err == EEmptyValue {
   420  		return nil
   421  	}
   422  	if err != nil {
   423  		return err
   424  	}
   425  	*f = v
   426  	return nil
   427  }
   428  
   429  // JSON does not define NaN, +Inf and -Inf
   430  func (f NullFloat64) MarshalJSON() ([]byte, error) {
   431  	switch {
   432  	case math.IsNaN(f.Value()):
   433  		return []byte("\"NaN\""), nil
   434  	case math.IsInf(f.Value(), -1):
   435  		return []byte("\"+Inf\""), nil
   436  	case math.IsInf(f.Value(), 1):
   437  		return []byte("\"-Inf\""), nil
   438  	default:
   439  		return json.Marshal(f.Value())
   440  	}
   441  }
   442  
   443  func (f *NullFloat64) UnmarshalJSON(data []byte) error {
   444  	if len(data) == 0 {
   445  		return nil
   446  	}
   447  	if data[0] == '"' {
   448  		return f.UnmarshalText(bytes.Trim(data, "\""))
   449  	}
   450  	return f.UnmarshalText(data)
   451  }
   452  
   453  func Max(x, y int) int {
   454  	if x < y {
   455  		return y
   456  	} else {
   457  		return x
   458  	}
   459  }
   460  
   461  func Min(x, y int) int {
   462  	if x > y {
   463  		return y
   464  	} else {
   465  		return x
   466  	}
   467  }
   468  
   469  func Max64(x, y int64) int64 {
   470  	if x < y {
   471  		return y
   472  	} else {
   473  		return x
   474  	}
   475  }
   476  
   477  func Min64(x, y int64) int64 {
   478  	if x > y {
   479  		return y
   480  	} else {
   481  		return x
   482  	}
   483  }