...
Tawesoft Logo

Source file src/tawesoft.co.uk/go/humanizex/humanizer.go

Documentation: src/tawesoft.co.uk/go/humanizex/humanizer.go

     1  package humanizex
     2  
     3  import (
     4      "math"
     5      "time"
     6  
     7      "golang.org/x/text/language"
     8      "golang.org/x/text/message"
     9      "tawesoft.co.uk/go/lxstrconv"
    10  )
    11  
    12  // String holds an Utf8-encoded and an Ascii-compatible encoding of a string.
    13  type String struct {
    14      // Utf8 is the native Utf8-encoded Unicode representation
    15      Utf8   string
    16  
    17      // Ascii is an alternative version accepted for non-Unicode inputs (such
    18      // as when a user does not know how to enter µ on their keyboard (on mine,
    19      // it's Right-Alt+m)) or for non-Unicode output (such as legacy systems).
    20      Ascii  string
    21  }
    22  
    23  // Unit describes some quantity e.g. "m" for length in metres, "k" for the SI
    24  // unit prefix kilo, "km" for a kilometre.
    25  type Unit String
    26  
    27  // Cat concatenates two units (u + v) and returns the result.
    28  func (u Unit) Cat(v Unit) Unit {
    29      return Unit{
    30          u.Utf8 + v.Utf8,
    31          u.Ascii + v.Ascii,
    32      }
    33  }
    34  
    35  // Part describes some component of a formatting result e.g. 1.5 km or 1 hour
    36  type Part struct {
    37      Magnitude float64
    38      Unit      Unit
    39  }
    40  
    41  func partEqual(a Part, b Part, epsilon float64) bool {
    42      if a.Unit != b.Unit { return false }
    43      return math.Abs(a.Magnitude - b.Magnitude) < epsilon
    44  }
    45  
    46  func partsEqual(a []Part, b []Part, epsilon float64) bool {
    47      if len(a) != len(b) { return false }
    48  
    49      for i := 0; i < len(a); i++ {
    50          if !partEqual(a[i], b[i], epsilon) { return false }
    51      }
    52  
    53      return true
    54  }
    55  
    56  // Humanizer implements a locale-aware way to parse and format humanized
    57  // quantities.
    58  type Humanizer interface {
    59      // Format is a general purpose locale-aware way to format any quantity
    60      // with a defined set of factors. The unit argument is the base unit
    61      // e.g. s for seconds, m for meters, B for bytes.
    62      Format(value float64, unit Unit, factors Factors) String
    63  
    64      FormatNumber(number float64) String             // e.g. 12 k
    65      FormatDistance(meters float64) String           // e.g. 10 µm, 10 km
    66      FormatDuration(duration time.Duration) string   // e.g. 1 h 50 min
    67      FormatSeconds(seconds float64) string           // e.g. 1 h 50 min
    68      FormatBytesJEDEC(bytes int64) string            // e.g. 12 KB, 5 MB
    69      FormatBytesIEC(bytes int64) string              // e.g. 12 kB, 5 MB
    70      FormatBytesSI(bytes int64) string               // e.g. 12 KiB, 5 MiB
    71  
    72      // Accept is a general purpose locale-aware way to parse any quantity
    73      // with a defined set of factors from the start of the string str. The
    74      // provided unit is optional and is accepted if it appears in str.
    75      //
    76      // Accept returns the value, the number of bytes successfully parsed (which
    77      // may be zero), or an error.
    78      Accept(str string, unit Unit, factors Factors) (float64, int, error)
    79  
    80      // Parse is a general purpose locale-aware way to parse any quantity
    81      // with a defined set of factors.  The provided unit is optional and is
    82      // accepted if it appears in str.
    83      Parse(str string, unit Unit, factors Factors) (float64, error)
    84  
    85      ParseDuration(str string) (time.Duration, error)
    86      ParseBytesJEDEC(str string) (int64, error)
    87      ParseBytesIEC(str string) (int64, error)
    88      ParseBytesSI(str string) (int64, error)
    89  }
    90  
    91  type humanizer struct {
    92      Tag language.Tag
    93      NF lxstrconv.NumberFormat
    94      Printer *message.Printer
    95  }
    96  
    97  func (h *humanizer) FormatDistance(meters float64) String {
    98      return h.Format(meters, CommonUnits.Meter, CommonFactors.Distance)
    99  }
   100  
   101  func (h *humanizer) FormatBytesJEDEC(bytes int64) string {
   102      return h.Format(float64(bytes), CommonUnits.Byte, CommonFactors.JEDEC).Utf8
   103  }
   104  
   105  func (h *humanizer) FormatBytesIEC(bytes int64) string {
   106      return h.Format(float64(bytes), CommonUnits.Byte, CommonFactors.IEC).Utf8
   107  }
   108  
   109  func (h *humanizer) FormatBytesSI(bytes int64) string {
   110      return h.Format(float64(bytes), CommonUnits.Byte, CommonFactors.SI).Utf8
   111  }
   112  
   113  func (h *humanizer) FormatDuration(duration time.Duration) string {
   114      return h.Format(duration.Seconds(), CommonUnits.Second, CommonFactors.Time).Utf8
   115  }
   116  
   117  func (h *humanizer) FormatSeconds(seconds float64) string {
   118      return h.Format(seconds, CommonUnits.Second, CommonFactors.Time).Utf8
   119  }
   120  
   121  func (h *humanizer) FormatNumber(number float64) String {
   122      return h.Format(number, CommonUnits.None, CommonFactors.SI)
   123  }
   124  
   125  func (h *humanizer) ParseDuration(str string) (time.Duration, error) {
   126      v, err := h.Parse(str, CommonUnits.Second, CommonFactors.Time)
   127      return time.Second * time.Duration(v), err
   128  }
   129  
   130  func (h *humanizer) ParseBytesJEDEC(str string) (int64, error) {
   131      v, err := h.Parse(str, CommonUnits.Byte, CommonFactors.JEDEC)
   132      return int64(v), err
   133  }
   134  
   135  func (h *humanizer) ParseBytesIEC(str string) (int64, error) {
   136      v, err := h.Parse(str, CommonUnits.Byte, CommonFactors.IEC)
   137      return int64(v), err
   138  }
   139  
   140  func (h *humanizer) ParseBytesSI(str string) (int64, error) {
   141      v, err := h.Parse(str, CommonUnits.Byte, CommonFactors.SI)
   142      return int64(v), err
   143  }
   144  
   145  // NewHumanizer initialises a human language number encoder/decoder for the
   146  // given tag (representing a specific language or locale).
   147  //
   148  // The language.Tag is usually a named language from golang.org/x/text/language
   149  // e.g. language.English and controls how numbers are written e.g. comma
   150  // placement, decimal point, digits.
   151  func NewHumanizer(tag language.Tag, options ... interface{}) Humanizer {
   152      return &humanizer{
   153          Tag:     tag,
   154          NF:      lxstrconv.NewDecimalFormat(tag),
   155          Printer: message.NewPrinter(tag),
   156      }
   157  }
   158  

View as plain text