github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/cmd/spc/parse.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math"
    10  	"math/big"
    11  	"os"
    12  	"strings"
    13  
    14  	"SiaPrime/encoding"
    15  	"SiaPrime/types"
    16  )
    17  
    18  var errUnableToParseSize = errors.New("unable to parse size")
    19  
    20  // filesize returns a string that displays a filesize in human-readable units.
    21  func filesizeUnits(size int64) string {
    22  	if size == 0 {
    23  		return "0 B"
    24  	}
    25  	sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
    26  	i := int(math.Log10(float64(size)) / 3)
    27  	return fmt.Sprintf("%.*f %s", i, float64(size)/math.Pow10(3*i), sizes[i])
    28  }
    29  
    30  // parseFilesize converts strings of form 10GB to a size in bytes. Fractional
    31  // sizes are truncated at the byte size.
    32  func parseFilesize(strSize string) (string, error) {
    33  	units := []struct {
    34  		suffix     string
    35  		multiplier int64
    36  	}{
    37  		{"kb", 1e3},
    38  		{"mb", 1e6},
    39  		{"gb", 1e9},
    40  		{"tb", 1e12},
    41  		{"kib", 1 << 10},
    42  		{"mib", 1 << 20},
    43  		{"gib", 1 << 30},
    44  		{"tib", 1 << 40},
    45  		{"b", 1}, // must be after others else it'll match on them all
    46  	}
    47  
    48  	strSize = strings.ToLower(strSize)
    49  	for _, unit := range units {
    50  		if strings.HasSuffix(strSize, unit.suffix) {
    51  			r, ok := new(big.Rat).SetString(strings.TrimSuffix(strSize, unit.suffix))
    52  			if !ok {
    53  				return "", errUnableToParseSize
    54  			}
    55  			r.Mul(r, new(big.Rat).SetInt(big.NewInt(unit.multiplier)))
    56  			if !r.IsInt() {
    57  				f, _ := r.Float64()
    58  				return fmt.Sprintf("%d", int64(f)), nil
    59  			}
    60  			return r.RatString(), nil
    61  		}
    62  	}
    63  
    64  	return "", errUnableToParseSize
    65  }
    66  
    67  // periodUnits turns a period in terms of blocks to a number of weeks.
    68  func periodUnits(blocks types.BlockHeight) string {
    69  	return fmt.Sprint(blocks / 1008) // 1008 blocks per week
    70  }
    71  
    72  // parsePeriod converts a duration specified in blocks, hours, or weeks to a
    73  // number of blocks.
    74  func parsePeriod(period string) (string, error) {
    75  	units := []struct {
    76  		suffix     string
    77  		multiplier float64
    78  	}{
    79  		{"b", 1},        // blocks
    80  		{"block", 1},    // blocks
    81  		{"blocks", 1},   // blocks
    82  		{"h", 6},        // hours
    83  		{"hour", 6},     // hours
    84  		{"hours", 6},    // hours
    85  		{"d", 144},      // days
    86  		{"day", 144},    // days
    87  		{"days", 144},   // days
    88  		{"w", 1008},     // weeks
    89  		{"week", 1008},  // weeks
    90  		{"weeks", 1008}, // weeks
    91  	}
    92  
    93  	period = strings.ToLower(period)
    94  	for _, unit := range units {
    95  		if strings.HasSuffix(period, unit.suffix) {
    96  			var base float64
    97  			_, err := fmt.Sscan(strings.TrimSuffix(period, unit.suffix), &base)
    98  			if err != nil {
    99  				return "", errUnableToParseSize
   100  			}
   101  			blocks := int(base * unit.multiplier)
   102  			return fmt.Sprint(blocks), nil
   103  		}
   104  	}
   105  
   106  	return "", errUnableToParseSize
   107  }
   108  
   109  // currencyUnits converts a types.Currency to a string with human-readable
   110  // units. The unit used will be the largest unit that results in a value
   111  // greater than 1. The value is rounded to 4 significant digits.
   112  func currencyUnits(c types.Currency) string {
   113  	pico := types.SiacoinPrecision.Div64(1e12)
   114  	if c.Cmp(pico) < 0 {
   115  		return c.String() + " H"
   116  	}
   117  
   118  	// iterate until we find a unit greater than c
   119  	mag := pico
   120  	unit := ""
   121  	for _, unit = range []string{"pS", "nS", "uS", "mS", "SCP", "KS", "MS", "GS", "TS"} {
   122  		if c.Cmp(mag.Mul64(1e3)) < 0 {
   123  			break
   124  		} else if unit != "TS" {
   125  			// don't want to perform this multiply on the last iter; that
   126  			// would give us 1.235 TS instead of 1235 TS
   127  			mag = mag.Mul64(1e3)
   128  		}
   129  	}
   130  
   131  	num := new(big.Rat).SetInt(c.Big())
   132  	denom := new(big.Rat).SetInt(mag.Big())
   133  	res, _ := new(big.Rat).Mul(num, denom.Inv(denom)).Float64()
   134  
   135  	return fmt.Sprintf("%.4g %s", res, unit)
   136  }
   137  
   138  // parseCurrency converts a siacoin amount to base units.
   139  func parseCurrency(amount string) (string, error) {
   140  	units := []string{"pS", "nS", "uS", "mS", "SCP", "KS", "MS", "GS", "TS"}
   141  	for i, unit := range units {
   142  		if strings.HasSuffix(amount, unit) {
   143  			// scan into big.Rat
   144  			r, ok := new(big.Rat).SetString(strings.TrimSuffix(amount, unit))
   145  			if !ok {
   146  				return "", errors.New("malformed amount")
   147  			}
   148  			// convert units
   149  			exp := 24 + 3*(int64(i)-4)
   150  			mag := new(big.Int).Exp(big.NewInt(10), big.NewInt(exp), nil)
   151  			r.Mul(r, new(big.Rat).SetInt(mag))
   152  			// r must be an integer at this point
   153  			if !r.IsInt() {
   154  				return "", errors.New("non-integer number of hastings")
   155  			}
   156  			return r.RatString(), nil
   157  		}
   158  	}
   159  	// check for hastings separately
   160  	if strings.HasSuffix(amount, "H") {
   161  		return strings.TrimSuffix(amount, "H"), nil
   162  	}
   163  
   164  	return "", errors.New("amount is missing units; run 'wallet --help' for a list of units")
   165  }
   166  
   167  // yesNo returns "Yes" if b is true, and "No" if b is false.
   168  func yesNo(b bool) string {
   169  	if b {
   170  		return "Yes"
   171  	}
   172  	return "No"
   173  }
   174  
   175  // parseTxn decodes a transaction from s, which can be JSON, base64, or a path
   176  // to a file containing either encoding.
   177  func parseTxn(s string) (types.Transaction, error) {
   178  	// first assume s is a file
   179  	txnBytes, err := ioutil.ReadFile(s)
   180  	if os.IsNotExist(err) {
   181  		// assume s is a literal encoding
   182  		txnBytes = []byte(s)
   183  	} else if err != nil {
   184  		return types.Transaction{}, errors.New("could not read transaction file: " + err.Error())
   185  	}
   186  	// txnBytes now contains either s or the contents of the file, so it is
   187  	// either JSON or base64
   188  	var txn types.Transaction
   189  	if json.Valid(txnBytes) {
   190  		if err := json.Unmarshal(txnBytes, &txn); err != nil {
   191  			return types.Transaction{}, errors.New("could not decode JSON transaction: " + err.Error())
   192  		}
   193  	} else {
   194  		bin, err := base64.StdEncoding.DecodeString(string(txnBytes))
   195  		if err != nil {
   196  			return types.Transaction{}, errors.New("argument is not valid JSON, base64, or filepath")
   197  		}
   198  		if err := encoding.Unmarshal(bin, &txn); err != nil {
   199  			return types.Transaction{}, errors.New("could not decode binary transaction: " + err.Error())
   200  		}
   201  	}
   202  	return txn, nil
   203  }