New Upstream Release - golang-github-dsnet-golib
Ready changes
Summary
Merged new upstream version: 1.0.2 (was: 0.0~git20171103.1ea1667).
Resulting package
Built on 2022-11-25T00:10 (took 2m32s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-dsnet-golib-dev
Lintian Result
Diff
diff --git a/README.md b/README.md
index baaea89..1f033b0 100644
--- a/README.md
+++ b/README.md
@@ -7,20 +7,17 @@ Functionality that the author (Joe Tsai) found common among his various projects
are pulled out and placed here.
-## Installation ##
-
-Run the command:
-
-```go get -u github.com/dsnet/golib```
-
-
## Packages ##
+Each top-level package is a sub-module so that the individual packages
+can be versioned separately from each other.
+
| Package | Description |
| :------ | :---------- |
-| [bufpipe](http://godoc.org/github.com/dsnet/golib/bufpipe) | Package bufpipe implements a buffered pipe. |
-| [cron](http://godoc.org/github.com/dsnet/golib/cron) | Package cron parses and runs cron schedules. |
-| [hashmerge](http://godoc.org/github.com/dsnet/golib/hashmerge) | Package hashmerge merges hash checksums. |
-| [jsonfmt](http://godoc.org/github.com/dsnet/golib/jsonfmt) | Package jsonfmt implements a JSON formatter. |
-| [memfile](http://godoc.org/github.com/dsnet/golib/memfile) | Package memfile implements an in-memory emulation of os.File. |
-| [unitconv](http://godoc.org/github.com/dsnet/golib/unitconv) | Package unitconv implements string conversion functionality for unit prefixes. |
+| [bufpipe](https://pkg.go.dev/github.com/dsnet/golib/bufpipe) | Package bufpipe implements a buffered pipe. |
+| [cron](https://pkg.go.dev/github.com/dsnet/golib/cron) | Package cron parses and runs cron schedules. |
+| [hashmerge](https://pkg.go.dev/github.com/dsnet/golib/hashmerge) | Package hashmerge merges hash checksums. |
+| [jsoncs](https://pkg.go.dev/github.com/dsnet/golib/jsoncs) | Package jsoncs implements JSON Canonicalization Scheme (JCS) as specified in RFC 8785. |
+| [jsonfmt](https://pkg.go.dev/github.com/dsnet/golib/jsonfmt) | Package jsonfmt implements a JSON formatter. |
+| [memfile](https://pkg.go.dev/github.com/dsnet/golib/memfile) | Package memfile implements an in-memory emulation of os.File. |
+| [unitconv](https://pkg.go.dev/github.com/dsnet/golib/unitconv) | Package unitconv implements string conversion functionality for unit prefixes. |
diff --git a/bufpipe/LICENSE.md b/bufpipe/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/bufpipe/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/bufpipe/go.mod b/bufpipe/go.mod
new file mode 100644
index 0000000..36aadea
--- /dev/null
+++ b/bufpipe/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/bufpipe
+
+go 1.12
diff --git a/cron/LICENSE.md b/cron/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/cron/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cron/go.mod b/cron/go.mod
new file mode 100644
index 0000000..85890a4
--- /dev/null
+++ b/cron/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/cron
+
+go 1.12
diff --git a/debian/changelog b/debian/changelog
index 589c213..a0fd0ca 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-dsnet-golib (1.0.2-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 25 Nov 2022 00:08:50 -0000
+
golang-github-dsnet-golib (0.0~git20171103.1ea1667-1) unstable; urgency=medium
* Initial release (Closes: #902735)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..761bb47
--- /dev/null
+++ b/go.mod
@@ -0,0 +1 @@
+module github.com/dsnet/golib
diff --git a/hashmerge/LICENSE.md b/hashmerge/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/hashmerge/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hashmerge/go.mod b/hashmerge/go.mod
new file mode 100644
index 0000000..5e508c9
--- /dev/null
+++ b/hashmerge/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/hashmerge
+
+go 1.12
diff --git a/jsoncs/LICENSE.md b/jsoncs/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/jsoncs/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/jsoncs/decode.go b/jsoncs/decode.go
new file mode 100644
index 0000000..fd6f788
--- /dev/null
+++ b/jsoncs/decode.go
@@ -0,0 +1,340 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+ "unicode"
+ "unicode/utf16"
+ "unicode/utf8"
+)
+
+type (
+ jsonValue = interface{}
+ jsonArray = []jsonValue
+ jsonObject = map[string]jsonValue
+)
+
+var (
+ nullLiteral = []byte("null")
+ trueLiteral = []byte("true")
+ falseLiteral = []byte("false")
+)
+
+// decode unmarshals the JSON input as a JSON value.
+//
+// It is similar to:
+//
+// var v interface{}
+// err := json.Unmarshal(b, &v)
+// return v, err
+//
+// However, it is stricter than the standard library implementation in that
+// it rejects JSON objects with duplicate keys and invalid UTF-8
+// in adherence of RFC 7493.
+func decode(b []byte) (jsonValue, error) {
+ v, b, err := decodeValue(b)
+ if err != nil {
+ return nil, err
+ }
+ if len(trimSpace(b)) > 0 {
+ return nil, errors.New("invalid data after top-level value")
+ }
+ return v, nil
+}
+
+// decodeValue unmarshals the next JSON value per RFC 7159, section 3.
+// It consume the leading value, and returns the remaining bytes.
+func decodeValue(b []byte) (jsonValue, []byte, error) {
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return nil, nil, io.ErrUnexpectedEOF
+ case b[0] == '{':
+ return decodeObject(b)
+ case b[0] == '[':
+ return decodeArray(b)
+ case b[0] == '"':
+ return decodeString(b)
+ case (b[0] == '-' || ('0' <= b[0] && b[0] <= '9')):
+ return decodeNumber(b)
+ case bytes.HasPrefix(b, nullLiteral):
+ return nil, b[len(nullLiteral):], nil
+ case bytes.HasPrefix(b, trueLiteral):
+ return true, b[len(trueLiteral):], nil
+ case bytes.HasPrefix(b, falseLiteral):
+ return false, b[len(falseLiteral):], nil
+ default:
+ return nil, b, errors.New("expected next JSON value")
+ }
+}
+
+// decodeObject unmarshals the next JSON object per RFC 7159, section 4,
+// with special attention paid to RFC 7493, section 2.3
+// regarding rejection of duplicate entry names.
+// It consume the leading value, and returns the remaining bytes.
+func decodeObject(b []byte) (jsonObject, []byte, error) {
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return nil, b, io.ErrUnexpectedEOF
+ case b[0] == '{':
+ b = b[1:]
+ default:
+ return nil, b, errors.New("expected '{' character in JSON object")
+ }
+
+ var init bool
+ var obj = make(jsonObject)
+ var err error
+ for {
+ b = trimSpace(b)
+ if len(b) > 0 && b[0] == '}' {
+ return obj, b[1:], nil
+ }
+
+ if init {
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return nil, b, io.ErrUnexpectedEOF
+ case b[0] == ',':
+ b = b[1:]
+ default:
+ return nil, b, errors.New("expected ',' character in JSON object")
+ }
+ }
+
+ var k string
+ k, b, err = decodeString(b)
+ if err != nil {
+ return nil, b, err
+ }
+ if _, ok := obj[k]; ok {
+ return nil, b, fmt.Errorf("duplicate key %q in JSON object", k)
+ }
+
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return nil, b, io.ErrUnexpectedEOF
+ case b[0] == ':':
+ b = b[1:]
+ default:
+ return nil, b, errors.New("expected ':' character in JSON object")
+ }
+
+ var v jsonValue
+ v, b, err = decodeValue(b)
+ if err != nil {
+ return nil, b, err
+ }
+
+ obj[k] = v
+ init = true
+ }
+}
+
+// decodeArray unmarshals the next JSON object per RFC 7159, section 5.
+// It consume the leading value, and returns the remaining bytes.
+func decodeArray(b []byte) (jsonArray, []byte, error) {
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return nil, b, io.ErrUnexpectedEOF
+ case b[0] == '[':
+ b = b[1:]
+ default:
+ return nil, b, errors.New("expected '[' character in JSON array")
+ }
+
+ var init bool
+ var arr jsonArray
+ var err error
+ for {
+ b = trimSpace(b)
+ if len(b) > 0 && b[0] == ']' {
+ return arr, b[1:], nil
+ }
+
+ if init {
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return nil, b, io.ErrUnexpectedEOF
+ case b[0] == ',':
+ b = b[1:]
+ default:
+ return nil, b, errors.New("expected ',' character in JSON array")
+ }
+ }
+
+ var v jsonValue
+ v, b, err = decodeValue(b)
+ if err != nil {
+ return nil, b, err
+ }
+
+ arr = append(arr, v)
+ init = true
+ }
+}
+
+// decodeString unmarshals the next JSON string per RFC 7159, section 7,
+// with special attention paid to RFC 7493, section 2.1
+// regarding rejection of unpaired surrogate halves.
+// It consume the leading value, and returns the remaining bytes.
+func decodeString(b []byte) (string, []byte, error) {
+ // indexNeedEscape returns the index of the character that needs escaping.
+ indexNeedEscape := func(b []byte) int {
+ for i, r := range string(b) {
+ if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
+ return i
+ }
+ }
+ return len(b)
+ }
+
+ b = trimSpace(b)
+ switch {
+ case len(b) == 0:
+ return "", b, io.ErrUnexpectedEOF
+ case b[0] == '"':
+ b = b[1:]
+ default:
+ return "", b, errors.New("expected '\"' character in JSON string")
+ }
+
+ var s []byte
+ i := indexNeedEscape(b)
+ b, s = b[i:], append(s, b[:i]...)
+ for len(b) > 0 {
+ switch r, n := utf8.DecodeRune(b); {
+ case r == utf8.RuneError && n == 1:
+ return "", b, errors.New("invalid UTF-8 in JSON string")
+ case r < ' ':
+ return "", b, fmt.Errorf("invalid character %q in string", r)
+ case r == '"':
+ b = b[1:]
+ return string(s), b, nil
+ case r == '\\':
+ if len(b) < 2 {
+ return "", b, io.ErrUnexpectedEOF
+ }
+ switch r := b[1]; r {
+ case '"', '\\', '/':
+ b, s = b[2:], append(s, r)
+ case 'b':
+ b, s = b[2:], append(s, '\b')
+ case 'f':
+ b, s = b[2:], append(s, '\f')
+ case 'n':
+ b, s = b[2:], append(s, '\n')
+ case 'r':
+ b, s = b[2:], append(s, '\r')
+ case 't':
+ b, s = b[2:], append(s, '\t')
+ case 'u':
+ if len(b) < 6 {
+ return "", b, io.ErrUnexpectedEOF
+ }
+ v, err := strconv.ParseUint(string(b[2:6]), 16, 16)
+ if err != nil {
+ return "", b, fmt.Errorf("invalid escape code %q in string", b[:6])
+ }
+ b = b[6:]
+
+ r := rune(v)
+ if utf16.IsSurrogate(r) {
+ if len(b) < 6 {
+ return "", b, io.ErrUnexpectedEOF
+ }
+ v, err := strconv.ParseUint(string(b[2:6]), 16, 16)
+ r = utf16.DecodeRune(r, rune(v))
+ if b[0] != '\\' || b[1] != 'u' || r == unicode.ReplacementChar || err != nil {
+ return "", b, fmt.Errorf("invalid escape code %q in string", b[:6])
+ }
+ b = b[6:]
+ }
+ s = append(s, string(r)...)
+ default:
+ return "", b, fmt.Errorf("invalid escape code %q in string", b[:2])
+ }
+ default:
+ i := indexNeedEscape(b[n:])
+ b, s = b[n+i:], append(s, b[:n+i]...)
+ }
+ }
+ return "", b, io.ErrUnexpectedEOF
+}
+
+// decodeNumber unmarshals the next JSON number per RFC 7159, section 6.
+// It consume the leading value, and returns the remaining bytes.
+func decodeNumber(b []byte) (float64, []byte, error) {
+ b = trimSpace(b)
+ b0 := b
+ if len(b) > 0 && b[0] == '-' {
+ b = b[1:]
+ }
+ switch {
+ case len(b) == 0:
+ return 0, b, io.ErrUnexpectedEOF
+ case b[0] == '0':
+ b = b[1:]
+ case '1' <= b[0] && b[0] <= '9':
+ b = b[1:]
+ for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+ b = b[1:]
+ }
+ default:
+ return 0, nil, errors.New("expected digit character in JSON number")
+ }
+ if len(b) > 0 && b[0] == '.' {
+ b = b[1:]
+ switch {
+ case len(b) == 0:
+ return 0, b, io.ErrUnexpectedEOF
+ case '0' <= b[0] && b[0] <= '9':
+ b = b[1:]
+ default:
+ return 0, nil, errors.New("expected digit character in JSON number")
+ }
+ for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+ b = b[1:]
+ }
+ }
+ if len(b) > 0 && (b[0] == 'e' || b[0] == 'E') {
+ b = b[1:]
+ if len(b) > 0 && (b[0] == '-' || b[0] == '+') {
+ b = b[1:]
+ }
+ switch {
+ case len(b) == 0:
+ return 0, b, io.ErrUnexpectedEOF
+ case '0' <= b[0] && b[0] <= '9':
+ b = b[1:]
+ default:
+ return 0, nil, errors.New("expected digit character in JSON number")
+ }
+ for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+ b = b[1:]
+ }
+ }
+
+ f, err := strconv.ParseFloat(string(b0[:len(b0)-len(b)]), 64)
+ if err != nil {
+ return 0, b, fmt.Errorf("invalid JSON number: %s", b0[:len(b0)-len(b)])
+ }
+ return f, b, nil
+}
+
+// trimSpace strips leading whitespace per RFC 7159, section 2.
+func trimSpace(b []byte) []byte {
+ return bytes.TrimLeft(b, " \t\n\r")
+}
diff --git a/jsoncs/float.go b/jsoncs/float.go
new file mode 100644
index 0000000..7bd7e87
--- /dev/null
+++ b/jsoncs/float.go
@@ -0,0 +1,140 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "bytes"
+ "math"
+ "strconv"
+)
+
+// BUG(https://golang.org/issue/29491): On Go 1.12 and earlier, float formatting
+// produces incorrect results in some rare cases due to a rounding error.
+
+// formatES6 appends a canonically formatted float according to ES6,
+// formally defined in ECMA-262, 6th edition, section 7.1.12.1.
+// This implements the formatting specified in RFC 8785, section 3.2.2.3,
+// except that this handles NaNs and Infinity.
+func formatES6(b []byte, m float64) []byte {
+ switch {
+ // 1. If m is NaN, return the String "NaN".
+ case math.IsNaN(m):
+ return append(b, "NaN"...)
+ // 2. If m is +0 or -0, return the String "0".
+ case m == 0:
+ return append(b, "0"...)
+ // 3. If m is less than zero, return the String concatenation of the String "-" and ToString(-m).
+ case m < 0:
+ return formatES6(append(b, '-'), -m)
+ // 4. If m is +∞, return the String "Infinity".
+ case math.IsInf(m, +1):
+ return append(b, "Infinity"...)
+ }
+
+ // 5. Otherwise, let n, k, and s be integers such that k ≥ 1, 10^(k-1) ≤ s < 10^k,
+ // the Number value for s ⨯ 10^(n-k) is m, and k is as small as possible.
+ // If there are multiple possibilities for s, choose the value of s for which s ⨯ 10^(n-k)
+ // is closest in value to m. If there are two such possible values of s, choose the one that is even.
+ // Note that k is the number of digits in the decimal representation of s
+ // and that s is not divisible by 10.
+ var n, k int64
+ var s []byte // decimal representation of s
+ {
+ // Unfortunately strconv.FormatFloat does not directly expose n, k, s,
+ // nor is the output defined as stable in any way.
+ // However, it's implementation is guaranteed to produce precise n, k, s values.
+ // Format a float with the 'e' format and derive n, k, s from the output.
+ var arr [32]byte
+ b := strconv.AppendFloat(arr[:0], m, 'e', -1, 64) // e.g., "d.dddde±dd"
+
+ // Parse the exponent.
+ i := bytes.IndexByte(b, 'e')
+ nk, err := strconv.ParseInt(string(b[i+len("e"):]), 10, 64) // i.e., n-k
+ if err != nil {
+ panic("BUG: unexpected strconv.ParseInt error: " + err.Error())
+ }
+
+ // Format the significand.
+ s = b[:i]
+ if len(b) > 1 && b[1] == '.' {
+ s[1], s = s[0], s[1:] // e.g., "d.dddd" => "ddddd"
+ for len(s) > 1 && s[len(s)-1] == '0' {
+ s = s[:len(s)-1] // trim trailing zeros
+ }
+ nk -= int64(len(s) - 1)
+ }
+
+ k = int64(len(s)) // k is the number of digits in the decimal representation of s
+ n = nk + k // nk=n-k => n=nk+k
+ }
+
+ const zeros = "000000000000000000000"
+ switch {
+ // 6. If k ≤ n ≤ 21, …
+ case k <= n && n <= 21:
+ // … return the String consisting of the code units of the k digits of
+ // the decimal representation of s (in order, with no leading zeros), …
+ b = append(b, s...)
+ // … followed by n-k occurrences of the code unit 0x0030 (DIGIT ZERO).
+ b = append(b, zeros[:n-k]...)
+
+ // 7. If 0 < n ≤ 21, …
+ case 0 < n && n <= 21:
+ // … return the String consisting of the code units of the
+ // most significant n digits of the decimal representation of s, …
+ b = append(b, s[:n]...)
+ // … followed by the code unit 0x002E (FULL STOP), …
+ b = append(b, '.')
+ // … followed by the code units of the remaining k-n digits of the decimal representation of s.
+ b = append(b, s[n:]...)
+
+ // 8. If -6 < n ≤ 0, …
+ case -6 < n && n <= 0:
+ // … return the String consisting of the code unit 0x0030 (DIGIT ZERO), …
+ b = append(b, '0')
+ // … followed by the code unit 0x002E (FULL STOP), …
+ b = append(b, '.')
+ // … followed by -n occurrences of the code unit 0x0030 (DIGIT ZERO), …
+ b = append(b, zeros[:-n]...)
+ // … followed by the code units of the k digits of the decimal representation of s.
+ b = append(b, s...)
+
+ // 9. If k = 1, …
+ case k == 1:
+ // … return the String consisting of the code unit of the single digit of s, …
+ b = append(b, s...)
+ // … followed by code unit 0x0065 (LATIN SMALL LETTER E), …
+ b = append(b, 'e')
+ // … followed by code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS) according to whether n-1 is positive or negative, …
+ // … followed by the code units of the decimal representation of the integer abs(n-1) (with no leading zeroes).
+ switch {
+ case n-1 > 0:
+ b = strconv.AppendInt(append(b, '+'), n-1, 10)
+ case n-1 < 0:
+ b = strconv.AppendInt(append(b, '-'), 1-n, 10)
+ }
+
+ // 10. Otherwise …
+ default:
+ // … return the String consisting of the code units of the
+ // most significant digit of the decimal representation of s, …
+ b = append(b, s[0])
+ // … followed by code unit 0x002E (FULL STOP), …
+ b = append(b, '.')
+ // … followed by the code units of the remaining k-1 digits of the decimal representation of s, …
+ b = append(b, s[1:]...)
+ // … followed by code unit 0x0065 (LATIN SMALL LETTER E), …
+ b = append(b, 'e')
+ // … followed by code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS) according to whether n-1 is positive or negative, …
+ // … followed by the code units of the decimal representation of the integer abs(n-1) (with no leading zeroes).
+ switch {
+ case n-1 > 0:
+ b = strconv.AppendInt(append(b, '+'), n-1, 10)
+ case n-1 < 0:
+ b = strconv.AppendInt(append(b, '-'), 1-n, 10)
+ }
+ }
+ return b
+}
diff --git a/jsoncs/float_test.go b/jsoncs/float_test.go
new file mode 100644
index 0000000..bf22bb9
--- /dev/null
+++ b/jsoncs/float_test.go
@@ -0,0 +1,58 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "math"
+ "testing"
+)
+
+func TestFormatES6(t *testing.T) {
+ tests := []struct {
+ in float64
+ want string
+ }{
+ {math.NaN(), "NaN"},
+ {math.Inf(-1), "-Infinity"},
+ {math.Inf(+1), "Infinity"},
+ {math.Copysign(0, -1), "0"},
+ {math.Copysign(0, +1), "0"},
+
+ // The following cases are from RFC 8785, Appendix B.
+ {math.Float64frombits(0x0000000000000000), "0"},
+ {math.Float64frombits(0x8000000000000000), "0"},
+ {math.Float64frombits(0x0000000000000001), "5e-324"},
+ {math.Float64frombits(0x8000000000000001), "-5e-324"},
+ {math.Float64frombits(0x7fefffffffffffff), "1.7976931348623157e+308"},
+ {math.Float64frombits(0xffefffffffffffff), "-1.7976931348623157e+308"},
+ {math.Float64frombits(0x4340000000000000), "9007199254740992"},
+ {math.Float64frombits(0xc340000000000000), "-9007199254740992"},
+ {math.Float64frombits(0x4430000000000000), "295147905179352830000"},
+ {math.Float64frombits(0x7fffffffffffffff), "NaN"},
+ {math.Float64frombits(0x7ff0000000000000), "Infinity"},
+ {math.Float64frombits(0x44b52d02c7e14af5), "9.999999999999997e+22"},
+ {math.Float64frombits(0x44b52d02c7e14af6), "1e+23"},
+ {math.Float64frombits(0x44b52d02c7e14af7), "1.0000000000000001e+23"},
+ {math.Float64frombits(0x444b1ae4d6e2ef4e), "999999999999999700000"},
+ {math.Float64frombits(0x444b1ae4d6e2ef4f), "999999999999999900000"},
+ {math.Float64frombits(0x444b1ae4d6e2ef50), "1e+21"},
+ {math.Float64frombits(0x3eb0c6f7a0b5ed8c), "9.999999999999997e-7"},
+ {math.Float64frombits(0x3eb0c6f7a0b5ed8d), "0.000001"},
+ {math.Float64frombits(0x41b3de4355555553), "333333333.3333332"},
+ {math.Float64frombits(0x41b3de4355555554), "333333333.33333325"},
+ {math.Float64frombits(0x41b3de4355555555), "333333333.3333333"},
+ {math.Float64frombits(0x41b3de4355555556), "333333333.3333334"},
+ {math.Float64frombits(0x41b3de4355555557), "333333333.33333343"},
+ {math.Float64frombits(0xbecbf647612f3696), "-0.0000033333333333333333"},
+ {math.Float64frombits(0x43143ff3c1cb0959), "1424953923781206.2"},
+ }
+
+ for _, tt := range tests {
+ got := string(formatES6(nil, tt.in))
+ if got != tt.want {
+ t.Errorf("formatES6(%016x) = %v, want %v", math.Float64bits(tt.in), got, tt.want)
+ }
+ }
+}
diff --git a/jsoncs/format.go b/jsoncs/format.go
new file mode 100644
index 0000000..9cbeb90
--- /dev/null
+++ b/jsoncs/format.go
@@ -0,0 +1,158 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// Package jsoncs implements of the JSON Canonicalization Scheme (JCS)
+// as specified in RFC 8785.
+package jsoncs
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "math"
+ "math/bits"
+ "sort"
+ "strconv"
+ "unicode/utf8"
+)
+
+// Format transforms the JSON input to its canonical form.
+// The input must comply with RFC 7493.
+// It reuses the provided input buffer.
+func Format(b []byte) ([]byte, error) {
+ if Valid(b) {
+ return b, nil
+ }
+ v, err := decode(b)
+ if err != nil {
+ return nil, err
+ }
+ return formatValue(b[:0], v)
+}
+
+// formatValue canonically marshals a JSON value.
+func formatValue(b []byte, v jsonValue) ([]byte, error) {
+ switch v := v.(type) {
+ case jsonObject:
+ return formatObject(b, v)
+ case jsonArray:
+ return formatArray(b, v)
+ case string:
+ return formatString(b, v)
+ case float64:
+ return formatNumber(b, v)
+ case nil:
+ return append(b, nullLiteral...), nil
+ case bool:
+ switch v {
+ case true:
+ return append(b, trueLiteral...), nil
+ case false:
+ return append(b, falseLiteral...), nil
+ }
+ }
+ return nil, fmt.Errorf("invalid type: %T", v)
+}
+
+// formatObject canonically marshals a JSON object per RFC 8785, section 3.2.3.
+func formatObject(b []byte, obj jsonObject) ([]byte, error) {
+ var ks []string
+ for k := range obj {
+ ks = append(ks, k)
+ }
+ sort.Slice(ks, func(i, j int) bool {
+ return lessUTF16(ks[i], ks[j])
+ })
+
+ var err error
+ b = append(b, '{')
+ for _, k := range ks {
+ b, err = formatString(b, k)
+ if err != nil {
+ return nil, err
+ }
+ b = append(b, ':')
+ b, err = formatValue(b, obj[k])
+ if err != nil {
+ return nil, err
+ }
+ b = append(b, ',')
+ }
+ b = bytes.TrimRight(b, ",")
+ b = append(b, '}')
+ return b, nil
+}
+
+// formatArray canonically marshals a JSON array.
+func formatArray(b []byte, arr jsonArray) ([]byte, error) {
+ var err error
+ b = append(b, '[')
+ for _, v := range arr {
+ b, err = formatValue(b, v)
+ if err != nil {
+ return nil, err
+ }
+ b = append(b, ',')
+ }
+ b = bytes.TrimRight(b, ",")
+ b = append(b, ']')
+ return b, nil
+}
+
+// formatArray canonically marshals a JSON string per RFC 8785, section 3.2.2.2.
+func formatString(b []byte, s string) ([]byte, error) {
+ // indexNeedEscape returns the index of the character that needs escaping.
+ indexNeedEscape := func(s string) int {
+ for i, r := range s {
+ if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
+ return i
+ }
+ }
+ return len(s)
+ }
+
+ b = append(b, '"')
+ i := indexNeedEscape(s)
+ s, b = s[i:], append(b, s[:i]...)
+ for len(s) > 0 {
+ switch r, n := utf8.DecodeRuneInString(s); {
+ case r == utf8.RuneError && n == 1:
+ return nil, errors.New("invalid UTF-8 in string")
+ case r < ' ' || r == '"' || r == '\\':
+ b = append(b, '\\')
+ switch r {
+ case '"', '\\':
+ b = append(b, byte(r))
+ case '\b':
+ b = append(b, 'b')
+ case '\f':
+ b = append(b, 'f')
+ case '\n':
+ b = append(b, 'n')
+ case '\r':
+ b = append(b, 'r')
+ case '\t':
+ b = append(b, 't')
+ default:
+ b = append(b, 'u')
+ b = append(b, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
+ b = strconv.AppendUint(b, uint64(r), 16)
+ }
+ s = s[n:]
+ default:
+ i := indexNeedEscape(s[n:])
+ s, b = s[n+i:], append(b, s[:n+i]...)
+ }
+ }
+ b = append(b, '"')
+ return b, nil
+}
+
+// formatNumber canonically marshals a JSON number per RFC 8785, section 3.2.2.3.
+func formatNumber(b []byte, f float64) ([]byte, error) {
+ if math.IsNaN(f) || math.IsInf(f, 0) {
+ return nil, fmt.Errorf("invalid float value: %v", f)
+ }
+ return formatES6(b, f), nil
+}
diff --git a/jsoncs/format_test.go b/jsoncs/format_test.go
new file mode 100644
index 0000000..03f7309
--- /dev/null
+++ b/jsoncs/format_test.go
@@ -0,0 +1,485 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "io"
+ "strings"
+ "testing"
+ "unicode/utf8"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+)
+
+type containsError string
+
+func (e containsError) Error() string {
+ return string(e)
+}
+func (e containsError) Is(e2 error) bool {
+ if e2 == nil {
+ return false
+ }
+ return strings.Contains(e2.Error(), string(e))
+}
+
+func TestFormat(t *testing.T) {
+ tests := []struct {
+ inJSON string
+ wantJSON string
+ wantErr error
+ }{{
+ inJSON: "",
+ wantErr: io.ErrUnexpectedEOF,
+ }, {
+ inJSON: "?",
+ wantErr: containsError("expected next JSON value"),
+ }, {
+ inJSON: "null",
+ wantJSON: "null",
+ }, {
+ inJSON: " \n\r\tnull \n\r\t",
+ wantJSON: "null",
+ }, {
+ inJSON: "nullnull",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "true",
+ wantJSON: "true",
+ }, {
+ inJSON: " \n\r\ttrue \n\r\t",
+ wantJSON: "true",
+ }, {
+ inJSON: "truetrue",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "false",
+ wantJSON: "false",
+ }, {
+ inJSON: " \n\r\tfalse \n\r\t",
+ wantJSON: "false",
+ }, {
+ inJSON: "falsefalse",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "0",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0",
+ wantJSON: "0",
+ }, {
+ inJSON: "1",
+ wantJSON: "1",
+ }, {
+ inJSON: "-1",
+ wantJSON: "-1",
+ }, {
+ inJSON: "0.1",
+ wantJSON: "0.1",
+ }, {
+ inJSON: "-0.1",
+ wantJSON: "-0.1",
+ }, {
+ inJSON: "1234",
+ wantJSON: "1234",
+ }, {
+ inJSON: "-1234",
+ wantJSON: "-1234",
+ }, {
+ inJSON: "12.34",
+ wantJSON: "12.34",
+ }, {
+ inJSON: "-12.34",
+ wantJSON: "-12.34",
+ }, {
+ inJSON: "12E0",
+ wantJSON: "12",
+ }, {
+ inJSON: "12E1",
+ wantJSON: "120",
+ }, {
+ inJSON: "12e34",
+ wantJSON: "1.2e+35",
+ }, {
+ inJSON: "12E-0",
+ wantJSON: "12",
+ }, {
+ inJSON: "12e+1",
+ wantJSON: "120",
+ }, {
+ inJSON: "12e-34",
+ wantJSON: "1.2e-33",
+ }, {
+ inJSON: "-12E0",
+ wantJSON: "-12",
+ }, {
+ inJSON: "-12E1",
+ wantJSON: "-120",
+ }, {
+ inJSON: "-12e34",
+ wantJSON: "-1.2e+35",
+ }, {
+ inJSON: "-12E-0",
+ wantJSON: "-12",
+ }, {
+ inJSON: "-12e+1",
+ wantJSON: "-120",
+ }, {
+ inJSON: "-12e-34",
+ wantJSON: "-1.2e-33",
+ }, {
+ inJSON: "1.2E0",
+ wantJSON: "1.2",
+ }, {
+ inJSON: "1.2E1",
+ wantJSON: "12",
+ }, {
+ inJSON: "1.2e34",
+ wantJSON: "1.2e+34",
+ }, {
+ inJSON: "1.2E-0",
+ wantJSON: "1.2",
+ }, {
+ inJSON: "1.2e+1",
+ wantJSON: "12",
+ }, {
+ inJSON: "1.2e-34",
+ wantJSON: "1.2e-34",
+ }, {
+ inJSON: "-1.2E0",
+ wantJSON: "-1.2",
+ }, {
+ inJSON: "-1.2E1",
+ wantJSON: "-12",
+ }, {
+ inJSON: "-1.2e34",
+ wantJSON: "-1.2e+34",
+ }, {
+ inJSON: "-1.2E-0",
+ wantJSON: "-1.2",
+ }, {
+ inJSON: "-1.2e+1",
+ wantJSON: "-12",
+ }, {
+ inJSON: "-1.2e-34",
+ wantJSON: "-1.2e-34",
+ }, {
+ inJSON: "0E0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0E1",
+ wantJSON: "0",
+ }, {
+ inJSON: "0e34",
+ wantJSON: "0",
+ }, {
+ inJSON: "0E-0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0e+1",
+ wantJSON: "0",
+ }, {
+ inJSON: "0e-34",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0E0",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0E1",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0e34",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0E-0",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0e+1",
+ wantJSON: "0",
+ }, {
+ inJSON: "-0e-34",
+ wantJSON: "0",
+ }, {
+ inJSON: "12345678901234567890",
+ wantJSON: "12345678901234567000",
+ }, {
+ inJSON: "-123456789.0123456789e+0123",
+ wantJSON: "-1.2345678901234568e+131",
+ }, {
+ inJSON: " \n\r\t-123456789.0123456789e+0123 \n\r\t",
+ wantJSON: "-1.2345678901234568e+131",
+ }, {
+ inJSON: "- 123456789.0123456789e+0123",
+ wantErr: containsError("expected digit character in JSON number"),
+ }, {
+ inJSON: "-123456789 .0123456789e+0123",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "-123456789. 0123456789e+0123",
+ wantErr: containsError("expected digit character in JSON number"),
+ }, {
+ inJSON: "-123456789.0123456789 e+0123",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "-123456789.0123456789e +0123",
+ wantErr: containsError("expected digit character in JSON number"),
+ }, {
+ inJSON: "-123456789.0123456789e+ 0123",
+ wantErr: containsError("expected digit character in JSON number"),
+ }, {
+ inJSON: "+0",
+ wantErr: containsError("expected next JSON value"),
+ }, {
+ inJSON: "00",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "01",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "0f",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: "0.e0",
+ wantErr: containsError("expected digit character in JSON number"),
+ }, {
+ inJSON: "0.0E0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0.0E+0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0.0E-0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0.0E+0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0.0E-0",
+ wantJSON: "0",
+ }, {
+ inJSON: "0.0E0.",
+ wantErr: containsError("invalid data"),
+ }, {
+ inJSON: `""`,
+ wantJSON: `""`,
+ }, {
+ inJSON: " \n\r\t\"\" \n\r\t",
+ wantJSON: `""`,
+ }, {
+ inJSON: " \n\r\t\"hello\" \n\r\t",
+ wantJSON: `"hello"`,
+ }, {
+ inJSON: "\"\x00\"",
+ wantErr: containsError("invalid character"),
+ }, {
+ inJSON: "\"\xff\"",
+ wantErr: containsError("invalid UTF-8"),
+ }, {
+ inJSON: `"` + string(utf8.RuneError) + `"`,
+ wantJSON: `"` + string(utf8.RuneError) + `"`,
+ }, {
+ inJSON: `"\uFFFD"`,
+ wantJSON: "\"\uFFFD\"",
+ }, {
+ inJSON: `"\x"`,
+ wantErr: containsError("invalid escape code"),
+ }, {
+ inJSON: `"\uXXXX"`,
+ wantErr: containsError("invalid escape code"),
+ }, {
+ inJSON: `"\uDEAD"`, // unmatched surrogate pair
+ wantErr: io.ErrUnexpectedEOF,
+ }, {
+ inJSON: `"\uDEAD______"`, // unmatched surrogate pair
+ wantErr: containsError("invalid escape code"),
+ }, {
+ inJSON: `"\uDEAD\uBEEF"`, // invalid surrogate half
+ wantErr: containsError("invalid escape code"),
+ }, {
+ inJSON: `"\uD800\udead"`, // valid surrogate pair
+ wantJSON: `"𐊭"`,
+ }, {
+ inJSON: `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f"`,
+ wantJSON: `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f"`,
+ }, {
+ inJSON: `"\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"`,
+ wantJSON: `"\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"`,
+ }, {
+ inJSON: `"\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002a\u002b\u002c\u002d\u002e\u002f"`,
+ wantJSON: `" !\"#$%&'()*+,-./"`,
+ }, {
+ inJSON: `"\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003a\u003b\u003c\u003d\u003e\u003f"`,
+ wantJSON: `"0123456789:;<=>?"`,
+ }, {
+ inJSON: `"\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f"`,
+ wantJSON: `"@ABCDEFGHIJKLMNO"`,
+ }, {
+ inJSON: `"\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u005b\u005c\u005d\u005e\u005f"`,
+ wantJSON: `"PQRSTUVWXYZ[\\]^_"`,
+ }, {
+ inJSON: `"\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f"`,
+ wantJSON: "\"`abcdefghijklmno\"",
+ }, {
+ inJSON: `"\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u007b\u007c\u007d\u007e\u007f"`,
+ wantJSON: "\"pqrstuvwxyz{|}~\u007f\"",
+ }, {
+ inJSON: `"\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f"`,
+ wantJSON: "\"\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f\"",
+ }, {
+ inJSON: `"\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f"`,
+ wantJSON: "\"\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f\"",
+ }, {
+ inJSON: `"\u00a0\u00a1\u00a2\u00a3\u00a4\u00a5\u00a6\u00a7\u00a8\u00a9\u00aa\u00ab\u00ac\u00ad\u00ae\u00af"`,
+ wantJSON: "\"\u00a0¡¢£¤¥¦§¨©ª«¬\u00ad®¯\"",
+ }, {
+ inJSON: `"\u00b0\u00b1\u00b2\u00b3\u00b4\u00b5\u00b6\u00b7\u00b8\u00b9\u00ba\u00bb\u00bc\u00bd\u00be\u00bf"`,
+ wantJSON: `"°±²³´µ¶·¸¹º»¼½¾¿"`,
+ }, {
+ inJSON: `"\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf"`,
+ wantJSON: `"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ"`,
+ }, {
+ inJSON: `"\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d7\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u00df"`,
+ wantJSON: `"ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß"`,
+ }, {
+ inJSON: `"\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef"`,
+ wantJSON: `"àáâãäåæçèéêëìíîï"`,
+ }, {
+ inJSON: `"\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00fe\u00ff"`,
+ wantJSON: `"ðñòóôõö÷øùúûüýþÿ"`,
+ }, {
+ inJSON: `"בְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ׃"`,
+ wantJSON: `"בְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ׃"`,
+ }, {
+ inJSON: `"ἐν ἀρχῇ ἐποίησεν ὁ θεὸς τὸν οὐρανὸν καὶ τὴν γῆν"`,
+ wantJSON: `"ἐν ἀρχῇ ἐποίησεν ὁ θεὸς τὸν οὐρανὸν καὶ τὴν γῆν"`,
+ }, {
+ inJSON: `"في البدء خلق الله السموات والارض."`,
+ wantJSON: `"في البدء خلق الله السموات والارض."`,
+ }, {
+ inJSON: `"起 初 , 神 创 造 天 地 。"`,
+ wantJSON: `"起 初 , 神 创 造 天 地 。"`,
+ }, {
+ inJSON: `"ในเริ่มแรกนั้นพระเจ้าทรงเนรมิตสร้างฟ้าและแผ่นดินโลก"`,
+ wantJSON: `"ในเริ่มแรกนั้นพระเจ้าทรงเนรมิตสร้างฟ้าและแผ่นดินโลก"`,
+ }, {
+ inJSON: "[]",
+ wantJSON: "[]",
+ }, {
+ inJSON: " \n\r\t[ \n\r\t] \n\r\t",
+ wantJSON: "[]",
+ }, {
+ inJSON: " \n\r\t[ \n\r\t0 \n\r\t] \n\r\t",
+ wantJSON: "[0]",
+ }, {
+ inJSON: " \n\r\t[ \n\r\t0 \n\r\t, \n\r\t1 \n\r\t] \n\r\t",
+ wantJSON: "[0,1]",
+ }, {
+ inJSON: "[0{}]",
+ wantErr: containsError("expected ',' character in JSON array"),
+ }, {
+ inJSON: "[][]",
+ wantErr: containsError("invalid data after top-level value"),
+ }, {
+ inJSON: "{}",
+ wantJSON: "{}",
+ }, {
+ inJSON: " \n\r\t{ \n\r\t} \n\r\t",
+ wantJSON: "{}",
+ }, {
+ inJSON: " \n\r\t{ \n\r\t\"0\" \n\r\t: \n\r\t0 \n\r\t} \n\r\t",
+ wantJSON: `{"0":0}`,
+ }, {
+ inJSON: " \n\r\t{ \n\r\t\"0\" \n\r\t: \n\r\t0 \n\r\t, \n\r\t\"1\" \n\r\t: \n\r\t1 \n\r\t} \n\r\t",
+ wantJSON: `{"0":0,"1":1}`,
+ }, {
+ inJSON: `{"k"}`,
+ wantErr: containsError("expected ':' character in JSON object"),
+ }, {
+ inJSON: `{"k":0,}`,
+ wantErr: containsError("expected '\"' character in JSON string"),
+ }, {
+ inJSON: `{"k":0[]}`,
+ wantErr: containsError("expected ',' character in JSON object"),
+ }, {
+ inJSON: `{"k":0,"\u006b":0}`,
+ wantErr: containsError("duplicate key \"k\" in JSON object"),
+ }, {
+ inJSON: "{}{}",
+ wantErr: containsError("invalid data after top-level value"),
+ }, {
+ inJSON: `[
+ 56,
+ {
+ "d": true,
+ "10": null,
+ "1": [ ]
+ }
+ ]`,
+ wantJSON: `[56,{"1":[],"10":null,"d":true}]`,
+ }, {
+ inJSON: `{
+ "peach": "This sorting order",
+ "péché": "is wrong according to French",
+ "pêche": "but canonicalization MUST",
+ "sin": "ignore locale"
+ }`,
+ wantJSON: `{"peach":"This sorting order","péché":"is wrong according to French","pêche":"but canonicalization MUST","sin":"ignore locale"}`,
+ }, {
+ inJSON: `{
+ "1": {"f": {"f": "hi","F": 5} ,"\n": 56.0},
+ "10": { },
+ "": "empty",
+ "a": { },
+ "111": [ {"e": "yes","E": "no" } ],
+ "A": { }
+ }`,
+ wantJSON: `{"":"empty","1":{"\n":56,"f":{"F":5,"f":"hi"}},"10":{},"111":[{"E":"no","e":"yes"}],"A":{},"a":{}}`,
+ }, {
+ inJSON: `{
+ "Unnormalized Unicode":"A\u030a"
+ }`,
+ wantJSON: `{"Unnormalized Unicode":"Å"}`,
+ }, {
+ inJSON: `{
+ "numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001],
+ "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
+ "literals": [null, true, false]
+ }`,
+ wantJSON: `{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}`,
+ }, {
+ inJSON: `{
+ "\u20ac": "Euro Sign",
+ "\r": "Carriage Return",
+ "\u000a": "Newline",
+ "1": "One",
+ "\u0080": "Control\u007f",
+ "\ud83d\ude02": "Smiley",
+ "\u00f6": "Latin Small Letter O With Diaeresis",
+ "\ufb33": "Hebrew Letter Dalet With Dagesh",
+ "</script>": "Browser Challenge"
+ }`,
+ wantJSON: `{"\n":"Newline","\r":"Carriage Return","1":"One","</script>":"Browser Challenge","` + "\u0080" + `":"Control` + "\u007f" + `","ö":"Latin Small Letter O With Diaeresis","€":"Euro Sign","😂":"Smiley","דּ":"Hebrew Letter Dalet With Dagesh"}`,
+ }}
+
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ t.Logf("Input Data: %q", tt.inJSON)
+ gotJSON, gotErr := Format([]byte(tt.inJSON))
+ if diff := cmp.Diff(string(tt.wantJSON), string(gotJSON)); diff != "" {
+ t.Errorf("Format output mismatch (-want +got):\n%s", diff)
+ }
+ if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
+ t.Errorf("Format output mismatch (-want +got):\n%s", diff)
+ }
+
+ gotValid := Valid([]byte(tt.inJSON))
+ wantValid := string(tt.inJSON) == string(gotJSON) && gotErr == nil
+ if gotValid != wantValid {
+ t.Errorf("Valid() = %v, want %v", gotValid, wantValid)
+ }
+ })
+ }
+}
diff --git a/jsoncs/go.mod b/jsoncs/go.mod
new file mode 100644
index 0000000..75f5934
--- /dev/null
+++ b/jsoncs/go.mod
@@ -0,0 +1,5 @@
+module github.com/dsnet/golib/jsoncs
+
+go 1.9
+
+require github.com/google/go-cmp v0.5.1
diff --git a/jsoncs/go.sum b/jsoncs/go.sum
new file mode 100644
index 0000000..6295580
--- /dev/null
+++ b/jsoncs/go.sum
@@ -0,0 +1,4 @@
+github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/jsoncs/string.go b/jsoncs/string.go
new file mode 100644
index 0000000..45c3b7a
--- /dev/null
+++ b/jsoncs/string.go
@@ -0,0 +1,66 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "unicode/utf16"
+ "unicode/utf8"
+)
+
+// lessUTF16 reports whether x is lexicographically less than y according
+// to the UTF-16 codepoints of the UTF-8 encoded input strings.
+// This implements the ordering specified in RFC 8785, section 3.2.3.
+func lessUTF16(x, y string) bool {
+ for {
+ if len(x) == 0 || len(y) == 0 {
+ return len(x) < len(y)
+ }
+
+ // ASCII fast-path.
+ if x[0] < utf8.RuneSelf || y[0] < utf8.RuneSelf {
+ if x[0] != y[0] {
+ return x[0] < y[0]
+ }
+ x, y = x[1:], y[1:]
+ continue
+ }
+
+ // Decode next pair of runes as UTF-8.
+ rx, nx := utf8.DecodeRuneInString(x)
+ ry, ny := utf8.DecodeRuneInString(y)
+ switch {
+
+ // Both runes encode as either a single or surrogate pair
+ // of UTF-16 codepoints.
+ case isUTF16Self(rx) == isUTF16Self(ry):
+ if rx != ry {
+ return rx < ry
+ }
+
+ // The x rune is a single UTF-16 codepoint, while
+ // the y rune is a surrogate pair of UTF-16 codepoints.
+ case isUTF16Self(rx):
+ ry, _ := utf16.EncodeRune(ry)
+ if rx != ry {
+ return rx < ry
+ }
+ panic("invalid UTF-8") // implies rx is an unpaired surrogate half
+
+ // The y rune is a single UTF-16 codepoint, while
+ // the x rune is a surrogate pair of UTF-16 codepoints.
+ case isUTF16Self(ry):
+ rx, _ := utf16.EncodeRune(rx)
+ if rx != ry {
+ return rx < ry
+ }
+ panic("invalid UTF-8") // implies ry is an unpaired surrogate half
+ }
+ x, y = x[nx:], y[ny:]
+ }
+}
+
+func isUTF16Self(r rune) bool {
+ return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF')
+}
diff --git a/jsoncs/string_test.go b/jsoncs/string_test.go
new file mode 100644
index 0000000..1ea8cc5
--- /dev/null
+++ b/jsoncs/string_test.go
@@ -0,0 +1,54 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "math/rand"
+ "sort"
+ "testing"
+ "unicode/utf16"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestLessUTF16(t *testing.T) {
+ want := []string{"", "\r", "1", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"}
+
+ got1 := append([]string(nil), want...)
+ got2 := append([]string(nil), want...)
+ for i, j := range rand.Perm(len(want)) {
+ got1[i], got1[j] = got1[j], got1[i]
+ got2[i], got2[j] = got2[j], got2[i]
+ }
+
+ // Sort using optimized lessUTF16 implementation.
+ sort.Slice(got1, func(i, j int) bool {
+ return lessUTF16(got1[i], got1[j])
+ })
+ if diff := cmp.Diff(want, got1); diff != "" {
+ t.Errorf("sort.Slice(LessUTF16.Optimized) mismatch (-want +got)\n%s", diff)
+ }
+
+ // Sort using simple, but slow lessUTF16 implementation.
+ lessUTF16 := func(x, y string) bool {
+ ux := utf16.Encode([]rune(x))
+ uy := utf16.Encode([]rune(y))
+ for {
+ if len(ux) == 0 || len(uy) == 0 {
+ return len(ux) < len(uy)
+ }
+ if ux[0] != uy[0] {
+ return ux[0] < uy[0]
+ }
+ ux, uy = ux[1:], uy[1:]
+ }
+ }
+ sort.Slice(got2, func(i, j int) bool {
+ return lessUTF16(got2[i], got2[j])
+ })
+ if diff := cmp.Diff(want, got2); diff != "" {
+ t.Errorf("sort.Slice(LessUTF16.Simplified) mismatch (-want +got)\n%s", diff)
+ }
+}
diff --git a/jsoncs/valid.go b/jsoncs/valid.go
new file mode 100644
index 0000000..a88be37
--- /dev/null
+++ b/jsoncs/valid.go
@@ -0,0 +1,194 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+ "bytes"
+)
+
+// Valid reports whether the JSON input is in canonical form.
+// Invalid JSON input is reported as false.
+func Valid(b []byte) bool {
+ b, ok := validValue(b)
+ return ok && len(b) == 0
+}
+
+// validValue reports whether the next JSON value is in its canonical form.
+// It consume the leading value, and returns the remaining bytes.
+func validValue(b []byte) ([]byte, bool) {
+ switch {
+ case len(b) > 0 && b[0] == '{':
+ return validObject(b)
+ case len(b) > 0 && b[0] == '[':
+ return validArray(b)
+ case len(b) > 0 && b[0] == '"':
+ return validString(b)
+ case len(b) > 0 && (b[0] == '-' || ('0' <= b[0] && b[0] <= '9')):
+ return validNumber(b)
+ case bytes.HasPrefix(b, nullLiteral):
+ return b[len(nullLiteral):], true
+ case bytes.HasPrefix(b, trueLiteral):
+ return b[len(trueLiteral):], true
+ case bytes.HasPrefix(b, falseLiteral):
+ return b[len(falseLiteral):], true
+ default:
+ return b, false
+ }
+}
+
+// validObject reports whether the next JSON object is in its canonical form
+// per RFC 8785, section 3.2.3 regarding object name ordering.
+// It consume the leading value, and returns the remaining bytes.
+func validObject(b []byte) ([]byte, bool) {
+ if len(b) == 0 || b[0] != '{' {
+ return b, false
+ }
+ b = b[1:]
+
+ var init, ok bool
+ var prevKey string
+ for {
+ if len(b) > 0 && b[0] == '}' {
+ return b[1:], true
+ }
+
+ if init {
+ if len(b) == 0 || b[0] != ',' {
+ return b, false
+ }
+ b = b[1:]
+ }
+
+ currKey, _, _ := decodeString(b)
+ b, ok = validString(b)
+ if !ok {
+ return b, ok
+ }
+ if init && !lessUTF16(prevKey, currKey) {
+ return b, ok
+ }
+ prevKey = currKey
+
+ if len(b) == 0 || b[0] != ':' {
+ return b, false
+ }
+ b = b[1:]
+
+ b, ok = validValue(b)
+ if !ok {
+ return b, ok
+ }
+
+ init = true
+ }
+}
+
+// validArray reports whether the next JSON array is in its canonical form.
+// It consume the leading value, and returns the remaining bytes.
+func validArray(b []byte) ([]byte, bool) {
+ if len(b) == 0 || b[0] != '[' {
+ return b, false
+ }
+ b = b[1:]
+
+ var init, ok bool
+ for {
+ if len(b) > 0 && b[0] == ']' {
+ return b[1:], true
+ }
+
+ if init {
+ if len(b) == 0 || b[0] != ',' {
+ return b, false
+ }
+ b = b[1:]
+ }
+
+ b, ok = validValue(b)
+ if !ok {
+ return b, ok
+ }
+
+ init = true
+ }
+}
+
+// validString reports whether the next JSON string is in its canonical form
+// per RFC 8785, section 3.2.2.2.
+// It consume the leading value, and returns the remaining bytes.
+func validString(b []byte) ([]byte, bool) {
+ if len(b) == 0 || b[0] != '"' {
+ return b, false
+ }
+
+ // Fast-path optimization for unescaped ASCII.
+ for b := b[1:]; len(b) > 0; b = b[1:] {
+ if b[0] == '"' {
+ return b[1:], true
+ }
+ if !(0x20 <= b[0] && b[0] < 0x80 && b[0] != '"' && b[0] != '\\') {
+ break
+ }
+ }
+
+ s, b2, err := decodeString(b)
+ got := b[:len(b)-len(b2)]
+ want, _ := formatString(nil, s)
+ return b2, bytes.Equal(got, want) && err == nil
+}
+
+// validNumber reports whether the next JSON number is in its canonical form
+// per RFC 8785, section 3.2.2.3.
+// It consume the leading value, and returns the remaining bytes.
+func validNumber(b []byte) ([]byte, bool) {
+ if len(b) == 0 || !(b[0] == '-' || ('0' <= b[0] && b[0] <= '9')) {
+ return b, false
+ }
+
+ // Fast-path optimization for integers.
+ // Integer values in the range of ±2⁵³ are represented in decimal,
+ // which is encoded using up to 16 digits (excluding the sign).
+ {
+ b := b
+ var neg bool
+ if len(b) > 0 && b[0] == '-' {
+ b = b[1:]
+ neg = true
+ }
+ switch {
+ case len(b) == 0:
+ break
+ case b[0] == '0':
+ b = b[1:]
+ if neg {
+ break // -0 is not permitted
+ }
+ if len(b) > 0 && (b[0] == '.' || b[0] == 'e' || b[0] == 'E') {
+ break // number is not yet terminated
+ }
+ return b, true
+ case '1' <= b[0] && b[0] <= '9':
+ var n int
+ b = b[1:]
+ n++
+ for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+ b = b[1:]
+ n++
+ }
+ if n >= 16 {
+ break // possibly exceeds ±2⁵³
+ }
+ if len(b) > 0 && (b[0] == '.' || b[0] == 'e' || b[0] == 'E') {
+ break // number is not yet terminated
+ }
+ return b, true
+ }
+ }
+
+ f, b2, err := decodeNumber(b)
+ got := b[:len(b)-len(b2)]
+ want, _ := formatNumber(nil, f)
+ return b2, bytes.Equal(got, want) && err == nil
+}
diff --git a/jsonfmt/LICENSE.md b/jsonfmt/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/jsonfmt/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/jsonfmt/go.mod b/jsonfmt/go.mod
new file mode 100644
index 0000000..4fae97d
--- /dev/null
+++ b/jsonfmt/go.mod
@@ -0,0 +1,5 @@
+module github.com/dsnet/golib/jsonfmt
+
+go 1.12
+
+require github.com/google/go-cmp v0.3.0
diff --git a/jsonfmt/go.sum b/jsonfmt/go.sum
new file mode 100644
index 0000000..7e5f1f7
--- /dev/null
+++ b/jsonfmt/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
diff --git a/memfile/LICENSE.md b/memfile/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/memfile/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/memfile/go.mod b/memfile/go.mod
new file mode 100644
index 0000000..64d66c3
--- /dev/null
+++ b/memfile/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/memfile
+
+go 1.12
diff --git a/unitconv/LICENSE.md b/unitconv/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/unitconv/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/unitconv/go.mod b/unitconv/go.mod
new file mode 100644
index 0000000..883d47b
--- /dev/null
+++ b/unitconv/go.mod
@@ -0,0 +1,5 @@
+module github.com/dsnet/golib/unitconv
+
+go 1.12
+
+require github.com/google/go-cmp v0.3.0
diff --git a/unitconv/go.sum b/unitconv/go.sum
new file mode 100644
index 0000000..7e5f1f7
--- /dev/null
+++ b/unitconv/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
diff --git a/unitconv/unitconv.go b/unitconv/unitconv.go
index 57b8914..2efa302 100644
--- a/unitconv/unitconv.go
+++ b/unitconv/unitconv.go
@@ -93,25 +93,25 @@ const (
// Prefix factors according to SI standards.
const (
- Yocto = 1E-24
- Zepto = 1E-21
- Atto = 1E-18
- Femto = 1E-15
- Pico = 1E-12
- Nano = 1E-9
- Micro = 1E-6
- Milli = 1E-3
-
- Unit = 1E0 // Not a standard SI prefix.
-
- Kilo = 1E+3
- Mega = 1E+6
- Giga = 1E+9
- Tera = 1E+12
- Peta = 1E+15
- Exa = 1E+18
- Zetta = 1E+21
- Yotta = 1E+24
+ Yocto = 1e-24
+ Zepto = 1e-21
+ Atto = 1e-18
+ Femto = 1e-15
+ Pico = 1e-12
+ Nano = 1e-9
+ Micro = 1e-6
+ Milli = 1e-3
+
+ Unit = 1e0 // Not a standard SI prefix.
+
+ Kilo = 1e+3
+ Mega = 1e+6
+ Giga = 1e+9
+ Tera = 1e+12
+ Peta = 1e+15
+ Exa = 1e+18
+ Zetta = 1e+21
+ Yotta = 1e+24
)
const (
diff --git a/unitconv/unitconv_test.go b/unitconv/unitconv_test.go
index 9944768..71b5530 100644
--- a/unitconv/unitconv_test.go
+++ b/unitconv/unitconv_test.go
@@ -273,8 +273,8 @@ func TestParsePrefix(t *testing.T) {
{"123K", AutoParse, 123 * Kilo, nil},
{"3Mi", AutoParse, 3 * Mebi, nil},
{"3M", AutoParse, 3 * Mega, nil},
- {"3E-3", AutoParse, 3E-3, nil},
- {"2E2", AutoParse, 2E2, nil},
+ {"3E-3", AutoParse, 3e-3, nil},
+ {"2E2", AutoParse, 2e2, nil},
}
for _, tt := range tests {
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/go.mod -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/decode.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/float.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/float_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/format.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/format_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/string.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/string_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/valid.go
No differences were encountered in the control files