pax_global_header00006660000000000000000000000064144570072660014525gustar00rootroot0000000000000052 comment=877afa880b10ab8bd040c4486c5fc6d2be67e51f golang-go4-0.0~git20230225.2148625/000077500000000000000000000000001445700726600156765ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/.gitignore000066400000000000000000000004121445700726600176630ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof golang-go4-0.0~git20230225.2148625/.travis.yml000066400000000000000000000003251445700726600200070ustar00rootroot00000000000000language: go arch: - amd64 - ppc64le go: - "1.13.x" - tip go_import_path: go4.org before_install: - go mod tidy - git diff --exit-code go.mod - git diff --exit-code go.sum - go mod download golang-go4-0.0~git20230225.2148625/AUTHORS000066400000000000000000000004721445700726600167510ustar00rootroot00000000000000# This is the official list of go4 authors for copyright purposes. # This is distinct from the CONTRIBUTORS file, which is the list of # people who have contributed, even if they don't own the copyright on # their work. Mathieu Lonjaret Daniel Theophanes Google golang-go4-0.0~git20230225.2148625/LICENSE000066400000000000000000000261361445700726600167130ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-go4-0.0~git20230225.2148625/README.md000066400000000000000000000045361445700726600171650ustar00rootroot00000000000000# go4 [![travis badge](https://travis-ci.org/go4org/go4.svg?branch=master)](https://travis-ci.org/go4org/go4 "Travis CI") [go4.org](http://go4.org) is a collection of packages for Go programmers. They started out living in [Perkeep](https://perkeep.org)'s repo and elsewhere but they have nothing to do with Perkeep, so we're moving them here. ## Details * **single repo**. go4 is a single repo. That means things can be changed and rearranged globally atomically with ease and confidence. * **no backwards compatibility**. go4 makes no backwards compatibility promises. If you want to use go4, vendor it. And next time you update your vendor tree, update to the latest API if things in go4 changed. The plan is to eventually provide tools to make this easier. * **forward progress** because we have no backwards compatibility, it's always okay to change things to make things better. That also means the bar for contributions is lower. We don't have to get the API 100% correct in the first commit. * **no Go version policy** go4 packages are usually built and tested with the latest Go stable version. However, go4 has no overarching version policy; each package can declare its own set of supported Go versions. * **code review** contributions must be code-reviewed. We're trying out Gerrithub, to see if we can find a mix of Github Pull Requests and Gerrit that works well for many people. We'll see. * **CLA compliant** contributors must agree to the Google CLA (the same as Go itself). This ensures we can move things into Go as necessary in the future. It also makes lawyers at various companies happy. The CLA is **not** a copyright *assignment*; you retain the copyright on your work. The CLA just says that your work is open source and you have permission to open source it. See https://golang.org/doc/contribute.html#cla * **docs, tests, portability** all code should be documented in the normal Go style, have tests, and be portable to different operating systems and architectures. We'll try to get builders in place to help run the tests on different OS/arches. For now we have Travis at least. ## Contact For any question, or communication when a Github issue is not appropriate, please contact the [Perkeep mailing list](https://groups.google.com/forum/#!forum/perkeep). golang-go4-0.0~git20230225.2148625/bytereplacer/000077500000000000000000000000001445700726600203575ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/bytereplacer/bytereplacer.go000066400000000000000000000162041445700726600233720ustar00rootroot00000000000000/* Copyright 2015 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package bytereplacer provides a utility for replacing parts of byte slices. package bytereplacer // import "go4.org/bytereplacer" import "bytes" // Replacer replaces a list of strings with replacements. // It is safe for concurrent use by multiple goroutines. type Replacer struct { r replacer } // replacer is the interface that a replacement algorithm needs to implement. type replacer interface { // Replace performs all replacements, in-place if possible. Replace(s []byte) []byte } // New returns a new Replacer from a list of old, new string pairs. // Replacements are performed in order, without overlapping matches. func New(oldnew ...string) *Replacer { if len(oldnew)%2 == 1 { panic("bytes.NewReplacer: odd argument count") } allNewBytes := true for i := 0; i < len(oldnew); i += 2 { if len(oldnew[i]) != 1 { return &Replacer{r: makeGenericReplacer(oldnew)} } if len(oldnew[i+1]) != 1 { allNewBytes = false } } if allNewBytes { r := byteReplacer{} for i := range r { r[i] = byte(i) } // The first occurrence of old->new map takes precedence // over the others with the same old string. for i := len(oldnew) - 2; i >= 0; i -= 2 { o := oldnew[i][0] n := oldnew[i+1][0] r[o] = n } return &Replacer{r: &r} } return &Replacer{r: makeGenericReplacer(oldnew)} } // Replace performs all replacements in-place on s. If the capacity // of s is not sufficient, a new slice is allocated, otherwise Replace // returns s. func (r *Replacer) Replace(s []byte) []byte { return r.r.Replace(s) } type trieNode struct { value []byte priority int prefix []byte next *trieNode table []*trieNode } func (t *trieNode) add(key, val []byte, priority int, r *genericReplacer) { if len(key) == 0 { if t.priority == 0 { t.value = val t.priority = priority } return } if len(t.prefix) > 0 { // Need to split the prefix among multiple nodes. var n int // length of the longest common prefix for ; n < len(t.prefix) && n < len(key); n++ { if t.prefix[n] != key[n] { break } } if n == len(t.prefix) { t.next.add(key[n:], val, priority, r) } else if n == 0 { // First byte differs, start a new lookup table here. Looking up // what is currently t.prefix[0] will lead to prefixNode, and // looking up key[0] will lead to keyNode. var prefixNode *trieNode if len(t.prefix) == 1 { prefixNode = t.next } else { prefixNode = &trieNode{ prefix: t.prefix[1:], next: t.next, } } keyNode := new(trieNode) t.table = make([]*trieNode, r.tableSize) t.table[r.mapping[t.prefix[0]]] = prefixNode t.table[r.mapping[key[0]]] = keyNode t.prefix = nil t.next = nil keyNode.add(key[1:], val, priority, r) } else { // Insert new node after the common section of the prefix. next := &trieNode{ prefix: t.prefix[n:], next: t.next, } t.prefix = t.prefix[:n] t.next = next next.add(key[n:], val, priority, r) } } else if t.table != nil { // Insert into existing table. m := r.mapping[key[0]] if t.table[m] == nil { t.table[m] = new(trieNode) } t.table[m].add(key[1:], val, priority, r) } else { t.prefix = key t.next = new(trieNode) t.next.add(nil, val, priority, r) } } func (r *genericReplacer) lookup(s []byte, ignoreRoot bool) (val []byte, keylen int, found bool) { // Iterate down the trie to the end, and grab the value and keylen with // the highest priority. bestPriority := 0 node := &r.root n := 0 for node != nil { if node.priority > bestPriority && !(ignoreRoot && node == &r.root) { bestPriority = node.priority val = node.value keylen = n found = true } if len(s) == 0 { break } if node.table != nil { index := r.mapping[s[0]] if int(index) == r.tableSize { break } node = node.table[index] s = s[1:] n++ } else if len(node.prefix) > 0 && bytes.HasPrefix(s, node.prefix) { n += len(node.prefix) s = s[len(node.prefix):] node = node.next } else { break } } return } // genericReplacer is the fully generic algorithm. // It's used as a fallback when nothing faster can be used. type genericReplacer struct { root trieNode // tableSize is the size of a trie node's lookup table. It is the number // of unique key bytes. tableSize int // mapping maps from key bytes to a dense index for trieNode.table. mapping [256]byte } func makeGenericReplacer(oldnew []string) *genericReplacer { r := new(genericReplacer) // Find each byte used, then assign them each an index. for i := 0; i < len(oldnew); i += 2 { key := oldnew[i] for j := 0; j < len(key); j++ { r.mapping[key[j]] = 1 } } for _, b := range r.mapping { r.tableSize += int(b) } var index byte for i, b := range r.mapping { if b == 0 { r.mapping[i] = byte(r.tableSize) } else { r.mapping[i] = index index++ } } // Ensure root node uses a lookup table (for performance). r.root.table = make([]*trieNode, r.tableSize) for i := 0; i < len(oldnew); i += 2 { r.root.add([]byte(oldnew[i]), []byte(oldnew[i+1]), len(oldnew)-i, r) } return r } func (r *genericReplacer) Replace(s []byte) []byte { var last int var prevMatchEmpty bool dst := s[:0] grown := false for i := 0; i <= len(s); { // Fast path: s[i] is not a prefix of any pattern. if i != len(s) && r.root.priority == 0 { index := int(r.mapping[s[i]]) if index == r.tableSize || r.root.table[index] == nil { i++ continue } } // Ignore the empty match iff the previous loop found the empty match. val, keylen, match := r.lookup(s[i:], prevMatchEmpty) prevMatchEmpty = match && keylen == 0 if match { dst = append(dst, s[last:i]...) if diff := len(val) - keylen; grown || diff < 0 { dst = append(dst, val...) i += keylen } else if diff <= cap(s)-len(s) { // The replacement is larger than the original, but can still fit in the original buffer. copy(s[i+len(val):cap(dst)], s[i+keylen:]) dst = append(dst, val...) s = s[:len(s)+diff] i += len(val) } else { // The output will grow larger than the original buffer. Allocate a new one. grown = true newDst := make([]byte, len(dst), cap(dst)+diff) copy(newDst, dst) dst = newDst dst = append(dst, val...) i += keylen } last = i continue } i++ } if last != len(s) { dst = append(dst, s[last:]...) } return dst } // byteReplacer is the implementation that's used when all the "old" // and "new" values are single ASCII bytes. // The array contains replacement bytes indexed by old byte. type byteReplacer [256]byte func (r *byteReplacer) Replace(s []byte) []byte { for i, b := range s { s[i] = r[b] } return s } golang-go4-0.0~git20230225.2148625/bytereplacer/bytereplacer_test.go000066400000000000000000000257711445700726600244420ustar00rootroot00000000000000/* Copyright 2015 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package bytereplacer import ( "bytes" "strings" "testing" ) var htmlEscaper = New( "&", "&", "<", "<", ">", ">", `"`, """, "'", "'", ) var htmlUnescaper = New( "&", "&", "<", "<", ">", ">", """, `"`, "'", "'", ) var capitalLetters = New("a", "A", "b", "B") func TestReplacer(t *testing.T) { type testCase struct { r *Replacer in, out string } var testCases []testCase // str converts 0xff to "\xff". This isn't just string(b) since that converts to UTF-8. str := func(b byte) string { return string([]byte{b}) } var s []string // inc maps "\x00"->"\x01", ..., "a"->"b", "b"->"c", ..., "\xff"->"\x00". s = nil for i := 0; i < 256; i++ { s = append(s, str(byte(i)), str(byte(i+1))) } inc := New(s...) // Test cases with 1-byte old strings, 1-byte new strings. testCases = append(testCases, testCase{capitalLetters, "brad", "BrAd"}, testCase{capitalLetters, strings.Repeat("a", (32<<10)+123), strings.Repeat("A", (32<<10)+123)}, testCase{capitalLetters, "", ""}, testCase{inc, "brad", "csbe"}, testCase{inc, "\x00\xff", "\x01\x00"}, testCase{inc, "", ""}, testCase{New("a", "1", "a", "2"), "brad", "br1d"}, ) // repeat maps "a"->"a", "b"->"bb", "c"->"ccc", ... s = nil for i := 0; i < 256; i++ { n := i + 1 - 'a' if n < 1 { n = 1 } s = append(s, str(byte(i)), strings.Repeat(str(byte(i)), n)) } repeat := New(s...) // Test cases with 1-byte old strings, variable length new strings. testCases = append(testCases, testCase{htmlEscaper, "No changes", "No changes"}, testCase{htmlEscaper, "I <3 escaping & stuff", "I <3 escaping & stuff"}, testCase{htmlEscaper, "&&&", "&&&"}, testCase{htmlEscaper, "", ""}, testCase{repeat, "brad", "bbrrrrrrrrrrrrrrrrrradddd"}, testCase{repeat, "abba", "abbbba"}, testCase{repeat, "", ""}, testCase{New("a", "11", "a", "22"), "brad", "br11d"}, ) // The remaining test cases have variable length old strings. testCases = append(testCases, testCase{htmlUnescaper, "&amp;", "&"}, testCase{htmlUnescaper, "<b>HTML's neat</b>", "HTML's neat"}, testCase{htmlUnescaper, "", ""}, testCase{New("a", "1", "a", "2", "xxx", "xxx"), "brad", "br1d"}, testCase{New("a", "1", "aa", "2", "aaa", "3"), "aaaa", "1111"}, testCase{New("aaa", "3", "aa", "2", "a", "1"), "aaaa", "31"}, ) // gen1 has multiple old strings of variable length. There is no // overall non-empty common prefix, but some pairwise common prefixes. gen1 := New( "aaa", "3[aaa]", "aa", "2[aa]", "a", "1[a]", "i", "i", "longerst", "most long", "longer", "medium", "long", "short", "xx", "xx", "x", "X", "X", "Y", "Y", "Z", ) testCases = append(testCases, testCase{gen1, "fooaaabar", "foo3[aaa]b1[a]r"}, testCase{gen1, "long, longerst, longer", "short, most long, medium"}, testCase{gen1, "xxxxx", "xxxxX"}, testCase{gen1, "XiX", "YiY"}, testCase{gen1, "", ""}, ) // gen2 has multiple old strings with no pairwise common prefix. gen2 := New( "roses", "red", "violets", "blue", "sugar", "sweet", ) testCases = append(testCases, testCase{gen2, "roses are red, violets are blue...", "red are red, blue are blue..."}, testCase{gen2, "", ""}, ) // gen3 has multiple old strings with an overall common prefix. gen3 := New( "abracadabra", "poof", "abracadabrakazam", "splat", "abraham", "lincoln", "abrasion", "scrape", "abraham", "isaac", ) testCases = append(testCases, testCase{gen3, "abracadabrakazam abraham", "poofkazam lincoln"}, testCase{gen3, "abrasion abracad", "scrape abracad"}, testCase{gen3, "abba abram abrasive", "abba abram abrasive"}, testCase{gen3, "", ""}, ) // foo{1,2,3,4} have multiple old strings with an overall common prefix // and 1- or 2- byte extensions from the common prefix. foo1 := New( "foo1", "A", "foo2", "B", "foo3", "C", ) foo2 := New( "foo1", "A", "foo2", "B", "foo31", "C", "foo32", "D", ) foo3 := New( "foo11", "A", "foo12", "B", "foo31", "C", "foo32", "D", ) foo4 := New( "foo12", "B", "foo32", "D", ) testCases = append(testCases, testCase{foo1, "fofoofoo12foo32oo", "fofooA2C2oo"}, testCase{foo1, "", ""}, testCase{foo2, "fofoofoo12foo32oo", "fofooA2Doo"}, testCase{foo2, "", ""}, testCase{foo3, "fofoofoo12foo32oo", "fofooBDoo"}, testCase{foo3, "", ""}, testCase{foo4, "fofoofoo12foo32oo", "fofooBDoo"}, testCase{foo4, "", ""}, ) // genAll maps "\x00\x01\x02...\xfe\xff" to "[all]", amongst other things. allBytes := make([]byte, 256) for i := range allBytes { allBytes[i] = byte(i) } allString := string(allBytes) genAll := New( allString, "[all]", "\xff", "[ff]", "\x00", "[00]", ) testCases = append(testCases, testCase{genAll, allString, "[all]"}, testCase{genAll, "a\xff" + allString + "\x00", "a[ff][all][00]"}, testCase{genAll, "", ""}, ) // Test cases with empty old strings. blankToX1 := New("", "X") blankToX2 := New("", "X", "", "") blankHighPriority := New("", "X", "o", "O") blankLowPriority := New("o", "O", "", "X") blankNoOp1 := New("", "") blankNoOp2 := New("", "", "", "A") blankFoo := New("", "X", "foobar", "R", "foobaz", "Z") testCases = append(testCases, testCase{blankToX1, "foo", "XfXoXoX"}, testCase{blankToX1, "", "X"}, testCase{blankToX2, "foo", "XfXoXoX"}, testCase{blankToX2, "", "X"}, testCase{blankHighPriority, "oo", "XOXOX"}, testCase{blankHighPriority, "ii", "XiXiX"}, testCase{blankHighPriority, "oiio", "XOXiXiXOX"}, testCase{blankHighPriority, "iooi", "XiXOXOXiX"}, testCase{blankHighPriority, "", "X"}, testCase{blankLowPriority, "oo", "OOX"}, testCase{blankLowPriority, "ii", "XiXiX"}, testCase{blankLowPriority, "oiio", "OXiXiOX"}, testCase{blankLowPriority, "iooi", "XiOOXiX"}, testCase{blankLowPriority, "", "X"}, testCase{blankNoOp1, "foo", "foo"}, testCase{blankNoOp1, "", ""}, testCase{blankNoOp2, "foo", "foo"}, testCase{blankNoOp2, "", ""}, testCase{blankFoo, "foobarfoobaz", "XRXZX"}, testCase{blankFoo, "foobar-foobaz", "XRX-XZX"}, testCase{blankFoo, "", "X"}, ) // single string replacer abcMatcher := New("abc", "[match]") testCases = append(testCases, testCase{abcMatcher, "", ""}, testCase{abcMatcher, "ab", "ab"}, testCase{abcMatcher, "abc", "[match]"}, testCase{abcMatcher, "abcd", "[match]d"}, testCase{abcMatcher, "cabcabcdabca", "c[match][match]d[match]a"}, ) // Issue 6659 cases (more single string replacer) noHello := New("Hello", "") testCases = append(testCases, testCase{noHello, "Hello", ""}, testCase{noHello, "Hellox", "x"}, testCase{noHello, "xHello", "x"}, testCase{noHello, "xHellox", "xx"}, ) // No-arg test cases. nop := New() testCases = append(testCases, testCase{nop, "abc", "abc"}, testCase{nop, "", ""}, ) // Run the test cases. for i, tc := range testCases { { // Replace with len(in) == cap(in) in := make([]byte, len(tc.in)) copy(in, tc.in) if s := string(tc.r.Replace(in)); s != tc.out { t.Errorf("%d. Replace(%q /* len == cap */) = %q, want %q", i, tc.in, s, tc.out) } } { // Replace with len(in) < cap(in) in := make([]byte, len(tc.in), len(tc.in)*2) copy(in, tc.in) if s := string(tc.r.Replace(in)); s != tc.out { t.Errorf("%d. Replace(%q /* len < cap */) = %q, want %q", i, tc.in, s, tc.out) } } } } func BenchmarkGenericNoMatch(b *testing.B) { str := []byte(strings.Repeat("A", 100) + strings.Repeat("B", 100)) generic := New("a", "A", "b", "B", "12", "123") // varying lengths forces generic for i := 0; i < b.N; i++ { generic.Replace(str) } } func BenchmarkGenericMatch1(b *testing.B) { str := []byte(strings.Repeat("a", 100) + strings.Repeat("b", 100)) generic := New("a", "A", "b", "B", "12", "123") for i := 0; i < b.N; i++ { generic.Replace(str) } } func BenchmarkGenericMatch2(b *testing.B) { str := bytes.Repeat([]byte("It's <b>HTML</b>!"), 100) for i := 0; i < b.N; i++ { htmlUnescaper.Replace(str) } } func benchmarkSingleString(b *testing.B, pattern, text string) { r := New(pattern, "[match]") buf := make([]byte, len(text), len(text)*7) b.SetBytes(int64(len(text))) b.ResetTimer() for i := 0; i < b.N; i++ { copy(buf, text) r.Replace(buf) } } func BenchmarkSingleMaxSkipping(b *testing.B) { benchmarkSingleString(b, strings.Repeat("b", 25), strings.Repeat("a", 10000)) } func BenchmarkSingleLongSuffixFail(b *testing.B) { benchmarkSingleString(b, "b"+strings.Repeat("a", 500), strings.Repeat("a", 1002)) } func BenchmarkSingleMatch(b *testing.B) { benchmarkSingleString(b, "abcdef", strings.Repeat("abcdefghijklmno", 1000)) } func benchmarkReplacer(b *testing.B, r *Replacer, str string) { buf := make([]byte, len(str)) b.ResetTimer() for i := 0; i < b.N; i++ { copy(buf, str) r.Replace(buf) } } func BenchmarkByteByteNoMatch(b *testing.B) { benchmarkReplacer(b, capitalLetters, strings.Repeat("A", 100)+strings.Repeat("B", 100)) } func BenchmarkByteByteMatch(b *testing.B) { benchmarkReplacer(b, capitalLetters, strings.Repeat("a", 100)+strings.Repeat("b", 100)) } func BenchmarkByteStringMatch(b *testing.B) { benchmarkReplacer(b, htmlEscaper, "<"+strings.Repeat("a", 99)+strings.Repeat("b", 99)+">") } func BenchmarkHTMLEscapeNew(b *testing.B) { benchmarkReplacer(b, htmlEscaper, "I <3 to escape HTML & other text too.") } func BenchmarkHTMLEscapeOld(b *testing.B) { str := "I <3 to escape HTML & other text too." buf := make([]byte, len(str)) for i := 0; i < b.N; i++ { copy(buf, str) oldHTMLEscape(buf) } } // The http package's old HTML escaping function in bytes form. func oldHTMLEscape(s []byte) []byte { s = bytes.Replace(s, []byte("&"), []byte("&"), -1) s = bytes.Replace(s, []byte("<"), []byte("<"), -1) s = bytes.Replace(s, []byte(">"), []byte(">"), -1) s = bytes.Replace(s, []byte(`"`), []byte("""), -1) s = bytes.Replace(s, []byte("'"), []byte("'"), -1) return s } // BenchmarkByteByteReplaces compares byteByteImpl against multiple Replaces. func BenchmarkByteByteReplaces(b *testing.B) { str := strings.Repeat("a", 100) + strings.Repeat("b", 100) for i := 0; i < b.N; i++ { bytes.Replace(bytes.Replace([]byte(str), []byte{'a'}, []byte{'A'}, -1), []byte{'b'}, []byte{'B'}, -1) } } // BenchmarkByteByteMap compares byteByteImpl against Map. func BenchmarkByteByteMap(b *testing.B) { str := strings.Repeat("a", 100) + strings.Repeat("b", 100) fn := func(r rune) rune { switch r { case 'a': return 'A' case 'b': return 'B' } return r } for i := 0; i < b.N; i++ { bytes.Map(fn, []byte(str)) } } golang-go4-0.0~git20230225.2148625/cloud/000077500000000000000000000000001445700726600170045ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/cloud/cloudlaunch/000077500000000000000000000000001445700726600213055ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/cloud/cloudlaunch/cloudlaunch.go000066400000000000000000000302101445700726600241310ustar00rootroot00000000000000/* Copyright 2015 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package cloudlaunch helps binaries run themselves on The Cloud, copying // themselves to GCE. package cloudlaunch // import "go4.org/cloud/cloudlaunch" import ( "encoding/json" "flag" "fmt" "io" "io/ioutil" "log" "net/http" "os" "path" "path/filepath" "runtime" "strings" "time" "go4.org/cloud/google/gceutil" "cloud.google.com/go/compute/metadata" "cloud.google.com/go/storage" "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" compute "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" "google.golang.org/api/option" storageapi "google.golang.org/api/storage/v1" ) func readFile(v string) string { slurp, err := ioutil.ReadFile(v) if err != nil { log.Fatalf("Error reading %s: %v", v, err) } return strings.TrimSpace(string(slurp)) } const baseConfig = `#cloud-config coreos: update: group: stable reboot-strategy: $REBOOT units: - name: $NAME.service command: start content: | [Unit] Description=$NAME service After=network.target [Service] Type=simple ExecStartPre=/bin/sh -c 'mkdir -p /opt/bin && /usr/bin/curl --silent -f -o /opt/bin/$NAME $URL?$(date +%s) && chmod +x /opt/bin/$NAME' ExecStart=/opt/bin/$NAME RestartSec=10 Restart=always StartLimitInterval=0 [Install] WantedBy=network-online.target ` // RestartPolicy controls whether the binary automatically restarts. type RestartPolicy int const ( RestartOnUpdates RestartPolicy = iota RestartNever // TODO: more graceful restarts; make systemd own listening on network sockets, // don't break connections. ) type Config struct { // Name is the name of a service to run. // This is the name of the systemd service (without .service) // and the name of the GCE instance. Name string // RestartPolicy controls whether the binary automatically restarts // on updates. The zero value means automatic. RestartPolicy RestartPolicy // UpdateStrategy sets the CoreOS automatic update strategy, and the // associated reboots. Possible values are "best-effort", "etcd-lock", // "reboot", "off", with "best-effort" being the default. See // https://coreos.com/os/docs/latest/update-strategies.html UpdateStrategy string // BinaryBucket and BinaryObject are the GCS bucket and object // within that bucket containing the Linux binary to download // on boot and occasionally run. This binary must be public // (at least for now). BinaryBucket string BinaryObject string // defaults to Name GCEProjectID string Zone string // defaults to us-central1-f SSD bool Scopes []string // any additional scopes MachineType string InstanceName string } // cloudLaunch is a launch of a Config. type cloudLaunch struct { *Config oauthClient *http.Client computeService *compute.Service } func (c *Config) binaryURL() string { return "https://storage.googleapis.com/" + c.BinaryBucket + "/" + c.binaryObject() } func (c *Config) instName() string { return c.Name } // for now func (c *Config) zone() string { return strDefault(c.Zone, "us-central1-f") } func (c *Config) machineType() string { return strDefault(c.MachineType, "g1-small") } func (c *Config) binaryObject() string { return strDefault(c.BinaryObject, c.Name) } func (c *Config) updateStrategy() string { return strDefault(c.UpdateStrategy, "best-effort") } func (c *Config) projectAPIURL() string { return "https://www.googleapis.com/compute/v1/projects/" + c.GCEProjectID } func (c *Config) machineTypeURL() string { return c.projectAPIURL() + "/zones/" + c.zone() + "/machineTypes/" + c.machineType() } func strDefault(a, b string) string { if a != "" { return a } return b } var ( doLaunch = flag.Bool("cloudlaunch", false, "Deploy or update this binary to the cloud. Must be on Linux, for now.") ) func (c *Config) MaybeDeploy() { flag.Parse() if !*doLaunch { go c.restartLoop() return } defer os.Exit(1) // backup, in case we return without Fatal or os.Exit later if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { log.Fatal("Can only use --cloudlaunch on linux/amd64, for now.") } if c.GCEProjectID == "" { log.Fatal("cloudconfig.GCEProjectID is empty") } filename := filepath.Join(os.Getenv("HOME"), "keys", c.GCEProjectID+".key.json") log.Printf("Using OAuth config from JSON service file: %s", filename) jwtConf, err := google.JWTConfigFromJSON([]byte(readFile(filename)), append([]string{ storageapi.DevstorageFullControlScope, compute.ComputeScope, "https://www.googleapis.com/auth/cloud-platform", }, c.Scopes...)...) if err != nil { log.Fatalf("ConfigFromJSON: %v", err) } cl := &cloudLaunch{ Config: c, oauthClient: jwtConf.Client(oauth2.NoContext), } cl.computeService, _ = compute.New(cl.oauthClient) cl.uploadBinary() cl.createInstance() os.Exit(0) } func (c *Config) restartLoop() { if !metadata.OnGCE() { return } if c.RestartPolicy == RestartNever { return } url := c.binaryURL() var lastEtag string for { res, err := http.Head(url + "?" + fmt.Sprint(time.Now().Unix())) if err != nil { log.Printf("Warning: %v", err) time.Sleep(15 * time.Second) continue } etag := res.Header.Get("Etag") if etag == "" { log.Printf("Warning, no ETag in response: %v", res) time.Sleep(15 * time.Second) continue } if lastEtag != "" && etag != lastEtag { log.Printf("Binary updated; restarting.") // TODO: more graceful restart, letting systemd own the network connections. // Then we can finish up requests here. os.Exit(0) } lastEtag = etag time.Sleep(15 * time.Second) } } // uploadBinary uploads the currently-running Linux binary. // It crashes if it fails. func (cl *cloudLaunch) uploadBinary() { ctx := context.Background() if cl.BinaryBucket == "" { log.Fatal("cloudlaunch: Config.BinaryBucket is empty") } stoClient, err := storage.NewClient(ctx, option.WithHTTPClient(cl.oauthClient)) if err != nil { log.Fatal(err) } w := stoClient.Bucket(cl.BinaryBucket).Object(cl.binaryObject()).NewWriter(ctx) if err != nil { log.Fatal(err) } w.ACL = []storage.ACLRule{ // If you don't give the owners access, the web UI seems to // have a bug and doesn't have access to see that it's public, so // won't render the "Shared Publicly" link. So we do that, even // though it's dumb and unnecessary otherwise: { Entity: storage.ACLEntity("project-owners-" + cl.GCEProjectID), Role: storage.RoleOwner, }, // Public, so our systemd unit can get it easily: { Entity: storage.AllUsers, Role: storage.RoleReader, }, } w.CacheControl = "no-cache" selfPath := getSelfPath() log.Printf("Uploading %q to %v", selfPath, cl.binaryURL()) f, err := os.Open(selfPath) if err != nil { log.Fatal(err) } defer f.Close() n, err := io.Copy(w, f) if err != nil { log.Fatal(err) } if err := w.Close(); err != nil { log.Fatal(err) } log.Printf("Uploaded %d bytes", n) } func getSelfPath() string { if runtime.GOOS != "linux" { panic("TODO") } v, err := os.Readlink("/proc/self/exe") if err != nil { log.Fatal(err) } return v } func zoneInRegion(zone, regionURL string) bool { if zone == "" { panic("empty zone") } if regionURL == "" { panic("empty regionURL") } // zone is like "us-central1-f" // regionURL is like "https://www.googleapis.com/compute/v1/projects/camlistore-website/regions/us-central1" region := path.Base(regionURL) // "us-central1" if region == "" { panic("empty region") } return strings.HasPrefix(zone, region) } // findIP finds an IP address to use, or returns the empty string if none is found. // It tries to find a reserved one in the same region where the name of the reserved IP // is "NAME-ip" and the IP is not in use. func (cl *cloudLaunch) findIP() string { // Try to find it by name. aggAddrList, err := cl.computeService.Addresses.AggregatedList(cl.GCEProjectID).Do() if err != nil { log.Fatal(err) } // https://godoc.org/google.golang.org/api/compute/v1#AddressAggregatedList var ip string IPLoop: for _, asl := range aggAddrList.Items { for _, addr := range asl.Addresses { log.Printf(" addr: %#v", addr) if addr.Name == cl.Name+"-ip" && addr.Status == "RESERVED" && zoneInRegion(cl.zone(), addr.Region) { ip = addr.Address break IPLoop } } } return ip } func (cl *cloudLaunch) createInstance() { inst := cl.lookupInstance() if inst != nil { log.Printf("Instance exists; not re-creating.") return } log.Printf("Instance doesn't exist; creating...") ip := cl.findIP() log.Printf("Found IP: %v", ip) cloudConfig := strings.NewReplacer( "$NAME", cl.Name, "$URL", cl.binaryURL(), "$REBOOT", cl.updateStrategy(), ).Replace(baseConfig) instance := &compute.Instance{ Name: cl.instName(), Description: cl.Name, MachineType: cl.machineTypeURL(), Disks: []*compute.AttachedDisk{cl.instanceDisk()}, Tags: &compute.Tags{ Items: []string{"http-server", "https-server"}, }, Metadata: &compute.Metadata{ Items: []*compute.MetadataItems{ { Key: "user-data", Value: googleapi.String(cloudConfig), }, }, }, NetworkInterfaces: []*compute.NetworkInterface{ &compute.NetworkInterface{ AccessConfigs: []*compute.AccessConfig{ &compute.AccessConfig{ Type: "ONE_TO_ONE_NAT", Name: "External NAT", NatIP: ip, }, }, Network: cl.projectAPIURL() + "/global/networks/default", }, }, ServiceAccounts: []*compute.ServiceAccount{ { Email: "default", Scopes: cl.Scopes, }, }, } log.Printf("Creating instance...") op, err := cl.computeService.Instances.Insert(cl.GCEProjectID, cl.zone(), instance).Do() if err != nil { log.Fatalf("Failed to create instance: %v", err) } opName := op.Name log.Printf("Created. Waiting on operation %v", opName) OpLoop: for { time.Sleep(2 * time.Second) op, err := cl.computeService.ZoneOperations.Get(cl.GCEProjectID, cl.zone(), opName).Do() if err != nil { log.Fatalf("Failed to get op %s: %v", opName, err) } switch op.Status { case "PENDING", "RUNNING": log.Printf("Waiting on operation %v", opName) continue case "DONE": if op.Error != nil { for _, operr := range op.Error.Errors { log.Printf("Error: %+v", operr) } log.Fatalf("Failed to start.") } log.Printf("Success. %+v", op) break OpLoop default: log.Fatalf("Unknown status %q: %+v", op.Status, op) } } inst, err = cl.computeService.Instances.Get(cl.GCEProjectID, cl.zone(), cl.instName()).Do() if err != nil { log.Fatalf("Error getting instance after creation: %v", err) } ij, _ := json.MarshalIndent(inst, "", " ") log.Printf("%s", ij) log.Printf("Instance created.") os.Exit(0) } // returns nil if instance doesn't exist. func (cl *cloudLaunch) lookupInstance() *compute.Instance { inst, err := cl.computeService.Instances.Get(cl.GCEProjectID, cl.zone(), cl.instName()).Do() if ae, ok := err.(*googleapi.Error); ok && ae.Code == 404 { return nil } else if err != nil { log.Fatalf("Instances.Get: %v", err) } return inst } func (cl *cloudLaunch) instanceDisk() *compute.AttachedDisk { imageURL, err := gceutil.CoreOSImageURL(cl.oauthClient) if err != nil { log.Fatalf("error looking up latest CoreOS stable image: %v", err) } diskName := cl.instName() + "-coreos-stateless-pd" var diskType string if cl.SSD { diskType = cl.projectAPIURL() + "/zones/" + cl.zone() + "/diskTypes/pd-ssd" } return &compute.AttachedDisk{ AutoDelete: true, Boot: true, Type: "PERSISTENT", InitializeParams: &compute.AttachedDiskInitializeParams{ DiskName: diskName, SourceImage: imageURL, DiskSizeGb: 50, DiskType: diskType, }, } } golang-go4-0.0~git20230225.2148625/cloud/google/000077500000000000000000000000001445700726600202605ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/cloud/google/gceutil/000077500000000000000000000000001445700726600217145ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/cloud/google/gceutil/gceutil.go000066400000000000000000000073331445700726600237050ustar00rootroot00000000000000/* Copyright 2015 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package gceutil provides utility functions to help with instances on // Google Compute Engine. package gceutil // import "go4.org/cloud/google/gceutil" import ( "encoding/json" "errors" "net/http" "strings" "time" "google.golang.org/api/compute/v1" ) // CoreOSImageURL returns the URL of the latest stable CoreOS image for running // on Google Compute Engine. func CoreOSImageURL(cl *http.Client) (string, error) { return osImageURL(cl, false) } // COSImageURL returns the URL of the latest stable Container-Optimized OS image // for running on Google Compute Engine. func COSImageURL(cl *http.Client) (string, error) { return osImageURL(cl, true) } func osImageURL(cl *http.Client, cos bool) (string, error) { project := "coreos-cloud" if cos { project = "cos-cloud" } resp, err := cl.Get("https://www.googleapis.com/compute/v1/projects/" + project + "/global/images") if err != nil { return "", err } defer resp.Body.Close() type osImage struct { SelfLink string CreationTimestamp time.Time Name string } type osImageList struct { Items []osImage } imageList := &osImageList{} if err := json.NewDecoder(resp.Body).Decode(imageList); err != nil { return "", err } if imageList == nil || len(imageList.Items) == 0 { return "", errors.New("no images list in response") } imageURL := "" var max time.Time // latest stable image creation time imgPrefix := "coreos-stable" if cos { imgPrefix = "cos-stable" } for _, v := range imageList.Items { if !strings.HasPrefix(v.Name, imgPrefix) { continue } if v.CreationTimestamp.After(max) { max = v.CreationTimestamp imageURL = v.SelfLink } } if imageURL == "" { if cos { return "", errors.New("no stable Container-Optimized OS image found") } return "", errors.New("no stable coreOS image found") } return imageURL, nil } // InstanceGroupAndManager contains both an InstanceGroup and // its InstanceGroupManager, if any. type InstanceGroupAndManager struct { Group *compute.InstanceGroup // Manager is the manager of the Group. It may be nil. Manager *compute.InstanceGroupManager } // InstanceGroups returns all the instance groups in a project's zone, along // with their associated InstanceGroupManagers. // The returned map is keyed by the instance group identifier URL. func InstanceGroups(svc *compute.Service, proj, zone string) (map[string]InstanceGroupAndManager, error) { managerList, err := svc.InstanceGroupManagers.List(proj, zone).Do() if err != nil { return nil, err } if managerList.NextPageToken != "" { return nil, errors.New("too many managers; pagination not supported") } managedBy := make(map[string]*compute.InstanceGroupManager) // instance group URL -> its manager for _, it := range managerList.Items { managedBy[it.InstanceGroup] = it } groupList, err := svc.InstanceGroups.List(proj, zone).Do() if err != nil { return nil, err } if groupList.NextPageToken != "" { return nil, errors.New("too many instance groups; pagination not supported") } ret := make(map[string]InstanceGroupAndManager) for _, it := range groupList.Items { ret[it.SelfLink] = InstanceGroupAndManager{it, managedBy[it.SelfLink]} } return ret, nil } golang-go4-0.0~git20230225.2148625/cloud/google/gcsutil/000077500000000000000000000000001445700726600217325ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/cloud/google/gcsutil/storage.go000066400000000000000000000121011445700726600237200ustar00rootroot00000000000000/* Copyright 2015 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package gcsutil provides tools for accessing Google Cloud Storage until they can be // completely replaced by cloud.google.com/go/storage. package gcsutil // import "go4.org/cloud/google/gcsutil" import ( "encoding/xml" "errors" "fmt" "io" "net/http" "net/url" "os" "strings" "cloud.google.com/go/storage" "go4.org/ctxutil" "golang.org/x/net/context" ) const gsAccessURL = "https://storage.googleapis.com" // An Object holds the name of an object (its bucket and key) within // Google Cloud Storage. type Object struct { Bucket string Key string } func (o *Object) valid() error { if o == nil { return errors.New("invalid nil Object") } if o.Bucket == "" { return errors.New("missing required Bucket field in Object") } if o.Key == "" { return errors.New("missing required Key field in Object") } return nil } // A SizedObject holds the bucket, key, and size of an object. type SizedObject struct { Object Size int64 } func (o *Object) String() string { if o == nil { return "" } return fmt.Sprintf("%v/%v", o.Bucket, o.Key) } func (so SizedObject) String() string { return fmt.Sprintf("%v/%v (%vB)", so.Bucket, so.Key, so.Size) } // Makes a simple body-less google storage request func simpleRequest(method, url_ string) (*http.Request, error) { req, err := http.NewRequest(method, url_, nil) if err != nil { return nil, err } req.Header.Set("x-goog-api-version", "2") return req, err } // ErrInvalidRange is used when the server has returned http.StatusRequestedRangeNotSatisfiable. var ErrInvalidRange = errors.New("gcsutil: requested range not satisfiable") // GetPartialObject fetches part of a Google Cloud Storage object. // This function relies on the ctx ctxutil.HTTPClient value being set to an OAuth2 // authorized and authenticated HTTP client. // If length is negative, the rest of the object is returned. // It returns ErrInvalidRange if the server replies with http.StatusRequestedRangeNotSatisfiable. // The caller must call Close on the returned value. func GetPartialObject(ctx context.Context, obj Object, offset, length int64) (io.ReadCloser, error) { if offset < 0 { return nil, errors.New("invalid negative offset") } if err := obj.valid(); err != nil { return nil, err } req, err := simpleRequest("GET", gsAccessURL+"/"+obj.Bucket+"/"+obj.Key) if err != nil { return nil, err } if length >= 0 { req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1)) } else { req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset)) } req.Cancel = ctx.Done() res, err := ctxutil.Client(ctx).Do(req) if err != nil { return nil, fmt.Errorf("GET (offset=%d, length=%d) failed: %v\n", offset, length, err) } if res.StatusCode == http.StatusNotFound { res.Body.Close() return nil, os.ErrNotExist } if !(res.StatusCode == http.StatusPartialContent || (offset == 0 && res.StatusCode == http.StatusOK)) { res.Body.Close() if res.StatusCode == http.StatusRequestedRangeNotSatisfiable { return nil, ErrInvalidRange } return nil, fmt.Errorf("GET (offset=%d, length=%d) got failed status: %v\n", offset, length, res.Status) } return res.Body, nil } // EnumerateObjects lists the objects in a bucket. // This function relies on the ctx oauth2.HTTPClient value being set to an OAuth2 // authorized and authenticated HTTP client. // If after is non-empty, listing will begin with lexically greater object names. // If limit is non-zero, the length of the list will be limited to that number. func EnumerateObjects(ctx context.Context, bucket, after string, limit int) ([]*storage.ObjectAttrs, error) { // Build url, with query params var params []string if after != "" { params = append(params, "marker="+url.QueryEscape(after)) } if limit > 0 { params = append(params, fmt.Sprintf("max-keys=%v", limit)) } query := "" if len(params) > 0 { query = "?" + strings.Join(params, "&") } req, err := simpleRequest("GET", gsAccessURL+"/"+bucket+"/"+query) if err != nil { return nil, err } req.Cancel = ctx.Done() res, err := ctxutil.Client(ctx).Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("gcsutil: bad enumerate response code: %v", res.Status) } var xres struct { Contents []SizedObject } if err = xml.NewDecoder(res.Body).Decode(&xres); err != nil { return nil, err } objAttrs := make([]*storage.ObjectAttrs, len(xres.Contents)) for k, o := range xres.Contents { objAttrs[k] = &storage.ObjectAttrs{ Name: o.Key, Size: o.Size, } } return objAttrs, nil } golang-go4-0.0~git20230225.2148625/ctxutil/000077500000000000000000000000001445700726600173725ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/ctxutil/ctxutil.go000066400000000000000000000026651445700726600214260ustar00rootroot00000000000000/* Copyright 2015 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package ctxutil contains golang.org/x/net/context related utilities. package ctxutil // import "go4.org/ctxutil" import ( "net/http" "golang.org/x/net/context" "golang.org/x/oauth2" ) // HTTPClient is the context key to use with golang.org/x/net/context's WithValue function // to associate an *http.Client value with a context. // // We use the same value as the oauth2 package (which first introduced this key) rather // than creating a new one and forcing users to possibly set two. var HTTPClient = oauth2.HTTPClient // Client returns the HTTP client to use for the provided context. // If ctx is non-nil and has an associated HTTP client, that client is returned. // Otherwise, http.DefaultClient is returned. func Client(ctx context.Context) *http.Client { if ctx != nil { if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { return hc } } return http.DefaultClient } golang-go4-0.0~git20230225.2148625/errorutil/000077500000000000000000000000001445700726600177255ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/errorutil/highlight.go000066400000000000000000000031131445700726600222210ustar00rootroot00000000000000/* Copyright 2011 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package errorutil helps make better error messages. package errorutil // import "go4.org/errorutil" import ( "bufio" "bytes" "fmt" "io" "strings" ) // HighlightBytePosition takes a reader and the location in bytes of a parse // error (for instance, from json.SyntaxError.Offset) and returns the line, column, // and pretty-printed context around the error with an arrow indicating the exact // position of the syntax error. func HighlightBytePosition(f io.Reader, pos int64) (line, col int, highlight string) { line = 1 br := bufio.NewReader(f) lastLine := "" thisLine := new(bytes.Buffer) for n := int64(0); n < pos; n++ { b, err := br.ReadByte() if err != nil { break } if b == '\n' { lastLine = thisLine.String() thisLine.Reset() line++ col = 1 } else { col++ thisLine.WriteByte(b) } } if line > 1 { highlight += fmt.Sprintf("%5d: %s\n", line-1, lastLine) } highlight += fmt.Sprintf("%5d: %s\n", line, thisLine.String()) highlight += fmt.Sprintf("%s^\n", strings.Repeat(" ", col+5)) return } golang-go4-0.0~git20230225.2148625/fault/000077500000000000000000000000001445700726600170115ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/fault/fault.go000066400000000000000000000032331445700726600204540ustar00rootroot00000000000000/* Copyright 2014 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package fault handles fault injection for testing. package fault // import "go4.org/fault" import ( "errors" "math/rand" "os" "strconv" "strings" ) var fakeErr = errors.New("fake injected error for testing") // An Injector reports whether fake errors should be returned. type Injector struct { failPercent int } // NewInjector returns a new fault injector with the given name. The // environment variable "FAULT_" + capital(name) + "_FAIL_PERCENT" // controls the percentage of requests that fail. If undefined or // zero, no requests fail. func NewInjector(name string) *Injector { var failPercent, _ = strconv.Atoi(os.Getenv("FAULT_" + strings.ToUpper(name) + "_FAIL_PERCENT")) return &Injector{ failPercent: failPercent, } } // ShouldFail reports whether a fake error should be returned. func (in *Injector) ShouldFail() bool { return in.failPercent > 0 && in.failPercent > rand.Intn(100) } // FailErr checks ShouldFail and, if true, assigns a fake error to err // and returns true. func (in *Injector) FailErr(err *error) bool { if !in.ShouldFail() { return false } *err = fakeErr return true } golang-go4-0.0~git20230225.2148625/go.mod000066400000000000000000000004571445700726600170120ustar00rootroot00000000000000module go4.org go 1.13 require ( cloud.google.com/go v0.53.0 cloud.google.com/go/storage v1.5.0 github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd golang.org/x/net v0.7.0 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.5.0 google.golang.org/api v0.17.0 ) golang-go4-0.0~git20230225.2148625/go.sum000066400000000000000000000634361445700726600170450ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.53.0 h1:MZQCQQaRwOrAcuKjiHWHrgKykt4fZyuwF2dtiG3fGW8= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0 h1:9/vpR43S4aJaROxqQHQ3nH9lfyKKV0dC3vOmnw8ebQQ= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0 h1:RPUcBvDeYgQFMfQu1eBMq6piD1SXmLH+vK3qjewZPus= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd h1:zkO/Lhoka23X63N9OSzpSeROEUQ5ODw47tM3YWjygbs= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0 h1:0q95w+VuFtv4PAx4PZVQdBMmYbaCHbnfKaEiDIcVyag= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= golang-go4-0.0~git20230225.2148625/go4test/000077500000000000000000000000001445700726600172675ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/go4test/cloudlaunch/000077500000000000000000000000001445700726600215705ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/go4test/cloudlaunch/serve_on_cloud.go000066400000000000000000000050501445700726600251250ustar00rootroot00000000000000// +build ignore /* Copyright 2016 The Go4 Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // The serve_on_cloud program deploys an HTTP server on Google Compute Engine, // serving from Google Cloud Storage. Its purpose is to help testing // go4.org/cloud/cloudlaunch and go4.org/wkfs/gcs. package main import ( "flag" "fmt" "log" "net/http" "os" "path" "regexp" "time" "go4.org/cloud/cloudlaunch" "go4.org/wkfs" _ "go4.org/wkfs/gcs" "cloud.google.com/go/compute/metadata" storageapi "google.golang.org/api/storage/v1" compute "google.golang.org/api/compute/v1" ) var httpAddr = flag.String("http", ":80", "HTTP address") var gcsBucket string func serveHTTP(w http.ResponseWriter, r *http.Request) { rc, err := wkfs.Open(path.Join("/gcs", gcsBucket, r.URL.Path)) if err != nil { http.Error(w, fmt.Sprintf("could not open %v: %v", r.URL.Path, err), 500) return } defer rc.Close() http.ServeContent(w, r, r.URL.Path, time.Now(), rc) } func main() { if !metadata.OnGCE() { bucket := os.Getenv("GCSBUCKET") if bucket == "" { log.Fatal("You need to set the GCSBUCKET env var to specify the Google Cloud Storage bucket to serve from.") } projectID := os.Getenv("GCEPROJECTID") if projectID == "" { log.Fatal("You need to set the GCEPROJECTID env var to specify the Google Cloud project where the instance will run.") } (&cloudlaunch.Config{ Name: "serveoncloud", BinaryBucket: bucket, GCEProjectID: projectID, Scopes: []string{ storageapi.DevstorageFullControlScope, compute.ComputeScope, }, }).MaybeDeploy() return } flag.Parse() storageURLRxp := regexp.MustCompile(`https://storage.googleapis.com/(.+?)/serveoncloud.*`) cloudConfig, err := metadata.InstanceAttributeValue("user-data") if err != nil || cloudConfig == "" { log.Fatalf("could not get cloud config from metadata: %v", err) } m := storageURLRxp.FindStringSubmatch(cloudConfig) if len(m) < 2 { log.Fatal("storage URL not found in cloud config") } gcsBucket = m[1] http.HandleFunc("/", serveHTTP) log.Fatal(http.ListenAndServe(*httpAddr, nil)) } golang-go4-0.0~git20230225.2148625/jsonconfig/000077500000000000000000000000001445700726600200355ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/jsonconfig/eval.go000066400000000000000000000212141445700726600213130ustar00rootroot00000000000000/* Copyright 2011 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package jsonconfig import ( "encoding/json" "errors" "fmt" "io" "log" "os" "path/filepath" "regexp" "runtime" "strconv" "strings" "go4.org/errorutil" "go4.org/wkfs" ) type stringVector struct { v []string } func (v *stringVector) Push(s string) { v.v = append(v.v, s) } func (v *stringVector) Pop() { v.v = v.v[:len(v.v)-1] } func (v *stringVector) Last() string { return v.v[len(v.v)-1] } // A File is the type returned by ConfigParser.Open. type File interface { io.ReadSeeker io.Closer Name() string } // ConfigParser specifies the environment for parsing a config file // and evaluating expressions. type ConfigParser struct { rootJSON Obj touchedFiles map[string]bool includeStack stringVector // Open optionally specifies an opener function. Open func(filename string) (File, error) // IncludeDirs optionally specifies where to find the other config files which are child // objects of this config, if any. Even if nil, the working directory is always searched // first. IncludeDirs []string } func (c *ConfigParser) open(filename string) (File, error) { if c.Open == nil { return wkfs.Open(filename) } return c.Open(filename) } // Validates variable names for config _env expresssions var envPattern = regexp.MustCompile(`\$\{[A-Za-z0-9_]+\}`) // ReadFile parses the provided path and returns the config file. // If path is empty, the c.Open function must be defined. func (c *ConfigParser) ReadFile(path string) (Obj, error) { if path == "" && c.Open == nil { return nil, errors.New("ReadFile of empty string but Open hook not defined") } c.touchedFiles = make(map[string]bool) var err error c.rootJSON, err = c.recursiveReadJSON(path) return c.rootJSON, err } // Decodes and evaluates a json config file, watching for include cycles. func (c *ConfigParser) recursiveReadJSON(configPath string) (decodedObject map[string]interface{}, err error) { if configPath != "" { absConfigPath, err := filepath.Abs(configPath) if err != nil { return nil, fmt.Errorf("Failed to expand absolute path for %s", configPath) } if c.touchedFiles[absConfigPath] { return nil, fmt.Errorf("ConfigParser include cycle detected reading config: %v", absConfigPath) } c.touchedFiles[absConfigPath] = true c.includeStack.Push(absConfigPath) defer c.includeStack.Pop() } var f File if f, err = c.open(configPath); err != nil { return nil, fmt.Errorf("Failed to open config: %v", err) } defer f.Close() decodedObject = make(map[string]interface{}) dj := json.NewDecoder(f) if err = dj.Decode(&decodedObject); err != nil { extra := "" if serr, ok := err.(*json.SyntaxError); ok { if _, serr := f.Seek(0, os.SEEK_SET); serr != nil { log.Fatalf("seek error: %v", serr) } line, col, highlight := errorutil.HighlightBytePosition(f, serr.Offset) extra = fmt.Sprintf(":\nError at line %d, column %d (file offset %d):\n%s", line, col, serr.Offset, highlight) } return nil, fmt.Errorf("error parsing JSON object in config file %s%s\n%v", f.Name(), extra, err) } if err = c.evaluateExpressions(decodedObject, nil, false); err != nil { return nil, fmt.Errorf("error expanding JSON config expressions in %s:\n%v", f.Name(), err) } return decodedObject, nil } var regFunc = map[string]expanderFunc{} // RegisterFunc registers a new function that may be called from JSON // configs using an array of the form ["_name", arg0, argN...]. // The provided name must begin with an underscore. func RegisterFunc(name string, fn func(c *ConfigParser, v []interface{}) (interface{}, error)) { if len(name) < 2 || !strings.HasPrefix(name, "_") { panic("illegal name") } if _, dup := regFunc[name]; dup { panic("duplicate registration of " + name) } regFunc[name] = fn } type expanderFunc func(c *ConfigParser, v []interface{}) (interface{}, error) func namedExpander(name string) (fn expanderFunc, ok bool) { switch name { case "_env": return (*ConfigParser).expandEnv, true case "_fileobj": return (*ConfigParser).expandFile, true } fn, ok = regFunc[name] return } func (c *ConfigParser) evalValue(v interface{}) (interface{}, error) { sl, ok := v.([]interface{}) if !ok { return v, nil } if name, ok := sl[0].(string); ok { if expander, ok := namedExpander(name); ok { newval, err := expander(c, sl[1:]) if err != nil { return nil, err } return newval, nil } } for i, oldval := range sl { newval, err := c.evalValue(oldval) if err != nil { return nil, err } sl[i] = newval } return v, nil } // CheckTypes parses m and returns an error if it encounters a type or value // that is not supported by this package. func (c *ConfigParser) CheckTypes(m map[string]interface{}) error { return c.evaluateExpressions(m, nil, true) } // evaluateExpressions parses recursively m, populating it with the values // that are found, unless testOnly is true. func (c *ConfigParser) evaluateExpressions(m map[string]interface{}, seenKeys []string, testOnly bool) error { for k, ei := range m { thisPath := append(seenKeys, k) switch subval := ei.(type) { case string, bool, float64, nil: continue case []interface{}: if len(subval) == 0 { continue } evaled, err := c.evalValue(subval) if err != nil { return fmt.Errorf("%s: value error %v", strings.Join(thisPath, "."), err) } if !testOnly { m[k] = evaled } case map[string]interface{}: if err := c.evaluateExpressions(subval, thisPath, testOnly); err != nil { return err } default: return fmt.Errorf("%s: unhandled type %T", strings.Join(thisPath, "."), ei) } } return nil } // Permit either: // ["_env", "VARIABLE"] (required to be set) // or ["_env", "VARIABLE", "default_value"] func (c *ConfigParser) expandEnv(v []interface{}) (interface{}, error) { hasDefault := false def := "" if len(v) < 1 || len(v) > 2 { return "", fmt.Errorf("_env expansion expected 1 or 2 args, got %d", len(v)) } s, ok := v[0].(string) if !ok { return "", fmt.Errorf("Expected a string after _env expansion; got %#v", v[0]) } boolDefault, wantsBool := false, false if len(v) == 2 { hasDefault = true switch vdef := v[1].(type) { case string: def = vdef case bool: wantsBool = true boolDefault = vdef default: return "", fmt.Errorf("Expected default value in %q _env expansion; got %#v", s, v[1]) } } var err error expanded := envPattern.ReplaceAllStringFunc(s, func(match string) string { envVar := match[2 : len(match)-1] val := os.Getenv(envVar) // Special case: if val == "" && envVar == "USER" && runtime.GOOS == "windows" { val = os.Getenv("USERNAME") } if val == "" { if hasDefault { return def } err = fmt.Errorf("couldn't expand environment variable %q", envVar) } return val }) if wantsBool { if expanded == "" { return boolDefault, nil } return strconv.ParseBool(expanded) } return expanded, err } func (c *ConfigParser) expandFile(v []interface{}) (exp interface{}, err error) { if len(v) != 1 { return "", fmt.Errorf("_file expansion expected 1 arg, got %d", len(v)) } var incPath string if incPath, err = c.ConfigFilePath(v[0].(string)); err != nil { return "", fmt.Errorf("Included config does not exist: %v", v[0]) } if exp, err = c.recursiveReadJSON(incPath); err != nil { return "", fmt.Errorf("In file included from %s:\n%v", c.includeStack.Last(), err) } return exp, nil } // ConfigFilePath checks if configFile is found and returns a usable path to it. // It first checks if configFile is an absolute path, or if it's found in the // current working directory. If not, it then checks if configFile is in one of // c.IncludeDirs. It returns an error if configFile is absolute and could not be // statted, or os.ErrNotExist if configFile was not found. func (c *ConfigParser) ConfigFilePath(configFile string) (path string, err error) { // Try to open as absolute / relative to CWD _, err = os.Stat(configFile) if err != nil && filepath.IsAbs(configFile) { return "", err } if err == nil { return configFile, nil } for _, d := range c.IncludeDirs { if _, err := os.Stat(filepath.Join(d, configFile)); err == nil { return filepath.Join(d, configFile), nil } } return "", os.ErrNotExist } golang-go4-0.0~git20230225.2148625/jsonconfig/jsonconfig.go000066400000000000000000000151401445700726600225240ustar00rootroot00000000000000/* Copyright 2011 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package jsonconfig defines a helper type for JSON objects to be // used for configuration. package jsonconfig // import "go4.org/jsonconfig" import ( "fmt" "sort" "strconv" "strings" ) // Obj is a JSON configuration map. type Obj map[string]interface{} // ReadFile reads JSON config data from the specified open file, expanding // all expressions. Use *ConfigParser.ReadFile instead if you // need to set c.IncludeDirs. func ReadFile(configPath string) (Obj, error) { var c ConfigParser return c.ReadFile(configPath) } func (jc Obj) RequiredObject(key string) Obj { return jc.obj(key, false) } func (jc Obj) OptionalObject(key string) Obj { return jc.obj(key, true) } func (jc Obj) obj(key string, optional bool) Obj { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if optional { return make(Obj) } jc.appendError(fmt.Errorf("Missing required config key %q (object)", key)) return make(Obj) } m, ok := ei.(map[string]interface{}) if !ok { jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei)) return make(Obj) } return m } func (jc Obj) RequiredString(key string) string { return jc.string(key, nil) } func (jc Obj) OptionalString(key, def string) string { return jc.string(key, &def) } func (jc Obj) string(key string, def *string) string { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if def != nil { return *def } jc.appendError(fmt.Errorf("Missing required config key %q (string)", key)) return "" } s, ok := ei.(string) if !ok { jc.appendError(fmt.Errorf("Expected config key %q to be a string", key)) return "" } return s } func (jc Obj) RequiredStringOrObject(key string) interface{} { return jc.stringOrObject(key, true) } func (jc Obj) OptionalStringOrObject(key string) interface{} { return jc.stringOrObject(key, false) } func (jc Obj) stringOrObject(key string, required bool) interface{} { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if !required { return nil } jc.appendError(fmt.Errorf("Missing required config key %q (string or object)", key)) return "" } if _, ok := ei.(map[string]interface{}); ok { return ei } if _, ok := ei.(string); ok { return ei } jc.appendError(fmt.Errorf("Expected config key %q to be a string or object", key)) return "" } func (jc Obj) RequiredBool(key string) bool { return jc.bool(key, nil) } func (jc Obj) OptionalBool(key string, def bool) bool { return jc.bool(key, &def) } func (jc Obj) bool(key string, def *bool) bool { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if def != nil { return *def } jc.appendError(fmt.Errorf("Missing required config key %q (boolean)", key)) return false } switch v := ei.(type) { case bool: return v case string: b, err := strconv.ParseBool(v) if err != nil { jc.appendError(fmt.Errorf("Config key %q has bad boolean format %q", key, v)) } return b default: jc.appendError(fmt.Errorf("Expected config key %q to be a boolean", key)) return false } } func (jc Obj) RequiredInt(key string) int { return jc.int(key, nil) } func (jc Obj) OptionalInt(key string, def int) int { return jc.int(key, &def) } func (jc Obj) int(key string, def *int) int { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if def != nil { return *def } jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key)) return 0 } b, ok := ei.(float64) if !ok { jc.appendError(fmt.Errorf("Expected config key %q to be a number", key)) return 0 } return int(b) } func (jc Obj) RequiredInt64(key string) int64 { return jc.int64(key, nil) } func (jc Obj) OptionalInt64(key string, def int64) int64 { return jc.int64(key, &def) } func (jc Obj) int64(key string, def *int64) int64 { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if def != nil { return *def } jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key)) return 0 } b, ok := ei.(float64) if !ok { jc.appendError(fmt.Errorf("Expected config key %q to be a number", key)) return 0 } return int64(b) } func (jc Obj) RequiredList(key string) []string { return jc.requiredList(key, true) } func (jc Obj) OptionalList(key string) []string { return jc.requiredList(key, false) } func (jc Obj) requiredList(key string, required bool) []string { jc.noteKnownKey(key) ei, ok := jc[key] if !ok { if required { jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key)) } return nil } eil, ok := ei.([]interface{}) if !ok { jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei)) return nil } sl := make([]string, len(eil)) for i, ei := range eil { s, ok := ei.(string) if !ok { jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei)) return nil } sl[i] = s } return sl } func (jc Obj) noteKnownKey(key string) { _, ok := jc["_knownkeys"] if !ok { jc["_knownkeys"] = make(map[string]bool) } jc["_knownkeys"].(map[string]bool)[key] = true } func (jc Obj) appendError(err error) { ei, ok := jc["_errors"] if ok { jc["_errors"] = append(ei.([]error), err) } else { jc["_errors"] = []error{err} } } // UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls. func (jc Obj) UnknownKeys() []string { ei, ok := jc["_knownkeys"] var known map[string]bool if ok { known = ei.(map[string]bool) } var unknown []string for k, _ := range jc { if ok && known[k] { continue } if strings.HasPrefix(k, "_") { // Permit keys with a leading underscore as a // form of comments. continue } unknown = append(unknown, k) } sort.Strings(unknown) return unknown } func (jc Obj) Validate() error { unknown := jc.UnknownKeys() for _, k := range unknown { jc.appendError(fmt.Errorf("Unknown key %q", k)) } ei, ok := jc["_errors"] if !ok { return nil } errList := ei.([]error) if len(errList) == 1 { return errList[0] } strs := make([]string, 0) for _, v := range errList { strs = append(strs, v.Error()) } return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", ")) } golang-go4-0.0~git20230225.2148625/jsonconfig/jsonconfig_test.go000066400000000000000000000053021445700726600235620ustar00rootroot00000000000000/* Copyright 2011 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package jsonconfig import ( "os" "reflect" "strings" "testing" ) func testIncludes(configFile string, t *testing.T) { var c ConfigParser c.IncludeDirs = []string{"testdata"} obj, err := c.ReadFile(configFile) if err != nil { t.Fatal(err) } two := obj.RequiredObject("two") if err := obj.Validate(); err != nil { t.Error(err) } if g, e := two.RequiredString("key"), "value"; g != e { t.Errorf("sub object key = %q; want %q", g, e) } } func TestIncludesCWD(t *testing.T) { testIncludes("testdata/include1.json", t) } func TestIncludesIncludeDirs(t *testing.T) { testIncludes("testdata/include1bis.json", t) } func TestIncludeLoop(t *testing.T) { _, err := ReadFile("testdata/loop1.json") if err == nil { t.Fatal("expected an error about import cycles.") } if !strings.Contains(err.Error(), "include cycle detected") { t.Fatalf("expected an error about import cycles; got: %v", err) } } func TestBoolEnvs(t *testing.T) { os.Setenv("TEST_EMPTY", "") os.Setenv("TEST_TRUE", "true") os.Setenv("TEST_ONE", "1") os.Setenv("TEST_ZERO", "0") os.Setenv("TEST_FALSE", "false") obj, err := ReadFile("testdata/boolenv.json") if err != nil { t.Fatal(err) } if str := obj.RequiredString("emptystr"); str != "" { t.Errorf("str = %q, want empty", str) } tests := []struct { key string want bool }{ {"def_false", false}, {"def_true", true}, {"set_true_def_false", true}, {"set_false_def_true", false}, {"lit_true", true}, {"lit_false", false}, {"one", true}, {"zero", false}, } for _, tt := range tests { if v := obj.RequiredBool(tt.key); v != tt.want { t.Errorf("key %q = %v; want %v", tt.key, v, tt.want) } } if err := obj.Validate(); err != nil { t.Error(err) } } func TestListExpansion(t *testing.T) { os.Setenv("TEST_BAR", "bar") obj, err := ReadFile("testdata/listexpand.json") if err != nil { t.Fatal(err) } s := obj.RequiredString("str") l := obj.RequiredList("list") if err := obj.Validate(); err != nil { t.Error(err) } want := []string{"foo", "bar"} if !reflect.DeepEqual(l, want) { t.Errorf("got = %#v\nwant = %#v", l, want) } if s != "bar" { t.Errorf("str = %q, want %q", s, "bar") } } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/000077500000000000000000000000001445700726600216465ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/boolenv.json000066400000000000000000000005511445700726600242060ustar00rootroot00000000000000{ "emptystr": ["_env", "${TEST_EMPTY}", ""], "def_false": ["_env", "${TEST_EMPTY}", false], "def_true": ["_env", "${TEST_EMPTY}", true], "set_true_def_false": ["_env", "${TEST_TRUE}", false], "set_false_def_true": ["_env", "${TEST_FALSE}", true], "one": ["_env", "${TEST_ONE}"], "zero": ["_env", "${TEST_ZERO}"], "lit_true": true, "lit_false": false } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/include1.json000066400000000000000000000000641445700726600242450ustar00rootroot00000000000000{ "two": ["_fileobj", "testdata/include2.json"] } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/include1bis.json000066400000000000000000000000531445700726600247410ustar00rootroot00000000000000{ "two": ["_fileobj", "include2.json"] } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/include2.json000066400000000000000000000000251445700726600242430ustar00rootroot00000000000000{ "key": "value" } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/listexpand.json000066400000000000000000000001211445700726600247060ustar00rootroot00000000000000{ "list": ["foo", ["_env", "${TEST_BAR}"]], "str": ["_env", "${TEST_BAR}"] } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/loop1.json000066400000000000000000000000611445700726600235700ustar00rootroot00000000000000{ "obj": ["_fileobj", "testdata/loop2.json"] } golang-go4-0.0~git20230225.2148625/jsonconfig/testdata/loop2.json000066400000000000000000000000611445700726600235710ustar00rootroot00000000000000{ "obj": ["_fileobj", "testdata/loop1.json"] } golang-go4-0.0~git20230225.2148625/legal/000077500000000000000000000000001445700726600167625ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/legal/legal.go000066400000000000000000000017161445700726600204020ustar00rootroot00000000000000/* Copyright 2014 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package legal provides in-process storage for compiled-in licenses. package legal // import "go4.org/legal" var licenses []string // RegisterLicense stores the license text. // It doesn't check whether the text was already present. func RegisterLicense(text string) { licenses = append(licenses, text) return } // Licenses returns a slice of the licenses. func Licenses() []string { return licenses } golang-go4-0.0~git20230225.2148625/legal/legal_test.go000066400000000000000000000013771445700726600214440ustar00rootroot00000000000000/* Copyright 2014 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package legal import ( "testing" ) func TestRegisterLicense(t *testing.T) { initial := len(licenses) RegisterLicense("dummy") if initial+1 != len(licenses) { t.Fatal("didn't add a license") } } golang-go4-0.0~git20230225.2148625/lock/000077500000000000000000000000001445700726600166265ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/lock/.gitignore000066400000000000000000000000031445700726600206070ustar00rootroot00000000000000*~ golang-go4-0.0~git20230225.2148625/lock/lock.go000066400000000000000000000112641445700726600201110ustar00rootroot00000000000000/* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package lock is a file locking library. package lock // import "go4.org/lock" import ( "encoding/json" "fmt" "io" "os" "path/filepath" "sync" ) // Lock locks the given file, creating the file if necessary. If the // file already exists, it must have zero size or an error is returned. // The lock is an exclusive lock (a write lock), but locked files // should neither be read from nor written to. Such files should have // zero size and only exist to co-ordinate ownership across processes. // // A nil Closer is returned if an error occurred. Otherwise, close that // Closer to release the lock. // // On Linux, FreeBSD and OSX, a lock has the same semantics as fcntl(2)'s // advisory locks. In particular, closing any other file descriptor for the // same file will release the lock prematurely. // // Attempting to lock a file that is already locked by the current process // has undefined behavior. // // On other operating systems, lock will fallback to using the presence and // content of a file named name + '.lock' to implement locking behavior. func Lock(name string) (io.Closer, error) { abs, err := filepath.Abs(name) if err != nil { return nil, err } lockmu.Lock() defer lockmu.Unlock() if locked[abs] { return nil, fmt.Errorf("file %q already locked", abs) } c, err := lockFn(abs) if err != nil { return nil, fmt.Errorf("cannot acquire lock: %v", err) } locked[abs] = true return c, nil } var lockFn = lockPortable // lockPortable is a portable version not using fcntl. Doesn't handle crashes as gracefully, // since it can leave stale lock files. func lockPortable(name string) (io.Closer, error) { fi, err := os.Stat(name) if err == nil && fi.Size() > 0 { st := portableLockStatus(name) switch st { case statusLocked: return nil, fmt.Errorf("file %q already locked", name) case statusStale: os.Remove(name) case statusInvalid: return nil, fmt.Errorf("can't Lock file %q: has invalid contents", name) } } f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666) if err != nil { return nil, fmt.Errorf("failed to create lock file %s %v", name, err) } if err := json.NewEncoder(f).Encode(&pidLockMeta{OwnerPID: os.Getpid()}); err != nil { return nil, fmt.Errorf("cannot write owner pid: %v", err) } return &unlocker{ f: f, abs: name, portable: true, }, nil } type lockStatus int const ( statusInvalid lockStatus = iota statusLocked statusUnlocked statusStale ) type pidLockMeta struct { OwnerPID int } func portableLockStatus(path string) lockStatus { f, err := os.Open(path) if err != nil { return statusUnlocked } defer f.Close() var meta pidLockMeta if json.NewDecoder(f).Decode(&meta) != nil { return statusInvalid } if meta.OwnerPID == 0 { return statusInvalid } p, err := os.FindProcess(meta.OwnerPID) if err != nil { // e.g. on Windows return statusStale } // On unix, os.FindProcess always is true, so we have to send // it a signal to see if it's alive. if signalZero != nil { if p.Signal(signalZero) != nil { return statusStale } } return statusLocked } var signalZero os.Signal // nil or set by lock_sigzero.go var ( lockmu sync.Mutex locked = map[string]bool{} // abs path -> true ) type unlocker struct { portable bool f *os.File abs string // once guards the close method call. once sync.Once // err holds the error returned by Close. err error } func (u *unlocker) Close() error { u.once.Do(u.close) return u.err } func (u *unlocker) close() { lockmu.Lock() defer lockmu.Unlock() delete(locked, u.abs) if u.portable { // In the portable lock implementation, it's // important to close before removing because // Windows won't allow us to remove an open // file. if err := u.f.Close(); err != nil { u.err = err } if err := os.Remove(u.abs); err != nil { // Note that if both Close and Remove fail, // we care more about the latter than the former // so we'll return that error. u.err = err } return } // In other implementatioons, it's nice for us to clean up. // If we do do this, though, it needs to be before the // u.f.Close below. os.Remove(u.abs) u.err = u.f.Close() } golang-go4-0.0~git20230225.2148625/lock/lock_appengine.go000066400000000000000000000014161445700726600221350ustar00rootroot00000000000000// +build appengine /* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package lock import ( "errors" "io" ) func init() { lockFn = lockAppEngine } func lockAppEngine(name string) (io.Closer, error) { return nil, errors.New("Lock not available on App Engine") } golang-go4-0.0~git20230225.2148625/lock/lock_plan9.go000066400000000000000000000020141445700726600212050ustar00rootroot00000000000000/* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package lock import ( "fmt" "io" "os" ) func init() { lockFn = lockPlan9 } func lockPlan9(name string) (io.Closer, error) { fi, err := os.Stat(name) if err == nil && fi.Size() > 0 { return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name) } f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644) if err != nil { return nil, fmt.Errorf("Lock Create of %s failed: %v", name, err) } return &unlocker{f: f, abs: name}, nil } golang-go4-0.0~git20230225.2148625/lock/lock_sigzero.go000066400000000000000000000013211445700726600216440ustar00rootroot00000000000000// +build !appengine // +build linux darwin freebsd openbsd netbsd dragonfly /* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package lock import "syscall" func init() { signalZero = syscall.Signal(0) } golang-go4-0.0~git20230225.2148625/lock/lock_test.go000066400000000000000000000106431445700726600211500ustar00rootroot00000000000000/* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package lock import ( "bufio" "errors" "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" "strconv" "testing" ) func TestLock(t *testing.T) { testLock(t, false) } func TestLockPortable(t *testing.T) { testLock(t, true) } func TestLockInChild(t *testing.T) { f := os.Getenv("TEST_LOCK_FILE") if f == "" { // not child return } lock := Lock if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v { lock = lockPortable } var lk io.Closer for scan := bufio.NewScanner(os.Stdin); scan.Scan(); { var err error switch scan.Text() { case "lock": lk, err = lock(f) case "unlock": err = lk.Close() lk = nil case "exit": // Simulate a crash, or at least not unlocking the lock. os.Exit(0) default: err = fmt.Errorf("unexpected child command %q", scan.Text()) } if err != nil { fmt.Println(err) } else { fmt.Println("") } } } func testLock(t *testing.T, portable bool) { lock := Lock if portable { lock = lockPortable } t.Logf("test lock, portable %v", portable) td, err := ioutil.TempDir("", "") if err != nil { t.Fatal(err) } defer os.RemoveAll(td) path := filepath.Join(td, "foo.lock") proc := newChildProc(t, path, portable) defer proc.kill() t.Logf("First lock in child") if err := proc.do("lock"); err != nil { t.Fatalf("first lock in child process: %v", err) } t.Logf("Crash child") if err := proc.do("exit"); err != nil { t.Fatalf("crash in child process: %v", err) } proc = newChildProc(t, path, portable) defer proc.kill() t.Logf("Locking+unlocking in child...") if err := proc.do("lock"); err != nil { t.Fatalf("lock in child process after crashing child: %v", err) } if err := proc.do("unlock"); err != nil { t.Fatalf("lock in child process after crashing child: %v", err) } t.Logf("Locking in parent...") lk1, err := lock(path) if err != nil { t.Fatal(err) } t.Logf("Again in parent...") _, err = lock(path) if err == nil { t.Fatal("expected second lock to fail") } t.Logf("Locking in child...") if err := proc.do("lock"); err == nil { t.Fatalf("expected lock in child process to fail") } t.Logf("Unlocking lock in parent") if err := lk1.Close(); err != nil { t.Fatal(err) } t.Logf("Trying lock again in child...") if err := proc.do("lock"); err != nil { t.Fatal(err) } if err := proc.do("unlock"); err != nil { t.Fatal(err) } lk3, err := lock(path) if err != nil { t.Fatal(err) } lk3.Close() } type childLockCmd struct { op string reply chan<- error } type childProc struct { proc *os.Process c chan childLockCmd } func (c *childProc) kill() { c.proc.Kill() } func (c *childProc) do(op string) error { reply := make(chan error) c.c <- childLockCmd{ op: op, reply: reply, } return <-reply } func newChildProc(t *testing.T, path string, portable bool) *childProc { cmd := exec.Command(os.Args[0], "-test.run=LockInChild$") cmd.Env = []string{"TEST_LOCK_FILE=" + path} toChild, err := cmd.StdinPipe() if err != nil { t.Fatalf("cannot make pipe: %v", err) } fromChild, err := cmd.StdoutPipe() if err != nil { t.Fatalf("cannot make pipe: %v", err) } cmd.Stderr = os.Stderr if portable { cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1") } if err := cmd.Start(); err != nil { t.Fatalf("cannot start child: %v", err) } cmdChan := make(chan childLockCmd) go func() { defer fromChild.Close() defer toChild.Close() inScan := bufio.NewScanner(fromChild) for c := range cmdChan { fmt.Fprintln(toChild, c.op) ok := inScan.Scan() if c.op == "exit" { if ok { c.reply <- errors.New("child did not exit") } else { cmd.Wait() c.reply <- nil } break } if !ok { panic("child exited early") } if errText := inScan.Text(); errText != "" { c.reply <- errors.New(errText) } else { c.reply <- nil } } }() return &childProc{ c: cmdChan, proc: cmd.Process, } } golang-go4-0.0~git20230225.2148625/lock/lock_unix.go000066400000000000000000000026161445700726600211550ustar00rootroot00000000000000// +build linux darwin freebsd openbsd netbsd dragonfly solaris // +build !appengine /* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package lock import ( "fmt" "io" "os" "golang.org/x/sys/unix" ) func init() { lockFn = lockFcntl } func lockFcntl(name string) (io.Closer, error) { fi, err := os.Stat(name) if err == nil && fi.Size() > 0 { return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name) } f, err := os.Create(name) if err != nil { return nil, fmt.Errorf("Lock Create of %s failed: %v", name, err) } err = unix.FcntlFlock(f.Fd(), unix.F_SETLK, &unix.Flock_t{ Type: unix.F_WRLCK, Whence: int16(os.SEEK_SET), Start: 0, Len: 0, // 0 means to lock the entire file. Pid: 0, // only used by F_GETLK }) if err != nil { f.Close() return nil, fmt.Errorf("Lock FcntlFlock of %s failed: %v", name, err) } return &unlocker{f: f, abs: name}, nil } golang-go4-0.0~git20230225.2148625/lock/lock_windows.go000066400000000000000000000034101445700726600216550ustar00rootroot00000000000000/* Copyright 2013 The Go Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package lock import ( "fmt" "io" "os" "sync" "golang.org/x/sys/windows" ) func init() { lockFn = lockWindows } type winUnlocker struct { h windows.Handle abs string // err holds the error returned by Close. err error // once guards the close method call. once sync.Once } func (u *winUnlocker) Close() error { u.once.Do(u.close) return u.err } func (u *winUnlocker) close() { lockmu.Lock() defer lockmu.Unlock() delete(locked, u.abs) u.err = windows.CloseHandle(u.h) } func lockWindows(name string) (io.Closer, error) { fi, err := os.Stat(name) if err == nil && fi.Size() > 0 { return nil, fmt.Errorf("can't lock file %q: %s", name, "has non-zero size") } handle, err := winCreateEphemeral(name) if err != nil { return nil, fmt.Errorf("creation of lock %s failed: %v", name, err) } return &winUnlocker{h: handle, abs: name}, nil } func winCreateEphemeral(name string) (windows.Handle, error) { const ( FILE_ATTRIBUTE_TEMPORARY = 0x100 FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 ) handle, err := windows.CreateFile(windows.StringToUTF16Ptr(name), 0, 0, nil, windows.OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, 0) if err != nil { return 0, err } return handle, nil } golang-go4-0.0~git20230225.2148625/media/000077500000000000000000000000001445700726600167555ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/media/heif/000077500000000000000000000000001445700726600176705ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/media/heif/bmff/000077500000000000000000000000001445700726600206025ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/media/heif/bmff/bmff.go000066400000000000000000000451341445700726600220520ustar00rootroot00000000000000/* Copyright 2018 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package bmff reads ISO BMFF boxes, as used by HEIF, etc. // // This is not so much as a generic BMFF reader as it is a BMFF reader // as needed by HEIF, though that may change in time. For now, only // boxes necessary for the go4.org/media/heif package have explicit // parsers. // // This package makes no API compatibility promises; it exists // primarily for use by the go4.org/media/heif package. package bmff import ( "bufio" "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "strings" ) func NewReader(r io.Reader) *Reader { br, ok := r.(*bufio.Reader) if !ok { br = bufio.NewReader(r) } return &Reader{br: bufReader{Reader: br}} } type Reader struct { br bufReader lastBox Box // or nil noMoreBoxes bool // a box with size 0 (the final box) was seen } type BoxType [4]byte // Common box types. var ( TypeFtyp = BoxType{'f', 't', 'y', 'p'} TypeMeta = BoxType{'m', 'e', 't', 'a'} ) func (t BoxType) String() string { return string(t[:]) } func (t BoxType) EqualString(s string) bool { // Could be cleaner, but see ohttps://github.com/golang/go/issues/24765 return len(s) == 4 && s[0] == t[0] && s[1] == t[1] && s[2] == t[2] && s[3] == t[3] } type parseFunc func(b box, br *bufio.Reader) (Box, error) // Box represents a BMFF box. type Box interface { Size() int64 // 0 means unknown (will read to end of file) Type() BoxType // Parses parses the box, populating the fields // in the returned concrete type. // // If Parse has already been called, Parse returns nil. // If the box type is unknown, the returned error is ErrUnknownBox // and it's guaranteed that no bytes have been read from the box. Parse() (Box, error) // Body returns the inner bytes of the box, ignoring the header. // The body may start with the 4 byte header of a "Full Box" if the // box's type derives from a full box. Most users will use Parse // instead. // Body will return a new reader at the beginning of the box if the // outer box has already been parsed. Body() io.Reader } // ErrUnknownBox is returned by Box.Parse for unrecognized box types. var ErrUnknownBox = errors.New("heif: unknown box") type parserFunc func(b *box, br *bufReader) (Box, error) func boxType(s string) BoxType { if len(s) != 4 { panic("bogus boxType length") } return BoxType{s[0], s[1], s[2], s[3]} } var parsers = map[BoxType]parserFunc{ boxType("dinf"): parseDataInformationBox, boxType("dref"): parseDataReferenceBox, boxType("ftyp"): parseFileTypeBox, boxType("hdlr"): parseHandlerBox, boxType("iinf"): parseItemInfoBox, boxType("infe"): parseItemInfoEntry, boxType("iloc"): parseItemLocationBox, boxType("ipco"): parseItemPropertyContainerBox, boxType("ipma"): parseItemPropertyAssociation, boxType("iprp"): parseItemPropertiesBox, boxType("irot"): parseImageRotation, boxType("ispe"): parseImageSpatialExtentsProperty, boxType("meta"): parseMetaBox, boxType("pitm"): parsePrimaryItemBox, } type box struct { size int64 // 0 means unknown, will read to end of file (box container) boxType BoxType body io.Reader parsed Box // if non-nil, the Parsed result slurp []byte // if non-nil, the contents slurped to memory } func (b *box) Size() int64 { return b.size } func (b *box) Type() BoxType { return b.boxType } func (b *box) Body() io.Reader { if b.slurp != nil { return bytes.NewReader(b.slurp) } return b.body } func (b *box) Parse() (Box, error) { if b.parsed != nil { return b.parsed, nil } parser, ok := parsers[b.Type()] if !ok { return nil, ErrUnknownBox } v, err := parser(b, &bufReader{Reader: bufio.NewReader(b.Body())}) if err != nil { return nil, err } b.parsed = v return v, nil } type FullBox struct { *box Version uint8 Flags uint32 // 24 bits } // ReadBox reads the next box. // // If the previously read box was not read to completion, ReadBox consumes // the rest of its data. // // At the end, the error is io.EOF. func (r *Reader) ReadBox() (Box, error) { if r.noMoreBoxes { return nil, io.EOF } if r.lastBox != nil { if _, err := io.Copy(ioutil.Discard, r.lastBox.Body()); err != nil { return nil, err } } var buf [8]byte _, err := io.ReadFull(r.br, buf[:4]) if err != nil { return nil, err } box := &box{ size: int64(binary.BigEndian.Uint32(buf[:4])), } _, err = io.ReadFull(r.br, box.boxType[:]) // 4 more bytes if err != nil { return nil, err } // Special cases for size: var remain int64 switch box.size { case 1: // 1 means it's actually a 64-bit size, after the type. _, err = io.ReadFull(r.br, buf[:8]) if err != nil { return nil, err } box.size = int64(binary.BigEndian.Uint64(buf[:8])) if box.size < 0 { // Go uses int64 for sizes typically, but BMFF uses uint64. // We assume for now that nobody actually uses boxes larger // than int64. return nil, fmt.Errorf("unexpectedly large box %q", box.boxType) } remain = box.size - 2*4 - 8 case 0: // 0 means unknown & to read to end of file. No more boxes. r.noMoreBoxes = true default: remain = box.size - 2*4 } if remain < 0 { return nil, fmt.Errorf("Box header for %q has size %d, suggesting %d (negative) bytes remain", box.boxType, box.size, remain) } if box.size > 0 { box.body = io.LimitReader(r.br, remain) } else { box.body = r.br } r.lastBox = box return box, nil } // ReadAndParseBox wraps the ReadBox method, ensuring that the read box is of type typ // and parses successfully. It returns the parsed box. func (r *Reader) ReadAndParseBox(typ BoxType) (Box, error) { box, err := r.ReadBox() if err != nil { return nil, fmt.Errorf("error reading %q box: %v", typ, err) } if box.Type() != typ { return nil, fmt.Errorf("error reading %q box: got box type %q instead", typ, box.Type()) } pbox, err := box.Parse() if err != nil { return nil, fmt.Errorf("error parsing read %q box: %v", typ, err) } return pbox, nil } func readFullBox(outer *box, br *bufReader) (fb FullBox, err error) { fb.box = outer // Parse FullBox header. buf, err := br.Peek(4) if err != nil { return FullBox{}, fmt.Errorf("failed to read 4 bytes of FullBox: %v", err) } fb.Version = buf[0] buf[0] = 0 fb.Flags = binary.BigEndian.Uint32(buf[:4]) br.Discard(4) return fb, nil } type FileTypeBox struct { *box MajorBrand string // 4 bytes MinorVersion string // 4 bytes Compatible []string // all 4 bytes } func parseFileTypeBox(outer *box, br *bufReader) (Box, error) { buf, err := br.Peek(8) if err != nil { return nil, err } ft := &FileTypeBox{ box: outer, MajorBrand: string(buf[:4]), MinorVersion: string(buf[4:8]), } br.Discard(8) for { buf, err := br.Peek(4) if err == io.EOF { return ft, nil } if err != nil { return nil, err } ft.Compatible = append(ft.Compatible, string(buf[:4])) br.Discard(4) } } type MetaBox struct { FullBox Children []Box } func parseMetaBox(outer *box, br *bufReader) (Box, error) { fb, err := readFullBox(outer, br) if err != nil { return nil, err } mb := &MetaBox{FullBox: fb} return mb, br.parseAppendBoxes(&mb.Children) } func (br *bufReader) parseAppendBoxes(dst *[]Box) error { if br.err != nil { return br.err } boxr := NewReader(br.Reader) for { inner, err := boxr.ReadBox() if err == io.EOF { return nil } if err != nil { br.err = err return err } slurp, err := ioutil.ReadAll(inner.Body()) if err != nil { br.err = err return err } inner.(*box).slurp = slurp *dst = append(*dst, inner) } } // ItemInfoEntry represents an "infe" box. // // TODO: currently only parses Version 2 boxes. type ItemInfoEntry struct { FullBox ItemID uint16 ProtectionIndex uint16 ItemType string // always 4 bytes Name string // If Type == "mime": ContentType string ContentEncoding string // If Type == "uri ": ItemURIType string } func parseItemInfoEntry(outer *box, br *bufReader) (Box, error) { fb, err := readFullBox(outer, br) if err != nil { return nil, err } ie := &ItemInfoEntry{FullBox: fb} if fb.Version != 2 { return nil, fmt.Errorf("TODO: found version %d infe box. Only 2 is supported now.", fb.Version) } ie.ItemID, _ = br.readUint16() ie.ProtectionIndex, _ = br.readUint16() if !br.ok() { return nil, br.err } buf, err := br.Peek(4) if err != nil { return nil, err } ie.ItemType = string(buf[:4]) ie.Name, _ = br.readString() switch ie.ItemType { case "mime": ie.ContentType, _ = br.readString() if br.anyRemain() { ie.ContentEncoding, _ = br.readString() } case "uri ": ie.ItemURIType, _ = br.readString() } if !br.ok() { return nil, br.err } return ie, nil } // ItemInfoBox represents an "iinf" box. type ItemInfoBox struct { FullBox Count uint16 ItemInfos []*ItemInfoEntry } func parseItemInfoBox(outer *box, br *bufReader) (Box, error) { fb, err := readFullBox(outer, br) if err != nil { return nil, err } ib := &ItemInfoBox{FullBox: fb} ib.Count, _ = br.readUint16() var itemInfos []Box br.parseAppendBoxes(&itemInfos) if br.ok() { for _, box := range itemInfos { pb, err := box.Parse() if err != nil { return nil, fmt.Errorf("error parsing ItemInfoEntry in ItemInfoBox: %v", err) } if iie, ok := pb.(*ItemInfoEntry); ok { ib.ItemInfos = append(ib.ItemInfos, iie) } } } if !br.ok() { return FullBox{}, br.err } return ib, nil } // bufReader adds some HEIF/BMFF-specific methods around a *bufio.Reader. type bufReader struct { *bufio.Reader err error // sticky error } // ok reports whether all previous reads have been error-free. func (br *bufReader) ok() bool { return br.err == nil } func (br *bufReader) anyRemain() bool { if br.err != nil { return false } _, err := br.Peek(1) return err == nil } func (br *bufReader) readUintN(bits uint8) (uint64, error) { if br.err != nil { return 0, br.err } if bits == 0 { return 0, nil } nbyte := bits / 8 buf, err := br.Peek(int(nbyte)) if err != nil { br.err = err return 0, err } defer br.Discard(int(nbyte)) switch bits { case 8: return uint64(buf[0]), nil case 16: return uint64(binary.BigEndian.Uint16(buf[:2])), nil case 32: return uint64(binary.BigEndian.Uint32(buf[:4])), nil case 64: return binary.BigEndian.Uint64(buf[:8]), nil default: br.err = fmt.Errorf("invalid uintn read size") return 0, br.err } } func (br *bufReader) readUint8() (uint8, error) { if br.err != nil { return 0, br.err } v, err := br.ReadByte() if err != nil { br.err = err return 0, err } return v, nil } func (br *bufReader) readUint16() (uint16, error) { if br.err != nil { return 0, br.err } buf, err := br.Peek(2) if err != nil { br.err = err return 0, err } v := binary.BigEndian.Uint16(buf[:2]) br.Discard(2) return v, nil } func (br *bufReader) readUint32() (uint32, error) { if br.err != nil { return 0, br.err } buf, err := br.Peek(4) if err != nil { br.err = err return 0, err } v := binary.BigEndian.Uint32(buf[:4]) br.Discard(4) return v, nil } func (br *bufReader) readString() (string, error) { if br.err != nil { return "", br.err } s0, err := br.ReadString(0) if err != nil { br.err = err return "", err } s := strings.TrimSuffix(s0, "\x00") if len(s) == len(s0) { err = fmt.Errorf("unexpected non-null terminated string") br.err = err return "", err } return s, nil } // HEIF: ipco type ItemPropertyContainerBox struct { *box Properties []Box // of ItemProperty or ItemFullProperty } func parseItemPropertyContainerBox(outer *box, br *bufReader) (Box, error) { ipc := &ItemPropertyContainerBox{box: outer} return ipc, br.parseAppendBoxes(&ipc.Properties) } // HEIF: iprp type ItemPropertiesBox struct { *box PropertyContainer *ItemPropertyContainerBox Associations []*ItemPropertyAssociation // at least 1 } func parseItemPropertiesBox(outer *box, br *bufReader) (Box, error) { ip := &ItemPropertiesBox{ box: outer, } var boxes []Box err := br.parseAppendBoxes(&boxes) if err != nil { return nil, err } if len(boxes) < 2 { return nil, fmt.Errorf("expect at least 2 boxes in children; got 0") } cb, err := boxes[0].Parse() if err != nil { return nil, fmt.Errorf("failed to parse first box, %q: %v", boxes[0].Type(), err) } var ok bool ip.PropertyContainer, ok = cb.(*ItemPropertyContainerBox) if !ok { return nil, fmt.Errorf("unexpected type %T for ItemPropertieBox.PropertyContainer", cb) } // Association boxes ip.Associations = make([]*ItemPropertyAssociation, 0, len(boxes)-1) for _, box := range boxes[1:] { boxp, err := box.Parse() if err != nil { return nil, fmt.Errorf("failed to parse association box: %v", err) } ipa, ok := boxp.(*ItemPropertyAssociation) if !ok { return nil, fmt.Errorf("unexpected box %q instead of ItemPropertyAssociation", boxp.Type()) } ip.Associations = append(ip.Associations, ipa) } return ip, nil } type ItemPropertyAssociation struct { FullBox EntryCount uint32 Entries []ItemPropertyAssociationItem } // not a box type ItemProperty struct { Essential bool Index uint16 } // not a box type ItemPropertyAssociationItem struct { ItemID uint32 AssociationsCount int // as declared Associations []ItemProperty // as parsed } func parseItemPropertyAssociation(outer *box, br *bufReader) (Box, error) { fb, err := readFullBox(outer, br) if err != nil { return nil, err } ipa := &ItemPropertyAssociation{FullBox: fb} count, _ := br.readUint32() ipa.EntryCount = count for i := uint64(0); i < uint64(count) && br.ok(); i++ { var itemID uint32 if fb.Version < 1 { itemID16, _ := br.readUint16() itemID = uint32(itemID16) } else { itemID, _ = br.readUint32() } assocCount, _ := br.readUint8() ipai := ItemPropertyAssociationItem{ ItemID: itemID, AssociationsCount: int(assocCount), } for j := 0; j < int(assocCount) && br.ok(); j++ { first, _ := br.readUint8() essential := first&(1<<7) != 0 first &^= byte(1 << 7) var index uint16 if fb.Flags&1 != 0 { second, _ := br.readUint8() index = uint16(first)<<8 | uint16(second) } else { index = uint16(first) } ipai.Associations = append(ipai.Associations, ItemProperty{ Essential: essential, Index: index, }) } ipa.Entries = append(ipa.Entries, ipai) } if !br.ok() { return nil, br.err } return ipa, nil } type ImageSpatialExtentsProperty struct { FullBox ImageWidth uint32 ImageHeight uint32 } func parseImageSpatialExtentsProperty(outer *box, br *bufReader) (Box, error) { fb, err := readFullBox(outer, br) if err != nil { return nil, err } w, err := br.readUint32() if err != nil { return nil, err } h, err := br.readUint32() if err != nil { return nil, err } return &ImageSpatialExtentsProperty{ FullBox: fb, ImageWidth: w, ImageHeight: h, }, nil } type OffsetLength struct { Offset, Length uint64 } // not a box type ItemLocationBoxEntry struct { ItemID uint16 ConstructionMethod uint8 // actually uint4 DataReferenceIndex uint16 BaseOffset uint64 // uint32 or uint64, depending on encoding ExtentCount uint16 Extents []OffsetLength } // box "iloc" type ItemLocationBox struct { FullBox offsetSize, lengthSize, baseOffsetSize, indexSize uint8 // actually uint4 ItemCount uint16 Items []ItemLocationBoxEntry } func parseItemLocationBox(outer *box, br *bufReader) (Box, error) { fb, err := readFullBox(outer, br) if err != nil { return nil, err } ilb := &ItemLocationBox{ FullBox: fb, } buf, err := br.Peek(4) if err != nil { return nil, err } ilb.offsetSize = buf[0] >> 4 ilb.lengthSize = buf[0] & 15 ilb.baseOffsetSize = buf[1] >> 4 if fb.Version > 0 { // version 1 ilb.indexSize = buf[1] & 15 } ilb.ItemCount = binary.BigEndian.Uint16(buf[2:4]) br.Discard(4) for i := 0; br.ok() && i < int(ilb.ItemCount); i++ { var ent ItemLocationBoxEntry ent.ItemID, _ = br.readUint16() if fb.Version > 0 { // version 1 cmeth, _ := br.readUint16() ent.ConstructionMethod = byte(cmeth & 15) } ent.DataReferenceIndex, _ = br.readUint16() if br.ok() && ilb.baseOffsetSize > 0 { br.Discard(int(ilb.baseOffsetSize) / 8) } ent.ExtentCount, _ = br.readUint16() for j := 0; br.ok() && j < int(ent.ExtentCount); j++ { var ol OffsetLength ol.Offset, _ = br.readUintN(ilb.offsetSize * 8) ol.Length, _ = br.readUintN(ilb.lengthSize * 8) if br.err != nil { return nil, br.err } ent.Extents = append(ent.Extents, ol) } ilb.Items = append(ilb.Items, ent) } if !br.ok() { return nil, br.err } return ilb, nil } // a "hdlr" box. type HandlerBox struct { FullBox HandlerType string // always 4 bytes; usually "pict" for iOS Camera images Name string } func parseHandlerBox(gen *box, br *bufReader) (Box, error) { fb, err := readFullBox(gen, br) if err != nil { return nil, err } hb := &HandlerBox{ FullBox: fb, } buf, err := br.Peek(20) if err != nil { return nil, err } hb.HandlerType = string(buf[4:8]) br.Discard(20) hb.Name, _ = br.readString() return hb, br.err } // a "dinf" box type DataInformationBox struct { *box Children []Box } func parseDataInformationBox(gen *box, br *bufReader) (Box, error) { dib := &DataInformationBox{box: gen} return dib, br.parseAppendBoxes(&dib.Children) } // a "dref" box. type DataReferenceBox struct { FullBox EntryCount uint32 Children []Box } func parseDataReferenceBox(gen *box, br *bufReader) (Box, error) { fb, err := readFullBox(gen, br) if err != nil { return nil, err } drb := &DataReferenceBox{FullBox: fb} drb.EntryCount, _ = br.readUint32() return drb, br.parseAppendBoxes(&drb.Children) } // "pitm" box type PrimaryItemBox struct { FullBox ItemID uint16 } func parsePrimaryItemBox(gen *box, br *bufReader) (Box, error) { fb, err := readFullBox(gen, br) if err != nil { return nil, err } pib := &PrimaryItemBox{FullBox: fb} pib.ItemID, _ = br.readUint16() if !br.ok() { return nil, br.err } return pib, nil } // ImageRotation is a HEIF "irot" rotation property. type ImageRotation struct { *box Angle uint8 // 1 means 90 degrees counter-clockwise, 2 means 180 counter-clockwise } func parseImageRotation(gen *box, br *bufReader) (Box, error) { v, err := br.readUint8() if err != nil { return nil, err } return &ImageRotation{box: gen, Angle: v & 3}, nil } golang-go4-0.0~git20230225.2148625/media/heif/dumpheif/000077500000000000000000000000001445700726600214715ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/media/heif/dumpheif/dumpheif.go000066400000000000000000000123031445700726600236200ustar00rootroot00000000000000/* Copyright 2018 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // The dumpheif program dumps the structure and metadata of a HEIF file. // // It exists purely for debugging the go4.org/media/heif and // go4.org/media/heif/bmff packages; it makes no backwards // compatibility promises. package main import ( "bytes" "flag" "fmt" "io" "io/ioutil" "log" "os" "strings" "github.com/rwcarlsen/goexif/exif" "github.com/rwcarlsen/goexif/tiff" "go4.org/media/heif" "go4.org/media/heif/bmff" ) var ( exifItemID uint16 exifLoc bmff.ItemLocationBoxEntry ) func main() { flag.Parse() if flag.NArg() != 1 { fmt.Fprintf(os.Stderr, "usage: dumpheif \n") os.Exit(1) } f, err := os.Open(flag.Arg(0)) if err != nil { log.Fatal(err) } defer f.Close() hf := heif.Open(f) it, err := hf.PrimaryItem() if err != nil { log.Fatalf("PrimaryItem: %v", err) } fmt.Printf("primary item: %v\n", it.ID) width, height, ok := it.SpatialExtents() if ok { fmt.Printf("spatial extents: %d x %d\n", width, height) } fmt.Printf("properties:\n") for _, prop := range it.Properties { fmt.Printf("\t%q: %#v\n", prop.Type(), prop) } if len(it.Properties) == 0 { fmt.Printf("\t(no properties)\n") } if ex, err := hf.EXIF(); err == nil { fmt.Printf("EXIF dump:\n") ex, err := exif.Decode(bytes.NewReader(ex)) if err != nil { log.Fatalf("EXIF decode: %v", err) } ex.Walk(exifWalkFunc(func(name exif.FieldName, tag *tiff.Tag) error { fmt.Printf("\t%v = %v\n", name, tag) return nil })) fmt.Printf("\n") } fmt.Printf("BMFF boxes:\n") r := bmff.NewReader(f) for { box, err := r.ReadBox() if err == io.EOF { break } if err != nil { log.Fatalf("ReadBox: %v", err) } dumpBox(box, 0) } } type exifWalkFunc func(exif.FieldName, *tiff.Tag) error func (f exifWalkFunc) Walk(name exif.FieldName, tag *tiff.Tag) error { return f(name, tag) } func dumpBox(box bmff.Box, depth int) { indent := strings.Repeat(" ", depth) fmt.Printf("%sBox: type %q, size %v\n", indent, box.Type(), box.Size()) box2, err := box.Parse() if err == bmff.ErrUnknownBox { slurp, err := ioutil.ReadAll(box.Body()) if err != nil { log.Fatalf("%sreading body: %v", indent, err) } if len(slurp) < 5000 { fmt.Printf("%s- contents: %q\n", indent, slurp) } else { fmt.Printf("%s- contents: (... %d bytes, starting with %q ...)\n", indent, len(slurp), slurp[:100]) } return } if err != nil { slurp, _ := ioutil.ReadAll(box.Body()) log.Fatalf("Parse box type %q: %v; slurp: %q", box.Type(), err, slurp) } switch v := box2.(type) { case *bmff.FileTypeBox, *bmff.HandlerBox, *bmff.PrimaryItemBox: fmt.Printf("%s- %T: %+v\n", indent, v, v) case *bmff.MetaBox: fmt.Printf("%s- %T, %d children:\n", indent, v, len(v.Children)) for _, child := range v.Children { dumpBox(child, depth+1) } case *bmff.ItemInfoBox: //slurp, _ := ioutil.ReadAll(box.Body()) //fmt.Printf("%s- %T raw: %q\n", indent, v, slurp) fmt.Printf("%s- %T, %d children (%d in slice):\n", indent, v, v.Count, len(v.ItemInfos)) for _, child := range v.ItemInfos { dumpBox(child, depth+1) } case *bmff.ItemInfoEntry: fmt.Printf("%s- %T, %+v\n", indent, v, v) if v.ItemType == "Exif" { exifItemID = v.ItemID } case *bmff.ItemPropertiesBox: fmt.Printf("%s- %T\n", indent, v) if v.PropertyContainer != nil { dumpBox(v.PropertyContainer, depth+1) } for _, child := range v.Associations { dumpBox(child, depth+1) } case *bmff.ItemPropertyAssociation: fmt.Printf("%s- %T: %d declared entries, %d parsed:\n", indent, v, v.EntryCount, len(v.Entries)) for _, ai := range v.Entries { fmt.Printf("%s for Item ID %d, %d associations declared, %d parsed:\n", indent, ai.ItemID, ai.AssociationsCount, len(ai.Associations)) for _, ass := range ai.Associations { fmt.Printf("%s index: %d, essential: %v\n", indent, ass.Index, ass.Essential) } } case *bmff.DataInformationBox: fmt.Printf("%s- %T\n", indent, v) for _, child := range v.Children { dumpBox(child, depth+1) } case *bmff.DataReferenceBox: fmt.Printf("%s- %T\n", indent, v) for _, child := range v.Children { dumpBox(child, depth+1) } case *bmff.ItemPropertyContainerBox: fmt.Printf("%s- %T\n", indent, v) for _, child := range v.Properties { dumpBox(child, depth+1) } case *bmff.ItemLocationBox: fmt.Printf("%s- %T: %d items declared, %d parsed:\n", indent, v, v.ItemCount, len(v.Items)) for _, lbe := range v.Items { fmt.Printf("%s %+v\n", indent, lbe) if exifItemID != 0 && lbe.ItemID == exifItemID { exifLoc = lbe } } case *bmff.ImageSpatialExtentsProperty: fmt.Printf("%s- %T dimensions: %d x %d\n", indent, v, v.ImageWidth, v.ImageHeight) default: fmt.Printf("%s- gotype: %T\n", indent, box2) } } golang-go4-0.0~git20230225.2148625/media/heif/heif.go000066400000000000000000000164121445700726600211360ustar00rootroot00000000000000/* Copyright 2018 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package heif reads HEIF containers, as found in Apple HEIC/HEVC images. // This package does not decode images; it only reads the metadata. // // This package is a work in progress and makes no API compatibility // promises. package heif import ( "errors" "fmt" "io" "log" "go4.org/media/heif/bmff" ) // File represents a HEIF file. // // Methods on File should not be called concurrently. type File struct { ra io.ReaderAt primary *Item // Populated lazily, by getMeta: metaErr error meta *BoxMeta } // BoxMeta contains the low-level BMFF metadata boxes. type BoxMeta struct { FileType *bmff.FileTypeBox Handler *bmff.HandlerBox PrimaryItem *bmff.PrimaryItemBox ItemInfo *bmff.ItemInfoBox Properties *bmff.ItemPropertiesBox ItemLocation *bmff.ItemLocationBox } // EXIFItemID returns the item ID of the EXIF part, or 0 if not found. func (m *BoxMeta) EXIFItemID() uint32 { if m.ItemInfo == nil { return 0 } for _, ife := range m.ItemInfo.ItemInfos { if ife.ItemType == "Exif" { return uint32(ife.ItemID) } } return 0 } // Item represents an item in a HEIF file. type Item struct { f *File ID uint32 Info *bmff.ItemInfoEntry Location *bmff.ItemLocationBoxEntry // location in file Properties []bmff.Box } // SpatialExtents returns the item's spatial extents property values, if present, // not correcting from any camera rotation metadata. func (it *Item) SpatialExtents() (width, height int, ok bool) { for _, p := range it.Properties { if p, ok := p.(*bmff.ImageSpatialExtentsProperty); ok { return int(p.ImageWidth), int(p.ImageHeight), true } } return } // Rotations returns the number of 90 degree rotations counter-clockwise that this // image should be rendered at, in the range [0,3]. func (it *Item) Rotations() int { for _, p := range it.Properties { if p, ok := p.(*bmff.ImageRotation); ok { return int(p.Angle) } } return 0 } // VisualDimensions returns the item's width and height after correcting // for any rotations. func (it *Item) VisualDimensions() (width, height int, ok bool) { width, height, ok = it.SpatialExtents() for i := 0; i < it.Rotations(); i++ { width, height = height, width } return } // TODO: add HEIF imir (mirroring) accessor, like Image.SpatialExtents. // Open returns a handle to access a HEIF file. func Open(f io.ReaderAt) *File { return &File{ra: f} } // ErrNoEXIF is returned by File.EXIF when a file does not contain an EXIF item. var ErrNoEXIF = errors.New("heif: no EXIF found") // ErrUnknownItem is returned by File.ItemByID for unknown items. var ErrUnknownItem = errors.New("heif: unknown item") // EXIF returns the raw EXIF data from the file. // The error is ErrNoEXIF if the file did not contain EXIF. // // The raw EXIF data can be parsed by the // github.com/rwcarlsen/goexif/exif package's Decode function. func (f *File) EXIF() ([]byte, error) { meta, err := f.getMeta() if err != nil { return nil, err } exifID := meta.EXIFItemID() if exifID == 0 { return nil, ErrNoEXIF } it, err := f.ItemByID(exifID) if err != nil { return nil, err } if it.Location == nil { return nil, errors.New("heif: file said it contained EXIF, but didn't say where") } if n := len(it.Location.Extents); n != 1 { return nil, fmt.Errorf("heif: expected 1 EXIF section, saw %d", n) } offLen := it.Location.Extents[0] const maxSize = 20 << 10 // 20MB of EXIF seems excessive; cap it for sanity if offLen.Length > maxSize { return nil, fmt.Errorf("heif: declared EXIF size %d exceeds threshold of %d bytes", offLen.Length, maxSize) } buf := make([]byte, offLen.Length-4) n, err := f.ra.ReadAt(buf, int64(offLen.Offset)+4) // TODO: why 4? did I miss something? if err != nil { log.Printf("Read %d bytes + %v: %q", n, err, buf) return nil, err } return buf, nil } func (f *File) setMetaErr(err error) error { if f.metaErr != nil { f.metaErr = err } return err } func (f *File) getMeta() (*BoxMeta, error) { if f.metaErr != nil { return nil, f.metaErr } if f.meta != nil { return f.meta, nil } const assumedMaxSize = 5 << 40 // arbitrary sr := io.NewSectionReader(f.ra, 0, assumedMaxSize) bmr := bmff.NewReader(sr) meta := &BoxMeta{} pbox, err := bmr.ReadAndParseBox(bmff.TypeFtyp) if err != nil { return nil, f.setMetaErr(err) } meta.FileType = pbox.(*bmff.FileTypeBox) pbox, err = bmr.ReadAndParseBox(bmff.TypeMeta) if err != nil { return nil, f.setMetaErr(err) } metabox := pbox.(*bmff.MetaBox) for _, box := range metabox.Children { boxp, err := box.Parse() if err == bmff.ErrUnknownBox { continue } if err != nil { return nil, f.setMetaErr(err) } switch v := boxp.(type) { case *bmff.HandlerBox: meta.Handler = v case *bmff.PrimaryItemBox: meta.PrimaryItem = v case *bmff.ItemInfoBox: meta.ItemInfo = v case *bmff.ItemPropertiesBox: meta.Properties = v case *bmff.ItemLocationBox: meta.ItemLocation = v } } f.meta = meta return f.meta, nil } // PrimaryItem returns the HEIF file's primary item. func (f *File) PrimaryItem() (*Item, error) { meta, err := f.getMeta() if err != nil { return nil, err } if meta.PrimaryItem == nil { return nil, errors.New("heif: HEIF file lacks primary item box") } return f.ItemByID(uint32(meta.PrimaryItem.ItemID)) } // ItemByID by returns the file's Item of a given ID. // If the ID is known, the returned error is ErrUnknownItem. func (f *File) ItemByID(id uint32) (*Item, error) { meta, err := f.getMeta() if err != nil { return nil, err } it := &Item{ f: f, ID: id, } if meta.ItemLocation != nil { for _, ilbe := range meta.ItemLocation.Items { if uint32(ilbe.ItemID) == id { shallowCopy := ilbe it.Location = &shallowCopy } } } if meta.ItemInfo != nil { for _, iie := range meta.ItemInfo.ItemInfos { if uint32(iie.ItemID) == id { it.Info = iie } } } if it.Info == nil { return nil, ErrUnknownItem } if meta.Properties != nil { allProps := meta.Properties.PropertyContainer.Properties for _, ipa := range meta.Properties.Associations { // TODO: I've never seen a file with more than // top-level ItemPropertyAssociation box, but // apparently they can exist with different // versions/flags. For now we just merge them // all together, but that's not really right. // So for now, just bail once a previous loop // found anything. if len(it.Properties) > 0 { break } for _, ipai := range ipa.Entries { if ipai.ItemID != id { continue } for _, ass := range ipai.Associations { if ass.Index != 0 && int(ass.Index) <= len(allProps) { box := allProps[ass.Index-1] boxp, err := box.Parse() if err == nil { box = boxp } it.Properties = append(it.Properties, box) } } } } } return it, nil } golang-go4-0.0~git20230225.2148625/media/heif/heif_test.go000066400000000000000000000047021445700726600221740ustar00rootroot00000000000000package heif import ( "bytes" "fmt" "os" "testing" "github.com/rwcarlsen/goexif/exif" "github.com/rwcarlsen/goexif/tiff" ) func TestAll(t *testing.T) { f, err := os.Open("testdata/park.heic") if err != nil { t.Fatal(err) } defer f.Close() h := Open(f) // meta _, err = h.getMeta() if err != nil { t.Fatalf("getMeta: %v", err) } it, err := h.PrimaryItem() if err != nil { t.Fatalf("PrimaryItem: %v", err) } if want := uint32(49); it.ID != want { t.Errorf("PrimaryIem ID = %v; want %v", it.ID, want) } if it.Location == nil { t.Errorf("Item.Location is nil") } if it.Info == nil { t.Errorf("Item.Info is nil") } if len(it.Properties) == 0 { t.Errorf("Item.Properties is empty") } for _, prop := range it.Properties { t.Logf(" property: %q, %#v", prop.Type(), prop) } if w, h, ok := it.SpatialExtents(); !ok || w == 0 || h == 0 { t.Errorf("no spatial extents found") } else { t.Logf("dimensions: %v x %v", w, h) } // exif exbuf, err := h.EXIF() if err != nil { t.Errorf("EXIF: %v", err) } else { const magic = "Exif\x00\x00" if !bytes.HasPrefix(exbuf, []byte(magic)) { t.Errorf("Exif buffer doesn't start with %q: got %q", magic, exbuf) } x, err := exif.Decode(bytes.NewReader(exbuf)) if err != nil { t.Fatalf("EXIF decode: %v", err) } got := map[string]string{} if err := x.Walk(walkFunc(func(name exif.FieldName, tag *tiff.Tag) error { got[fmt.Sprint(name)] = fmt.Sprint(tag) return nil })); err != nil { t.Fatalf("EXIF walk: %v", err) } if g, w := len(got), 56; g < w { t.Errorf("saw %v EXIF tags; want at least %v", g, w) } if g, w := got["GPSLongitude"], `["122/1","21/1","3776/100"]`; g != w { t.Errorf("GPSLongitude = %#q; want %#q", g, w) } } } func TestRotations(t *testing.T) { f, err := os.Open("testdata/rotate.heic") if err != nil { t.Fatal(err) } defer f.Close() h := Open(f) it, err := h.PrimaryItem() if err != nil { t.Fatalf("PrimaryItem: %v", err) } if r := it.Rotations(); r != 3 { t.Errorf("Rotations = %v; want %v", r, 3) } sw, sh, ok := it.SpatialExtents() if !ok { t.Fatalf("expected spatial extents") } vw, vh, ok := it.VisualDimensions() if !ok { t.Fatalf("expected visual dimensions") } if vw != sh || vh != sw { t.Errorf("visual dimensions = %v, %v; want %v, %v", vw, vh, sh, sw) } } type walkFunc func(exif.FieldName, *tiff.Tag) error func (f walkFunc) Walk(name exif.FieldName, tag *tiff.Tag) error { return f(name, tag) } golang-go4-0.0~git20230225.2148625/media/heif/testdata/000077500000000000000000000000001445700726600215015ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/media/heif/testdata/park.heic000066400000000000000000001415201445700726600232730ustar00rootroot00000000000000ftypheicmif1heic»meta"hdlrpict$dinfdref url pitm1fiinf4infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infe hvc1infe hvc1infe hvc1infe hvc1infe hvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infe hvc1infe!hvc1infe"hvc1infe#hvc1infe$hvc1infe%hvc1infe&hvc1infe'hvc1infe(hvc1infe)hvc1infe*hvc1infe+hvc1infe,hvc1infe-hvc1infe.hvc1infe/hvc1infe0hvc1infe1gridinfe2hvc1infe3Exif)infe4mimeapplication/rdf+xml¢irefldimg10  !"#$%&'()*+,-./0thmb21cdsc31cdsc41óiprp­ipco0colrprof$applmntrRGB XYZ á  acspAPPLAPPLöÖÓ-applÊ•‚%M8™ÕÑê‚ descüecprtd#wtptˆrXYZœgXYZ°bXYZÄrTRCØ chadø,bTRCØ gTRCØ desc Display P3textCopyright Apple Inc., 2017XYZ óQÌXYZ ƒß=¿ÿÿÿ»XYZ J¿±7 ¹XYZ (8 ȹparaffò§ YÐ [sf32 BÞÿÿó&“ýÿÿû¢ÿÿý£ÜÀnphvcCp°Zðüýøø  @ ÿÿp°Zp$¡"Bp°Z €X‡¹U7¤¢ DÀarÈ@S$ispeispeÀ Ð irotpixi0colrprof$applmntrRGB XYZ á  acspAPPLAPPLöÖÓ-applÊ•‚%M8™ÕÑê‚ descüecprtd#wtptˆrXYZœgXYZ°bXYZÄrTRCØ chadø,bTRCØ gTRCØ desc Display P3textCopyright Apple Inc., 2017XYZ óQÌXYZ ƒß=¿ÿÿÿ»XYZ J¿±7 ¹XYZ (8 ȹparaffò§ YÐ [sf32 BÞÿÿó&“ýÿÿû¢ÿÿý£ÜÀnphvcCp°<ðüýøø  @ ÿÿp°ipma2ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ ƒ‚ ƒ‚ ƒ‚ ƒ‚ ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ ƒ‚!ƒ‚"ƒ‚#ƒ‚$ƒ‚%ƒ‚&ƒ‚'ƒ‚(ƒ‚)ƒ‚*ƒ‚+ƒ‚,ƒ‚-ƒ‚.ƒ‚/ƒ‚0ƒ‚1…2‡ˆ … idatÀ ÐPilocD4dbÃØ(:¹æâ ¬¬ŽÌ±?Í ?à Ž5nA¨Nq– ˆ%Ð XBÞ™ 6ÛðÎ '©ïA  êåu ü_˯ Ȥz lˆ) ù± þ¶÷7 õíòèÍ{µ‚µ÷ky¼¡(ʉò£ÅQ·ô³j÷´²\Ña™,j¥òà ÓŸÉ œ«Ÿn!<Ã"ÿ¦–œ#–B Í$7˜o%Ï~Ðn&Ÿì·ê'WÖ· (☼)§ž *7§Œ(+ÃÏð,S¿{-ÎÁ¹¨. ˆi¿b/!GË”Õ0!Ü nf12ãDõ3TØè4\À¢mdat";3Dñ(¯ˆ'ÃIÒ·òc +˜Ñ§¢s×4PÈAmåuTò& á„àM ]Ú…x›ÚÃü6£ãÌ×J;ÙD‹yÚƒ¤Xα—À~ñ™›5>Sj\ûÑ\:Ư…&«¹®qgykü~ŒÜQƒG`vîÇKLtü²²âDU€IŸzü·p#@YÄ B“å'<–exZº[´ÎŒ|÷ÍpÅ#ZEÏ,—±D+ ³3> |¨ù0¿±•xФ|ë5¿îèws¡ùHd(X™Wç(_ƒ}%6‰©å(O«—±L0]3QìBå>´¥ÀHRp…u¿©˜Ë§sùÓs.;>ÁŒmNô¼p¥ÁõÕEQAB?Œädr†,)«8#(,Üÿp=š )ª=LÐy·*ØÝ3°:=ºµ»±‹×o°PÙ¡,ÈØôðbõËèh€ þÕO_„6´û5r• aï6ž]ΩµÔ©3] ´Ùxœ¤‚)@ èßÁ/j„?u­ÁZ›¥.¾?™˜î"Z§MÅŸ~¤ðD‚´â rꚩő²%ˆäÞéá)ÔWj®Ü›Åo³N¦2"íþ`Œ wq? ‘9ÚVuøþ´µR¡ð Œø\@DårÚ´­ccÅ?I©W{õ%Ysƒ4†Ð=ù˜³< ¾Þš5µÍèÏòW êc'¸™”1æ=™Äv"|ÛÏ‘£“Kl ö˜Ù÷VÝ*ŒªÂPXŒû#O‹Sú¡º2•íBà¢n‰Äê(hïÃ>~bßÝ… fJTôA§eßèp· g‰äÍôö³òhQ×ÌçpðæùŠÁóèåº)¯š(4Ç[HÉÐßéCc®Â)ˆÙy <£ÿKƒ•fu!Æ8Úž·î­øöLtm:ý%Ž‘׿öµ;GLÆ$“Þë„YÅõB¶B‚Îí¢{0.ËèPÝ„‰2=ÐîÕºEAžf¯*rÇÀË*c¦±JâØ)d©T;”¨§]ÇqÀëσýgžñè4&%ñ4ˆ«ŒÁˆ|¥‹¼±ð~1.ÆÙ²ƒù¢×/A¡ÏAðÄ)¼WF—Ý)Mþ4Ÿ’C âNKÓy ,ŒÚ0¿÷+UëY–7/ä¥) :Ý%dŽ—ü:*€bÛm)¢Yñµ&£"ÅT}Ä~8þñ#Jm„Ì©= ]+NËaäˆoÏLÙ.š‡&rÚ1Áó{ÍNØ4ß%kX€ dm4™'/ðf˜à5÷ÎÚÂíëõÙ¶AAÝàr‡Äî_“g£%ªG£ãŠWçhl>XBi™0°m=¶+IÔ |Úòƒä‘!<ó%ïÝ»l%zäŒypZ¢*^\Ÿ<~ ñ Yl³P"™¾Çcè‘çþÂ6A¿†ùj— ö3\Á= <_ÒYô2w{2…LnnÆZ?JÅ\ ”pùjÎ;ëïš—%1©92þ~83º™GÓT)7ùåîa½f ƒßöUµnWkÃɇ‚8¾⑸èÅ”ë´e‹øœ¶wµØì¹êþâR`„I!]+Ë¢ñøß• ÖTJ"myî'ß ,VN°fÛa&n¬÷òÎc2s¼NŸr÷e¡›xþJª×®š£•¨Àâ˜Sä%V<»ð¼BrN5`b$õ5¡Íì¥wêüÅ 2* ›7ÉmrËd¸#TÔ£¢ÊJ &¥ ¾›ãÎÚó_À4•áÛùkN/áPnFøèôTŠ,øü¢eP¿>òß×ôVbŠpSì¿¿qIa}W‚óÐ?i²yš”Vx+¤G$U|‘PL9;B&ÈÄó#+áSc½jèZ4êPÀÞÙò˶ñÝe¨‘³WX_þ3´ùU¤èÍšF%e›á€+«·©tz¤EІ¯ý&}ŸÉ ø§±¡ :*9vB²+/Ò *‘)NÕ}†»‰ÜÙ,o¹Kþĺ1õuÑGˆÇÙL+¡[¤æäðŸïùœ '€”þ_É"Uu¹ˆi÷  á™¦¯\‡¨.ßÔ¬ƒïÇ’{Ê56ï*.?ãŠèØúlJ@dûT°À]Á3RäÄ%slÑ›™þ8ÓÇþ½¢2~Åû4Œ,ÄãtÚ醓‘AgƒzP Ò͘@Wâõ…?üE¤xŠÝ?Á€¢O3‡Ç”s¢~asFi­ä{:DeAƒŸ º¬~¥ä7ÿs¯p¹¾Õ!þ†M£Á7w9)¢©xŒhZ×µзù% Äú·r·–¼^î ¥å¬4ü-éNç¿aLIŽKýÜ0§*iº\Ü®ô—^C^×ß¹À6 Y1z<®¦XP»Ól<=䛈N´l´)/;:‹ 6Lé¥"‘ìóò-ÉÓ„UÔPpDr … ¶?Ì2/y$ÞG€Æø\Î.Âj¥¾=jˆ‹Ì0ÜÒnJ{Ëÿ%så¬C·x%=ÛIùe¼°ª:/;'ÄeÞë¨- ÒO°z3”B}y‰´Q׋éˆ? ¹9¾Ëwð*@ê¸.»xó]ô9¡ýd˜’X·Eb ï©BnN‹í¢Çÿ™÷Àͳ&*‡~Í«íTXX%þ| l¯Éà¹ë)é♿zåU¾ê£ª±ÔN5þûoèôDýH#¡&j0ˆE{Üù=;žá”mó¾.5”Ý)DšÃiX,„÷À"F¥…ñ§—ñâ¨Dö26 Ç5a! uûÛŽÐf°Êr»‚$#’_|ùæžœ¡Óü£ ‚IÈåH‚gZöp¡qxÀz—â*ÿ9§Š/eg‰Ù'žËœ÷ÀýÝÃÒw^¤­;.":3¥LºÑín©tÔ$¬äp϶ü­ªôKó¸5l²BÖÇñ‚– >|²8Ó¸)À©JBó”ГŒ(Ër‰»<ÐcEÍ ‰mï^M4+n5EðÀc‚ÁéhóL5ikÔ—ÑA—&y sP>kßé}2$.„6ûI³<°l¦êk .rFÎ äÓÖœ‰:•EYcÄ©–³‰[¾Av¶f×*Ít¥•0qZÀÞs«òj-–9%nžv‘Õ†ÆÜiHxèwÁžKÀœv+8JüPž´%Üç¥Qw‰ XHWž¸êi†DmçæôCER=§>Ý• §;AAnJJ ˜·»äš+N*_kÀTßü0µö‹¸©€¡›¬9!MM³VñÖG¦«²U6üI¨ç0WfQ,úÐoOž‘OÖ:½!FõŒtãRÕÏíGvdPã—ñ{Ð@9Ôä˜Ð÷ÊëA½Ð tÉѰt\ôŠ·QîåæHÍn» ’‰åØÇ…Z­ƒø¹[Ð.Ýa–PZˋʄ›Þ'׈¶Søe‰èðFMOO¤Ÿ{ìC5n€Ô¨Úùn"(‹Õu9Í Ë÷3ÑŽˆ8šJïp8°ß„]˜þS•ßzµo•í… ãÀÀZx P€A`~ ¹LOÌ÷DhÚµûàôÅ›ò•²ìˆ#ÉM}é3æØ¸K¼žÙy!6´å¢Å¥–/i¡æ®À`FªŽVmæg´ñÂfðD#Ϩ¢'ãBÁCR~Ãùî\ÍóÔCΫ™ bµqŸÖ(ì´#…ÄáåÎ?üÄÈ;/Ìý†ï ˜¸&Ú=΢ZIJ¦ªÈ8¹zL„ÿ«‚]ß5þn»>Aæ †Cº0ǃ¸|Ö'zÄÓ!ZvòA¨|ŠHørxˆ ¯S^vh`:i]¼œœy´qÔ}Ð0U™ k!`ï‰=,&áJ;¦ ΂·³Ùo,Œî÷ú´uÿSámûÖhz„Gv:ûj‰_Þm–64âµ—Èç®vZ@ŸÛ§‰o±é‹†•lÕ/Yvê²ÏÚlæA×9‡ëˆÙÇv=f‘‘”ŠÕQ›ör½‰¿Oþ"Hëyÿ®Ä†}:Çà­VøÏª™0Y1@oÓl]I‹¹1x´«¯5—8·ä†½—¾9ÒI«ŸÆ\!ÙíXÏ`–ħdZ‰ÑL`á5µTªƒ°ÍÅ¥dFKÿû´ÞC`G.²±¢}]Å]¬{ÇÐÉCmz®Y_V,ùsAO©nEûÞ'2ÓÈzQ"[ åùä t° ×Ùó@ à ›ý<÷‡– ÎM—:ý«^ [ÌL¹Q6|›AÆkúgxJ¾ËÛqø ÊÜò“ü!NæAPÛȵæuQ]kGè·#`ß&/eFnd…¡€ôÈD (Üñ†<ÛT@½“Œc w!ÄN8L|33\í´ð*ÓÜöÅ0& ÏȲ…š”›iàƒB*±µÌ{@ˆ|nmFšå&É AÙ<Š®ØÎi‡^ÔìÆ!?”9ä‰{'Ðx§õrzèë½Pº†T]âùùìOÄЇ—ˆãêÞ!c—³¶Ž)u;\œJ(wÔ(’Î#‹²Glάz@Kxh©ýõp„…”"vqC»eÝ{º'&û‹z±>{»OPÏX´8Á;£¼µQƒ6ã«ã¥è®t¬ˆùˆ—I‹÷Ú4L«?ƒÉhܼþYÁ)ÊÕìͧdH‘êíròèÜD(>QìJÒØj°ò„OtÔFr±¼¦™çLäç-2ÆÇ‡ö%2Øxþ4•AbE01zV'-8]þki#BÌŽöèÿ>÷#_ñŠ'²k Ÿ6Þáf%ˆ³d4[p±¯œÝõw ŠŠs*!êoŸ‘þ{0µÎ=ƒìZ‹Š"K[Kéä|$÷¯÷RŒ%%ª/9™‹Ý´Œý¯oûdÂoñÉà,ÚCL p 4®«NeÔù¨B"ûëa¼BW ¡§Óº ¼euñï”< ô»@':/L·yÝ5HG÷ÅY·±þv-ûlXIyãkÀ²âºÓ’­s$´_ýIÔà4$æ¸_û¿#ˆME„yÉé@ÉØ8‹ðN%k¡øüz8Ü[úš„šÌ9RXF‡Gç•Ñå[Ø€ãœúˆ/: ÷9 b~z~,®r°Yá9!Cf`µQÇ,O꥽´ë35ÒáÄW–ñ@ öþxwÏiyØ‹^“aÒ̤ SS¤¢]á…9Rz]ãAù5niÑG·­.€¼Êܰûv-œDíV/Ê]ØÑï¬È:uŒö6µkf£Uì­êXäü§mt%ÂfÞ±àîéñƒÐB¸K_Ķ2 :ç†ë3\GÁ™@â>µÝ8È7–ÚÍ#ìï=2òñ™¨8jð¦óLV‚4` ^”.·¦ô"óân>½z!Q;Ùi²Ï~ÑgÚ®œ¸/rDìJ—: ¡ZæõˆT¯ ³cº€¶.?ÿ£-¬™[ÓÛcg’#þ2\€§hö5žJ‡‘¹ôPuh#8 ±Ñ¦=] z‘ ?ìÎ誱hQ®Wº»IÓeUóØN*+¤NG¨m‹ÊÀñ*dæýdkðeˆžæ ~{ÒñÙ"Ä ›)DÄg<„¥PÈÅVÐwNUxˆRñh—k‘ÃÁ¥l/ ,§9F,ÏÊ,îi‘Òæ™]Éã_øqš.Š”ÿ‰J¬!m!õ7­WÊ/)Ú[f¨<(ØÙ¡æ¶³íe©ŽFÿnç`tzâÄßmo\¸Ñ{J‚CÖ¯:Bûg^Íâ\@0[/äAG2Õﯖb eûÀ=‚wxMi„@ ò—¬²™@lšÓtCMÒâ.nR…Øï#ú»5ìHO^¯{ƒþLÑKÓé2Ac…ÃÒŽ$»]Üzeüs 4bãpföÕd“_ºíon¿1ÚHÔ»'«úT¬ëfO4í,mUsj¾„–šÍÂ€ÈæÅ 7(Ž“¥T”rw¢Fè•þ†Ýú ½ž:Ö:poék~lûÄ0ú Å 6M•“üôP=¸¨ë¸í<6÷¼åç…1ŸÅ÷qñóžkmÉa¸cág£ª;Ý €Œ¬­~aÝ*”øS§7‚qE/R@sÃö"Y¸J6Ò­ =/NDü%Ç  šÇ©ýwB”¸âDn¹K¬ôæÛÔ¶Æ„w0ÿXjÉÓù°]TlìA=q1Ô¤*Þ)ÿ\̯Û¥0Z<™F¢ÆØºsB¡à¬Þ %\ûBÖ2AvfóÛqTëFá­•*‡v¿„É4›<ÓT>ØÂ! éú"jxÌÉá¶±gû+0U¥aµ‚õ®¯ÝÎ+´»ªŠ–sÊ7Öh?],a¥þ6Ô i*°ØÈCÔGÖ“ç»°öj?–ñgu¢cñTø•ãp›² ¦vI¨Ã&Ím‘o—ÿ¶èð§V‰céØd_Gl0çpTOU¸Qs”=s ¶qZŸíúæHô6)Ÿneß%Ÿ^ºTu†ƒ§¹[ˆy`È#‚yN»îpô8?P¢ †æ Ýuï>—3mI(L&„=À†2bZè3üÝ„5FžG9³J÷IÌ&±ýÎ7‚G ¹RÁ¯»¨¥*.íAt=sù|}ØgpsAj0uy±ÖŽLé -¥ðv&¿]qy…²ª© &‚ù¯"Jˆ„ÄÞ>e>ƒ+y*:›®¥ŒP}÷Ò”h—ÃäÆ75¦±ž¨¦|ÎX;UÍ/kÊÔTÛ®/\J¦6b¥öîUµ}Ya+3gZЍ§nà£ÔÐå5¨ãÕmš$÷™åzÈQµs+Wr:u4ôÖ$e^7ê˹J +s’»7óP˜Þê -<ÃFzÓc¼ÀGý‚Ú½i.R‚]À7æá1™ÕÑ9 F¬òèø5âán9ýÍwûõœs }<[5̸,ˆ† æþ=’Ën¬w¾Ñ? Af -9yøbŠÚkíœ> ¾¶6FFéüæà–àð墬¼§ìŠú·ÊµÄ¬ŠÛÓ'—A¤ë1ËÚÏ#c'ÀêÛØµzÖ“‘u]S4gåèDëUcÿ]l£Þš¿‹ B2[àÂÿUú N˺¬8ºðpD‘ÖcÉñRì-ô‡%„¨Jš™vJ´ÝÞ‰Y¨/ëÃ,í°p³»¢Z÷¨:tO(þýå æÂW$ZSÀRŸâmÝÑBòéå"OâfYícw+JÞáEF>Ç‹±'àÆÙÖ¦ x^õQ‘¬”ƒce9Ìߊ׷‚Û¤xÁf-ñ:a4|'¹u¾^¸ v¯¯’y‰§l|R\¾³HÊ®.cÄ/lç 3F%}Tz$_2T’—þ·ÓczZßµ´ýӠಠ¹âC¿:Æ|Ât9¯Æ–Ùá ˆ¢¥87 FÍëSAñ佨å2±¾=äsbî›çyÈó@®tMhˆµ„¶" \ ¨ /ò Ú5R\/ÛɺÓÈ Ó±”nÅ$7Žy¼j”eÁ0{ú¢|¤Z7£÷DcLÄI©«lÅ—˜/w¢|ç£ñ|²ú+Ð …¡Šr‡Ñ?GÎK ¾>Z‰Ù'"Lÿ´öª‚A  ^pAþ­®³h”OùüfÆÜÓU¢þúœÀ$‰ð“‚ÁQxSn³{MŸñ+ÊhÃèY'ÖÎóðV~4ƒµ„øªÿzCg˜ P/*ÿ`â@-ºáWR9¬niž’toCåJHŽ$ö»žuêÉ>hÜ|·˜} 5š:Ù8#Beê™'Çx-Ô>{Tµ.÷»‰uZ®Ž´úv>ìåÁ¸ Fü~„&ìë2̲ÿ,©Ã2·PßFTg‘YdW±3}ñ—ëœ" ŠÚ?5—€t>!ѶŠ5 ˆh-xÔâÿnaq¾ÄÙOì÷ìOái<f:yR p=W‹g‘3,ϲ:tFó⺂/RëçÞ¶j`¦&€<,ŒV„h gú{FKq×1˜9rö¹¥Sá‹âQ5íjùV¶Ã¿@_C Ú³¨,;lÒ|˜}Ã÷¹˜$îfB £)pJßewzÉ¿è2Ñ¥-|Ãü˜™c©Ž‹®‰Á½lymQy@¿=kf´¤£ª®!aÿ_îÏþÚòH6”‚«[{´ p«Ü$™Œê„ \ Ôù„í¾¯°1U9»g‘šÞÊ’ºªÅŽƒdÁfë• Fær=ˆ¬ð±àM5iÁ@ÊAè:+z¡›=X-_–[½Þè¢&h› èþ&ä÷øçœöÀLã‡À~“øÐ…]°¤ÁÝ4\鯿Ü3ç¨v½\¡ø¡ÅèÙFsMn]Oj0>Ätaó«úÙH‡ÿbwœnLsC‚­ÉŸÛ…yÅjôÂV«…6å;[·‡¸¤„áòPË ö$œ¯(U/€¹&m*£qú]ŠU"\y‚ ;‚ƒ§Óò6®ãü½=æ«=@'9á<å]þb\–ÆP¹˜M)þj^kΜrxÀ“–ýŽZ¥OY_±¥ìf¿)ú-꺙¢“î¯yêž­Ÿ±(ÍrÑDž¨â¨Ë0+8)FIíC+ì±jJ^é:®>2YÖi0½Ê•‘´ËŠ6þ %dó}ôøéÜØ^%m/"b—pGc§×—¿4ï3zŽU§ÿo;a½>:OÞµx×86/”{üY´8V&­ÿŒ-úùÐ9™Ô†sÝ(¦À¦z®ç®ÉuÝR{¾K¥¥Jt0¤­'LíOÍŒ 2Ð3 ‰÷_²)AšÌ™ÊÀS•?Y„´(8Ö¤œWs#¨)¢§¹ò²~Õ™ªˆÐ5M Ô¤ñ¹eÚDöTó@Dv-³¢^X{œ*0…3¬ˆ ƒÄØÈþ¯Sí æÂ"ö;„6”™ÿs~ l§Œ÷ÃÈuèÁe ¯ÈÜi0tËÃBC¬Ø×`ø‹Äër¹×Ü— ãÈÁ]&é£ÞV‘IòŽÌ9rzÀÿí2½½²òqq!&á|Aç(õ>ÜA”ÖîÏ8÷mboÇß§Âr_ WªŒ/»t„†B˜“w(GòMÍÿŸ!ïõ¥Kz;å?ˆ†PÅÏ1•çñš¥É ¶n— ½8FÁ!À>\ˆ¡°ÌÃô7œ€;AºÅNÕùE’ZmvruA¤Ÿ²~´v]êé 6~ˆJº¡À ÈåËĬà˳Kõv¹“oÚ8Ýä úÅQýÀç¬ÎùÄÊq8ší¹f-cu·®ºŒ}µà#Ve.7}’À©Ùw‡Ž´qÎhtŒS_lTY†Ý{P7I^ÜDËR?ÜÔáô|V·TÜjO;IÁYïèId*SYŠ­öõÒý“¬TmMS ä!eØ‘Y„|%Ʊ_Ù»sN¥(Ì ¤sÊm9ˆ}Ê…ÐÙ|Úðzqñ —¬‹zR·i6Á)ùªp €(§ŸÖˆéƒ UÚïø>6=Ð1œr²MêMÅ¡UcU­ x"ycuM-U°—f!Ú§XÔüÖu‚åW'¤€¸ÍúÛÍ5ÄWÛ“§ÆÔ,â‘Éo;®<ÑðÁЦe3¤Íš\PG5m4ixÈú:”~–¦“³â6‡eâÿý#`.Ëm肇úE*š.r 9gNëw³Ü®pã+åëŒøœ¼¶i½ ‚”ÁêÁ±‚%ÒhŸWu òï v#–gc…[ï+ 'dWAÍãÈ”€h­–UX¡ x äÒ×Ç—¿•ãs}ÛU{çe%•ö¨ôòžlŠ‚žl8Tí9}ÝËʃŠ! $[dr{ÖŽÞÀÖ Šöókä®—r¬0›lNP¢á.­ëãUø@˜ž—d³kRRì„£…"É Â~•‡f½Aþ³ÙM.'ʬ³c‘4ÚÒþ7Ú|æ¶m­Ü2ÊÙ0À‚f¼B1m¦J  O(œØ\ïpûõÿhÖEÃÒs¿1 QXå)OšÅ§C` Q/±ø „‹‡(¢Þ&¿…Né+YøV¼;hž£}‡j Ö̧–<Þ†`,‘ÜÆÆ]:zMìŒîJm#8†{_pšwq“Ådky¥9<\Ý¿²§SeVälíN½žB÷7²<ÖÛ2îÕT ˜ü³@ÍôÆ‹€Ed}ÛÄó¿¹xµsD’ýok„ÛæÐâIdqHÙẚÁüÓáIׇóhÒúÑ u~Ä’ù+#g4Öût™#np¶J¸²YÿõÙÖ¸.;Ë´+,ܽí©õèlLi9mÛ>: Z “—Ž,w x,!DhI#: ·§";š{¹,$Ýÿ@öF²ãwƒ‚ ±ý¯EZG÷öò—8<&;¼¨5šѶ´·ÙøêÂ5*µU|\¢–æYžbP*ÒjH''’ÅœZÕ{¦°?ŠœË6¨¥xh¯Ù}ää8kg÷_é'kƒ!1óÚ%ïqúPG”;3ËŸ]ÿåÞ¿}ã:v†¨ø ¡YÓŒ¯ò¤_Bæi¬©Mb𠍶cTÃ|.Ù ´“HÇ"ä'åÅ7L$ƒ5ýwÃyÆŸ«6#IÇ¬Ó´íšæ¦q9úëÛ  !Û§dTÑÒÂ@½ωèù';Û€/ÂþbóR„vÆþÀwØãìšÆ>¹8³Qn¦l}üO {E_–;¶ 죗²÷Øk1šh%J—æžÜ6x•² ‰MºìƒÁSAɈ„û¹çZ¬â®¢t– ¿|½Ck¡3 ŸÀ9¥Ô––C5Êõž² ©ùl˜V,•_ð.öâìmáš¡.P”9(À’Y/ kÁYU6•X4Ò¥¥ê)ü]L&í÷'h€JôZÃ!S0î¤$jgÈŸìu0’ß!’ñœLqÎÝ@ˆùïïµ²¿™òQqlêâ#Ì+¿ÖǪ ²£Î@ÿÄ¿ wݹÁ§hë“ëwÄJÌ” ~~F¥ÜàŠ'h€ÎHÊ3±J3š ¶A&§Âµ¼ßžb™q?V¢C¡ÇaôöD¾tJÏÑ­=UmöPó%çUZ{ÊÄ dœ+['¯›’‚=âÔ-“?þ $ˆ/g¨‚Ì4ž‚é“ \ÐNÛ]í›±.nØÍtîä;\K·ùI¿Ò´æ%JoÅ^ŸCÊ[ê3õx@-ÛSØS3~M|ù÷©šf‹ñ£šCßPä$ÖåÁWð ’¤,¾ØuÓ¢’ Û5PxÂç¾ÐHóW/,Ч÷6Ý :KÁ{yéã‹3­øâd½³CœÙôÂÄ1%ßaƒ(NùT ø,RY:•!0äô/èÒ8ý8¯ -&Í#Á'Ä8¹*÷x±û>׸ZF·Ú a£ÒíìiÊ>%Và»Çÿ÷4v·Ãÿ´û£¾Ë$œÅ`UöxDálê™*76ƒ‰†OM©Ü ÓNþµåÓ*Ö“Z¡Ym(‚4+!í0+݌֓¢±%²C}U‹@ïÖdÝÀ¾!ÿ÷½Þœi iñë “®dt йÙ{ ¢M“›µÓ¼é”žåßËh>6%¯ JóhUPŠfœoÖ‹÷šý¯ýþƒ i²<ÊÐÒP°Ÿ+ìüÞÐ;fÅÖf‰¥sViß(»QkO&~F#ÖoÑߨq¬Œ#²¡ýmXò,©ÛˆÈŠ@² ùÐwèca¢ñdw"D‡úo2h|n,ÃÓŽðê燺± §ÿsx†Ì¥A\8Uñq ¡zýÿ×°“ߟ×̹â<ÍW <Ž•OB„ï)ÎÊÇT¤OÇÎÎD°×"¾»Ûò§mÚv Ä÷ofU…f~‡¦‰ÝmÆ®šVÈr+îô!Íj³ï»b·ó"™âE¾O$ùXÖû†vít†ÊVùTÿë…æM²Ýùk½Ùk ¸ÞÓ­³²ç™µeîÓMÁB?c ’ {1¬ÒU?ÏøA 0eÏŽUíÕˆºí1yjóÙû/K›òŸ½*¼Ìù±j<Êy aœ©•8 … ^u—ïÚ\óLò“oÃ×À묇z£5þ_Ï„Þf_¼¦Ê—’ó¤çè†ÇÁ÷5[ó†Ç@½ØcFU­îj|Eºh8  †ÈÕÛ[ŽGÄ¿·ÜŽüë`e/!¢ô°ÆÚ÷¾©‘<;æ\%i~¬€Õ+<Ó¤QìÏÁfl™fdÞ­¤n€¿^^ê*fM‰ËxYKÓ¶ïcx^lzu½WE°APú³X‘®qE.¬¢V·Þ<Óþ2ê<–'Z^¡~&ÆMu± RL@’Þ\ êÅ´¨‘öëÓJï~b ÛA~1IhQª»µô;§ö99úLâëÊJ¤Áµës¢ÝÀð²™âv8¢å+ÏÿBv~\›÷íôaÑ7’s­INS¥…CÔÐÖòß‚ŽÎàÃçœ}dÝ7®³_Ð,9.Èc"…dÁ¬Å÷ôr“PYŽ *Djû¾´BÅd öñ4ÈØð¢awh.dêwY+¹¥– dzþûB¥ÉƒædA8½ÌéHvªÊœ#·r¤²Ã\¡:YI¾°»îò _Oüd÷ ¾Nå9Co`åpiðçóAÛÀÌI€˜€Çžò¦žLVNšysí;ì~jg*p<øå¾eïïsÈßñ_Ô-%­AU¼ÑoøÓ»?•Gà(bzQ)qÅY@AØ+N©NñVìÕ—÷–& õ´˜d‘ï->ôàòóµJÛÓ^ZÜÄ“aéo7ÿ#í¸W|žKî ®$‚#ôå¹°½‰ݹd@¼?Ej,=HÒ>gdΠâçþŽœŒ›~ÖIWã=ÁÖF'„HM9´é­qò¯ÖÇõæöû=ÎŽ…6å{öàg²×¦"ÝrJ×qÏ,¹g:Ÿ9²ð*hìMúmÅ/:öÌ~Ç´;à*7´[<þ»N.®hqëŠ ø8´£,Âxt€¿Q¸h%hûãÃoQà r‡¾O×´~ð*BL} †²|åçõs½*Ý­ª"©Ä¶(Š| ›ìHë_è–ú5vSLq‡ý:I¿-üÜ1 ŽÆs7ã¥mé< À8D¡#!›àÖ…ÿ™ñF'üuF&‚Y6LŽÀýß¹¸d€}»YÖbgÑ+2rUQ‰2b×ÊëMµ"%÷ž =œæ"ÛvK¹†©ó¶Ä>"éŠuŠPpÍB5¢?nÕEMäø&ÙµC>BV“]Ô[l³K ÊeÀO¨©úI–ê?aufgÁsG…ßIº¸ø›ù ÒT­BÝTáOŘcK­&VÀ¯Ÿs2}™±¡âµ Y¢¸ÃâÔçùN8np fŒHÚââ—}.Z«àª‚›‘*Ùt2¤É79Xñ¯Û ¢‡{Ò’ÍTùä7(¥¤­E8{ãç{sëÂä/`¦&|j4Ÿ{&,û°”0k›hQtc%ç*öKÓ6Ð/ݤøÑØKí¥Ì Úò¡q!R-@^Oû#ßHmåÚÃÏ EX°œÝÕ°’ºÆƒ8¬o!ÂNÎ’ÑÑê8-1ºåÇÖ‘@³+ (½Êð-^#ÃÔÜ(=4€ñß²¥”²ÉK¿@﬽ÊþíýfKp PE $ÀŸèADV÷;iƒôØ@=qød VÔjeϽ¿ªÐl{†#$}U ç’™ÌOáètLt1±­TG!:U@G½)S¹YBQI—(¤šg³Å»B «‰líÍ}Ajuª¸<ãDq®É0Z¸ïÏuŠáj„ƒ*ºz­©…ð}Ë”\¤ÓD´oç éÐMC”F‹Lh$¦MÏ‹ÕZiloX ¹¦oQH_ѹ9þÈÔÅHjU‡1 ÄåäÃ͆xÇ•£gñ‘m‡éƒŸ2Û¶àÓûñt4}•eÁ·‡ºÍS.É"~ǹ ñlžr/N8 w™Ÿqhåp.áч¦o­—Øu"&ï÷™¨VŠžèj¹¶Ý4ûƉØë‹4ΖjËyjE4Ã:5ànÂÄ"^ 5$™”ˆTn §zØMˆû‰ —!â"“˜uêä­mLš:'Gú¯Srµ#™AµºÏJEc1ƒ¨¢j…8÷$XÔ;&’„dÈ'êøt Bä–@7T(×vL¨qrõÖÂF#’Á¯“"1ZïÚõÈMvÕô1ÏòêàQ0åœÚú¨³¶hJ„ÿS”îìi£ƒê™AžÿÀUÝX¾m˰üR'¨o3®çRAÆ1]Ø £mª8ÅrõÌJÖ„‰ÏÐ:Sù¼ß©è^•(ÈBñ¼¹dj³ÚþÏó³iP'{ƒá:œ{1˜Š4Ö¤±ÿ„šÉHþ÷U¸kÐí\ ’BÌíïSt€·ºßóU䦣ÂÚÓfW$º'·™ÇÅß–ïåYõê!\ öW‚è9f¯’¯ª™U@tН_~…5ì^[«A &÷À‘BÓ O¿Aƒ´^oD@Ñ¿ÊÚŸ(ŠÊKãÿ¤JZv»X¡IÏ“¨²auO=ª_P ßò‰u0…Þ´òg8~ŠHÒW[q7ÿ8^çÏñ?ˆ?~@Àˆ“•g lõÚ³òXkj¬åXô·^´ˆ *w<ž'u€"‰³6Ÿ–,ð÷ü-Ëeìl…‰Dj‘Û‹Ž;ÝFûXá1ÞÖÙÞ±3žï3Êèy„Ñ]{0+lLñ9d•KAØõ¼Ê[(ά¼ƒ#¦ÉÕõIh'¼Y‰3g€|íG«gƒ”€ÀÆA‰­ÿmêL£ÆvÖ‹ió™h”ƒW_'…–ä4ºY¾7U¤[Y˜J ƒ ó:¥¸ãQÓB˜Ç1€¾£Dˆ//aéˆ]Lü§ºwRvÚg±ïÿídŠœaƒD„V#ó0ük‘úž|€Õ´ºuǻ閳”&¡Q\xæ?â­NwÃu½IK%Ÿp¶,ºŽ‹çõ/¶€×M¬pêA0kôþ9œ<Ì—fEj4•Mà~Ÿ»I³ê·‰–ç& ¥î|•!u‹ÝØzÆ*Žç5i»²ÇªÈwÀ¯5ႦýUuí>ZМPnÚiÛ0k¸Æ„ @yÓПhS³rƈÒÊâÿs6”• ïrœ°u€Wž <áVšáä=’ÿêGÅnÚ(Û˜«_@\ax­CµYo˜è~ÇÃ*ÌÖŠ9–-¡_¸Ëü‚Úšãù¸ŠÝBç}5U±5Ò¥`¾Í\(?1ÞÌc¼ƒ;I±!úñASna sì púý\âxž'kØ÷k*ÊÓUç¦7±¹mm!œ¬5VäSñ‹°¯B]I[}÷Ð{ãßM|üÐCeËDùw÷£‡Óê7‚À|¤¢µ*sÅ õÛ‚UœX~!Œ‰ÂBQl‘t| ºùz) g¨@1%û; ±òkdt%ø|”)o˜W¤]×{âÒ?UDsaÕè<êjQNÂ/+oUFª%EºÙFî¢LÖ ŸÙqÛ Çªþ”èº)Þøÿ^¦¶ÈíñÛ‚´æ÷4…nqÙÆ=ýÙ}JMQׇzow9”wQê"9Q'ûûÇÈá=°W"âd®Æ(y¿²SÍ_Œ9÷èY”%¶sÇœÀÂó_69q.Cˆ¥€¡J­^ Ù®/¤%å,|CX EÍêÑ CÏ誃 @Q“ ©<¿ùñYrÖSöÀ 9@Ï)Rzù VíÎ×Òÿ ìöüOöGÚ˜sI:.­#tzø_Âr Äü ÄUx@êÂÂQf{|E¾õƒŸ¸ªQàxœ#ѦuþW¹Ÿ’"I˜ŠÎf¨›ÈÚP±Â%(‰)ÖMàŒ$„œ©ô©;#“Ã\ú¿oŽT ÈÏÀ …doPˆ|ÔOÖ†:Ò­¦öx}±  û›?ÚÞdñoÏ›®ã¢Ûó"É{ L'ß_vžÔk“1Ä'v ¥GÂJaú½dÛòá[9®^>ûÜa|±Zjì –m”Ö]P+»r6#—Ÿ´¥Áz@9β¦×j,Ã4X}ö VÁB Þxê%ä$Viùo˜KÚ2Ü- Ämƒ½r¼çlÖûû™d Ðg¨Âkœðº&s‚IŽÖ=Scˬ‘}uc2Ë’{GÍ·*ëàíhSÝv,r Þ/$ä~Czv48ºÔs¸h¨޳ê³Þµ[ô]l~Wo¦Ì?ŒÉØ:?èd÷P‘hô­)ЂÝì6¢/Çt>?6¹'àm̃_y…ÿ;ö?'³À¿×ïŸ?k®ré¹ÑÇÀ·ƒDúSŠíÉ=²ÕF´瀟xJG¬æw¡à )Zrÿx‘iòÜ㸘§²~¶”r%)aú½Tüù’É¼î –Þß[?7:Cw`šõfá;Ú`v5Ž›£–¤~µÈ_½% o‚Ò8K²u%vBU]Šü3Ûf™ê^Rá“°ÍÅK¶(»ûHÜØšx“gël(,1Lge…ïpò7-«8à±¢Â|Ѽ<èAÈØrÇKTÊ#—Çßïlð‹ð ÉêúöAç ÿ­Ýš5‡rÇVdÜdÚÎõ™±# =k7¬„‰®€J'&K[,_´ ¯ wÅN xÒµ†“ÀÉ¥£ä¹WOeœ'Õ„…õbÑ 2Mò6Øý0Ñ}>ôÖ«ý³ÖDlŠŸÿôÄ1îíæçJ"gq>G1¶gúÄNÏÛ¡kMÀLóÛWñÊÎñ¢Éôzßûø2‡ÀâL™ŽEÓ-î6ž­V «( {m°œùè·‚ÑõbÃþƆ$y ëI/¡ø‘  ƒ|çò6…÷t<¼@*¡ž#”Ôê ¸õÀ‡ ¯pL¹ñôBS]Ï;tíÒìEu3@¬]ÂŽ’î'‹f¢"Ϻñ·ºðù<涇ÜEr²(ÍKPž›îr;šuöºK©íåœ[?9õrYÞ@õ=•n3U|KFùëž³û6åùì.lÊÃy’Ö¨ìË7SšzÕ=í>Yúš+>9Såòqª7 ƒ’H6&,Mx8+¹mI+ž˜¾§„¤Þýñ¦ûxË:}ïþèÚnÅížJëÜïß\sþ°4G… 9¢hŒƒq ÌÂô-wmæÙŽ9@¿C…F–Lÿ²PCEì E ¼˜p¢mØ<_bÄ)$PòÒÌÝØ“ ‡%œ­fmÈ¿-äO OXëƒC„5¢+]½˜q‚/U­F$Ô¿´µSH D3¹–ªÕ@p·àôÿ÷Á5paÚ=¼ýt3ƒPùó{íß@ ®ŽIæâ[lÒ§½ Ýw!;ù˜^³Äc*nÄø˜=‰ß5rÖ_nv1#’^‘s{à9XÅéÓܸK%]Þ“6¥‰h$”+9Û„Z.Dt›¡ëÉÌôßµ,‘›s8öËú®ô.°6ö½?©^£Í¥~<ˆÀ/_ ×ß]@k÷¶pèÍCq y^GïÜG;ßÓ “¯Å½1‡=×jèPÛ“.†ÙŠðì˜As…}uVŠ·€¨Ë"Ø|gÌ?Ê?ÑX XHÌùè‚Þš]]òêm¬Àg®aÒgŠUCä„2¼1j?ÿQÀÙ`D2x IޏjÓ²­Ù|œó­êj`ÝYYçEò¶Ü¨Õ¥oöËÀ.ä%kß“ÇrjÿýݳZ]Ù&¿áO&£ÆÖ¼ýQò%y­4“W›4§¡âk½fÏ0¬Ø×˸ƒ:ßVÎבø‘é|g B}ë@âöC˜®¨éY«3Õ¶Í‹ fŽîï± Ó¨'ò0kL*5B‚ ÌrÔüu½÷Ýø2V¢Zøÿªo´Ên\´žÔz'†oׇΌhÅÒÕ`nâÒtáÓZ ±K>éLV@˜Ž‹ÿÁIâ«‚Š«{nQ<ª2°\BûéÂi—)l?Z"aÛd?©æ;`[ø•i~n¹êGåúHWS,ßZ ­É˜+óÝëM/Pr×Jo¸B¤ ì*€§ENŒã¢ ê5D ïmf‚ä_Œê_*,r£X?œíïÁíss¶,½jJÌÿÿð¢AŸö%@¾Ù;!@=ï$›ê( åŸq`hz ª†cOœÃÜjüS?L^(‘2›  º½°›ß‰l™nóç‚©õ«"–‰€ÅíÏ8µ‚úÓ;„b… õùÞ˜s4ªÇjÐfóÑaKfŠZíÞÇLL„·àNk¨)CÖ?lM:»'Ñž¸'ç Æv[CÆd+e`C ôšÜ5sÍ pX츲4¡HÿzÖÓ¾Î(Ê µÂGbÖ 4z>ÿÊ\BÓüydø_W±òúx,²º`î«xL(TFÊÄßKNße1kZbcçñWÄŽ:‘齂7Œøvõ”ù*Ub!S؃¾>{%™iš5yÿ£²‰“Žeô—ü]ÈŒiÜux…ñX³¹ïHT“aóÀëÑòr×;Û5Ü 1Μòqè‹ÃhµBÊ(Œ\¸å†à6!>ŠŽ£#¼õv ‰3«u`°m¢eÿþöŽDÐòþÛGZ7 Û¿AèÛèŒs>íìl' %|p^!-Ä]‚pnA3î,ƒ:,ïA‰…°´°à6Í Zã$ÙpjþÎ×-f^hÔ”#Ò×ìu¨ÿ—T´n:ï”hÄ « xQUƒ†…D­}ÏGF°c,ǵϡd÷‚RèÖͰ÷õ̽Ò\Q s éðE›¹­SýÐùM´î%dA.Ü™·€@4¿²4¹ÄØÕðÄsB6y¢›–ðÞÊŽqtŸy‰#‰½¨6Š„…qµM•fn«õ©ž‹yω€mo´Ê‰TU[b$Ë£{Ï]ƒÁ½«¢rn=l¾Œ‹ø²¬¹ÏùqoDghr×`Íe0ªÑû|ooFegeF¯­§í¬´Æ¼jkkGjb^J\ bplist00ÔUflagsUvalueYtimescaleUepoch”ZýAÐõ;šÊ'-/8= ?ÿÿ÷¥ aÿÿý©@2ÿÿ÷»@@q825sdd  AppleiPhone 7 back camera 3.99mm f/1.8N`Wx˜ K °T¸TÀ ÈÔ/&„dzÀdˆ%"LdlHÃHÃ2018:04:07  0.46499999999999997 0.033000000000000029 0.62749999999999995 0.043999999999999984 normalized Face 0 0 99 10670243046314 1 3024 4032 pixel ÃÔ(¯„]f>î-§Ú]ý_V|äUÌÞž=Þî†gÜ+(ü'‚ ^1(]äb™ÈA/å€;r¸›Œä0ÜÉ+\U]»~U¡(€ãŒ/)-9s©ñ³‚"¬td™Y÷K4|˜ÝØŠôÆÞ•aÒJC7‘~5ñ©‡âæéBþ…£>ª(“ŠEsÙLÞ^%g¢Áž¹.Ñ€J¤þ70…Û8 ;ÿógœoMÌñ°`y×{ß¶å”ì× €? Þt(kçwõ¤ ®uw$ ÏÒ ê‚Âåvá1{•³ëIíB oØÞ0ÿ\EG002Ü ‰Ð[ j©Ý I »l÷XŒêíå×[ÁñØÉeeP;iz϶ñË‘9¬Âjµ÷@¶p•;w?{Ø2”ÑwžÛ—3yl€sH¨Gé¦ÂU”ðÊ*c¿g¬¦NûÛ¤tpj+‹ÊˆHÁ 8Ðm˜[+÷l츫¸zåÚqÌ^ôt¾ÙÉ –å/Ð^ÙÖ /à}˜Ä‡ý ßÛÉ»Wÿ”Y«Hë. ¢¦áXæu… ¡ÅøŽœÂõj܇¡þ‡=ƒewŽûFÃFu(ånæÂ åENî’ké®Ü• :Sœ¨xiT„t×ÝÖ4L‘ð ÿ¾õkW2£Ž1M½¯âðÇJß”^®pöÒøÓýn…šÁĈU³’}–kò5Íð‹t»W1¥ÐàV¥;tη̸ŠïKÚ‡€8C¤Iôs½IØõù¾:ÐÝ’YyâyùÓ,+‰ÝÄoˆ3#L¨lèöýîO¦Â™ NرMðø6ůäR•u"ç€l¥ 0b‹b´ôjû¤¦©»*yÂUÏ6ûØ»ƒ#Tµì"s1.›΀yÍõ¬ªN_Ú®üvõfŠôqœEZö•LÑðç+E‚–/I¹’uP²*¥eÐÒŠ/c×Ô;¶Eä·N׿ݹÌñµC ¢‰‰ãŸÏØîКtƒ‘HtTŸç­äDæSk‹Òž¸œ—4¥°ÆŽ-q¬rzÊ6ðˆ‚V‘ÈÑb­º®›îóö=ïóœM“‘ÀZŽùt¤hÝpOºfÉ yöönk`¼9iqåjK›÷M‚°ü3qܺA@È çκ¸¤véwí}•f4=÷þÛdžĉ¸;®$ÐIó¹Ú{¼¿¸¾+ÑyNÊ>ÁþR:¶©a‡F|F^¨{"ÓÅH3–0›‘ÝI §«Z%I)gĤrÇÒ%…²•:rá<ú_ ÐütÎÝæ ‰Ôí‹gÆ}dQ·—1ûÏ/×IV„€Aõ6ü´Š;ø d§Od˜Á üŸw!´7¾ˆÓ‘^M´øÈ-ýÔDl‘ŸÎõÞ+ö8 …c7?>û/Ѧº†d>š@×!âÿ]ßqñýŽÿ48!e¾Sß·H©62ÉÌMí_ÐÖ<ˆÅ9WWEÓ3¸¯eÉQܤAÆÍšÍϨ™8ÌÂ7H¬Ù¢C[lAh-ºjˆ‚æ°9‚i­Z¸Ú¬ø0\+¼Qªã\XS–ÇUßlEù¦ÿË%"jISÕ‹'{ÃD0OpaX“Ùµ]›Šl 2/Á  àfžº«†„BÉ|èg•Ù]©y H®‰ëË[¿ÿKÛ:‰Uó‘€ÛS æw¬‡\uvêoOËØý³‡©Ÿ$Ovüxè5­pÒ›U¤À³4G,¼q?§Ñ.¬g`0'jE ^€{îËû™BEöµµÌ¡ò÷(­Œ„8#~à±HµžŽÝa²ó¸´’½ý·â¡Xµ ´¤¡ÐúÉý4n €t÷-It“´®IKþhI M‚sb²ß^Ú¨¸`U?¯¶v§ÑãÄ$¼tì{  ¶'EM¿â+_/å 7ËDÌ×;äPeÞFåh×3÷1Œ“"àjM=¨6›U÷{´xVG<w'\ÕÖŸŒ†³uöjîþuˆ N¤Q¢œå¸„¤Fÿ&N´üdª3C±:ö¸p?Ññ– KœÄ‹M·dàQ´( Å'c…¯¶¿oÐ.ËŽyThÞ:sî“8:fìxØíƒt¤ qç–sW”2ÕãÜ±Õ gïÚƒº<26 ¾*Õ,Š~žt1Ȥ¼ÆŠŽôø‡E˜3a±ú†ÂGR¶Ôû÷ªØ8'£LcåeJ°D0ôîÍ£á!Q›þÎg§…rJ‡¿„¼¦YzSk5Ô*¥ù\o¦l•ëŒ ´"JÛƒ5X5ëšI憳5FL|xÈ$DI)ÔnÌíE ÑÏH…Y‡]¨‹Uïî| 1·†‘áÍ*þ#È9üÄ«ŸÃ .DŒRÿú#•Ç#n&Û·yšv1€!‘!µÖó3 ú!¥g°7ÿÉÔÈa ‡– àO÷]£ŠS11]g4ລæ.1Ž´ðHXn-9N¼EH9Vô£0CN>oFë‘ ÜØ#ƒgˆ^-Ýô:eâÑñòezIôþWqˆÉëuÅ¢î34Sµ`n÷ov‰2A.HÝ«úráštZ/ŸBð+Å;žÌ± Kæw}¹¯ªÅÏaŽeÒn±2°—8-T•ü_UtÙf£é¡»ùÍFÙ «pldI? 9´³hFÏóU‰¿áIôÐUD(.‘kOær ÕEójÅÄ'õdùûP“’’|¤ 4’›(÷z7ļ+™`aÞUb,1Íù‚GÏ÷Ba»þ X0Go ~ïAÒÑ–t­ŽÌ!ZUPáÑ*º 8rË9¡®f&´¾Î2ˆl—±;‰"°‘)øüŸ¶{D5S-È´8‡Awí§¼ÀŠŸÿX‹¾W[H7Êr”¥lüÇ÷¦ 2­µ@ÈLY ò9ý|¨z|~0¡ƒ’7 ‡š5½ØSõî¥ ‹Tª@Y÷V:³^øôˆ_S»ÃmŒ X¤%/óB‹7 Áž ?QudÊ Ù[2|“.Š-§^àáÓ&’óÍ/ |ú']—WQØæ4ϲ©í­»%^øN¹í²&²;1&D?¢e?t¶>q ŽöcGXÉ\:@Ý“\'„T³WiŽL¾ô7*0F›?!‰ó*»Ï^´ éº$Z9…R‹†aôjRz>ëzD¨¾mþï|g¶a_„f‹»}]]LàWZ“N$r!ëB €9›é©xÏ ‘¤¼ùsǽæ-~qTþ¨»ËȲРgR  þø AŽ®}¥2¬LFÌÊ~]æ<†7¦xå1Õ bw¿U0ðÌá°Ö‰öÒ ¾ØÒ˜P¢«ì`B3’ŠÄð‘I“¿œÍ¤Øí᩹À.|BŽÐhVí’òWŽÇÕ™`êÄiŽ,lK>²–ÕîD6—mƯ­R%|¨e8êqdþJJhÓ2² éGU,C(åŽþ¿ ì×%ž¨”H—SZ¡ŽôÉ‹ƒq1/¥ë£°1BËõƒ{’I¸éZÎæ;gþ™½3–‚{6»©n¿“ŠØ?ù¿¨R–œ=ù‘ùRåCÎj‡V‚ê䘯«×_<Ë6è<Ò[øæsŸ"ׄc7K60±Þ/ß³gËŠ™.b£Ÿ‹\òF^ sº*T¢ üÚÑbIboÑ’ñš_ªˆ£s=Ù1«YK²‘­“•úv¨+u_øˆqðîÓ)”ybÉD4<óÜ ]Òé·:õ14¨Šú×s‘ ¡±”ø½ªÏ¤±û‘™zHS[UðËG+PÙ9V*Àó*Wì¿ÀBùÆä¦Y$ÒRܘhrw4‰‹TYXaMÔUªòw¤^6õc«Ó¬wŒ"ºØåcøM O<­¶7—PªeŸ åìµ~”®uZ ›á{¾=,:òä¦USéš#ç ý3X4½ûy7‚xcàU ÕOÒEéÈÌA0JjÈèu O–-K–  —ZL~»’¼ñx.xÍ éÆ‹¦Ç;-Ùo¤(A"w´™x;ë yA—5ûаsuFekš!mw"]b¥"ëw#µvžÉޏ4)N¾ÅáÃhàìx*FK—ý ò¨ØŸlä\jm¸ºiêÐ29Ž/¦[CT¦v>É<ñª×à–Ne«I†wå[ oK“#ˆ×À8ò"çDWÁäÀkÎjÉÞ$œ]¬/û=¡™VÊCE†ÅÄ„Ÿ¡ÑCsï’ èØq°«þÕ<œQj¦1JÏ{Ô}ˬˆ!<Ñ‹û5ït>@J5g50SñªøwßÇM^íýáT;/¬NnÏŽk h…­s,Vùˆè4/øùJs3GŽF⇸»l~¸ýM®ÖDoíITT©Uz(VÕèÛQ­X»òNîÌ´fKy¥)ø½bßÄC)ãffoµ6&㺧ÝàªikꫬÔLn dy1#R£q´Â`äÑÚŪqF“¼NX/hûÀð&{/'<ø“‘d—þZÓÿ:æ¦Ak$ÛÄP`Né–"ýgÀQö7*@ØÐ¹¯ãÊ’àŠû#½ä L*¶&òʱVtÁÇœ!'Y‡T†’wQH •öKe¥*ÝÝ4*ÚkÂÖÙXˆ›½‘†6WDƯšQG†¬ës å¨~¨ü’2ÛMP¸ðùª§÷?ëM£ƒÈ)𞣪3›Fـ؂t>’ÛP¡½æD$f”Çú4øP>ëgG¯À9–‡½¿<ó=híÜ+–9q9\FîôçOä‹ÏO¹Q¥jdŽu©« !ºb¡ úšc®‹¸dëí”ÂéÇ“Ob®·줎.S_UOý¼Ñ+´é LW·¨Ó\7ÂvÏU"Õ(»â>¥\ðå}ÂÙ¬Û’ÉlÑŠög_l69ªA¼¬3W¶6`n²™>¸Ž¶‡cî LKFFÝ¡ äÂb—')«ð4ˆB˨ ·_¹¶È›O¸æwC3u»®¾¡Zê'líÓÂ;ƒ÷ÃD×|€8I™uiLÑ>t^¹¯ïõÓ€4ªÿËbJÿˆ ë²XpºJSfÉoáMµ«ÙPÚ±«v.£Z¾(ŸMÉUqˆ0ßFg Oâfßiê»ø!ŽØ:†"jÖ²™S"FNÔÊÁ¡µEq Cþw¾[ÜP€%ÛÏ —’x0¯,îþ¶*S4“3uæð´ÏÿIpLxMóÂ}PX*dQ¯îšº;«—4qyFîdƒ…ˆé~*°¤0-µ ‚cžc÷YG…QבS9yzŸ=ƒ>ÅHáÐzùZa»ºOeÞbëÆ”Àëdµ³%ãs ©JñÓµ6´çM?veý ~ÆŸ¡Bm%ß Š÷ªÿlƒ'reS<Úã¶xhEÛ¬£k'°GS¼¯ËKM½’o 7Ú ßÀÿŽÁЃ@¾ÛzJçó-3u±ÊÈ©3;Rø¥nèV]’ù”’Ö¶Îê[ ì)åöáëíw¬ÁÆ.A¤ÜÜíWxŽðý­IKÚ¯rÙm8¹Ø»B‘¢ïãFlÔPò‰˜ÔII¥wj/±¥$=Å÷>¥#"J‰:¦òX’ÓXIM¶z€òrÉ °°_ÎüQRŸLÞÛBÃ"¶ mfv†)#¢¦ÔÔcºÈ«éy˜¾ügTaJ§Oó'ó$r&Ñf;>z˜qÌ”'B½$ñ©9áëcK)B4'd3}±ôÊm&å¸Üj¦d)æ^ÙpñE?-lù»8ÌJ³FÓd[Í’sYx4ÐEàt¾Ou!uR+ݹéùµa§mÓù¬xlC¦.ehä° %Ñ™õ² =A¼W’ù–°Q¼eÜÁ˜ ¸tA*ºAhkÁ]§xJ›´¯ÙM Ëòþê,%¤0+ÿaš#ùåw,qgìwΟŸ+-—+ȦþªèG3‹xþ -¦ZH ¢/¦n–Pœê½\+5 î¦t®)“&ÝÅ™O¨+ô@›È^ËS4!‹¨TïMæâ¼¹ß"?ÒÑóÌ ,€x'x?tEã—j)œûÑǤހ\¥±%ñÎ$æ’+û[Þªgúw¨§Ž7™¡rö-—å,¿p7`åª\ÊèœòbF0©Bi›¨‚;ã…R¢r0ö »Çn0S›Õ(‡Š^)”•âØ#Çj¿nt‰¡9;’’Í¿œBM¹˜û bxjçyX ç|š0¯äÞàÿÓ'ýK¬¡êŒ sšdç<¾šÈB!¶èHÎüÜ#õ;@¦°ç½w>“JnZ CéÕƒmj§2„v%†›„q¥ó0D´Í^øõî×f)Z~ âélqú4ÇV'xðéúYdc-wÞzßÒl'iGW„i¶E> ‘k¬Å ¼8øF¬xæÚ`¸‘³Ÿïè¥FÀôŸ#NbfLb§,¿aåå]x±0¿}Þ’bäÔN¾¹R½&žÞ=õPç '7“ös7ÆÈ\%ù²mjíÝL¥4/Ë5ša8•¹ÏÇŽÔ‡‚+Z¬cZÑætWxU†RU“´˜]Þ•6onØ—ä‹E_¹“H…JÃwÚÚnÀ#ê”…Û¬/‡Éc5,±GgÖ.£èDÁ1ACh¼êÖ]ÿ³¬&ÞsÇVÙí0½L á¥w`Új¹¤¸‰HU¤Sj„LHl£Ü¼BÃb‹ŽÓ숌vöñéîƒd,†»¸V£ú–=ü¶>#¹(Áp¶åëZUÁä±±}]fã’DMNs§Š‰|]- ò8²‹ïc××80V@­xÈ…Ž ȵm·.Ò`L>ë s0­ÄuæBo#ßs¥ç¸Å-l7êÞE“¹g‰h……ŽMB”Qþ\f Ö÷OU„+1 BÛˆZ¦©.yY¥ÖˆßÀCe¤>ý¨bÄr·€²YIhG°Æå±ré9›O&‰„Øe?r)Š[õØE›=…L-®ÙÁ½@£ ºšÚÀ!I¤+uiŸ“‘ußIªoÎe)EýÒZÝK¢Iö”…èÁRã¦ÒÎÚsµM¨¸—‚)U(Ê4%¢~vÆk5¤FD‘Q!æâéºÖí ÚŒ.bjòÈÔáƒÕè/ZY¥h%Ôâ‰f’Ä2˘ç_©È±…èàGàúìDßææD9Ä ®ôб4–‡LfËv~–Çz£XmHC„)ûhGz>jõwö‚p^Øo"½®SeÆG|3Ï‚ä‚Øîm|ƒ Û§§]™=Â Š ÕÁ ÷Åð„~”{ûd†Ë¬¼öD˜Ôñ*àKPðh·GÅ$ñöCýÀ^†ÄüóúÊçç´N¹“ö– ÔòÇ›óÄͪB¸¢Xu ¦-E¿áËÅÀ˜ ›œæq;E¾E v¼åuð҉ȮßÒ'åw1 ãDrbK—J¶ð“6Š$È âæ©¯—àšUË·òµÀ’Ê“AÞ…`–‰ÝÅb,+ 혿ñN©Ç_£¯¯+Ã#ý§Õ5C”SSÒÁ9U¨*r è)‹|f¬;ÁXúÅ+]×ø?£loà²&¯€z>þ‘¯£ØÛÃ'ô•ÉÓ¡œG{V×¥:Öª1mrV“ ˜¼qØÉþ ¹¥ØÃÉ7µ"Ss# XïBHË·G)2BõBUÒuDæ[Ú€}œ: æJº›tHJ`Ðw§P,Ãñ‹•>#Ÿ%oºC9öðó>â ƒîAõFwpûˆXÍGò³1#+’ûû"ÃÑôImNËÁp¨Ñ}òõ–ˆb¾ ºÈDð¥Y4ENdZNY»ìÞEåÒnÙÍá¶ð2y±^iÓ£~*ðcýh4àÜ]·B«9 ŸîrÖ¾m®ÿ9겺 Ë–¸¡³×ÚŽ  ãd™Èôªí_Äa.Ê‹4Èਲí^ñ‘Ê—|C]´ü<  l¹Ð2VBÊCí4¢LŽ4Ð&B•AeÝìf'­©)Iáø2Çk¬ºDüÐq4EKi’!ƒ‰î#m¶ ögËV)–£ÿ×W½éøù2<º7*OC®bgNJštÈ­ aŠ7ÒÀ‡ö„&¿á¥»<,&RTóåë[©f]æ¶=-x Õ…¬@4R/ÙýXÔ^ª¨9îlî•8J\„ò:+xë|4h-·ƒ@¯UWÅëuS²üxÜ÷$r'¼½ žH;UŒèÆT´kÿuúC/¥†ÿ–xõE÷ö"ñFJV Ò6Õ¬Rz˜ö¤;ÐZ"¬³\6kÜt…/„ã1çÒ0'þä\D'*xº» •ÙI§÷È*)õæå(·ˆŠxwÓt~Xìô~iì®ñ3ÐøH™f2Z &h¢ Ó˜"r‡C õ¹X¨w¹U®ðOR'¢±»¹™×?}³¿þ£Á. Eù_Ã]úµÊt>h'ÈY$ò;š£­T—be n”ÛM°$û_²­ùŒ²aqfÞ ¾ÀzÓÇûZ¨x-'é„ûu}$Õëþõå/Á3©mßøÛ-—ц÷e{/D.$y#‹1œkn¥±ílö+GXêæ“¡è‰ ê¡HËÂáš^ƒ"™_s«ˆx›)3Uÿ¶—"WÆÃ{Òµ¥‰4ŽÏ“¤)Û=Èê'šüÙ’õLDg_´ÏüA;FRÖ7_,Œ‘¿Àg†îîÃ#naZósH'KÕì TÒ‡çí}±Ä@ z¶N.«OßÃ?úŠ^”ÓØÿw˜v/ŸNQø,çâŒS†Jt3Ðß_Ì8pÿ4È ÷}XSÉ)îô>¤µxÄÛt@Ĺv­5_^Î(P¸½³Íe6 ÿ4t/ƒàM^·ÑX¥%™f^ãžò˜¾ËÐQR›&Éë 0†ölº«çÕDeaÈ¢™…ú΋§ÿ¡ : øO圕M6CÃäy‰ñô•ã!í:ÕaºÏ]tNÜNñmkÀÈ ¢¼ªdÀ1P¹wpÑtÞô¨2µ6iùA©Wȇ€.‘Œ$â‘@:¢ù¬ßW* VFÞƒ@tÁ€„µ2˜µ¸ZÙÓhל÷éøôB_FŠA’_£§ëºHÒK©FÏ‘Ñ×m¹“†5ˆ‹)”ٶϺŸ¤ÊEcž,£Ídl|9„‰‚ì´åò¾>IQH8%æÀÊøÛ4á-ÅFM«Ôœë830QÉŸÆ™oú!ò-l2N¸›šòšwŽÒMSUžLL®FÅ‘!f·Þeó5yxRÄjþâÀõö¦ü&Êo\Jƒé¹…„YÌp­ÅÞ3æèÝÒ°ÿ2)LLs©‰Ç§!´@.2y~ãÙtº\Ñ?é|òÄCû\Å"°ℳ¬Éû/[¡5ÃþR:»0Ìä‘'©v Gg¤"‘µ:°O0c ²˜Îà ép.r•”ŠÍ’Úh¼ÌÝdH'|ƒæ6Hx+‹Ü ?e £‘Mý%À£´éч½dK¨,5¬GmÈæ×±c-6UwÉr¶Æ‡8Á5R‰ì3´mä*=cj)‰®k¥JI¿”ïä¦þÎÍÀVþ{¢öÊ›tŽøhÓî s«.Êœ#óÊtü£ ô©ˆíÕÎÍÆî‹[xF*0w-<–íòðUÍvn(Í®Ì O˜+ß),ÎD]­é&™HpôM°åNæl죮¥}Ø2$|Wt!IyÝåáýÝîÅ“ÞêA€_b1Þ¸.àP>kϹ‹0ø‰3hfOM—>£,Jˆ¿Ô äfaÆE‡N躻I›d¤™[³ÜãW0‹+hZ¤¾ï¼R+‘ŽZ€­T…¢^[}Ÿ]r=0¹ÕÆaeî8âÛ–ï‚“eqqäqf ÉmÁÞu!T«Û _€·°ùU¢ ÛþúНšÈ4ÜzF‰Ò%£¦ÄÁ&ìü_ûj^îÙÒ=ž1ðkŸ¶'Ù]/¼¯a0hØcF+5ìðe4I ,ƒa,@OhDÎMº>;´HäÙ@;@’Yj­:k4k¸IQhh8*=ÑçòÏj<¥ôÕ $zÊÜl-Ðá7¦ ÈKPÈ!$fÒ!Jã¯Â°ׯšc¯à»{“‚ƒ ò-b.›•èØê³…¸V@O)‚ÇQ„5åÛʯÿMæè~……)Å»ïìåS“¸“ÚdJ-qÙ¡ƒ„!iì,š‡¼ Ã; Y<=Î,çå' q?VÑ[ñå¯ßÆ´Ϲz ÂlW6T¦zRq>`\8xdþ¥Úh;Ò<”jOhú…5~þËÒSn¡JÅ›ÄcÓÀ¸j©B×Ö<¥÷˧Q(KÿR<·óÙh¡÷ °¬×±ó·õøšà†27¨X¦æ`Í[ËVÛj<ë$Œ&öï« Iš"€6s³ _œEò9±ã– lšëà7Bk0dx4(˜}Î)ÕHåÎ'[§¾¡Gìcë+øÉµtalg3s¨Ã÷ÛÖÕCÄ8ͽ…ðb`rÅœ,Ü@pg¡6$@JfÄ3AA틺Ó)Ì÷Ôu›Ð ²¢…*=+?¤¸Vt‹°y¯ƒéf}j9y¶lVU`ät™yÿ%o¯gº–e¼ÙR%Œ ßâfßMïPuŽ›‚O bqÚ|ÅÛu<Ü †¼lkÙu$Ít D?þ6’—Ì¥º}š‰¢ëŸT‡ ÷ˆÂe)J4žSõˆ]ƒ-ŠnI¾E°òÁ° ¿0Að\4š_ùÕV$Deç¸õbR#?Úìò\”×›Z¦ÛçÁ–& ¥„ó—!áJkÉ«—øÛHßËÝuD]ÙóÓ´Ö,ߌtWs÷îkE(Ù-npèÑS²ú÷ß„”’·i,îO&¥µP¾¡îîÐûð¨â«\ªm/ú6Lþà6’u%ŠIŽâ¯PéekÐ'%†ðúÏÛ¼‘AhÓÅ(bÉcƒg€ÚgÁÂî[6‰ lž¢àÍowQÙ&;¾2G…ÃnôØ"S­è}v›0c”±„eÙí׬±XŒôŽž?d)9ÊË´ç`C½C£ôã(­˜õ z’«@'ö£]/u¿Ç™J^_:á·Nå7.CY{¤Ÿäós'½Å^*L iNI_®?À=ëfÇ4G’R¡ˆÌŽhyžîôVLäð»¨Ù³íxæŽ,Åïäæ³qK”‹RwÑÅKßt§R¹òìŽfù‘(dM›éî»ßÒ˜<¢ÖÊE!3ÝÚü)6i~õAi<á¬'Lµž¢?J°N^X Û><É¢˜BbÓy1=ÏÑ(„ŠrñAEwi(cCo?¥ bZ8Ò â„„n¨”•šAúÏ´”…l\ä9-í-a–N÷Êõ‡¯fp;Ñ¿2ÆQ”`x;ŽvHÄ ÔÍì NMÜceÊ‹øÑStð÷xæëdŸ®ØaŸN!¸s#w'ÎpÞþvŸi“ š®—éPgÕ_ ßüR„ƒ ›tÌ$ƒ…¶wV[µúŠ»¬K5Q$iîiÞ£oµÖXî´óA®)î×Z6Ê.“‹„ì1¢ØBÉ—Ȳ[QŽóý÷… ã–ÊÀqW˜Ã‚äÚêJRž|â?+ÞÈCü€T›²µR|ø®L…1fx,ÐÀʇ5‚y¥òá #­=íg{ðÝ/!)YÚ³¦˜lÊް¤V‘%ò¦‰®Vòxˆ#[!ìmIÖRP´‹¯Ö|ëlÜJÎ!9Œ4Ò PW«Š¿é6iቔ‹é}ÈÔÞ"MV¿¼ÉDN·"¿þ£|&'e±±tΞÎh7ñ¨c¿ôM!JTDÊÂ.9Š–^Û‡žü˜ ³¡øL6¿É¬lÝÕÌô²­’Dg':¢k‹³Ä=¹Ä€ÝP›ÄÙDéˆ:,ž]w#ïAPâå`ëÏz¨ÜÖ ÕåÚtF|«>¦ÿÁ[ É$š„ 88¦ÕWêõ»é•ÁÕ¸ïImr67I &ªÜ ª£æйH8ƒæ¡›Jôõ•.nŽÍ\»ï«8]1“ˆàr¬2°kŽ¿¾‹9{ tOù¨[eaÌßxªÅñÒhÖhª>âŒðÎÔÝŸàdømž¸‡”kã²)Ù_9˜&xíYhŸˆ9AЉ£àb3J ài÷>ܧ™6ÐåÚcl‡§—û’ÆžÈYEG”jmw&*H‚M?]/l¨Â´wŒù¶È¾‚·†ß"VüIˆU2CGÔcÜu\9Çáæ]‹s¦Ù@9Ú‚·Û.’öîI&¨Wyà‹\Jò\DÈg€¹é.Ï‘ô·×ií¾³þ%áä«M&á˜\ƒé*;­‘xŽCò}žè\â?Uᮿ‡…tàÖ€›™ÕÒõ¦ ™Îq¦t€ZLßF ƒ=Û·l4É Ý-ÊÁ†Æ=“aÕ^YV,2ã«ÆÎÒîÔ²Xç)k:T›Âh鋵Ӷ† <búùÊ%eÈ“éw[eº§W˦kQ›ò|8F[·ºŽJ}Zqø0o_÷,©Á)m¶|d—¨Cµœ§s)¿ä…Tè§Áâ0‘œüf9®È ˆ'8 –ŒnüºDx޹·Ÿd^"ÊDÓë[8¥«¬™r}H»ÖÐOJÓ‘þÖ„û™h»ÙÍX&'‰Ï‚»e=hùò­6·È¬ÉX áZiýD¿ëž.OCEs¡xTÝOõ£æzõf¨qO…Ë’J)»Tº8a†Ê:‰GFV‡ušYŒ¬ò€RäK|¿›ÏÛª¡²e!Àš¶µ¼ $Ê4УUÜCEøàíƒ xÄë¹û‰XŠO”ã=¡Ip”ݪ¢šºhW$õ #VMÕ!ÔdŸa¨e™Ú¿W^[lÚ}©rhj1IËQÈè½î¦H56)–™¦>ÆxçþÊÝ}[ÆÎ°ÐmÕôxÎÉOe_ ýn†ãqQ¨Ú·>[Vá–È5 «FžHNü kF²­¢Õë"øÃg¿ë¿µn–“cŠŠU8Ú]¦¦?§ô˜Ãð®dy,GŒÄôÛ(+ûKP%R_.Š–A«dëíŒ÷ Éok{ún<º"äH ä¢^gÏËb†Õè÷ËÈå!ž^·üm¬fÛÒÙÍð‘µ,¿»]û;Å-=Í¢cJ\\hè ‰ÎO‡mÄbôùkÜ+ó!LeN`s”(Ð+M“k!Ö®tײgûTF1ÀKijˆ9ÂÔä‘Ñ'ž\>Es)3I52kÆm4ã©èO´ú¿-Ý MÜÇ>E,nžùò4‚w 5*Ž,-¶Ï!J hæ±Â}Æ>O*•pÛˆðfÖt @>h™UOøO ’ÛØÐoc>ï ½Ÿ¹PnÝ2zP¤L©­pyŠ&°‰âg²92Á¢Ë+ÚÐóÀÙG{“bÍ `¬5 løaà5–ÕÖ!²•H«y=—×4®1‘ Ý-µ£€Hõ鬕ídá ¯.ˆc+ÉL¬žOÛ;ÇYánð9;I:d?<¤&Úû´³•:„]Z¥yâüo…·QÏGKùhRaGŽcÈáÒ Ô&ÌWé ùàZžË ù²Qu‰WÀQWÍÙ}rkåE°$£T`Qä)›î‰"àG×Bõz+ê^)Á7»,A­,. o‹|.°ç€ã ü&ÃÉ­ÕˆíÆyÐÌiIEK˜çd16¦RJI®ÐNáæ«¦Âz3½`ŒÚ?Éð¡€i’dþœ“¸Kñš²ßÌ‘Ä{¶ò‰‹ã³©ãÞ!n‰1h‘UMÁc´ ÂïäL:ž$Tªa9U ãU®–)¹XÊ&".u& [sVE9×ÞÔ¤À=ðéwâ[ê•¡;–ØP×öVŠŠ8J`¹ Tö⚘7[ì=­Çró*^FóÈrÒÌ„¨‘NLOxtÕ`š9÷ä£ñœË3§Ë¼²“Ñ .‡CS öµÜ9mw¿ßy‹,ókˆfúñ:èÅ‘4ûd­Ö# FM¯hKº˜QZNAéoJ•fHÙ :„ŸDM2MiE£‹ñùÈ8ÄM*¸•LËéH²~vRåàbˆ'4â¨:ÏêXâ<^¥qBÜï³jëßy3M÷”[S 0Þ·™m5M*`~Š(Ü™m‡J9:A¼èBYRy·öBi ÿ.1cÙhhåÁH<ù·‰té‚s°Ÿ‡d.¡9û†*êd”'ÛKt%þœ‰:\`ƒ󀵘ÛáùâF~ÊÈh pá!]ŠÅ‹dbüÓ£Št]%¥;šÌ\GAã+[5`(ÄLú‹6êþ©‰¬lŽc=ErÕÎàHU8™Rc+  t‘Á‚€Q”°{É’€üÙSð‡w<3[psz}6†ð!Ö Šj§ìÍœ:n*"[ÿæÂ`•Ð/oYnÿ[tÊ+Eñ©ÀBœ†–ìîŬúcIçõé~„7¹ÏK÷ºHÊb}I¤6?PKTû|ý$à3¶üUí¦3Qâ Vå3œÈÒ`ÅJôìD«}cRä@t¤®Q~\›háê’Z—Cÿ?Nu×Ç…ÊO†3d£âeçxÐ…âÍà¶Îk|‚Yˆ›œˆƒÙ˜p_Íe…ôxåDÐSŽ0ÒêK×Þl¡'6„ ¹În½áˆ«Õ¨÷FòBÁ 0PÀÅå`ò^ïù`¢‚Ž<‹½NxèÞ$î®tUHŒí KºûÄ{%/®ÖjÔˆ~˜óÛúÎF¼>ר½CR`_å.ˆà$¼ÄG3kZhž7Àêlï{9ÞZSsÂê %öq1vjæ‚à²äsúOðÚÖÚ}¥žý²=´Œ™—¦Ñµˆ´t]ÑÓŠfÄÔÚ­ ­YþòK49ï£2é“ ¾SlD†–ŠyLe#GQ8áf®þ# tÙeÈ@ÿPO¢¾Zܸàc=­‘¢þÇçÒ¢¢“ÂÒÖ“¾´útÀû”ØYÜ÷ Ãm_Ø6ƒbþþ‹ûžsԥŴž°Ä­*n“%zçï°SéN7V"è"öýjïÂ/ŒD˜w§vÊFJ\$6µJªÌ?–Â>ç\¡”…“+/oŽ]c}•Ah) q5Ÿ4k‡”/ÜÀüph1ˆ!êÃß!ÉwOÔxZÌ?ÿ³ŽŸr«ÂK.<}F‰*P³ë§?ØÑè!(hfÂ_d¬Ÿ«¶PœÛ̲>ÜÃkó]dà{ËEÂjÃÍó“ó:CJ;NˆE]D%% ÷ºX+Jêzn.gGt@â_÷¶›‰™PÛFi~ÿ §íîõzÈ"®ó¤#͉tÉv½­@òUj¢Ø©ûÏv]52^O%ÑØ¾Àr²¹XxÞFbQ¦p#v#?[E þøuå¸ÇähŠÀVµ€_.¹eçq É„çñã“ôRã*Ð}³nÄE»džû‚<ó–˜vw¢y~98ÓÌKËñ­öpÔÄ*kNöuùá)k·…ðÉîTK·ŸaY/fê/¸e|_´#k-úâñ_ëüPRüˆ¤¡£î¤µ'ŸÎUíD ee:6?·Î‡Öåas¤Æßõr£’Žø˜çû”ÃRâz_’(<ûçÜ:å|»Î³mžFöóoÊÚiéú-%{0íÅ?å$‹ú¢4àèítœˆ9¤“ïx’ïìèÐï­Ñp€Žóílþ̼PÀv¹YX¢?:ÆqQe«éï˜dЃãÒt_À·BÔÌ>¿uÌ‚Ž.èm›œ˜ÎéOƒ¾|6L’§.î_±à*"Û>ŸÃ îU}5kQ§ò4¿ÅÙ>õ‡[ïyÐG‡mUh{UûCkNT:rƒ9ºÁd³PºèR•I3ùJÁ÷±âaB»­Á¡÷¶×r4‡ØZO9-Åä Øõ"‚¾ûaŸ±“d64VY&Ôl6à剴s¶'¸ì³]ð"N-k±·‹» ¯À#s X–-‚ š‹™…µ™Xº5IcÚ¡|×ìuØAX:eÏhñ>Xõ$?G@Êë`t& e<ÕÝ.ƾzµloqOÃ<ŽöóoÛ›@Ô«¹àHÝñ»&ðý‘œ€z±Q¶í¥¡6a—1Ê?X{ošmjøIŒÉ÷£‹}MInjØ>p/.¾m;ÅJaµf—ZÒ)àkÛ·EÆñèP›uü¸*EÑ—°‡È/!Ѱ·tm8íàe+&˜íÔXH`‘ª‘—©õÑJÕËw#Õ¹•6„÷˜ŒN<–M¿[¸¥,:B¾=ŸµšÜµú2cb-@“ÀvU-Ûp²y¶üˆ)·±U<éAcÑ´ÈÚqø}_[N«1IñÑáέ¾[zŠ:ÁË[l“£øÎªÁ|Ý`¬&ïøàM¦¹º~dCáá}‡ŠF,ó‰{s”/‚>Óð9…l\xËb²Î –ÙeD±Ðª†–Ý¡ðy+¡ãEƒTÅÓŠd¾Àø•.ùéÃO»Â&ú¯ø¯ú?éçêÊR.Ó~{\‹¤Žq n;n6}|ôr½Í÷’uÆŽÉ"ù÷efèý‡ì´ƒ8öîq’ z£°÷«à ~çr…DïO¬DÉ©ºÒ1!6|ÛxmxÆž™_¸$?EL•/ˆräÄ.½ç©U ‡E/ªvnšò„„¬“±¬w:vÄ^ägö…ê|Åy*ãÛZXÐt}b8·n|cîgx'q' "C=¬qRv¯-íþð-É‹ÕU+àÿfr©?…`â#ØŸ%/& _pÓ v¼lü.â-ȧ’(0¶¾pr*±y mVî3Tmæ¶/ò›“šþF+Éö¡îUÚN?êówûy~Ë¡ri+œ¸ßCŠ“o™Xé €I¥:žˆÔÇ]U2e“ hûs”sa'B*«ŒÂk¼ñ;éÈç$9kÚ´‘ñj/ÌÇoÆVàÍ+•™-û_5ÂBñ —a,H×¯Æ <+\U Ø€`”aòäÊGåN.ü¹p*Øàzð)%†½ïëN¨•ÖfOdÃJ¸’îü8QAAQÃ\N«¬ý8wmµhŽ÷*lŒJÇ_‘)jÎEað/WbÜQè•Â…Œ4››.Y³ƒkuÙw~Ñ”ëM©¨OóàG\~Ý'ðÔÐÆ1þ³¡+œHS#3.ÇB÷æ~®>LRÓ“š[…1 7¡•\’Þä`r>žXõ¯ˆ¦¹ðÈÚ–Zp5j5 ?½*wÜLrW£V6|ƒMTiµm[¸JéS—w.%V—-­“É^#ãÐ3é]ãÓüG#³'/'’Ø6[ù[“½×å,×7µqÁ»“C q?Ñß—5½9ìšñ5‰BB™/¹—cÈ­k¦á Êòµ„|(®"§ÃbLeg+ýhñfRÆÎ°°Wg yw°<¹u b\ÿ‹ôóœüYS9JéuÝxàÙäE-‡Æ6î7&ÀOÓß©Žr3Ì8Œ1…ê–U€ZÚ-˜É|">‰¾«D·]—cækWy‘0J{†ˆP»ô€Åa:Šnriß®`Òy“cdrÆ«f|9¨!Ðú¾YÖý§ •¢aÜÝBä¡ë¯º 9Ùv< 3˜F“ºD+â6|3Wò?wÌÃ-(ð+ní¡‘–áËÐ9}yôkC"&ÿ*¿ÅhRÚÑÎE“±M. 8g«ã‚Q¨ê÷»`»°»]õ!Aê|VjžZPïe NYD†nKqއŒÕÌ ’×[ì¿ÔòužxÏ›@âȾÞE—I}֭φÿ‘¹«ê„S®˜€.EAû¯‹ÌëZù(m›x!ømIÜõ§ìèÔ¤kÔÆ•%Ò¶³ŸþK jÉ5FU•ãÚ-Ö1ãe€ëµú›kôCâU]¹,3ÝÓÄ&ÍÅ0á1(X1Ç \½cl*mî½ÔAt ¥`ȱ5¦ðÝ]yØÙÛOLÀ:;·EtÅml¹±"¦Mû³àÒI¯Ç «6‡<œÞ˜QˆY?[Qyˆ­róè#…ài,$ÈOÏÖβ½æÓàœØÝÛ$ÔañË OÁ"ýùöˆv² .&Q ÀÒ–ƒÌ›ªKN¶úû{MÜK›¦B*;Ç< §‹«ãŨÅ*ùˡ݉@ã%·ožoݽ~øµö]ÍþkñV5ý§´µÖï¿YÃM,UWÿÁ›Ö»Îâ}’ ñÚc[™nÜûÞ5E zQ°¶=|Ô¼*›#á®glÊ%³÷;ã*í¢›uCKƒäò÷ ¬tîñrওéôýiÝ+W÷&ÆŸ  ¦ö½þùÀ¯~ß º[©â‡NcNGç«Ý‰áº‡fýæ?ìÕ²Æò*|Õág‡èº:¿…‹Asð—2M)^C¾À^ûÀ†ã²_€€\ÂZ"áf<ÿLë/Ѿ9ÐÙÐ…·~Ž<·^U^…Ê ¤ã§%o» â!L}™©èP!W/Ê éÄpI³ÐGÁxéÅz«l¸Ì^±õ;{ÎÝfB VõËï}A¿7 jNË 7Üò/1nÊdy†¤g­(&úG@…õ]®\{o°LDF÷X™-#ÛÐ%á¼µâÅPî8q+ +ÈV}R SŠÌÊÃN«\Yè¢fòXÒêPŸj¦áède# ¶áêB›Õƒít§ŠB}»Àç£82fé7lÿǘ0 4q ¥ZÜÑÙnsOá3ÄÎ…ª¬¦ÉÈw›ÏN2f€ @ò!~ZŠóÞI–ǽÓÚqþ£> •i6öq+*üÁª…2´Åš‚ÝŒ÷ÁËQB•€ MŽíj¡àP’#ÈÒš£5HÎäÔMÐ{,oK6²½m›À'F«é¬à/"¼Oõžnó#ſᵠ5í j¹­¶)?VëÊ+Tà€ wpP?HE§û90x­S¦?Ef'º–¯5ºÌaÞZyEZ;hOÐ3xCÒì4e¸¬6›Z<€¸Ã>HpˆCVÓù]GަûϤÿp1á¼ÒSqñ¼P–Q`(ª1L;ÛhìxfjNÁñØYhšù‡D%™R÷kœq ¦&ÇQd:XøƒÚÕáÛÚ÷˜HâH|çÔˆí²xK'þ»cš,IA;#Hùj;pÝžJ÷îOÇÃÒnÂØ4 ¤ó´¿2Zįáož[öZmể)µXĽ yÑ#âbŠÎ’Ø­x»t¾…Oõħ+íM¥ª£®y¹’è¬õ¥Šë…yHÅMzaXNIÜvrIÍ“Ž …´aX6ÈIpÓ¬@49%3=$ó^æ_Ôà"<Ý“G±ÝŽª×¡%il”Òk@)ÁG=[ï”Ö¬aéKk_ËH¬²/jQ—‡ Îl½I‰QI^Ì'WñY²s ã™9ú¦q"­u\Š:÷p á½Mr§DÅ=ãc2Mcu}…uSÌ2Õ¨u¿Å`ÓËdkâ„; ¦Ëž²Ôöë QÿÍž§rë°§ÿgÐ>ü×åß§õK;ÈáÖ|Sóá=…Ç() 1 òËG)äZã£;ȹH 8ê/ùÐñ }½ˆ¿oÐt /Ƥ ¶°…݈ÍMƒdU="AüÜ37trbmMnçW6êÞŠ™©êÓ;N‹ Ò·6° OÖekçäw¢ï+Ó©Ê  À:¨tØf׊©¿æ¢þHoošÇ ËÓLõ.8ÿ6ÕaûÑG{á2œŸŽÿ´Ä8‹áù…ÅŽó¢&Ùq‘j’`’ÅÑ´ÍDÕþË„À’ÆñØ\ü”å5z¥özzã» #: .Ũ ´µæU+KÚþ»¯wÓkòFÀIèÆ2ˆâó€/2åLÀ°úÞVæÖ£ãLM*+EùTiÒ‹¿ô‰K¤¥–½"³~!kÛ "õã³Ç)Ÿä4·‘tnÊ?.W€Ž1yéÄð^žF®iVÎo¥…<_–üD”Eêº-ƒÛrN4v™ßžU=qÎM\J–^âIÉë°mú @øÖPRÖÁ˜ñç/;ÉœòR˜?ÊÐ,ôC?ÖÐÿ±Ri‹Ê¼oª’,Yy‡ j]w•^ðÉ’ºŠ!5´wZÂ{ÊúpvÓ¡èŠYNþÖ>ùø»ì3£–"(&iZQjŽE6?ùËHhŠ §õá]2ÉŒ¨#^gTmJ7W†Û9á‹ñ°ðó¦.:fA‡Ð“±8Á±” âHXP\A¡¥D–¬è›«\7¾"­ž+kp7kRóΧa^uì”}ÑÒYú׆Èý‡ê_¯Ih˜²WÇôtl†‚ähÙ¡ â<çFSÚú‘×?êI|%ß@‰›¼‚û¦ª„£ Çç¾.‰w 2ËôiXäÿ)nGƒµãªZ…Ķ(wújEC# [Ôibêåî#ä\vŒp€Õ/š¢ïï ‹nÚãÜ»ª-°=÷lu¾9 Û´!oÎ ‹:!•,¬·¶ÂJk8$Ý´G>~ö“6ãýú™¹R¦òÕ}gè;’»hÿ¼­ÒºÀ-3ãLó@‡.2±Ûv_ú#$qd: 1dFX÷¼%}Y¸.3欮ê+ô"èY»|¦%á®'¡—§‹÷c D(¡v,IÿÚu¸ËS˜©òkïðÍKÖ.P(¤„ÚcÒÊ^+áš9Bï·¤®&Ó}ÿ>¹Ãz]¾ÊÁfú_ÎÄF$^i×cù>ÓµxßtSUâçF²²öW×e˜É Ýi2¡ë^ÕD„Çš†îü5žfkµà¶lq¢Œáaµ¶|ÖÂêQ‹‰ýîðÑ¥ÚÎÒóìÙˆ=_Žœ ý&ù †¾Le -å\È›VŒõqüyÑŽ¿ŒeÓOƒ¬ßc’âÇ¥ÒˆdˆŽö»åt·”©±C¯Îž”)F?8({t ‚º4¿ëMÔ {T7:§üÑzõ\?×ö^Ñ r4UÄÔÀ@ê[¼%‡XigÈgx,޾åÉjòøà,ƒº<'n_Az.åam^_ñüJþ/û»$6Ú`*)Àè"tü ‡Sëô¾ü„%¤Áè¤t ²Ë—ï­Šì2@CX9”€‰ŸùLB…«ZLJÓŤ)n«úç$äW˜Ãà /„~E¾ =ª<Ú À«anî™Ç³Ø¢|7'u&¢ˆÚ…bD44W€º˜UÄ6^l«r»´=²ÀWGX­áè•áÀ{a‡˜q¸no¬8®­Î{g»zÙ“FÆB/éÕ»î*Cöö*£É1õ£§þÅ!^[*ß`i{”ÈEŒ3\q ­÷]c(šGî½Þ~ KVÄÍ0€rG¼‹J+êu?ô”ŽkkD8Ù….»Ô7“Ôðƒó|U׸ߤ™Kl 1±¡÷mŒ2¼«ìWµ 8#W¶µ§jëzÌ&u±ë)zÑGÃÐIhäº/¦0Ó¤d–`ªÐI;kgVr5³DJÆ_8ø~†€šo§ÍKbÿ‚¯¬ÿ±%Ø· fƒ.ãꙂSóÉà´,!ri”°TðöwËÜy(jt•ÎÁqÂŒzI­}øžÐž´Ôï  A¦°•3ø  ˜0š‰½>èǃÅÐk˜²C›æà Ñ·|q2¯„̯¾ÜÞ– Š‡ÌŒ/6`‘Ùd6¿Ù®.…RÒå»"«Š\§7÷X&'³ß~î4•æ­a}ñ¥hÔÚ’E÷C4i+#NÔÜ$Â~rïß—] ïûÊ?_Fv´œiÉŠxÖ‚ Ê.Ñ„ì׎“ûý «èfFÆezuÌ€'à`Y²ƒ—ùÑzUã–›¹'ðžc­CRRà´µ¨Sr¦û’À+¯ÿ©>Ò…×BŽŠœ qõö'9{±Ý’£¨åw9÷@‚³Ÿ(ó#q`7ºëûh¸?%Ï·®[ óýu"ÑÕ¾œ·öœ RÍ_då„ý‘ç²ïÎË4wuÛa—(“˜ bâL)Çf™hà/Pìq5§³:èy)ÌmVÓÿ^h 0$®¯Š¦ŽÚ³¶zN)”'gí0¼0Øš8ø9¯ÑÐ7£pö³x§‰R?Þaaéø€­ñìR0’Ú÷”µÑ•„0¢_Ø¿“µ»Î(`ÑDÀ¯aÑ$ùv+£E´¶ÑïN>C¾—“­ï/üO ŸT¼s~’ ðÛó…|¤EŸƒ'Üø~Ãk7Àw¯|' …Õÿ”o*¥[¸¸¿ª£‘Wv¶bgôTöÒªž;ˆ{5ÀÄGŒÓóDI/áM@4é[¡p…„e¼¢N(¶3È€ £-+¯OüˆüíƒB¯…ìÜQÑå/<8–Ù(ÄP7.ÂÈ~1ÕCü9¶ftF—l"ˆpÝŠpèâ¯_gÏ\æã¼HþæKKÌwž8€^Ú¤£`g¦¬AnøÈ”T¾QÎÓtDè±<ÓPÍ\+ŽC/܈ä×:IoôJÏ2> y_[¹5OÔSpïiûÒlþ­^ëFNŽ„zý¤tEC5˜_•{¸]¸-±# ƒÙÐPèO — m< d±eØ7¢=ƒ!àZ­v7„CMÜ>Ùp‘èiêÀÅ`Ö:'f^fÕf¡GŽ’ E朑yЇÁÈibj¤F…Ñó±.>5®¡ƒ!œœœ§Ä&àréyLÞâ?رZåÌ]œâ/ x0q(¹±‹¼˜7Z 0>¼°>›œx쀨WYm§yEÛþÔA7fÅjI¥zÚøô.Õ×+Ýä/–çß>#+Ë´ÔÓÖ6LWKh¶ƒºú¼©ãã z¼íü—ŠpÆ€+xªd í U\Ä€>÷↠¦äóšJîò;¤ñRØ^Nß9‡Yäß3 œžàœÙC,C°Y™Z ¿H 2ÉaC”v´±¿iNá!yü¦Oé«4¯¼nÀà`ÍÃÛfKôiÜ‹ˆ§®·È½‡âŸàRuF,¨›P߇Nz³@–ÏeÍÓ á^B}”öeÞQux[Ã’4Ö3„ý·NE*&Oc3$x4ËÙ»åÒ£E©ä¢³Öú¼Ą.Æ|S€!>æmºCñgÑö¯ãIŽiûŒwÉ÷‰à Åš+9]T׋¦’c4÷4 ùÿ¤¡Uû>†1ÄúçV{G6ŒÄO6ã×Rè~ÿA(Ñè.ã Dž^¨’ÃKCóq‘ç÷…•å5˜j%û:•“‘|î15‚ûâ+œgø$Gàë’F¢Èù2Å|_EÊýãb}ß§¸ ÃÀ±³²Uvžy\¡»Çuå°äšß.uæn÷#£Ùd?·m\‰ÞVSF»`;ïµCn˜u·Q| Ÿí™ ”8(úˆÿ¸¢|ËËÁU ²V‚D”žÑpq2˜RîáŒÐ/Ëš‰\cŒ’^pߪ°\Z&e%9áb—P’W¦}Ûèl[®¨ØÛ˜âŸSãúÍMgÁ. ž½8ÄaûhPt"7H‚¾“ Ø.TúìXºÍͺ MõJé`ÿAC:8¼ÝìfƒÔÊ^dyø,F·¼œ#ýWÿ $ít4Pwa}¯j­åÕ)ñáÌ àW4Ò£„Æ8ñ°À–’ÿ¥ÆÔÖðì2}q¾é€Å€Pþá„/àíΰ¢oÊ\}F+‘!\Ïlñ}—€ûOó@ö,¡ð G }5þŽWíõɦA¥œ¾üMuwá2±°÷ EäÛÓK¹À“ÃkZÞj‚ iXw¹RÈAÓã€$ålnÔQEç䎂 i(ä/Ý+죩 ‡ãÑh~pEeÚK‡×}1VM¤LI;Ü8¨º”Ä‚Kl½Ü–ÍYÍs§ÉÞ!\æÓT2½?Ï!§”{D Ë`Jw®h•bg+Œxf£q^!ßaŒ¢:2a&2Þxh»N¢f;åoh¦¶RfÛ3ùÎU—À˜ÜW¼î&lÇvƒwý0´11‘ÙÅ _‡É·ÅÑ?{‚à”û•Ò†`tk­D@`dÿÁF2.„µÀoØC€C>Ø~[Ãl"AÇö¢¼1U+"_®ý»¹‚’¿Í@¨†I™Ð£#ưFüŠž|ÛÀd†â‰©Þ)x»ëš]óœi¸üM)$󼺰^Èrán"6ïRgÿ_ ¦")þUK¢›µ%\Ÿ/çÙpx¦2>=DÓÀˆÑìF%‡úœ€8VêчˆäŠy†®;8†‰\Ü;2§mö è™Ü2di–áÇUÂyAú>ÿî[S–”;MwA tŽÝr­›¼Jõ®šÂA{+ƒìÀrxƒ…Ö½[øÒ¾ýÝÆ¡þÞòCòA—^Œ ÏÔë4¦º}vë¹òQ‰%á0°Óô‘;øÃ£‘6_/PîG­!"­ëÁ“¼Y Ÿ‚9éD¼ËzfÕájV ÓÐqÍÕ‰šê8!KW$–ðg†thK&ÂŽWõViÏKă –w[7ÃÊK å½¹·³”[‘¿¹XK…_Ý—Úø^¬¡rÛ}¥¾ƒ·9wzæŒÇ“È€Ïæv¨•’†7@0—=<¿PæàN^8Ìp_q˜/$ÕV¸Æ"œèX÷›Ë’Ø1çðÕteäÜs»VÇ`a \ÊZ1~Ó‰eâƒwúCÒ$>\]A)ñP˜iª\îûØq»šX¸±þÉŽfÀGÞ§¸réÈååEGWàdÌß¾MÃKẆYoþ>–‘ÿiW E#*“$Êðòjv0F0þZ.ÝM\´K5Ñ«`’^†Ä¿B®XF½Î™I^*:­Iw–2@C-&ÛåÉF*û4)6J݉H]*?Å–ýJ›RIÀ"¢(Z^Ïz1> n,ÿOÝþB¸„ãVy|ÔH¶ƒ%­ÜÈátƒ¦ï>л2i]êªó[‡ƒu¶íäÅÞ­Bú †:s+= ¨WõµÑYj: ÓDßQ’Ev,5[³aýæÙ4Ä4.¬6@¹ÎuO[sí³nt‹ÐAÜØ?dvÖ³¼"qñu¾¯ÎÉòf hPEáö˜ðÊï.8ûsã:þ¶!˰Ð%Oq#¶å¯¼^y··ÕDÃ^¤À³ˆ ,4sél;TÖFdÍÇXv¤G†¹Z‰¹Ú.“Î\6áBóOŸ-Ì˧{YsÆçóÎXk*˜èý‹ŸÐ±!´‘,@ò k:}Ç?#šPÀ)ò–Î+.ˆºôu÷£±(¯G7Àf§„‰7È#”U/öM&Ò¦÷”5„§Üü é_“P‘×^Ë2QµMêÕÀá,+¤ÉÐû:¹Ö5±â ]¹@¨øîPúÇá%K³hFžà`_é¸ÌiÊ]¤“švgªÇ¹3QÃ5Íߟ„Õòìs2 Ïá´À›ª@6;uI'v golang-go4-0.0~git20230225.2148625/media/heif/testdata/rotate.heic000066400000000000000000000100001445700726600236200ustar00rootroot00000000000000ftypheicmif1heic»meta"hdlrpict$dinfdref url pitm1fiinf4infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infe hvc1infe hvc1infe hvc1infe hvc1infe hvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infehvc1infe hvc1infe!hvc1infe"hvc1infe#hvc1infe$hvc1infe%hvc1infe&hvc1infe'hvc1infe(hvc1infe)hvc1infe*hvc1infe+hvc1infe,hvc1infe-hvc1infe.hvc1infe/hvc1infe0hvc1infe1gridinfe2hvc1infe3Exif)infe4mimeapplication/rdf+xml¢irefldimg10  !"#$%&'()*+,-./0thmb21cdsc31cdsc41óiprp­ipco0colrprof$applmntrRGB XYZ á  acspAPPLAPPLöÖÓ-applÊ•‚%M8™ÕÑê‚ descüecprtd#wtptˆrXYZœgXYZ°bXYZÄrTRCØ chadø,bTRCØ gTRCØ desc Display P3textCopyright Apple Inc., 2017XYZ óQÌXYZ ƒß=¿ÿÿÿ»XYZ J¿±7 ¹XYZ (8 ȹparaffò§ YÐ [sf32 BÞÿÿó&“ýÿÿû¢ÿÿý£ÜÀnphvcCp°Zðüýøø  @ ÿÿp°Zp$¡"Bp°Z €X‡¹U7¤¢ DÀarÈ@S$ispeispeÀ Ð irotpixi0colrprof$applmntrRGB XYZ á  acspAPPLAPPLöÖÓ-applÊ•‚%M8™ÕÑê‚ descüecprtd#wtptˆrXYZœgXYZ°bXYZÄrTRCØ chadø,bTRCØ gTRCØ desc Display P3textCopyright Apple Inc., 2017XYZ óQÌXYZ ƒß=¿ÿÿÿ»XYZ J¿±7 ¹XYZ (8 ȹparaffò§ YÐ [sf32 BÞÿÿó&“ýÿÿû¢ÿÿý£ÜÀnphvcCp°<ðüýøø  @ ÿÿp°ipma2ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ ƒ‚ ƒ‚ ƒ‚ ƒ‚ ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ƒ‚ ƒ‚!ƒ‚"ƒ‚#ƒ‚$ƒ‚%ƒ‚&ƒ‚'ƒ‚(ƒ‚)ƒ‚*ƒ‚+ƒ‚,ƒ‚-ƒ‚.ƒ‚/ƒ‚0ƒ‚1…2‡ˆ … idatÀ ÐPilocD4UFã›òBcÞUW÷6LNÓ…XÝ!mJ#\¦¢hÐ rFÆ V8jš ÀÒnY /+•) ÄTŠðODdì´0‚¾6îq9¨'M<õc`@U£ˆÄÞg‰zgážð Ña† hW°  bzO ’±Kn ÞTš 2¹iŸ œXŽ +檱 Ö—{§ R>·˜  Öi!rè(3"›^#ùqô$kŠÎ%õÝnÕ&d²p˜'ÕJ–«(kõt|)àq5*÷¦=+4¬Oü,„¨[ø-à O\./ükÒ/›ÎnW0 %q12ã5–3Eyô4Mm¢mdatkÓ5’(¯ˆ«ï? ²ðm6½Ð*޳F}Ñ™¿Ÿgolang-go4-0.0~git20230225.2148625/must/000077500000000000000000000000001445700726600166665ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/must/must.go000066400000000000000000000020151445700726600202030ustar00rootroot00000000000000/* Copyright 2019 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package must contains helpers that panic on failure. package must // import "go4.org/must" import "io" // Close calls c.Close and panics if it returns an error. // The panic value is the return value from Close. func Close(c io.Closer) { if err := c.Close(); err != nil { panic(err) } } // Do runs fn and panics if it returns an error. // The panic value is the return value from fn. func Do(fn func() error) { if err := fn(); err != nil { panic(err) } } golang-go4-0.0~git20230225.2148625/net/000077500000000000000000000000001445700726600164645ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/net/throttle/000077500000000000000000000000001445700726600203315ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/net/throttle/throttle.go000066400000000000000000000054211445700726600225270ustar00rootroot00000000000000/* Copyright 2012 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package throttle provides a net.Listener that returns // artificially-delayed connections for testing real-world // connectivity. package throttle // import "go4.org/net/throttle" import ( "fmt" "net" "sync" "time" ) const unitSize = 1400 // read/write chunk size. ~MTU size. type Rate struct { KBps int // or 0, to not rate-limit bandwidth Latency time.Duration } // byteTime returns the time required for n bytes. func (r Rate) byteTime(n int) time.Duration { if r.KBps == 0 { return 0 } return time.Duration(float64(n)/1024/float64(r.KBps)) * time.Second } type Listener struct { net.Listener Down Rate // server Writes to Client Up Rate // server Reads from client } func (ln *Listener) Accept() (net.Conn, error) { c, err := ln.Listener.Accept() time.Sleep(ln.Up.Latency) if err != nil { return nil, err } tc := &conn{Conn: c, Down: ln.Down, Up: ln.Up} tc.start() return tc, nil } type nErr struct { n int err error } type writeReq struct { writeAt time.Time p []byte resc chan nErr } type conn struct { net.Conn Down Rate // for reads Up Rate // for writes wchan chan writeReq closeOnce sync.Once closeErr error } func (c *conn) start() { c.wchan = make(chan writeReq, 1024) go c.writeLoop() } func (c *conn) writeLoop() { for req := range c.wchan { time.Sleep(req.writeAt.Sub(time.Now())) var res nErr for len(req.p) > 0 && res.err == nil { writep := req.p if len(writep) > unitSize { writep = writep[:unitSize] } n, err := c.Conn.Write(writep) time.Sleep(c.Up.byteTime(len(writep))) res.n += n res.err = err req.p = req.p[n:] } req.resc <- res } } func (c *conn) Close() error { c.closeOnce.Do(func() { err := c.Conn.Close() close(c.wchan) c.closeErr = err }) return c.closeErr } func (c *conn) Write(p []byte) (n int, err error) { defer func() { if e := recover(); e != nil { n = 0 err = fmt.Errorf("%v", err) return } }() resc := make(chan nErr, 1) c.wchan <- writeReq{time.Now().Add(c.Up.Latency), p, resc} res := <-resc return res.n, res.err } func (c *conn) Read(p []byte) (n int, err error) { const max = 1024 if len(p) > max { p = p[:max] } n, err = c.Conn.Read(p) time.Sleep(c.Down.byteTime(n)) return } golang-go4-0.0~git20230225.2148625/oauthutil/000077500000000000000000000000001445700726600177145ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/oauthutil/oauth.go000066400000000000000000000100511445700726600213600ustar00rootroot00000000000000/* Copyright 2015 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package oauthutil contains OAuth 2 related utilities. package oauthutil // import "go4.org/oauthutil" import ( "encoding/json" "errors" "fmt" "time" "go4.org/wkfs" "golang.org/x/oauth2" ) // TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization // code should be returned in the title bar of the browser, with the page text // prompting the user to copy the code and paste it in the application. const TitleBarRedirectURL = "urn:ietf:wg:oauth:2.0:oob" // ErrNoAuthCode is returned when Token() has not found any valid cached token // and TokenSource does not have an AuthCode for getting a new token. var ErrNoAuthCode = errors.New("oauthutil: unspecified TokenSource.AuthCode") // TokenSource is an implementation of oauth2.TokenSource. It uses CacheFile to store and // reuse the the acquired token, and AuthCode to provide the authorization code that will be // exchanged for a token otherwise. type TokenSource struct { Config *oauth2.Config // CacheFile is where the token will be stored JSON-encoded. Any call to Token // first tries to read a valid token from CacheFile. CacheFile string // AuthCode provides the authorization code that Token will exchange for a token. // It usually is a way to prompt the user for the code. If CacheFile does not provide // a token and AuthCode is nil, Token returns ErrNoAuthCode. AuthCode func() string } var errExpiredToken = errors.New("expired token") // cachedToken returns the token saved in cacheFile. It specifically returns // errTokenExpired if the token is expired. func cachedToken(cacheFile string) (*oauth2.Token, error) { tok := new(oauth2.Token) tokenData, err := wkfs.ReadFile(cacheFile) if err != nil { return nil, err } if err = json.Unmarshal(tokenData, tok); err != nil { return nil, err } if !tok.Valid() { if tok != nil && time.Now().After(tok.Expiry) { return nil, errExpiredToken } return nil, errors.New("invalid token") } return tok, nil } // Token first tries to find a valid token in CacheFile, and otherwise uses // Config and AuthCode to fetch a new token. This new token is saved in CacheFile // (if not blank). If CacheFile did not provide a token and AuthCode is nil, // ErrNoAuthCode is returned. func (src TokenSource) Token() (*oauth2.Token, error) { var tok *oauth2.Token var err error if src.CacheFile != "" { tok, err = cachedToken(src.CacheFile) if err == nil { return tok, nil } if err != errExpiredToken { fmt.Printf("Error getting token from %s: %v\n", src.CacheFile, err) } } if src.AuthCode == nil { return nil, ErrNoAuthCode } tok, err = src.Config.Exchange(oauth2.NoContext, src.AuthCode()) if err != nil { return nil, fmt.Errorf("could not exchange auth code for a token: %v", err) } if src.CacheFile == "" { return tok, nil } tokenData, err := json.Marshal(&tok) if err != nil { return nil, fmt.Errorf("could not encode token as json: %v", err) } if err := wkfs.WriteFile(src.CacheFile, tokenData, 0600); err != nil { return nil, fmt.Errorf("could not cache token in %v: %v", src.CacheFile, err) } return tok, nil } // NewRefreshTokenSource returns a token source that obtains its initial token // based on the provided config and the refresh token. func NewRefreshTokenSource(config *oauth2.Config, refreshToken string) oauth2.TokenSource { var noInitialToken *oauth2.Token = nil return oauth2.ReuseTokenSource(noInitialToken, config.TokenSource( oauth2.NoContext, // TODO: maybe accept a context later. &oauth2.Token{RefreshToken: refreshToken}, )) } golang-go4-0.0~git20230225.2148625/osutil/000077500000000000000000000000001445700726600172155ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/osutil/exec_plan9.go000066400000000000000000000016221445700726600215740ustar00rootroot00000000000000// Copyright 2015 The go4 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build plan9 package osutil import ( "fmt" "os" "path/filepath" "syscall" ) func executable() (string, error) { fn := fmt.Sprintf("/proc/%d/text", os.Getpid()) f, err := os.Open(fn) if err != nil { return "", err } defer f.Close() p, err := syscall.Fd2path(int(f.Fd())) return filepath.Clean(p), err } golang-go4-0.0~git20230225.2148625/osutil/exec_procfs.go000066400000000000000000000021531445700726600220450ustar00rootroot00000000000000// Copyright 2015 The go4 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build linux netbsd openbsd dragonfly nacl package osutil import ( "errors" "os" "path/filepath" "runtime" ) func executable() (string, error) { var procfn string switch runtime.GOOS { default: return "", errors.New("Executable not implemented for " + runtime.GOOS) case "linux": procfn = "/proc/self/exe" case "netbsd": procfn = "/proc/curproc/exe" case "openbsd": procfn = "/proc/curproc/file" case "dragonfly": procfn = "/proc/curproc/file" } p, err := os.Readlink(procfn) return filepath.Clean(p), err } golang-go4-0.0~git20230225.2148625/osutil/exec_solaris_amd64.go000066400000000000000000000032221445700726600232160ustar00rootroot00000000000000// Copyright 2015 The go4 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build amd64,solaris package osutil import ( "os" "syscall" "unsafe" ) //go:cgo_import_dynamic libc_getexecname getexecname "libc.so" //go:linkname libc_getexecname libc_getexecname var libc_getexecname uintptr func getexecname() (path unsafe.Pointer, err error) { r0, _, e1 := syscall.Syscall6(uintptr(unsafe.Pointer(&libc_getexecname)), 0, 0, 0, 0, 0, 0) path = unsafe.Pointer(r0) if e1 != 0 { err = syscall.Errno(e1) } return } func syscallGetexecname() (path string, err error) { ptr, err := getexecname() if err != nil { return "", err } bytes := (*[1 << 29]byte)(ptr)[:] for i, b := range bytes { if b == 0 { return string(bytes[:i]), nil } } panic("unreachable") } var initCwd, initCwdErr = os.Getwd() func executable() (string, error) { path, err := syscallGetexecname() if err != nil { return path, err } if len(path) > 0 && path[0] != '/' { if initCwdErr != nil { return path, initCwdErr } if len(path) > 2 && path[0:2] == "./" { // skip "./" path = path[2:] } return initCwd + "/" + path, nil } return path, nil } golang-go4-0.0~git20230225.2148625/osutil/exec_sysctl.go000066400000000000000000000053431445700726600220760ustar00rootroot00000000000000// Copyright 2015 The go4 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // +build freebsd darwin package osutil import ( "bytes" "errors" "os" "path/filepath" "runtime" "syscall" "unsafe" ) var cacheWD, cacheWDErr = os.Getwd() func exec_darwin() (string, error) { mib := [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1} n := uintptr(0) // get length _, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) if err != 0 { return "", err } if n == 0 { // shouldn't happen return "", nil } buf := make([]byte, n) _, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) if err != 0 { return "", err } if n == 0 { // shouldn't happen return "", nil } // Because KERN_PROC_ARGS returns a list of NULL separated items, // and we want the first one. parts := bytes.Split(buf[:n-1], []byte{0}) if len(parts) < 2 { return "", nil } p := string(parts[0]) if !filepath.IsAbs(p) { if cacheWDErr != nil { return p, cacheWDErr } p = filepath.Join(cacheWD, filepath.Clean(p)) } return filepath.EvalSymlinks(p) } func exec_freebsd() (string, error) { mib := [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} n := uintptr(0) // get length _, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) if err != 0 { return "", err } if n == 0 { // shouldn't happen return "", nil } buf := make([]byte, n) _, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) if err != 0 { return "", err } if n == 0 { // shouldn't happen return "", nil } p := string(buf[:n-1]) if !filepath.IsAbs(p) { if cacheWDErr != nil { return p, cacheWDErr } p = filepath.Join(cacheWD, filepath.Clean(p)) } return filepath.EvalSymlinks(p) } func executable() (string, error) { switch runtime.GOOS { case "freebsd": return exec_freebsd() case "darwin": return exec_darwin() } return "", errors.New("unsupported OS") } golang-go4-0.0~git20230225.2148625/osutil/exec_test.go000066400000000000000000000045671445700726600215430ustar00rootroot00000000000000// Copyright 2015 The go4 Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package osutil import ( "fmt" "os" osexec "os/exec" "path/filepath" "runtime" "testing" ) const executable_EnvVar = "OSTEST_OUTPUT_EXECPATH" func TestExecutable(t *testing.T) { if runtime.GOOS == "nacl" { t.Skip() } ep, err := Executable() if err != nil { switch goos := runtime.GOOS; goos { case "openbsd": // procfs is not mounted by default t.Skipf("Executable failed on %s: %v, expected", goos, err) } t.Fatalf("Executable failed: %v", err) } // we want fn to be of the form "dir/prog" dir := filepath.Dir(filepath.Dir(ep)) fn, err := filepath.Rel(dir, ep) if err != nil { t.Fatalf("filepath.Rel: %v", err) } cmd := &osexec.Cmd{} // make child start with a relative program path cmd.Dir = dir cmd.Path = fn // forge argv[0] for child, so that we can verify we could correctly // get real path of the executable without influenced by argv[0]. cmd.Args = []string{"-", "-test.run=XXXX"} cmd.Env = []string{fmt.Sprintf("%s=1", executable_EnvVar)} out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("exec(self) failed: %v", err) } outs := string(out) if !filepath.IsAbs(outs) { t.Fatalf("Child returned %q, want an absolute path", out) } if !sameFile(outs, ep) { t.Fatalf("Child returned %q, not the same file as %q", out, ep) } } func sameFile(fn1, fn2 string) bool { fi1, err := os.Stat(fn1) if err != nil { return false } fi2, err := os.Stat(fn2) if err != nil { return false } return os.SameFile(fi1, fi2) } func init() { if e := os.Getenv(executable_EnvVar); e != "" { // first chdir to another path dir := "/" if runtime.GOOS == "windows" { dir = filepath.VolumeName(".") } os.Chdir(dir) if ep, err := Executable(); err != nil { fmt.Fprint(os.Stderr, "ERROR: ", err) } else { fmt.Fprint(os.Stderr, ep) } os.Exit(0) } } golang-go4-0.0~git20230225.2148625/osutil/exec_windows.go000066400000000000000000000030361445700726600222440ustar00rootroot00000000000000/* Copyright 2015 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package osutil import ( "path/filepath" "syscall" "unsafe" ) var ( modkernel32 = syscall.MustLoadDLL("kernel32.dll") procGetModuleFileNameW = modkernel32.MustFindProc("GetModuleFileNameW") ) func getModuleFileName(handle syscall.Handle) (string, error) { n := uint32(1024) var buf []uint16 for { buf = make([]uint16, n) r, err := syscallGetModuleFileName(handle, &buf[0], n) if err != nil { return "", err } if r < n { break } // r == n means n not big enough n += 1024 } return syscall.UTF16ToString(buf), nil } func executable() (string, error) { p, err := getModuleFileName(0) return filepath.Clean(p), err } func syscallGetModuleFileName(module syscall.Handle, fn *uint16, len uint32) (n uint32, err error) { r0, _, e1 := syscall.Syscall(procGetModuleFileNameW.Addr(), 3, uintptr(module), uintptr(unsafe.Pointer(fn)), uintptr(len)) n = uint32(r0) if n == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } golang-go4-0.0~git20230225.2148625/osutil/osutil.go000066400000000000000000000022221445700726600210610ustar00rootroot00000000000000/* Copyright 2015 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package osutil contains os level functions. package osutil // import "go4.org/osutil" // capture executable on package init to work around various os issues if // captured after executable has been renamed. var execPath, execError = executable() // Executable returns the path name for the executable that starts the // current process. The result is the path that was used to start the // current process, but there is no guarantee that the path is still // pointing to the correct executable. // // OpenBSD is currently unsupported. func Executable() (string, error) { return execPath, execError } golang-go4-0.0~git20230225.2148625/readerutil/000077500000000000000000000000001445700726600200365ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/readerutil/bufreaderat.go000066400000000000000000000023311445700726600226500ustar00rootroot00000000000000/* Copyright 2018 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import "io" // NewBufferingReaderAt returns an io.ReaderAt that reads from r as // necessary and keeps a copy of all data read in memory. func NewBufferingReaderAt(r io.Reader) io.ReaderAt { return &bufReaderAt{r: r} } type bufReaderAt struct { r io.Reader buf []byte } func (br *bufReaderAt) ReadAt(p []byte, off int64) (n int, err error) { endOff := off + int64(len(p)) need := endOff - int64(len(br.buf)) if need > 0 { buf := make([]byte, need) var rn int rn, err = io.ReadFull(br.r, buf) br.buf = append(br.buf, buf[:rn]...) } if int64(len(br.buf)) >= off { n = copy(p, br.buf[off:]) } if n == len(p) { err = nil } return } golang-go4-0.0~git20230225.2148625/readerutil/bufreaderat_test.go000066400000000000000000000036441445700726600237170ustar00rootroot00000000000000/* Copyright 2018 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import "testing" type trackingReader struct { off int reads int readBytes int } func (t *trackingReader) Read(p []byte) (n int, err error) { t.reads++ t.readBytes += len(p) for len(p) > 0 { p[0] = '0' + byte(t.off%10) t.off++ p = p[1:] n++ } return } func TestBufferingReaderAt(t *testing.T) { tr := new(trackingReader) ra := NewBufferingReaderAt(tr) for i, tt := range []struct { off int64 want string wantReads int wantReadBytes int }{ {off: 0, want: "0123456789", wantReads: 1, wantReadBytes: 10}, {off: 5, want: "56789", wantReads: 1, wantReadBytes: 10}, // already buffered {off: 6, want: "67890", wantReads: 2, wantReadBytes: 11}, // need 1 more byte {off: 0, want: "0123456789", wantReads: 2, wantReadBytes: 11}, // already buffered } { got := make([]byte, len(tt.want)) n, err := ra.ReadAt(got, tt.off) if err != nil || n != len(tt.want) { t.Errorf("step %d: ReadAt = %v, %v; want %v, %v", i, n, err, len(tt.want), nil) continue } if string(got) != tt.want { t.Errorf("step %d: ReadAt read %q; want %q", i, got, tt.want) } if tr.reads != tt.wantReads { t.Errorf("step %d: num reads = %d; want %d", i, tr.reads, tt.wantReads) } if tr.readBytes != tt.wantReadBytes { t.Errorf("step %d: read bytes = %d; want %d", i, tr.reads, tt.wantReads) } } } golang-go4-0.0~git20230225.2148625/readerutil/countingreader.go000066400000000000000000000015661445700726600234060ustar00rootroot00000000000000/* Copyright 2011 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import "io" // CountingReader wraps a Reader, incrementing N by the number of // bytes read. No locking is performed. type CountingReader struct { Reader io.Reader N *int64 } func (cr CountingReader) Read(p []byte) (n int, err error) { n, err = cr.Reader.Read(p) *cr.N += int64(n) return } golang-go4-0.0~git20230225.2148625/readerutil/fakeseeker.go000066400000000000000000000033571445700726600225020ustar00rootroot00000000000000/* Copyright 2014 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import ( "errors" "fmt" "io" "os" ) // fakeSeeker can seek to the ends but any read not at the current // position will fail. type fakeSeeker struct { r io.Reader size int64 fakePos int64 realPos int64 } // NewFakeSeeker returns a ReadSeeker that can pretend to Seek (based // on the provided total size of the reader's content), but any reads // will fail if the fake seek position doesn't match reality. func NewFakeSeeker(r io.Reader, size int64) io.ReadSeeker { return &fakeSeeker{r: r, size: size} } func (fs *fakeSeeker) Seek(offset int64, whence int) (int64, error) { var newo int64 switch whence { default: return 0, errors.New("invalid whence") case os.SEEK_SET: newo = offset case os.SEEK_CUR: newo = fs.fakePos + offset case os.SEEK_END: newo = fs.size + offset } if newo < 0 { return 0, errors.New("negative seek") } fs.fakePos = newo return newo, nil } func (fs *fakeSeeker) Read(p []byte) (n int, err error) { if fs.fakePos != fs.realPos { return 0, fmt.Errorf("attempt to read from fake seek offset %d; real offset is %d", fs.fakePos, fs.realPos) } n, err = fs.r.Read(p) fs.fakePos += int64(n) fs.realPos += int64(n) return } golang-go4-0.0~git20230225.2148625/readerutil/fakeseeker_test.go000066400000000000000000000035501445700726600235340ustar00rootroot00000000000000/* Copyright 2014 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import ( "os" "strings" "testing" ) func TestFakeSeeker(t *testing.T) { rs := NewFakeSeeker(strings.NewReader("foobar"), 6) if pos, err := rs.Seek(0, os.SEEK_END); err != nil || pos != 6 { t.Fatalf("SEEK_END = %d, %v; want 6, nil", pos, err) } if pos, err := rs.Seek(0, os.SEEK_CUR); err != nil || pos != 6 { t.Fatalf("SEEK_CUR = %d, %v; want 6, nil", pos, err) } if pos, err := rs.Seek(0, os.SEEK_SET); err != nil || pos != 0 { t.Fatalf("SEEK_SET = %d, %v; want 0, nil", pos, err) } buf := make([]byte, 3) if n, err := rs.Read(buf); n != 3 || err != nil || string(buf) != "foo" { t.Fatalf("First read = %d, %v (buf = %q); want foo", n, err, buf) } if pos, err := rs.Seek(0, os.SEEK_CUR); err != nil || pos != 3 { t.Fatalf("Seek cur pos after first read = %d, %v; want 3, nil", pos, err) } if n, err := rs.Read(buf); n != 3 || err != nil || string(buf) != "bar" { t.Fatalf("Second read = %d, %v (buf = %q); want foo", n, err, buf) } if pos, err := rs.Seek(1, os.SEEK_SET); err != nil || pos != 1 { t.Fatalf("SEEK_SET = %d, %v; want 1, nil", pos, err) } const msg = "attempt to read from fake seek offset" if _, err := rs.Read(buf); err == nil || !strings.Contains(err.Error(), msg) { t.Fatalf("bogus Read after seek = %v; want something containing %q", err, msg) } } golang-go4-0.0~git20230225.2148625/readerutil/multireaderat.go000066400000000000000000000037761445700726600232440ustar00rootroot00000000000000/* Copyright 2016 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import ( "io" "sort" ) // NewMultiReaderAt is like io.MultiReader but produces a ReaderAt // (and Size), instead of just a reader. func NewMultiReaderAt(parts ...SizeReaderAt) SizeReaderAt { m := &multiRA{ parts: make([]offsetAndSource, 0, len(parts)), } var off int64 for _, p := range parts { m.parts = append(m.parts, offsetAndSource{off, p}) off += p.Size() } m.size = off return m } type offsetAndSource struct { off int64 SizeReaderAt } type multiRA struct { parts []offsetAndSource size int64 } func (m *multiRA) Size() int64 { return m.size } func (m *multiRA) ReadAt(p []byte, off int64) (n int, err error) { wantN := len(p) // Skip past the requested offset. skipParts := sort.Search(len(m.parts), func(i int) bool { // This function returns whether parts[i] will // contribute any bytes to our output. part := m.parts[i] return part.off+part.Size() > off }) parts := m.parts[skipParts:] // How far to skip in the first part. needSkip := off if len(parts) > 0 { needSkip -= parts[0].off } for len(parts) > 0 && len(p) > 0 { readP := p partSize := parts[0].Size() if int64(len(readP)) > partSize-needSkip { readP = readP[:partSize-needSkip] } pn, err0 := parts[0].ReadAt(readP, needSkip) if err0 != nil { return n, err0 } n += pn p = p[pn:] if int64(pn)+needSkip == partSize { parts = parts[1:] } needSkip = 0 } if n != wantN { err = io.ErrUnexpectedEOF } return } golang-go4-0.0~git20230225.2148625/readerutil/multireaderat_test.go000066400000000000000000000025021445700726600242650ustar00rootroot00000000000000/* Copyright 2016 The Go4 Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import ( "io" "io/ioutil" "strings" "testing" ) func TestMultiReaderAt(t *testing.T) { sra := NewMultiReaderAt( io.NewSectionReader(strings.NewReader("xaaax"), 1, 3), io.NewSectionReader(strings.NewReader("xxbbbbxx"), 2, 3), io.NewSectionReader(strings.NewReader("cccx"), 0, 3), ) if sra.Size() != 9 { t.Fatalf("Size = %d; want 9", sra.Size()) } const full = "aaabbbccc" for start := 0; start < len(full); start++ { for end := start; end < len(full); end++ { want := full[start:end] got, err := ioutil.ReadAll(io.NewSectionReader(sra, int64(start), int64(end-start))) if err != nil { t.Fatal(err) } if string(got) != want { t.Errorf("for start=%d, end=%d: ReadAll = %q; want %q", start, end, got, want) } } } } golang-go4-0.0~git20230225.2148625/readerutil/readersize.go000066400000000000000000000026641445700726600225320ustar00rootroot00000000000000/* Copyright 2012 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package readerutil provides and operates on io.Readers. package readerutil // import "go4.org/readerutil" import ( "bytes" "io" "os" "strings" ) // Size tries to determine the length of r. If r is an io.Seeker, Size may seek // to guess the length. func Size(r io.Reader) (size int64, ok bool) { switch rt := r.(type) { case *bytes.Buffer: return int64(rt.Len()), true case *bytes.Reader: return int64(rt.Len()), true case *strings.Reader: return int64(rt.Len()), true case io.Seeker: pos, err := rt.Seek(0, os.SEEK_CUR) if err != nil { return } end, err := rt.Seek(0, os.SEEK_END) if err != nil { return } size = end - pos pos1, err := rt.Seek(pos, os.SEEK_SET) if err != nil || pos1 != pos { msg := "failed to restore seek position" if err != nil { msg += ": " + err.Error() } panic(msg) } return size, true } return 0, false } golang-go4-0.0~git20230225.2148625/readerutil/readersize_test.go000066400000000000000000000026421445700726600235650ustar00rootroot00000000000000/* Copyright 2012 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import ( "bytes" "io" "io/ioutil" "os" "testing" ) const text = "HelloWorld" type testSrc struct { name string src io.Reader want int64 } func (tsrc *testSrc) run(t *testing.T) { n, ok := Size(tsrc.src) if !ok { t.Fatalf("failed to read size for %q", tsrc.name) } if n != tsrc.want { t.Fatalf("wanted %v, got %v", tsrc.want, n) } } func TestBytesBuffer(t *testing.T) { buf := bytes.NewBuffer([]byte(text)) tsrc := &testSrc{"buffer", buf, int64(len(text))} tsrc.run(t) } func TestSeeker(t *testing.T) { f, err := ioutil.TempFile("", "camliTestReaderSize") if err != nil { t.Fatal(err) } defer os.Remove(f.Name()) defer f.Close() size, err := f.Write([]byte(text)) if err != nil { t.Fatal(err) } pos, err := f.Seek(5, 0) if err != nil { t.Fatal(err) } tsrc := &testSrc{"seeker", f, int64(size) - pos} tsrc.run(t) } golang-go4-0.0~git20230225.2148625/readerutil/readerutil.go000066400000000000000000000037421445700726600225330ustar00rootroot00000000000000/* Copyright 2016 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package readerutil contains io.Reader types. package readerutil // import "go4.org/readerutil" import ( "expvar" "io" ) // A SizeReaderAt is a ReaderAt with a Size method. // // An io.SectionReader implements SizeReaderAt. type SizeReaderAt interface { Size() int64 io.ReaderAt } // A ReadSeekCloser can Read, Seek, and Close. type ReadSeekCloser interface { io.Reader io.Seeker io.Closer } type ReaderAtCloser interface { io.ReaderAt io.Closer } // TODO(wathiede): make sure all the stat readers work with code that // type asserts ReadFrom/WriteTo. type varStatReader struct { *expvar.Int r io.Reader } // NewStatsReader returns an io.Reader that will have the number of bytes // read from r added to v. func NewStatsReader(v *expvar.Int, r io.Reader) io.Reader { return &varStatReader{v, r} } func (v *varStatReader) Read(p []byte) (int, error) { n, err := v.r.Read(p) v.Int.Add(int64(n)) return n, err } type varStatReadSeeker struct { *expvar.Int rs io.ReadSeeker } // NewStatsReadSeeker returns an io.ReadSeeker that will have the number of bytes // read from rs added to v. func NewStatsReadSeeker(v *expvar.Int, rs io.ReadSeeker) io.ReadSeeker { return &varStatReadSeeker{v, rs} } func (v *varStatReadSeeker) Read(p []byte) (int, error) { n, err := v.rs.Read(p) v.Int.Add(int64(n)) return n, err } func (v *varStatReadSeeker) Seek(offset int64, whence int) (int64, error) { return v.rs.Seek(offset, whence) } golang-go4-0.0~git20230225.2148625/readerutil/readerutil_test.go000066400000000000000000000017641445700726600235740ustar00rootroot00000000000000/* Copyright 2016 The Go4 Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package readerutil import ( "expvar" "fmt" "io" "io/ioutil" "strings" ) func ExampleNewStatsReader() { var ( // r is the io.Reader we'd like to count read from. r = strings.NewReader("Hello world") v = expvar.NewInt("read-bytes") sw = NewStatsReader(v, r) ) // Read from the wrapped io.Reader, StatReader will count the bytes. io.Copy(ioutil.Discard, sw) fmt.Printf("Read %s bytes\n", v.String()) // Output: Read 11 bytes } golang-go4-0.0~git20230225.2148625/readerutil/singlereader/000077500000000000000000000000001445700726600225025ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/readerutil/singlereader/opener.go000066400000000000000000000050621445700726600243240ustar00rootroot00000000000000/* Copyright 2013 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // package singlereader provides Open and Close operations, reusing existing // file descriptors when possible. package singlereader // import "go4.org/readerutil/singlereader" import ( "sync" "go4.org/readerutil" "go4.org/syncutil/singleflight" "go4.org/wkfs" ) var ( openerGroup singleflight.Group openFileMu sync.Mutex // guards openFiles openFiles = make(map[string]*openFile) ) type openFile struct { wkfs.File path string // map key of openFiles refCount int } type openFileHandle struct { closed bool *openFile } func (f *openFileHandle) Close() error { openFileMu.Lock() if f.closed { openFileMu.Unlock() return nil } f.closed = true f.refCount-- if f.refCount < 0 { panic("unexpected negative refcount") } zero := f.refCount == 0 if zero { delete(openFiles, f.path) } openFileMu.Unlock() if !zero { return nil } return f.openFile.File.Close() } // Open opens the given file path for reading, reusing existing file descriptors // when possible. func Open(path string) (readerutil.ReaderAtCloser, error) { openFileMu.Lock() of := openFiles[path] if of != nil { of.refCount++ openFileMu.Unlock() return &openFileHandle{false, of}, nil } openFileMu.Unlock() // release the lock while we call os.Open winner := false // this goroutine made it into Do's func // Returns an *openFile resi, err := openerGroup.Do(path, func() (interface{}, error) { winner = true f, err := wkfs.Open(path) if err != nil { return nil, err } of := &openFile{ File: f, path: path, refCount: 1, } openFileMu.Lock() openFiles[path] = of openFileMu.Unlock() return of, nil }) if err != nil { return nil, err } of = resi.(*openFile) // If our os.Open was dup-suppressed, we have to increment our // reference count. if !winner { openFileMu.Lock() if of.refCount == 0 { // Winner already closed it. Try again (rare). openFileMu.Unlock() return Open(path) } of.refCount++ openFileMu.Unlock() } return &openFileHandle{false, of}, nil } golang-go4-0.0~git20230225.2148625/readerutil/singlereader/opener_test.go000066400000000000000000000032341445700726600253620ustar00rootroot00000000000000/* Copyright 2013 The Go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package singlereader import ( "bytes" "fmt" "io/ioutil" "os" "runtime" "testing" ) func TestOpenSingle(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) f, err := ioutil.TempFile("", "foo") if err != nil { t.Fatal(err) } defer os.Remove(f.Name()) contents := []byte("Some file contents") if _, err := f.Write(contents); err != nil { t.Fatal(err) } f.Close() const j = 4 errc := make(chan error, j) for i := 1; i < j; i++ { go func() { buf := make([]byte, len(contents)) for i := 0; i < 400; i++ { rac, err := Open(f.Name()) if err != nil { errc <- err return } n, err := rac.ReadAt(buf, 0) if err != nil { errc <- err return } if n != len(contents) || !bytes.Equal(buf, contents) { errc <- fmt.Errorf("read %d, %q; want %d, %q", n, buf, len(contents), contents) return } if err := rac.Close(); err != nil { errc <- err return } } errc <- nil }() } for i := 1; i < j; i++ { if err := <-errc; err != nil { t.Error(err) } } } golang-go4-0.0~git20230225.2148625/reflectutil/000077500000000000000000000000001445700726600202205ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/reflectutil/asm_b.s000066400000000000000000000010131445700726600214600ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.8 // +build go1.6 // +build arm #include "textflag.h" #include "funcdata.h" // func typedmemmove(reflect_rtype, src unsafe.Pointer, size uintptr) TEXT ·typedmemmove(SB),(NOSPLIT|WRAPPER),$0-24 B runtime·typedmemmove(SB) // func memmove(dst, src unsafe.Pointer, size uintptr) TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24 B runtime·memmove(SB) golang-go4-0.0~git20230225.2148625/reflectutil/asm_b_14.s000066400000000000000000000005471445700726600217770ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.5 // +build arm #include "textflag.h" #include "funcdata.h" // func memmove(dst, src unsafe.Pointer, size uintptr) TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24 B runtime·memmove(SB) golang-go4-0.0~git20230225.2148625/reflectutil/asm_jmp.s000066400000000000000000000010521445700726600220300ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.8 // +build go1.5,!js,!safe,!appengine // +build amd64 386 #include "textflag.h" #include "funcdata.h" // func typedmemmove(reflect_rtype, src unsafe.Pointer, size uintptr) TEXT ·typedmemmove(SB),(NOSPLIT|WRAPPER),$0-24 JMP runtime·typedmemmove(SB) // func memmove(dst, src unsafe.Pointer, size uintptr) TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24 JMP runtime·memmove(SB) golang-go4-0.0~git20230225.2148625/reflectutil/asm_jmp_14.s000066400000000000000000000006041445700726600223360ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.5,!js,!safe,!appengine // +build amd64 386 #include "textflag.h" #include "funcdata.h" // func memmove(dst, src unsafe.Pointer, size uintptr) TEXT ·memmove(SB),(NOSPLIT|WRAPPER),$0-24 JMP runtime·memmove(SB) golang-go4-0.0~git20230225.2148625/reflectutil/reflectutil.go000066400000000000000000000020311445700726600230650ustar00rootroot00000000000000// Copyright 2016 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 file. // Package reflectutil contains reflect utilities. package reflectutil import "reflect" // hasPointers reports whether the given type contains any pointers, // including any internal pointers in slices, funcs, maps, channels, // etc. // // This function exists for Swapper's internal use, instead of reaching // into the runtime's *reflect._rtype kind&kindNoPointers flag. func hasPointers(t reflect.Type) bool { if t == nil { panic("nil Type") } k := t.Kind() if k <= reflect.Complex128 { return false } switch k { default: // chan, func, interface, map, ptr, slice, string, unsafepointer // And anything else. It's safer to err on the side of true. return true case reflect.Array: return hasPointers(t.Elem()) case reflect.Struct: num := t.NumField() for i := 0; i < num; i++ { if hasPointers(t.Field(i).Type) { return true } } return false } } golang-go4-0.0~git20230225.2148625/reflectutil/reflectutil_test.go000066400000000000000000000024351445700726600241340ustar00rootroot00000000000000// Copyright 2016 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 file. package reflectutil import ( "reflect" "testing" ) func TestHasPointers(t *testing.T) { tests := []struct { val interface{} want bool }{ {false, false}, {int(1), false}, {int8(1), false}, {int16(1), false}, {int32(1), false}, {int64(1), false}, {uint(1), false}, {uint8(1), false}, {uint16(1), false}, {uint32(1), false}, {uint64(1), false}, {uintptr(1), false}, {float32(1.0), false}, {float64(1.0), false}, {complex64(1.0i), false}, {complex128(1.0i), false}, {[...]int{1, 2}, false}, {[...]*int{nil, nil}, true}, {make(chan bool), true}, {TestHasPointers, true}, {map[string]string{"foo": "bar"}, true}, {new(int), true}, {[]int{1, 2}, true}, {"foo", true}, {struct{}{}, false}, {struct{ int }{0}, false}, {struct { a int b bool }{0, false}, false}, {struct { a int b string }{0, ""}, true}, {struct{ *int }{nil}, true}, {struct { a *int b int }{nil, 0}, true}, } for i, tt := range tests { got := hasPointers(reflect.TypeOf(tt.val)) if got != tt.want { t.Errorf("%d. hasPointers(%T) = %v; want %v", i, tt.val, got, tt.want) } } } golang-go4-0.0~git20230225.2148625/reflectutil/swapper.go000066400000000000000000000014221445700726600222270ustar00rootroot00000000000000// Copyright 2016 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 file. package reflectutil import "reflect" // Swapper returns a function which swaps the elements in slice. // Swapper panics if the provided interface is not a slice. // // Its goal is to work safely and efficiently for all versions and // variants of Go: pre-Go1.5, Go1.5+, safe, unsafe, App Engine, // GopherJS, etc. // // Deprecated: this moved to the Go standard library. Use // reflect.Swapper in Go 1.8+ instead. func Swapper(slice interface{}) func(i, j int) { v := reflect.ValueOf(slice) if v.Kind() != reflect.Slice { panic(&reflect.ValueError{Method: "reflectutil.Swapper", Kind: v.Kind()}) } return swapper(v) } golang-go4-0.0~git20230225.2148625/reflectutil/swapper_safe.go000066400000000000000000000007751445700726600232370ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.8 // +build js appengine safe ppc64 ppc64le arm64 mips mipsle mips64 mips64le package reflectutil import "reflect" func swapper(slice reflect.Value) func(i, j int) { tmp := reflect.New(slice.Type().Elem()).Elem() return func(i, j int) { v1 := slice.Index(i) v2 := slice.Index(j) tmp.Set(v1) v1.Set(v2) v2.Set(tmp) } } golang-go4-0.0~git20230225.2148625/reflectutil/swapper_std.go000066400000000000000000000004701445700726600231030ustar00rootroot00000000000000// Copyright 2018 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 file. // +build go1.8 package reflectutil import "reflect" func swapper(slice reflect.Value) func(i, j int) { return reflect.Swapper(slice.Interface()) } golang-go4-0.0~git20230225.2148625/reflectutil/swapper_test.go000066400000000000000000000036061445700726600232740ustar00rootroot00000000000000// Copyright 2016 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 file. package reflectutil import ( "fmt" "reflect" "strconv" "testing" ) func TestSwapper(t *testing.T) { type I int var a, b, c I type pair struct { x, y int } type pairPtr struct { x, y int p *I } type S string tests := []struct { in interface{} i, j int want interface{} }{ { in: []int{1, 20, 300}, i: 0, j: 2, want: []int{300, 20, 1}, }, { in: []uintptr{1, 20, 300}, i: 0, j: 2, want: []uintptr{300, 20, 1}, }, { in: []int16{1, 20, 300}, i: 0, j: 2, want: []int16{300, 20, 1}, }, { in: []int8{1, 20, 100}, i: 0, j: 2, want: []int8{100, 20, 1}, }, { in: []*I{&a, &b, &c}, i: 0, j: 2, want: []*I{&c, &b, &a}, }, { in: []string{"eric", "sergey", "larry"}, i: 0, j: 2, want: []string{"larry", "sergey", "eric"}, }, { in: []S{"eric", "sergey", "larry"}, i: 0, j: 2, want: []S{"larry", "sergey", "eric"}, }, { in: []pair{{1, 2}, {3, 4}, {5, 6}}, i: 0, j: 2, want: []pair{{5, 6}, {3, 4}, {1, 2}}, }, { in: []pairPtr{{1, 2, &a}, {3, 4, &b}, {5, 6, &c}}, i: 0, j: 2, want: []pairPtr{{5, 6, &c}, {3, 4, &b}, {1, 2, &a}}, }, } for i, tt := range tests { inStr := fmt.Sprint(tt.in) Swapper(tt.in)(tt.i, tt.j) if !reflect.DeepEqual(tt.in, tt.want) { t.Errorf("%d. swapping %v and %v of %v = %v; want %v", i, tt.i, tt.j, inStr, tt.in, tt.want) } } } func BenchmarkSwap(b *testing.B) { const N = 1024 strs := make([]string, N) for i := range strs { strs[i] = strconv.Itoa(i) } swap := Swapper(strs) b.ResetTimer() i, j := 0, 1 for n := 0; n < b.N; n++ { i = (i + 1) % N j = (j + 2) % N swap(i, j) } } golang-go4-0.0~git20230225.2148625/reflectutil/swapper_unsafe.go000066400000000000000000000055231445700726600235760ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.8 // +build !ppc64,!ppc64le,!arm64,!mips,!mipsle,!mips64,!mips64le // +build !js,!appengine,!safe package reflectutil import ( "reflect" "unsafe" ) const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const // arrayAt returns the i-th element of p, a C-array whose elements are // eltSize wide (in bytes). func arrayAt(p unsafe.Pointer, i int, eltSize uintptr) unsafe.Pointer { return unsafe.Pointer(uintptr(p) + uintptr(i)*eltSize) } type sliceHeader struct { Data unsafe.Pointer Len int Cap int } func swapper(v reflect.Value) func(i, j int) { maxLen := uint(v.Len()) s := sliceHeader{unsafe.Pointer(v.Pointer()), int(maxLen), int(maxLen)} tt := v.Type() elemt := tt.Elem() typ := unsafe.Pointer(reflect.ValueOf(elemt).Pointer()) size := elemt.Size() hasPtr := hasPointers(elemt) // Some common & small cases, without using memmove: if hasPtr { if size == ptrSize { var ps []unsafe.Pointer *(*sliceHeader)(unsafe.Pointer(&ps)) = s return func(i, j int) { ps[i], ps[j] = ps[j], ps[i] } } if elemt.Kind() == reflect.String { var ss []string *(*sliceHeader)(unsafe.Pointer(&ss)) = s return func(i, j int) { ss[i], ss[j] = ss[j], ss[i] } } } else { switch size { case 8: var is []int64 *(*sliceHeader)(unsafe.Pointer(&is)) = s return func(i, j int) { is[i], is[j] = is[j], is[i] } case 4: var is []int32 *(*sliceHeader)(unsafe.Pointer(&is)) = s return func(i, j int) { is[i], is[j] = is[j], is[i] } case 2: var is []int16 *(*sliceHeader)(unsafe.Pointer(&is)) = s return func(i, j int) { is[i], is[j] = is[j], is[i] } case 1: var is []int8 *(*sliceHeader)(unsafe.Pointer(&is)) = s return func(i, j int) { is[i], is[j] = is[j], is[i] } } } // Allocate scratch space for swaps: tmpVal := reflect.New(elemt) tmp := unsafe.Pointer(tmpVal.Pointer()) // If no pointers (or Go 1.4 or below), we don't require typedmemmove: if !haveTypedMemmove || !hasPtr { return func(i, j int) { if uint(i) >= maxLen || uint(j) >= maxLen { panic("reflect: slice index out of range") } val1 := arrayAt(s.Data, i, size) val2 := arrayAt(s.Data, j, size) memmove(tmp, val1, size) memmove(val1, val2, size) memmove(val2, tmp, size) } } return func(i, j int) { if uint(i) >= maxLen || uint(j) >= maxLen { panic("reflect: slice index out of range") } val1 := arrayAt(s.Data, i, size) val2 := arrayAt(s.Data, j, size) typedmemmove(typ, tmp, val1) typedmemmove(typ, val1, val2) typedmemmove(typ, val2, tmp) } } // memmove copies size bytes from src to dst. // The memory must not contain any pointers. //go:noescape func memmove(dst, src unsafe.Pointer, size uintptr) golang-go4-0.0~git20230225.2148625/reflectutil/swapper_unsafe_14.go000066400000000000000000000007161445700726600241010ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !ppc64,!ppc64le,!arm64,!mips,!mipsle,!mips64,!mips64le // +build !go1.5,!js,!appengine,!safe package reflectutil import "unsafe" const haveTypedMemmove = false func typedmemmove(reflect_rtype, dst, src unsafe.Pointer) { panic("never called") // only here so swapper_unsafe.go compiles } golang-go4-0.0~git20230225.2148625/reflectutil/swapper_unsafe_15.go000066400000000000000000000007371445700726600241050ustar00rootroot00000000000000// Copyright 2016 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 file. // +build !go1.8 // +build !ppc64,!ppc64le,!arm64,!mips,!mipsle,!mips64,!mips64le // +build go1.5,!js,!appengine,!safe package reflectutil import "unsafe" const haveTypedMemmove = true // typedmemmove copies a value of type t to dst from src. //go:noescape func typedmemmove(reflect_rtype, dst, src unsafe.Pointer) golang-go4-0.0~git20230225.2148625/rollsum/000077500000000000000000000000001445700726600173735ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/rollsum/rollsum.go000066400000000000000000000043051445700726600214210ustar00rootroot00000000000000/* Copyright 2011 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package rollsum implements rolling checksums similar to apenwarr's bup, which // is similar to librsync. // // The bup project is at https://github.com/apenwarr/bup and its splitting in // particular is at https://github.com/apenwarr/bup/blob/master/lib/bup/bupsplit.c package rollsum // import "go4.org/rollsum" import ( "math/bits" ) const windowSize = 64 // Roll assumes windowSize is a power of 2 const charOffset = 31 const blobBits = 13 const blobSize = 1 << blobBits // 8k type RollSum struct { s1, s2 uint32 window [windowSize]uint8 wofs int } func New() *RollSum { return &RollSum{ s1: windowSize * charOffset, s2: windowSize * (windowSize - 1) * charOffset, } } func (rs *RollSum) add(drop, add uint32) { s1 := rs.s1 + add - drop rs.s1 = s1 rs.s2 += s1 - uint32(windowSize)*(drop+charOffset) } // Roll adds ch to the rolling sum. func (rs *RollSum) Roll(ch byte) { wp := &rs.window[rs.wofs] rs.add(uint32(*wp), uint32(ch)) *wp = ch rs.wofs = (rs.wofs + 1) & (windowSize - 1) } // OnSplit reports whether at least 13 consecutive trailing bits of // the current checksum are set the same way. func (rs *RollSum) OnSplit() bool { return (rs.s2 & (blobSize - 1)) == ((^0) & (blobSize - 1)) } // OnSplitWithBits reports whether at least n consecutive trailing bits // of the current checksum are set the same way. func (rs *RollSum) OnSplitWithBits(n uint32) bool { mask := (uint32(1) << n) - 1 return rs.s2&mask == (^uint32(0))&mask } func (rs *RollSum) Bits() int { rsum := rs.Digest() >> (blobBits + 1) return blobBits + bits.TrailingZeros32(^rsum) } func (rs *RollSum) Digest() uint32 { return (rs.s1 << 16) | (rs.s2 & 0xffff) } golang-go4-0.0~git20230225.2148625/rollsum/rollsum_test.go000066400000000000000000000041751445700726600224650ustar00rootroot00000000000000/* Copyright 2011 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package rollsum import ( "math/rand" "testing" ) func TestSum(t *testing.T) { var buf [100000]uint8 rnd := rand.New(rand.NewSource(4)) for i := range buf { buf[i] = uint8(rnd.Intn(256)) } roll := func(offset, len int) *RollSum { rs := New() for count := offset; count < len; count++ { rs.Roll(buf[count]) } return rs } sum := func(offset, len int) uint32 { rs := roll(offset, len) return rs.Digest() } sum1a := sum(0, len(buf)) sum1b := sum(1, len(buf)) sum2a := sum(len(buf)-windowSize*5/2, len(buf)-windowSize) sum2b := sum(0, len(buf)-windowSize) sum3a := sum(0, windowSize+3) sum3b := sum(3, windowSize+3) if sum1a != sum1b { t.Errorf("sum1a=%d sum1b=%d", sum1a, sum1b) } if sum2a != sum2b { t.Errorf("sum2a=%d sum2b=%d", sum2a, sum2b) } if sum3a != sum3b { t.Errorf("sum3a=%d sum3b=%d", sum3a, sum3b) } end := 500 rs := roll(0, windowSize) for i := 0; i < end; i++ { sumRoll := rs.Digest() newRoll := roll(i, i+windowSize).Digest() if sumRoll != newRoll { t.Errorf("Error: i=%d, buf[i]=%d, sumRoll=%d, newRoll=%d\n", i, buf[i], sumRoll, newRoll) } rs.Roll(buf[i+windowSize]) } } func BenchmarkRollsum(b *testing.B) { const bufSize = 5 << 20 buf := make([]byte, bufSize) for i := range buf { buf[i] = byte(rand.Int63()) } b.ResetTimer() rs := New() splits := 0 for i := 0; i < b.N; i++ { splits = 0 for _, b := range buf { rs.Roll(b) if rs.OnSplit() { _ = rs.Bits() splits++ } } } b.SetBytes(bufSize) b.Logf("num splits = %d; every %d bytes", splits, int(float64(bufSize)/float64(splits))) } golang-go4-0.0~git20230225.2148625/sort/000077500000000000000000000000001445700726600166655ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/sort/example_interface_test.go000066400000000000000000000016151445700726600237310ustar00rootroot00000000000000// Copyright 2011 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 file. package sort_test import ( "fmt" "go4.org/sort" ) type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("%s: %d", p.Name, p.Age) } // ByAge implements sort.Interface for []Person based on // the Age field. type ByAge []Person func (a ByAge) Len() int { return len(a) } func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } func ExampleSort() { people := []Person{ {"Bob", 31}, {"John", 42}, {"Michael", 17}, {"Jenny", 26}, } fmt.Println(people) sort.Sort(ByAge(people)) fmt.Println(people) // Output: // [Bob: 31 John: 42 Michael: 17 Jenny: 26] // [Michael: 17 Jenny: 26 Bob: 31 John: 42] } golang-go4-0.0~git20230225.2148625/sort/example_keys_test.go000066400000000000000000000052711445700726600227460ustar00rootroot00000000000000// Copyright 2013 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 file. package sort_test import ( "fmt" "sort" ) // A couple of type definitions to make the units clear. type earthMass float64 type au float64 // A Planet defines the properties of a solar system object. type Planet struct { name string mass earthMass distance au } // By is the type of a "less" function that defines the ordering of its Planet arguments. type By func(p1, p2 *Planet) bool // Sort is a method on the function type, By, that sorts the argument slice according to the function. func (by By) Sort(planets []Planet) { ps := &planetSorter{ planets: planets, by: by, // The Sort method's receiver is the function (closure) that defines the sort order. } sort.Sort(ps) } // planetSorter joins a By function and a slice of Planets to be sorted. type planetSorter struct { planets []Planet by func(p1, p2 *Planet) bool // Closure used in the Less method. } // Len is part of sort.Interface. func (s *planetSorter) Len() int { return len(s.planets) } // Swap is part of sort.Interface. func (s *planetSorter) Swap(i, j int) { s.planets[i], s.planets[j] = s.planets[j], s.planets[i] } // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. func (s *planetSorter) Less(i, j int) bool { return s.by(&s.planets[i], &s.planets[j]) } var planets = []Planet{ {"Mercury", 0.055, 0.4}, {"Venus", 0.815, 0.7}, {"Earth", 1.0, 1.0}, {"Mars", 0.107, 1.5}, } // ExampleSortKeys demonstrates a technique for sorting a struct type using programmable sort criteria. func Example_sortKeys() { // Closures that order the Planet structure. name := func(p1, p2 *Planet) bool { return p1.name < p2.name } mass := func(p1, p2 *Planet) bool { return p1.mass < p2.mass } distance := func(p1, p2 *Planet) bool { return p1.distance < p2.distance } decreasingDistance := func(p1, p2 *Planet) bool { return !distance(p1, p2) } // Sort the planets by the various criteria. By(name).Sort(planets) fmt.Println("By name:", planets) By(mass).Sort(planets) fmt.Println("By mass:", planets) By(distance).Sort(planets) fmt.Println("By distance:", planets) By(decreasingDistance).Sort(planets) fmt.Println("By decreasing distance:", planets) // Output: By name: [{Earth 1 1} {Mars 0.107 1.5} {Mercury 0.055 0.4} {Venus 0.815 0.7}] // By mass: [{Mercury 0.055 0.4} {Mars 0.107 1.5} {Venus 0.815 0.7} {Earth 1 1}] // By distance: [{Mercury 0.055 0.4} {Venus 0.815 0.7} {Earth 1 1} {Mars 0.107 1.5}] // By decreasing distance: [{Mars 0.107 1.5} {Earth 1 1} {Venus 0.815 0.7} {Mercury 0.055 0.4}] } golang-go4-0.0~git20230225.2148625/sort/example_multi_test.go000066400000000000000000000100301445700726600231120ustar00rootroot00000000000000// +build go1.6 // Copyright 2013 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 file. package sort_test import ( "fmt" "sort" ) // A Change is a record of source code changes, recording user, language, and delta size. type Change struct { user string language string lines int } type lessFunc func(p1, p2 *Change) bool // multiSorter implements the Sort interface, sorting the changes within. type multiSorter struct { changes []Change less []lessFunc } // Sort sorts the argument slice according to the less functions passed to OrderedBy. func (ms *multiSorter) Sort(changes []Change) { ms.changes = changes sort.Sort(ms) } // OrderedBy returns a Sorter that sorts using the less functions, in order. // Call its Sort method to sort the data. func OrderedBy(less ...lessFunc) *multiSorter { return &multiSorter{ less: less, } } // Len is part of sort.Interface. func (ms *multiSorter) Len() int { return len(ms.changes) } // Swap is part of sort.Interface. func (ms *multiSorter) Swap(i, j int) { ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i] } // Less is part of sort.Interface. It is implemented by looping along the // less functions until it finds a comparison that is either Less or // !Less. Note that it can call the less functions twice per call. We // could change the functions to return -1, 0, 1 and reduce the // number of calls for greater efficiency: an exercise for the reader. func (ms *multiSorter) Less(i, j int) bool { p, q := &ms.changes[i], &ms.changes[j] // Try all but the last comparison. var k int for k = 0; k < len(ms.less)-1; k++ { less := ms.less[k] switch { case less(p, q): // p < q, so we have a decision. return true case less(q, p): // p > q, so we have a decision. return false } // p == q; try the next comparison. } // All comparisons to here said "equal", so just return whatever // the final comparison reports. return ms.less[k](p, q) } var changes = []Change{ {"gri", "Go", 100}, {"ken", "C", 150}, {"glenda", "Go", 200}, {"rsc", "Go", 200}, {"r", "Go", 100}, {"ken", "Go", 200}, {"dmr", "C", 100}, {"r", "C", 150}, {"gri", "Smalltalk", 80}, } // ExampleMultiKeys demonstrates a technique for sorting a struct type using different // sets of multiple fields in the comparison. We chain together "Less" functions, each of // which compares a single field. func Example_sortMultiKeys() { // Closures that order the Change structure. user := func(c1, c2 *Change) bool { return c1.user < c2.user } language := func(c1, c2 *Change) bool { return c1.language < c2.language } increasingLines := func(c1, c2 *Change) bool { return c1.lines < c2.lines } decreasingLines := func(c1, c2 *Change) bool { return c1.lines > c2.lines // Note: > orders downwards. } // Simple use: Sort by user. OrderedBy(user).Sort(changes) fmt.Println("By user:", changes) // More examples. OrderedBy(user, increasingLines).Sort(changes) fmt.Println("By user,lines:", changes) OrderedBy(language, increasingLines).Sort(changes) fmt.Println("By language,lines: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}] // By language,= 23 }) // returns the smallest index i such that data[i] >= 23. If the caller // wants to find whether 23 is in the slice, it must test data[i] == 23 // separately. // // Searching data sorted in descending order would use the <= // operator instead of the >= operator. // // To complete the example above, the following code tries to find the value // x in an integer slice data sorted in ascending order: // // x := 23 // i := sort.Search(len(data), func(i int) bool { return data[i] >= x }) // if i < len(data) && data[i] == x { // // x is present at data[i] // } else { // // x is not present in data, // // but i is the index where it would be inserted. // } // // As a more whimsical example, this program guesses your number: // // func GuessingGame() { // var s string // fmt.Printf("Pick an integer from 0 to 100.\n") // answer := sort.Search(100, func(i int) bool { // fmt.Printf("Is your number <= %d? ", i) // fmt.Scanf("%s", &s) // return s != "" && s[0] == 'y' // }) // fmt.Printf("Your number is %d.\n", answer) // } // func Search(n int, f func(int) bool) int { // Define f(-1) == false and f(n) == true. // Invariant: f(i-1) == false, f(j) == true. i, j := 0, n for i < j { h := i + (j-i)/2 // avoid overflow when computing h // i ≤ h < j if !f(h) { i = h + 1 // preserves f(i-1) == false } else { j = h // preserves f(j) == true } } // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. return i } // Convenience wrappers for common cases. // SearchInts searches for x in a sorted slice of ints and returns the index // as specified by Search. The return value is the index to insert x if x is // not present (it could be len(a)). // The slice must be sorted in ascending order. // func SearchInts(a []int, x int) int { return Search(len(a), func(i int) bool { return a[i] >= x }) } // SearchFloat64s searches for x in a sorted slice of float64s and returns the index // as specified by Search. The return value is the index to insert x if x is not // present (it could be len(a)). // The slice must be sorted in ascending order. // func SearchFloat64s(a []float64, x float64) int { return Search(len(a), func(i int) bool { return a[i] >= x }) } // SearchStrings searches for x in a sorted slice of strings and returns the index // as specified by Search. The return value is the index to insert x if x is not // present (it could be len(a)). // The slice must be sorted in ascending order. // func SearchStrings(a []string, x string) int { return Search(len(a), func(i int) bool { return a[i] >= x }) } // Search returns the result of applying SearchInts to the receiver and x. func (p IntSlice) Search(x int) int { return SearchInts(p, x) } // Search returns the result of applying SearchFloat64s to the receiver and x. func (p Float64Slice) Search(x float64) int { return SearchFloat64s(p, x) } // Search returns the result of applying SearchStrings to the receiver and x. func (p StringSlice) Search(x string) int { return SearchStrings(p, x) } golang-go4-0.0~git20230225.2148625/sort/search_test.go000066400000000000000000000103741445700726600215250ustar00rootroot00000000000000// Copyright 2010 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 file. package sort_test import ( "runtime" . "sort" "testing" ) func f(a []int, x int) func(int) bool { return func(i int) bool { return a[i] >= x } } var data = []int{0: -10, 1: -5, 2: 0, 3: 1, 4: 2, 5: 3, 6: 5, 7: 7, 8: 11, 9: 100, 10: 100, 11: 100, 12: 1000, 13: 10000} var tests = []struct { name string n int f func(int) bool i int }{ {"empty", 0, nil, 0}, {"1 1", 1, func(i int) bool { return i >= 1 }, 1}, {"1 true", 1, func(i int) bool { return true }, 0}, {"1 false", 1, func(i int) bool { return false }, 1}, {"1e9 991", 1e9, func(i int) bool { return i >= 991 }, 991}, {"1e9 true", 1e9, func(i int) bool { return true }, 0}, {"1e9 false", 1e9, func(i int) bool { return false }, 1e9}, {"data -20", len(data), f(data, -20), 0}, {"data -10", len(data), f(data, -10), 0}, {"data -9", len(data), f(data, -9), 1}, {"data -6", len(data), f(data, -6), 1}, {"data -5", len(data), f(data, -5), 1}, {"data 3", len(data), f(data, 3), 5}, {"data 11", len(data), f(data, 11), 8}, {"data 99", len(data), f(data, 99), 9}, {"data 100", len(data), f(data, 100), 9}, {"data 101", len(data), f(data, 101), 12}, {"data 10000", len(data), f(data, 10000), 13}, {"data 10001", len(data), f(data, 10001), 14}, {"descending a", 7, func(i int) bool { return []int{99, 99, 59, 42, 7, 0, -1, -1}[i] <= 7 }, 4}, {"descending 7", 1e9, func(i int) bool { return 1e9-i <= 7 }, 1e9 - 7}, {"overflow", 2e9, func(i int) bool { return false }, 2e9}, } func TestSearch(t *testing.T) { for _, e := range tests { i := Search(e.n, e.f) if i != e.i { t.Errorf("%s: expected index %d; got %d", e.name, e.i, i) } } } // log2 computes the binary logarithm of x, rounded up to the next integer. // (log2(0) == 0, log2(1) == 0, log2(2) == 1, log2(3) == 2, etc.) // func log2(x int) int { n := 0 for p := 1; p < x; p += p { // p == 2**n n++ } // p/2 < x <= p == 2**n return n } func TestSearchEfficiency(t *testing.T) { n := 100 step := 1 for exp := 2; exp < 10; exp++ { // n == 10**exp // step == 10**(exp-2) max := log2(n) for x := 0; x < n; x += step { count := 0 i := Search(n, func(i int) bool { count++; return i >= x }) if i != x { t.Errorf("n = %d: expected index %d; got %d", n, x, i) } if count > max { t.Errorf("n = %d, x = %d: expected <= %d calls; got %d", n, x, max, count) } } n *= 10 step *= 10 } } // Smoke tests for convenience wrappers - not comprehensive. var fdata = []float64{0: -3.14, 1: 0, 2: 1, 3: 2, 4: 1000.7} var sdata = []string{0: "f", 1: "foo", 2: "foobar", 3: "x"} var wrappertests = []struct { name string result int i int }{ {"SearchInts", SearchInts(data, 11), 8}, {"SearchFloat64s", SearchFloat64s(fdata, 2.1), 4}, {"SearchStrings", SearchStrings(sdata, ""), 0}, {"IntSlice.Search", IntSlice(data).Search(0), 2}, {"Float64Slice.Search", Float64Slice(fdata).Search(2.0), 3}, {"StringSlice.Search", StringSlice(sdata).Search("x"), 3}, } func TestSearchWrappers(t *testing.T) { for _, e := range wrappertests { if e.result != e.i { t.Errorf("%s: expected index %d; got %d", e.name, e.i, e.result) } } } func runSearchWrappers() { SearchInts(data, 11) SearchFloat64s(fdata, 2.1) SearchStrings(sdata, "") IntSlice(data).Search(0) Float64Slice(fdata).Search(2.0) StringSlice(sdata).Search("x") } func TestSearchWrappersDontAlloc(t *testing.T) { if testing.Short() { t.Skip("skipping malloc count in short mode") } if runtime.GOMAXPROCS(0) > 1 { t.Skip("skipping; GOMAXPROCS>1") } allocs := testing.AllocsPerRun(100, runSearchWrappers) if allocs != 0 { t.Errorf("expected no allocs for runSearchWrappers, got %v", allocs) } } func BenchmarkSearchWrappers(b *testing.B) { for i := 0; i < b.N; i++ { runSearchWrappers() } } // Abstract exhaustive test: all sizes up to 100, // all possible return values. If there are any small // corner cases, this test exercises them. func TestSearchExhaustive(t *testing.T) { for size := 0; size <= 100; size++ { for targ := 0; targ <= size; targ++ { i := Search(size, func(i int) bool { return i >= targ }) if i != targ { t.Errorf("Search(%d, %d) = %d", size, targ, i) } } } } golang-go4-0.0~git20230225.2148625/sort/sort.go000066400000000000000000000436061445700726600202140ustar00rootroot00000000000000// Copyright 2009 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 file. //go:generate go run genzfunc.go // Package sort provides primitives for sorting slices and user-defined // collections. // // This is a copy of the Go standard library's sort package with the // addition of some helpers for sorting slices and using func literals // to sort, rather than having to create a sorter type. See the // additional MakeInterface, SliceSorter, and Slice functions. // Discussion of moving such helpers into the standard library is // at: // // https://golang.org/issue/16721 // // Per Go's "no +1 policy", please only leave a comment on that issue // if you have something unique to add. Use Github's emoji reactions // otherwise. package sort import ( "reflect" "go4.org/reflectutil" ) // A type, typically a collection, that satisfies sort.Interface can be // sorted by the routines in this package. The methods require that the // elements of the collection be enumerated by an integer index. type Interface interface { // Len is the number of elements in the collection. Len() int // Less reports whether the element with // index i should sort before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int) } // lessSwap is a pair of Less and Swap function for use with the // auto-generated func-optimized variant of sort.go in // zfuncversion.go. type lessSwap struct { Less func(i, j int) bool Swap func(i, j int) } // MakeInterface returns a sort Interface using the provided length // and pair of swap and less functions. func MakeInterface(length int, swap func(i, j int), less func(i, j int) bool) Interface { return &funcs{length, lessSwap{less, swap}} } // SliceSorter returns a sort.Interface to sort the provided slice // using the provided less function. // If the provided interface is not a slice, the function panics. func SliceSorter(slice interface{}, less func(i, j int) bool) Interface { return MakeInterface(reflect.ValueOf(slice).Len(), reflectutil.Swapper(slice), less) } // Slice sorts the provided slice using less. // If the provided interface is not a slice, the function panics. // The sort is not stable. For a stable sort, use sort.Stable with sort.SliceSorter. func Slice(slice interface{}, less func(i, j int) bool) { Sort(SliceSorter(slice, less)) } // funcs implements Interface, but is recognized by Sort and Stable // which use its lessSwap field with the non-interface sorting // routines in zfuncversion.go. type funcs struct { length int lessSwap } func (f *funcs) Len() int { return f.length } func (f *funcs) Swap(i, j int) { f.lessSwap.Swap(i, j) } func (f *funcs) Less(i, j int) bool { return f.lessSwap.Less(i, j) } // Insertion sort func insertionSort(data Interface, a, b int) { for i := a + 1; i < b; i++ { for j := i; j > a && data.Less(j, j-1); j-- { data.Swap(j, j-1) } } } // siftDown implements the heap property on data[lo, hi). // first is an offset into the array where the root of the heap lies. func siftDown(data Interface, lo, hi, first int) { root := lo for { child := 2*root + 1 if child >= hi { break } if child+1 < hi && data.Less(first+child, first+child+1) { child++ } if !data.Less(first+root, first+child) { return } data.Swap(first+root, first+child) root = child } } func heapSort(data Interface, a, b int) { first := a lo := 0 hi := b - a // Build heap with greatest element at top. for i := (hi - 1) / 2; i >= 0; i-- { siftDown(data, i, hi, first) } // Pop elements, largest first, into end of data. for i := hi - 1; i >= 0; i-- { data.Swap(first, first+i) siftDown(data, lo, i, first) } } // Quicksort, loosely following Bentley and McIlroy, // ``Engineering a Sort Function,'' SP&E November 1993. // medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. func medianOfThree(data Interface, m1, m0, m2 int) { // sort 3 elements if data.Less(m1, m0) { data.Swap(m1, m0) } // data[m0] <= data[m1] if data.Less(m2, m1) { data.Swap(m2, m1) // data[m0] <= data[m2] && data[m1] < data[m2] if data.Less(m1, m0) { data.Swap(m1, m0) } } // now data[m0] <= data[m1] <= data[m2] } func swapRange(data Interface, a, b, n int) { for i := 0; i < n; i++ { data.Swap(a+i, b+i) } } func doPivot(data Interface, lo, hi int) (midlo, midhi int) { m := lo + (hi-lo)/2 // Written like this to avoid integer overflow. if hi-lo > 40 { // Tukey's ``Ninther,'' median of three medians of three. s := (hi - lo) / 8 medianOfThree(data, lo, lo+s, lo+2*s) medianOfThree(data, m, m-s, m+s) medianOfThree(data, hi-1, hi-1-s, hi-1-2*s) } medianOfThree(data, lo, m, hi-1) // Invariants are: // data[lo] = pivot (set up by ChoosePivot) // data[lo < i < a] < pivot // data[a <= i < b] <= pivot // data[b <= i < c] unexamined // data[c <= i < hi-1] > pivot // data[hi-1] >= pivot pivot := lo a, c := lo+1, hi-1 for ; a < c && data.Less(a, pivot); a++ { } b := a for { for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot } for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot } if b >= c { break } // data[b] > pivot; data[c-1] <= pivot data.Swap(b, c-1) b++ c-- } // If hi-c<3 then there are duplicates (by property of median of nine). // Let be a bit more conservative, and set border to 5. protect := hi-c < 5 if !protect && hi-c < (hi-lo)/4 { // Lets test some points for equality to pivot dups := 0 if !data.Less(pivot, hi-1) { // data[hi-1] = pivot data.Swap(c, hi-1) c++ dups++ } if !data.Less(b-1, pivot) { // data[b-1] = pivot b-- dups++ } // m-lo = (hi-lo)/2 > 6 // b-lo > (hi-lo)*3/4-1 > 8 // ==> m < b ==> data[m] <= pivot if !data.Less(m, pivot) { // data[m] = pivot data.Swap(m, b-1) b-- dups++ } // if at least 2 points are equal to pivot, assume skewed distribution protect = dups > 1 } if protect { // Protect against a lot of duplicates // Add invariant: // data[a <= i < b] unexamined // data[b <= i < c] = pivot for { for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot } for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot } if a >= b { break } // data[a] == pivot; data[b-1] < pivot data.Swap(a, b-1) a++ b-- } } // Swap pivot into middle data.Swap(pivot, b-1) return b - 1, c } func quickSort(data Interface, a, b, maxDepth int) { for b-a > 12 { // Use ShellSort for slices <= 12 elements if maxDepth == 0 { heapSort(data, a, b) return } maxDepth-- mlo, mhi := doPivot(data, a, b) // Avoiding recursion on the larger subproblem guarantees // a stack depth of at most lg(b-a). if mlo-a < b-mhi { quickSort(data, a, mlo, maxDepth) a = mhi // i.e., quickSort(data, mhi, b) } else { quickSort(data, mhi, b, maxDepth) b = mlo // i.e., quickSort(data, a, mlo) } } if b-a > 1 { // Do ShellSort pass with gap 6 // It could be written in this simplified form cause b-a <= 12 for i := a + 6; i < b; i++ { if data.Less(i, i-6) { data.Swap(i, i-6) } } insertionSort(data, a, b) } } // Sort sorts data. // // It makes one call to data.Len to determine n, and O(n*log(n)) calls to // data.Less and data.Swap. The sort is not guaranteed to be stable. // // To sort slices without creating a type, see Slice. func Sort(data Interface) { n := data.Len() if fs, ok := data.(*funcs); ok { quickSort_func(fs.lessSwap, 0, n, maxDepth(n)) } else { quickSort(data, 0, n, maxDepth(n)) } } // With sorts data given the provided length, swap, and less // functions. // The sort is not guaranteed to be stable. func With(length int, swap func(i, j int), less func(i, j int) bool) { quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length)) } // maxDepth returns a threshold at which quicksort should switch // to heapsort. It returns 2*ceil(lg(n+1)). func maxDepth(n int) int { var depth int for i := n; i > 0; i >>= 1 { depth++ } return depth * 2 } type reverse struct { // This embedded Interface permits Reverse to use the methods of // another Interface implementation. Interface } // Less returns the opposite of the embedded implementation's Less method. func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) } // Reverse returns the reverse order for data. func Reverse(data Interface) Interface { return &reverse{data} } // IsSorted reports whether data is sorted. func IsSorted(data Interface) bool { n := data.Len() for i := n - 1; i > 0; i-- { if data.Less(i, i-1) { return false } } return true } // Convenience types for common cases // IntSlice attaches the methods of Interface to []int, sorting in increasing order. type IntSlice []int func (p IntSlice) Len() int { return len(p) } func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] } func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Sort is a convenience method. func (p IntSlice) Sort() { Sort(p) } // Float64Slice attaches the methods of Interface to []float64, sorting in increasing order. type Float64Slice []float64 func (p Float64Slice) Len() int { return len(p) } func (p Float64Slice) Less(i, j int) bool { return p[i] < p[j] || isNaN(p[i]) && !isNaN(p[j]) } func (p Float64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // isNaN is a copy of math.IsNaN to avoid a dependency on the math package. func isNaN(f float64) bool { return f != f } // Sort is a convenience method. func (p Float64Slice) Sort() { Sort(p) } // StringSlice attaches the methods of Interface to []string, sorting in increasing order. type StringSlice []string func (p StringSlice) Len() int { return len(p) } func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] } func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Sort is a convenience method. func (p StringSlice) Sort() { Sort(p) } // Convenience wrappers for common cases // Ints sorts a slice of ints in increasing order. func Ints(a []int) { Sort(IntSlice(a)) } // Float64s sorts a slice of float64s in increasing order. func Float64s(a []float64) { Sort(Float64Slice(a)) } // Strings sorts a slice of strings in increasing order. func Strings(a []string) { Sort(StringSlice(a)) } // IntsAreSorted tests whether a slice of ints is sorted in increasing order. func IntsAreSorted(a []int) bool { return IsSorted(IntSlice(a)) } // Float64sAreSorted tests whether a slice of float64s is sorted in increasing order. func Float64sAreSorted(a []float64) bool { return IsSorted(Float64Slice(a)) } // StringsAreSorted tests whether a slice of strings is sorted in increasing order. func StringsAreSorted(a []string) bool { return IsSorted(StringSlice(a)) } // Notes on stable sorting: // The used algorithms are simple and provable correct on all input and use // only logarithmic additional stack space. They perform well if compared // experimentally to other stable in-place sorting algorithms. // // Remarks on other algorithms evaluated: // - GCC's 4.6.3 stable_sort with merge_without_buffer from libstdc++: // Not faster. // - GCC's __rotate for block rotations: Not faster. // - "Practical in-place mergesort" from Jyrki Katajainen, Tomi A. Pasanen // and Jukka Teuhola; Nordic Journal of Computing 3,1 (1996), 27-40: // The given algorithms are in-place, number of Swap and Assignments // grow as n log n but the algorithm is not stable. // - "Fast Stable In-Place Sorting with O(n) Data Moves" J.I. Munro and // V. Raman in Algorithmica (1996) 16, 115-160: // This algorithm either needs additional 2n bits or works only if there // are enough different elements available to encode some permutations // which have to be undone later (so not stable on any input). // - All the optimal in-place sorting/merging algorithms I found are either // unstable or rely on enough different elements in each step to encode the // performed block rearrangements. See also "In-Place Merging Algorithms", // Denham Coates-Evely, Department of Computer Science, Kings College, // January 2004 and the references in there. // - Often "optimal" algorithms are optimal in the number of assignments // but Interface has only Swap as operation. // Stable sorts data while keeping the original order of equal elements. // // It makes one call to data.Len to determine n, O(n*log(n)) calls to // data.Less and O(n*log(n)*log(n)) calls to data.Swap. func Stable(data Interface) { if fs, ok := data.(*funcs); ok { stable_func(fs.lessSwap, fs.length) } else { stable(data, data.Len()) } } func stable(data Interface, n int) { blockSize := 20 // must be > 0 a, b := 0, blockSize for b <= n { insertionSort(data, a, b) a = b b += blockSize } insertionSort(data, a, n) for blockSize < n { a, b = 0, 2*blockSize for b <= n { symMerge(data, a, a+blockSize, b) a = b b += 2 * blockSize } if m := a + blockSize; m < n { symMerge(data, a, m, n) } blockSize *= 2 } } // SymMerge merges the two sorted subsequences data[a:m] and data[m:b] using // the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum // Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz // Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in // Computer Science, pages 714-723. Springer, 2004. // // Let M = m-a and N = b-n. Wolog M < N. // The recursion depth is bound by ceil(log(N+M)). // The algorithm needs O(M*log(N/M + 1)) calls to data.Less. // The algorithm needs O((M+N)*log(M)) calls to data.Swap. // // The paper gives O((M+N)*log(M)) as the number of assignments assuming a // rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation // in the paper carries through for Swap operations, especially as the block // swapping rotate uses only O(M+N) Swaps. // // symMerge assumes non-degenerate arguments: a < m && m < b. // Having the caller check this condition eliminates many leaf recursion calls, // which improves performance. func symMerge(data Interface, a, m, b int) { // Avoid unnecessary recursions of symMerge // by direct insertion of data[a] into data[m:b] // if data[a:m] only contains one element. if m-a == 1 { // Use binary search to find the lowest index i // such that data[i] >= data[a] for m <= i < b. // Exit the search loop with i == b in case no such index exists. i := m j := b for i < j { h := i + (j-i)/2 if data.Less(h, a) { i = h + 1 } else { j = h } } // Swap values until data[a] reaches the position before i. for k := a; k < i-1; k++ { data.Swap(k, k+1) } return } // Avoid unnecessary recursions of symMerge // by direct insertion of data[m] into data[a:m] // if data[m:b] only contains one element. if b-m == 1 { // Use binary search to find the lowest index i // such that data[i] > data[m] for a <= i < m. // Exit the search loop with i == m in case no such index exists. i := a j := m for i < j { h := i + (j-i)/2 if !data.Less(m, h) { i = h + 1 } else { j = h } } // Swap values until data[m] reaches the position i. for k := m; k > i; k-- { data.Swap(k, k-1) } return } mid := a + (b-a)/2 n := mid + m var start, r int if m > mid { start = n - b r = mid } else { start = a r = m } p := n - 1 for start < r { c := start + (r-start)/2 if !data.Less(p-c, c) { start = c + 1 } else { r = c } } end := n - start if start < m && m < end { rotate(data, start, m, end) } if a < start && start < mid { symMerge(data, a, start, mid) } if mid < end && end < b { symMerge(data, mid, end, b) } } // Rotate two consecutives blocks u = data[a:m] and v = data[m:b] in data: // Data of the form 'x u v y' is changed to 'x v u y'. // Rotate performs at most b-a many calls to data.Swap. // Rotate assumes non-degenerate arguments: a < m && m < b. func rotate(data Interface, a, m, b int) { i := m - a j := b - m for i != j { if i > j { swapRange(data, m-i, m, j) i -= j } else { swapRange(data, m-i, m+j-i, i) j -= i } } // i == j swapRange(data, m-i, m, i) } /* Complexity of Stable Sorting Complexity of block swapping rotation Each Swap puts one new element into its correct, final position. Elements which reach their final position are no longer moved. Thus block swapping rotation needs |u|+|v| calls to Swaps. This is best possible as each element might need a move. Pay attention when comparing to other optimal algorithms which typically count the number of assignments instead of swaps: E.g. the optimal algorithm of Dudzinski and Dydek for in-place rotations uses O(u + v + gcd(u,v)) assignments which is better than our O(3 * (u+v)) as gcd(u,v) <= u. Stable sorting by SymMerge and BlockSwap rotations SymMerg complexity for same size input M = N: Calls to Less: O(M*log(N/M+1)) = O(N*log(2)) = O(N) Calls to Swap: O((M+N)*log(M)) = O(2*N*log(N)) = O(N*log(N)) (The following argument does not fuzz over a missing -1 or other stuff which does not impact the final result). Let n = data.Len(). Assume n = 2^k. Plain merge sort performs log(n) = k iterations. On iteration i the algorithm merges 2^(k-i) blocks, each of size 2^i. Thus iteration i of merge sort performs: Calls to Less O(2^(k-i) * 2^i) = O(2^k) = O(2^log(n)) = O(n) Calls to Swap O(2^(k-i) * 2^i * log(2^i)) = O(2^k * i) = O(n*i) In total k = log(n) iterations are performed; so in total: Calls to Less O(log(n) * n) Calls to Swap O(n + 2*n + 3*n + ... + (k-1)*n + k*n) = O((k/2) * k * n) = O(n * k^2) = O(n * log^2(n)) Above results should generalize to arbitrary n = 2^k + p and should not be influenced by the initial insertion sort phase: Insertion sort is O(n^2) on Swap and Less, thus O(bs^2) per block of size bs at n/bs blocks: O(bs*n) Swaps and Less during insertion sort. Merge sort iterations start at i = log(bs). With t = log(bs) constant: Calls to Less O((log(n)-t) * n + bs*n) = O(log(n)*n + (bs-t)*n) = O(n * log(n)) Calls to Swap O(n * log^2(n) - (t^2+t)/2*n) = O(n * log^2(n)) */ golang-go4-0.0~git20230225.2148625/sort/sort_test.go000066400000000000000000000360151445700726600212470ustar00rootroot00000000000000// Copyright 2009 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 file. package sort_test import ( "fmt" "math" "math/rand" "reflect" "strconv" "testing" "go4.org/reflectutil" . "go4.org/sort" ) var ints = [...]int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586} var float64s = [...]float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8} var strings = [...]string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"} func TestSlice(t *testing.T) { s := []int{5, 4, 3, 2, 1} want := []int{1, 2, 3, 4, 5} Slice(s, func(i, j int) bool { return s[i] < s[j] }) if !reflect.DeepEqual(s, want) { t.Errorf("sorted = %v; want %v", s, want) } } func TestSortIntSlice(t *testing.T) { data := ints a := IntSlice(data[0:]) Sort(a) if !IsSorted(a) { t.Errorf("sorted %v", ints) t.Errorf(" got %v", data) } } func TestSortFloat64Slice(t *testing.T) { data := float64s a := Float64Slice(data[0:]) Sort(a) if !IsSorted(a) { t.Errorf("sorted %v", float64s) t.Errorf(" got %v", data) } } func TestSortStringSlice(t *testing.T) { data := strings a := StringSlice(data[0:]) Sort(a) if !IsSorted(a) { t.Errorf("sorted %v", strings) t.Errorf(" got %v", data) } } func TestInts(t *testing.T) { data := ints Ints(data[0:]) if !IntsAreSorted(data[0:]) { t.Errorf("sorted %v", ints) t.Errorf(" got %v", data) } } func TestFloat64s(t *testing.T) { data := float64s Float64s(data[0:]) if !Float64sAreSorted(data[0:]) { t.Errorf("sorted %v", float64s) t.Errorf(" got %v", data) } } func TestStrings(t *testing.T) { data := strings Strings(data[0:]) if !StringsAreSorted(data[0:]) { t.Errorf("sorted %v", strings) t.Errorf(" got %v", data) } } func TestStringsWithSwapper(t *testing.T) { data := strings With(len(data), reflectutil.Swapper(data[:]), func(i, j int) bool { return data[i] < data[j] }) if !StringsAreSorted(data[:]) { t.Errorf("sorted %v", strings) t.Errorf(" got %v", data) } } func TestSortLarge_Random(t *testing.T) { n := 1000000 if testing.Short() { n /= 100 } data := make([]int, n) for i := 0; i < len(data); i++ { data[i] = rand.Intn(100) } if IntsAreSorted(data) { t.Fatalf("terrible rand.rand") } Ints(data) if !IntsAreSorted(data) { t.Errorf("sort didn't sort - 1M ints") } } func TestReverseSortIntSlice(t *testing.T) { data := ints data1 := ints a := IntSlice(data[0:]) Sort(a) r := IntSlice(data1[0:]) Sort(Reverse(r)) for i := 0; i < len(data); i++ { if a[i] != r[len(data)-1-i] { t.Errorf("reverse sort didn't sort") } if i > len(data)/2 { break } } } type nonDeterministicTestingData struct { r *rand.Rand } func (t *nonDeterministicTestingData) Len() int { return 500 } func (t *nonDeterministicTestingData) Less(i, j int) bool { if i < 0 || j < 0 || i >= t.Len() || j >= t.Len() { panic("nondeterministic comparison out of bounds") } return t.r.Float32() < 0.5 } func (t *nonDeterministicTestingData) Swap(i, j int) { if i < 0 || j < 0 || i >= t.Len() || j >= t.Len() { panic("nondeterministic comparison out of bounds") } } func TestNonDeterministicComparison(t *testing.T) { // Ensure that sort.Sort does not panic when Less returns inconsistent results. // See https://golang.org/issue/14377. defer func() { if r := recover(); r != nil { t.Error(r) } }() td := &nonDeterministicTestingData{ r: rand.New(rand.NewSource(0)), } for i := 0; i < 10; i++ { Sort(td) } } func BenchmarkSortString1K(b *testing.B) { b.StopTimer() unsorted := make([]string, 1<<10) for i := range unsorted { unsorted[i] = strconv.Itoa(i ^ 0x2cc) } data := make([]string, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() Strings(data) b.StopTimer() } } func BenchmarkSortString1K_With(b *testing.B) { b.StopTimer() unsorted := make([]string, 1<<10) for i := range unsorted { unsorted[i] = strconv.Itoa(i ^ 0x2cc) } data := make([]string, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() With(len(data), func(i, j int) { data[i], data[j] = data[j], data[i] }, func(i, j int) bool { return data[i] < data[j] }) b.StopTimer() } } func BenchmarkSortString1K_WithSwapper(b *testing.B) { b.StopTimer() unsorted := make([]string, 1<<10) for i := range unsorted { unsorted[i] = strconv.Itoa(i ^ 0x2cc) } data := make([]string, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() With(len(data), reflectutil.Swapper(data), func(i, j int) bool { return data[i] < data[j] }) b.StopTimer() } } func BenchmarkStableString1K(b *testing.B) { b.StopTimer() unsorted := make([]string, 1<<10) for i := 0; i < len(data); i++ { unsorted[i] = strconv.Itoa(i ^ 0x2cc) } data := make([]string, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() Stable(StringSlice(data)) b.StopTimer() } } func BenchmarkSortInt1K(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { data := make([]int, 1<<10) for i := 0; i < len(data); i++ { data[i] = i ^ 0x2cc } b.StartTimer() Ints(data) b.StopTimer() } } func BenchmarkStableInt1K(b *testing.B) { b.StopTimer() unsorted := make([]int, 1<<10) for i := range unsorted { unsorted[i] = i ^ 0x2cc } data := make([]int, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() Stable(IntSlice(data)) b.StopTimer() } } func BenchmarkStableInt1K_With(b *testing.B) { b.StopTimer() unsorted := make([]int, 1<<10) for i := range unsorted { unsorted[i] = i ^ 0x2cc } data := make([]int, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() Stable(MakeInterface( len(data), func(i, j int) { data[i], data[j] = data[j], data[i] }, func(i, j int) bool { return data[i] < data[j] }, )) b.StopTimer() } } func BenchmarkStableInt1K_WithSwapper(b *testing.B) { b.StopTimer() unsorted := make([]int, 1<<10) for i := range unsorted { unsorted[i] = i ^ 0x2cc } data := make([]int, len(unsorted)) for i := 0; i < b.N; i++ { copy(data, unsorted) b.StartTimer() Stable(MakeInterface(len(data), reflectutil.Swapper(data), func(i, j int) bool { return data[i] < data[j] })) b.StopTimer() } } func BenchmarkSortInt64K(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { data := make([]int, 1<<16) for i := 0; i < len(data); i++ { data[i] = i ^ 0xcccc } b.StartTimer() Ints(data) b.StopTimer() } } func BenchmarkStableInt64K(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { data := make([]int, 1<<16) for i := 0; i < len(data); i++ { data[i] = i ^ 0xcccc } b.StartTimer() Stable(IntSlice(data)) b.StopTimer() } } const ( _Sawtooth = iota _Rand _Stagger _Plateau _Shuffle _NDist ) const ( _Copy = iota _Reverse _ReverseFirstHalf _ReverseSecondHalf _Sorted _Dither _NMode ) type testingData struct { desc string t *testing.T data []int maxswap int // number of swaps allowed ncmp, nswap int } func (d *testingData) Len() int { return len(d.data) } func (d *testingData) Less(i, j int) bool { d.ncmp++ return d.data[i] < d.data[j] } func (d *testingData) Swap(i, j int) { if d.nswap >= d.maxswap { d.t.Errorf("%s: used %d swaps sorting slice of %d", d.desc, d.nswap, len(d.data)) d.t.FailNow() } d.nswap++ d.data[i], d.data[j] = d.data[j], d.data[i] } func min(a, b int) int { if a < b { return a } return b } func lg(n int) int { i := 0 for 1<= d.keys[j] } func (d *adversaryTestingData) Swap(i, j int) { d.data[i], d.data[j] = d.data[j], d.data[i] } func TestAdversary(t *testing.T) { const size = 100 data := make([]int, size) for i := 0; i < size; i++ { data[i] = i } d := &adversaryTestingData{data, make(map[int]int), 0} Sort(d) // This should degenerate to heapsort. } func TestStableInts(t *testing.T) { data := ints Stable(IntSlice(data[0:])) if !IntsAreSorted(data[0:]) { t.Errorf("nsorted %v\n got %v", ints, data) } } type intPairs []struct { a, b int } // IntPairs compare on a only. func (d intPairs) Len() int { return len(d) } func (d intPairs) Less(i, j int) bool { return d[i].a < d[j].a } func (d intPairs) Swap(i, j int) { d[i], d[j] = d[j], d[i] } // Record initial order in B. func (d intPairs) initB() { for i := range d { d[i].b = i } } // InOrder checks if a-equal elements were not reordered. func (d intPairs) inOrder() bool { lastA, lastB := -1, 0 for i := 0; i < len(d); i++ { if lastA != d[i].a { lastA = d[i].a lastB = d[i].b continue } if d[i].b <= lastB { return false } lastB = d[i].b } return true } func TestStability(t *testing.T) { n, m := 100000, 1000 if testing.Short() { n, m = 1000, 100 } data := make(intPairs, n) // random distribution for i := 0; i < len(data); i++ { data[i].a = rand.Intn(m) } if IsSorted(data) { t.Fatalf("terrible rand.rand") } data.initB() Stable(data) if !IsSorted(data) { t.Errorf("Stable didn't sort %d ints", n) } if !data.inOrder() { t.Errorf("Stable wasn't stable on %d ints", n) } // already sorted data.initB() Stable(data) if !IsSorted(data) { t.Errorf("Stable shuffled sorted %d ints (order)", n) } if !data.inOrder() { t.Errorf("Stable shuffled sorted %d ints (stability)", n) } // sorted reversed for i := 0; i < len(data); i++ { data[i].a = len(data) - i } data.initB() Stable(data) if !IsSorted(data) { t.Errorf("Stable didn't sort %d ints", n) } if !data.inOrder() { t.Errorf("Stable wasn't stable on %d ints", n) } } var countOpsSizes = []int{1e2, 3e2, 1e3, 3e3, 1e4, 3e4, 1e5, 3e5, 1e6} func countOps(t *testing.T, algo func(Interface), name string) { sizes := countOpsSizes if testing.Short() { sizes = sizes[:5] } if !testing.Verbose() { t.Skip("Counting skipped as non-verbose mode.") } for _, n := range sizes { td := testingData{ desc: name, t: t, data: make([]int, n), maxswap: 1<<31 - 1, } for i := 0; i < n; i++ { td.data[i] = rand.Intn(n / 5) } algo(&td) t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp) } } func TestCountStableOps(t *testing.T) { countOps(t, Stable, "Stable") } func TestCountSortOps(t *testing.T) { countOps(t, Sort, "Sort ") } func bench(b *testing.B, size int, algo func(Interface), name string) { b.StopTimer() data := make(intPairs, size) x := ^uint32(0) for i := 0; i < b.N; i++ { for n := size - 3; n <= size+3; n++ { for i := 0; i < len(data); i++ { x += x x ^= 1 if int32(x) < 0 { x ^= 0x88888eef } data[i].a = int(x % uint32(n/5)) } data.initB() b.StartTimer() algo(data) b.StopTimer() if !IsSorted(data) { b.Errorf("%s did not sort %d ints", name, n) } if name == "Stable" && !data.inOrder() { b.Errorf("%s unstable on %d ints", name, n) } } } } func BenchmarkSort1e2(b *testing.B) { bench(b, 1e2, Sort, "Sort") } func BenchmarkStable1e2(b *testing.B) { bench(b, 1e2, Stable, "Stable") } func BenchmarkSort1e4(b *testing.B) { bench(b, 1e4, Sort, "Sort") } func BenchmarkStable1e4(b *testing.B) { bench(b, 1e4, Stable, "Stable") } func BenchmarkSort1e6(b *testing.B) { bench(b, 1e6, Sort, "Sort") } func BenchmarkStable1e6(b *testing.B) { bench(b, 1e6, Stable, "Stable") } golang-go4-0.0~git20230225.2148625/sort/zfuncversion.go000066400000000000000000000114401445700726600217470ustar00rootroot00000000000000// DO NOT EDIT; AUTO-GENERATED from sort.go using genzfunc.go // Copyright 2016 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 file. package sort // Auto-generated variant of sort.go:insertionSort func insertionSort_func(data lessSwap, a, b int) { for i := a + 1; i < b; i++ { for j := i; j > a && data.Less(j, j-1); j-- { data.Swap(j, j-1) } } } // Auto-generated variant of sort.go:siftDown func siftDown_func(data lessSwap, lo, hi, first int) { root := lo for { child := 2*root + 1 if child >= hi { break } if child+1 < hi && data.Less(first+child, first+child+1) { child++ } if !data.Less(first+root, first+child) { return } data.Swap(first+root, first+child) root = child } } // Auto-generated variant of sort.go:heapSort func heapSort_func(data lessSwap, a, b int) { first := a lo := 0 hi := b - a for i := (hi - 1) / 2; i >= 0; i-- { siftDown_func(data, i, hi, first) } for i := hi - 1; i >= 0; i-- { data.Swap(first, first+i) siftDown_func(data, lo, i, first) } } // Auto-generated variant of sort.go:medianOfThree func medianOfThree_func(data lessSwap, m1, m0, m2 int) { if data.Less(m1, m0) { data.Swap(m1, m0) } if data.Less(m2, m1) { data.Swap(m2, m1) if data.Less(m1, m0) { data.Swap(m1, m0) } } } // Auto-generated variant of sort.go:swapRange func swapRange_func(data lessSwap, a, b, n int) { for i := 0; i < n; i++ { data.Swap(a+i, b+i) } } // Auto-generated variant of sort.go:doPivot func doPivot_func(data lessSwap, lo, hi int) (midlo, midhi int) { m := lo + (hi-lo)/2 if hi-lo > 40 { s := (hi - lo) / 8 medianOfThree_func(data, lo, lo+s, lo+2*s) medianOfThree_func(data, m, m-s, m+s) medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s) } medianOfThree_func(data, lo, m, hi-1) pivot := lo a, c := lo+1, hi-1 for ; a < c && data.Less(a, pivot); a++ { } b := a for { for ; b < c && !data.Less(pivot, b); b++ { } for ; b < c && data.Less(pivot, c-1); c-- { } if b >= c { break } data.Swap(b, c-1) b++ c-- } protect := hi-c < 5 if !protect && hi-c < (hi-lo)/4 { dups := 0 if !data.Less(pivot, hi-1) { data.Swap(c, hi-1) c++ dups++ } if !data.Less(b-1, pivot) { b-- dups++ } if !data.Less(m, pivot) { data.Swap(m, b-1) b-- dups++ } protect = dups > 1 } if protect { for { for ; a < b && !data.Less(b-1, pivot); b-- { } for ; a < b && data.Less(a, pivot); a++ { } if a >= b { break } data.Swap(a, b-1) a++ b-- } } data.Swap(pivot, b-1) return b - 1, c } // Auto-generated variant of sort.go:quickSort func quickSort_func(data lessSwap, a, b, maxDepth int) { for b-a > 12 { if maxDepth == 0 { heapSort_func(data, a, b) return } maxDepth-- mlo, mhi := doPivot_func(data, a, b) if mlo-a < b-mhi { quickSort_func(data, a, mlo, maxDepth) a = mhi } else { quickSort_func(data, mhi, b, maxDepth) b = mlo } } if b-a > 1 { for i := a + 6; i < b; i++ { if data.Less(i, i-6) { data.Swap(i, i-6) } } insertionSort_func(data, a, b) } } // Auto-generated variant of sort.go:stable func stable_func(data lessSwap, n int) { blockSize := 20 a, b := 0, blockSize for b <= n { insertionSort_func(data, a, b) a = b b += blockSize } insertionSort_func(data, a, n) for blockSize < n { a, b = 0, 2*blockSize for b <= n { symMerge_func(data, a, a+blockSize, b) a = b b += 2 * blockSize } if m := a + blockSize; m < n { symMerge_func(data, a, m, n) } blockSize *= 2 } } // Auto-generated variant of sort.go:symMerge func symMerge_func(data lessSwap, a, m, b int) { if m-a == 1 { i := m j := b for i < j { h := i + (j-i)/2 if data.Less(h, a) { i = h + 1 } else { j = h } } for k := a; k < i-1; k++ { data.Swap(k, k+1) } return } if b-m == 1 { i := a j := m for i < j { h := i + (j-i)/2 if !data.Less(m, h) { i = h + 1 } else { j = h } } for k := m; k > i; k-- { data.Swap(k, k-1) } return } mid := a + (b-a)/2 n := mid + m var start, r int if m > mid { start = n - b r = mid } else { start = a r = m } p := n - 1 for start < r { c := start + (r-start)/2 if !data.Less(p-c, c) { start = c + 1 } else { r = c } } end := n - start if start < m && m < end { rotate_func(data, start, m, end) } if a < start && start < mid { symMerge_func(data, a, start, mid) } if mid < end && end < b { symMerge_func(data, mid, end, b) } } // Auto-generated variant of sort.go:rotate func rotate_func(data lessSwap, a, m, b int) { i := m - a j := b - m for i != j { if i > j { swapRange_func(data, m-i, m, j) i -= j } else { swapRange_func(data, m-i, m+j-i, i) j -= i } } swapRange_func(data, m-i, m, i) } golang-go4-0.0~git20230225.2148625/strutil/000077500000000000000000000000001445700726600174045ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/strutil/intern.go000066400000000000000000000023621445700726600212350ustar00rootroot00000000000000/* Copyright 2013 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package strutil var internStr = map[string]string{} // RegisterCommonString adds common strings to the interned string // table. This should be called during init from the main // goroutine, not later at runtime. func RegisterCommonString(s ...string) { for _, v := range s { internStr[v] = v } } // StringFromBytes returns string(v), minimizing copies for common values of v // as previously registered with RegisterCommonString. func StringFromBytes(v []byte) string { // In Go 1.3, this string conversion in the map lookup does not allocate // to make a new string. We depend on Go 1.3, so this is always free: if s, ok := internStr[string(v)]; ok { return s } return string(v) } golang-go4-0.0~git20230225.2148625/strutil/strconv.go000066400000000000000000000043611445700726600214350ustar00rootroot00000000000000/* Copyright 2013 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package strutil import ( "errors" "strconv" ) // ParseUintBytes is like strconv.ParseUint, but using a []byte. func ParseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) { var cutoff, maxVal uint64 if bitSize == 0 { bitSize = int(strconv.IntSize) } s0 := s switch { case len(s) < 1: err = strconv.ErrSyntax goto Error case 2 <= base && base <= 36: // valid base; nothing to do case base == 0: // Look for octal, hex prefix. switch { case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): base = 16 s = s[2:] if len(s) < 1 { err = strconv.ErrSyntax goto Error } case s[0] == '0': base = 8 default: base = 10 } default: err = errors.New("invalid base " + strconv.Itoa(base)) goto Error } n = 0 cutoff = cutoff64(base) maxVal = 1<= base { n = 0 err = strconv.ErrSyntax goto Error } if n >= cutoff { // n*base overflows n = 1<<64 - 1 err = strconv.ErrRange goto Error } n *= uint64(base) n1 := n + uint64(v) if n1 < n || n1 > maxVal { // n+v overflows n = 1<<64 - 1 err = strconv.ErrRange goto Error } n = n1 } return n, nil Error: return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err} } // Return the first number n such that n*base >= 1<<64. func cutoff64(base int) uint64 { if base < 2 { return 0 } return (1<<64-1)/uint64(base) + 1 } golang-go4-0.0~git20230225.2148625/strutil/strutil.go000066400000000000000000000113021445700726600214360ustar00rootroot00000000000000/* Copyright 2013 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package strutil contains string and byte processing functions. package strutil // import "go4.org/strutil" import ( "strings" "unicode" "unicode/utf8" ) // Fork of Go's implementation in pkg/strings/strings.go: // Generic split: splits after each instance of sep, // including sepSave bytes of sep in the subarrays. func genSplit(dst []string, s, sep string, sepSave, n int) []string { if n == 0 { return nil } if sep == "" { panic("sep is empty") } if n < 0 { n = strings.Count(s, sep) + 1 } c := sep[0] start := 0 na := 0 for i := 0; i+len(sep) <= len(s) && na+1 < n; i++ { if s[i] == c && (len(sep) == 1 || s[i:i+len(sep)] == sep) { dst = append(dst, s[start:i+sepSave]) na++ start = i + len(sep) i += len(sep) - 1 } } dst = append(dst, s[start:]) return dst } // AppendSplitN is like strings.SplitN but appends to and returns dst. // Unlike strings.SplitN, an empty separator is not supported. // The count n determines the number of substrings to return: // n > 0: at most n substrings; the last substring will be the unsplit remainder. // n == 0: the result is nil (zero substrings) // n < 0: all substrings func AppendSplitN(dst []string, s, sep string, n int) []string { return genSplit(dst, s, sep, 0, n) } // equalFoldRune compares a and b runes whether they fold equally. // // The code comes from strings.EqualFold, but shortened to only one rune. func equalFoldRune(sr, tr rune) bool { if sr == tr { return true } // Make sr < tr to simplify what follows. if tr < sr { sr, tr = tr, sr } // Fast check for ASCII. if tr < utf8.RuneSelf && 'A' <= sr && sr <= 'Z' { // ASCII, and sr is upper case. tr must be lower case. if tr == sr+'a'-'A' { return true } return false } // General case. SimpleFold(x) returns the next equivalent rune > x // or wraps around to smaller values. r := unicode.SimpleFold(sr) for r != sr && r < tr { r = unicode.SimpleFold(r) } if r == tr { return true } return false } // HasPrefixFold is like strings.HasPrefix but uses Unicode case-folding, // matching case insensitively. func HasPrefixFold(s, prefix string) bool { if prefix == "" { return true } for _, pr := range prefix { if s == "" { return false } // step with s, too sr, size := utf8.DecodeRuneInString(s) if sr == utf8.RuneError { return false } s = s[size:] if !equalFoldRune(sr, pr) { return false } } return true } // HasSuffixFold is like strings.HasSuffix but uses Unicode case-folding, // matching case insensitively. func HasSuffixFold(s, suffix string) bool { if suffix == "" { return true } // count the runes and bytes in s, but only till rune count of suffix bo, so := len(s), len(suffix) for bo > 0 && so > 0 { r, size := utf8.DecodeLastRuneInString(s[:bo]) if r == utf8.RuneError { return false } bo -= size sr, size := utf8.DecodeLastRuneInString(suffix[:so]) if sr == utf8.RuneError { return false } so -= size if !equalFoldRune(r, sr) { return false } } return so == 0 } // ContainsFold is like strings.Contains but uses Unicode case-folding. func ContainsFold(s, substr string) bool { if substr == "" { return true } if s == "" { return false } firstRune := rune(substr[0]) if firstRune >= utf8.RuneSelf { firstRune, _ = utf8.DecodeRuneInString(substr) } for i, rune := range s { if equalFoldRune(rune, firstRune) && HasPrefixFold(s[i:], substr) { return true } } return false } // IsPlausibleJSON reports whether s likely contains a JSON object, without // actually parsing it. It's meant to be a light heuristic. func IsPlausibleJSON(s string) bool { return startsWithOpenBrace(s) && endsWithCloseBrace(s) } func isASCIIWhite(b byte) bool { return b == ' ' || b == '\n' || b == '\r' || b == '\t' } func startsWithOpenBrace(s string) bool { for len(s) > 0 { switch { case s[0] == '{': return true case isASCIIWhite(s[0]): s = s[1:] default: return false } } return false } func endsWithCloseBrace(s string) bool { for len(s) > 0 { last := len(s) - 1 switch { case s[last] == '}': return true case isASCIIWhite(s[last]): s = s[:last] default: return false } } return false } golang-go4-0.0~git20230225.2148625/strutil/strutil_test.go000066400000000000000000000140431445700726600225020ustar00rootroot00000000000000/* Copyright 2013 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package strutil import ( "reflect" "strings" "testing" ) func TestAppendSplitN(t *testing.T) { var got []string tests := []struct { s, sep string n int }{ {"foo", "|", 1}, {"foo", "|", -1}, {"foo|bar", "|", 1}, {"foo|bar", "|", -1}, {"foo|bar|", "|", 2}, {"foo|bar|", "|", -1}, {"foo|bar|baz", "|", 1}, {"foo|bar|baz", "|", 2}, {"foo|bar|baz", "|", 3}, {"foo|bar|baz", "|", -1}, } for _, tt := range tests { want := strings.SplitN(tt.s, tt.sep, tt.n) got = AppendSplitN(got[:0], tt.s, tt.sep, tt.n) if !reflect.DeepEqual(want, got) { t.Errorf("AppendSplitN(%q, %q, %d) = %q; want %q", tt.s, tt.sep, tt.n, got, want) } } } func TestStringFromBytes(t *testing.T) { for _, s := range []string{"foo", "permanode", "file", "zzzz"} { got := StringFromBytes([]byte(s)) if got != s { t.Errorf("StringFromBytes(%q) didn't round-trip; got %q instead", s, got) } } } func TestHasPrefixFold(t *testing.T) { tests := []struct { s, prefix string result bool }{ {"camli", "CAML", true}, {"CAMLI", "caml", true}, {"cam", "Cam", true}, {"camli", "car", false}, {"caml", "camli", false}, {"Hello, 世界 dasdsa", "HeLlO, 世界", true}, {"Hello, 世界", "HeLlO, 世界-", false}, {"kelvin", "\u212A" + "elvin", true}, // "\u212A" is the Kelvin temperature sign {"Kelvin", "\u212A" + "elvin", true}, {"kelvin", "\u212A" + "el", true}, {"Kelvin", "\u212A" + "el", true}, {"\u212A" + "elvin", "Kelvin", true}, {"\u212A" + "elvin", "kelvin", true}, {"\u212A" + "elvin", "Kel", true}, {"\u212A" + "elvin", "kel", true}, } for _, tt := range tests { r := HasPrefixFold(tt.s, tt.prefix) if r != tt.result { t.Errorf("HasPrefixFold(%q, %q) returned %v", tt.s, tt.prefix, r) } } } func TestHasSuffixFold(t *testing.T) { tests := []struct { s, suffix string result bool }{ {"camli", "AMLI", true}, {"CAMLI", "amli", true}, {"mli", "MLI", true}, {"camli", "ali", false}, {"amli", "camli", false}, {"asas Hello, 世界", "HeLlO, 世界", true}, {"Hello, 世界", "HeLlO, 世界-", false}, {"KkkkKKkelvin", "\u212A" + "elvin", true}, // "\u212A" is the Kelvin temperature sign {"kelvin", "\u212A" + "elvin", true}, // "\u212A" is the Kelvin temperature sign {"Kelvin", "\u212A" + "elvin", true}, {"\u212A" + "elvin", "Kelvin", true}, {"\u212A" + "elvin", "kelvin", true}, {"\u212A" + "elvin", "vin", true}, {"\u212A" + "elvin", "viN", true}, } for _, tt := range tests { r := HasSuffixFold(tt.s, tt.suffix) if r != tt.result { t.Errorf("HasSuffixFold(%q, %q) returned %v", tt.s, tt.suffix, r) } } } func TestContainsFold(t *testing.T) { // TODO: more tests, more languages. tests := []struct { s, substr string result bool }{ {"camli", "CAML", true}, {"CAMLI", "caml", true}, {"cam", "Cam", true}, {"мир", "ми", true}, {"МИP", "ми", true}, {"КÐМЛИЙСТОР", "камлийÑ", true}, {"КаМлИйСтОр", "КаМлИйС", true}, {"camli", "car", false}, {"caml", "camli", false}, {"camli", "AMLI", true}, {"CAMLI", "amli", true}, {"mli", "MLI", true}, {"мир", "ир", true}, {"МИP", "ми", true}, {"КÐМЛИЙСТОР", "лийÑтор", true}, {"КаМлИйСтОр", "лИйСтОр", true}, {"мир", "Ñ€", true}, {"camli", "ali", false}, {"amli", "camli", false}, {"МИP", "и", true}, {"мир", "и", true}, {"КÐМЛИЙСТОР", "лийÑ", true}, {"КаМлИйСтОр", "лИйС", true}, {"árvíztűrÅ‘ tükörfúrógép", "árvíztŰrÅ", true}, {"I love ☕", "i love ☕", true}, {"k", "\u212A", true}, // "\u212A" is the Kelvin temperature sign {"\u212A" + "elvin", "k", true}, {"kelvin", "\u212A" + "elvin", true}, {"Kelvin", "\u212A" + "elvin", true}, {"\u212A" + "elvin", "Kelvin", true}, {"\u212A" + "elvin", "kelvin", true}, {"273.15 kelvin", "\u212A" + "elvin", true}, {"273.15 Kelvin", "\u212A" + "elvin", true}, {"273.15 \u212A" + "elvin", "Kelvin", true}, {"273.15 \u212A" + "elvin", "kelvin", true}, } for _, tt := range tests { r := ContainsFold(tt.s, tt.substr) if r != tt.result { t.Errorf("ContainsFold(%q, %q) returned %v", tt.s, tt.substr, r) } } } func TestIsPlausibleJSON(t *testing.T) { tests := []struct { in string want bool }{ {"{}", true}, {" {}", true}, {"{} ", true}, {"\n\r\t {}\t \r \n", true}, {"\n\r\t {x\t \r \n", false}, {"{x", false}, {"x}", false}, {"x", false}, {"", false}, } for _, tt := range tests { got := IsPlausibleJSON(tt.in) if got != tt.want { t.Errorf("IsPlausibleJSON(%q) = %v; want %v", tt.in, got, tt.want) } } } func BenchmarkHasSuffixFoldToLower(tb *testing.B) { a, b := "camlik", "AMLI\u212A" for i := 0; i < tb.N; i++ { if !strings.HasSuffix(strings.ToLower(a), strings.ToLower(b)) { tb.Fatalf("%q should have the same suffix as %q", a, b) } } } func BenchmarkHasSuffixFold(tb *testing.B) { a, b := "camlik", "AMLI\u212A" for i := 0; i < tb.N; i++ { if !HasSuffixFold(a, b) { tb.Fatalf("%q should have the same suffix as %q", a, b) } } } func BenchmarkHasPrefixFoldToLower(tb *testing.B) { a, b := "kamlistore", "\u212AAMLI" for i := 0; i < tb.N; i++ { if !strings.HasPrefix(strings.ToLower(a), strings.ToLower(b)) { tb.Fatalf("%q should have the same suffix as %q", a, b) } } } func BenchmarkHasPrefixFold(tb *testing.B) { a, b := "kamlistore", "\u212AAMLI" for i := 0; i < tb.N; i++ { if !HasPrefixFold(a, b) { tb.Fatalf("%q should have the same suffix as %q", a, b) } } } golang-go4-0.0~git20230225.2148625/syncutil/000077500000000000000000000000001445700726600175505ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/syncutil/gate.go000066400000000000000000000020171445700726600210170ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syncutil // A Gate limits concurrency. type Gate struct { c chan struct{} } // NewGate returns a new gate that will only permit max operations at once. func NewGate(max int) *Gate { return &Gate{make(chan struct{}, max)} } // Start starts an operation, blocking until the gate has room. func (g *Gate) Start() { g.c <- struct{}{} } // Done finishes an operation. func (g *Gate) Done() { select { case <-g.c: default: panic("Done called more than Start") } } golang-go4-0.0~git20230225.2148625/syncutil/group.go000066400000000000000000000030731445700726600212360ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syncutil import "sync" // A Group is like a sync.WaitGroup and coordinates doing // multiple things at once. Its zero value is ready to use. type Group struct { wg sync.WaitGroup mu sync.Mutex // guards errs errs []error } // Go runs fn in its own goroutine, but does not wait for it to complete. // Call Err or Errs to wait for all the goroutines to complete. func (g *Group) Go(fn func() error) { g.wg.Add(1) go func() { defer g.wg.Done() err := fn() if err != nil { g.mu.Lock() defer g.mu.Unlock() g.errs = append(g.errs, err) } }() } // Wait waits for all the previous calls to Go to complete. func (g *Group) Wait() { g.wg.Wait() } // Err waits for all previous calls to Go to complete and returns the // first non-nil error, or nil. func (g *Group) Err() error { g.wg.Wait() if len(g.errs) > 0 { return g.errs[0] } return nil } // Errs waits for all previous calls to Go to complete and returns // all non-nil errors. func (g *Group) Errs() []error { g.wg.Wait() return g.errs } golang-go4-0.0~git20230225.2148625/syncutil/once.go000066400000000000000000000033151445700726600210250ustar00rootroot00000000000000/* Copyright 2014 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syncutil import ( "sync" "sync/atomic" ) // A Once will perform a successful action exactly once. // // Unlike a sync.Once, this Once's func returns an error // and is re-armed on failure. type Once struct { m sync.Mutex done uint32 } // Do calls the function f if and only if Do has not been invoked // without error for this instance of Once. In other words, given // var once Once // if once.Do(f) is called multiple times, only the first call will // invoke f, even if f has a different value in each invocation unless // f returns an error. A new instance of Once is required for each // function to execute. // // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: // err := config.once.Do(func() error { return config.init(filename) }) func (o *Once) Do(f func() error) error { if atomic.LoadUint32(&o.done) == 1 { return nil } // Slow-path. o.m.Lock() defer o.m.Unlock() var err error if o.done == 0 { err = f() if err == nil { atomic.StoreUint32(&o.done, 1) } } return err } golang-go4-0.0~git20230225.2148625/syncutil/once_test.go000066400000000000000000000016651445700726600220720ustar00rootroot00000000000000package syncutil import ( "errors" "testing" ) func TestOnce(t *testing.T) { timesRan := 0 f := func() error { timesRan++ return nil } once := Once{} grp := Group{} for i := 0; i < 10; i++ { grp.Go(func() error { return once.Do(f) }) } if grp.Err() != nil { t.Errorf("Expected no errors, got %v", grp.Err()) } if timesRan != 1 { t.Errorf("Expected to run one time, ran %d", timesRan) } } // TestOnceErroring verifies we retry on every error, but stop after // the first success. func TestOnceErroring(t *testing.T) { timesRan := 0 f := func() error { timesRan++ if timesRan < 3 { return errors.New("retry") } return nil } once := Once{} grp := Group{} for i := 0; i < 10; i++ { grp.Go(func() error { return once.Do(f) }) } if len(grp.Errs()) != 2 { t.Errorf("Expected two errors, got %d", len(grp.Errs())) } if timesRan != 3 { t.Errorf("Expected to run two times, ran %d", timesRan) } } golang-go4-0.0~git20230225.2148625/syncutil/sem.go000066400000000000000000000030131445700726600206600ustar00rootroot00000000000000package syncutil import ( "fmt" "log" "sync" ) type debugT bool var debug = debugT(false) func (d debugT) Printf(format string, args ...interface{}) { if bool(d) { log.Printf(format, args...) } } // Sem implements a semaphore that can have multiple units acquired/released // at a time. type Sem struct { c *sync.Cond // Protects size max, free int64 } // NewSem creates a semaphore with max units available for acquisition. func NewSem(max int64) *Sem { return &Sem{ c: sync.NewCond(new(sync.Mutex)), free: max, max: max, } } // Acquire will deduct n units from the semaphore. If the deduction would // result in the available units falling below zero, the call will block until // another go routine returns units via a call to Release. If more units are // requested than the semaphore is configured to hold, error will be non-nil. func (s *Sem) Acquire(n int64) error { if n > s.max { return fmt.Errorf("sem: attempt to acquire more units than semaphore size %d > %d", n, s.max) } s.c.L.Lock() defer s.c.L.Unlock() for { debug.Printf("Acquire check max %d free %d, n %d", s.max, s.free, n) if s.free >= n { s.free -= n return nil } debug.Printf("Acquire Wait max %d free %d, n %d", s.max, s.free, n) s.c.Wait() } } // Release will return n units to the semaphore and notify any currently // blocking Acquire calls. func (s *Sem) Release(n int64) { s.c.L.Lock() defer s.c.L.Unlock() debug.Printf("Release max %d free %d, n %d", s.max, s.free, n) s.free += n s.c.Broadcast() } golang-go4-0.0~git20230225.2148625/syncutil/sem_test.go000066400000000000000000000007641445700726600217310ustar00rootroot00000000000000package syncutil_test import ( "testing" "go4.org/syncutil" ) func TestSem(t *testing.T) { s := syncutil.NewSem(5) if err := s.Acquire(2); err != nil { t.Fatal(err) } if err := s.Acquire(2); err != nil { t.Fatal(err) } go func() { s.Release(2) s.Release(2) }() if err := s.Acquire(5); err != nil { t.Fatal(err) } } func TestSemErr(t *testing.T) { s := syncutil.NewSem(5) if err := s.Acquire(6); err == nil { t.Fatal("Didn't get expected error for large acquire.") } } golang-go4-0.0~git20230225.2148625/syncutil/singleflight/000077500000000000000000000000001445700726600222275ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/syncutil/singleflight/singleflight.go000066400000000000000000000032741445700726600252430ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package singleflight provides a duplicate function call suppression // mechanism. package singleflight // import "go4.org/syncutil/singleflight" import "sync" // call is an in-flight or completed Do call type call struct { wg sync.WaitGroup val interface{} err error } // Group represents a class of work and forms a namespace in which // units of work can be executed with duplicate suppression. type Group struct { mu sync.Mutex // protects m m map[string]*call // lazily initialized } // Do executes and returns the results of the given function, making // sure that only one execution is in-flight for a given key at a // time. If a duplicate comes in, the duplicate caller waits for the // original to complete and receives the same results. func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { g.mu.Lock() if g.m == nil { g.m = make(map[string]*call) } if c, ok := g.m[key]; ok { g.mu.Unlock() c.wg.Wait() return c.val, c.err } c := new(call) c.wg.Add(1) g.m[key] = c g.mu.Unlock() c.val, c.err = fn() c.wg.Done() g.mu.Lock() delete(g.m, key) g.mu.Unlock() return c.val, c.err } golang-go4-0.0~git20230225.2148625/syncutil/singleflight/singleflight_test.go000066400000000000000000000035571445700726600263060ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package singleflight import ( "errors" "fmt" "sync" "sync/atomic" "testing" "time" ) func TestDo(t *testing.T) { var g Group v, err := g.Do("key", func() (interface{}, error) { return "bar", nil }) if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { t.Errorf("Do = %v; want %v", got, want) } if err != nil { t.Errorf("Do error = %v", err) } } func TestDoErr(t *testing.T) { var g Group someErr := errors.New("Some error") v, err := g.Do("key", func() (interface{}, error) { return nil, someErr }) if err != someErr { t.Errorf("Do error = %v; want someErr %v", err, someErr) } if v != nil { t.Errorf("unexpected non-nil value %#v", v) } } func TestDoDupSuppress(t *testing.T) { var g Group c := make(chan string) var calls int32 fn := func() (interface{}, error) { atomic.AddInt32(&calls, 1) return <-c, nil } const n = 10 var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go func() { v, err := g.Do("key", fn) if err != nil { t.Errorf("Do error: %v", err) } if v.(string) != "bar" { t.Errorf("got %q; want %q", v, "bar") } wg.Done() }() } time.Sleep(100 * time.Millisecond) // let goroutines above block c <- "bar" wg.Wait() if got := atomic.LoadInt32(&calls); got != 1 { t.Errorf("number of calls = %d; want 1", got) } } golang-go4-0.0~git20230225.2148625/syncutil/syncdebug/000077500000000000000000000000001445700726600215335ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/syncutil/syncdebug/syncdebug.go000066400000000000000000000110001445700726600240350ustar00rootroot00000000000000/* Copyright 2013 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package syncdebug contains facilities for debugging synchronization // problems. package syncdebug // import "go4.org/syncutil/syncdebug" import ( "bytes" "fmt" "log" "runtime" "sync" "sync/atomic" "time" "go4.org/strutil" ) // RWMutexTracker is a sync.RWMutex that tracks who owns the current // exclusive lock. It's used for debugging deadlocks. type RWMutexTracker struct { mu sync.RWMutex // Atomic counters for number waiting and having read and write locks. nwaitr int32 nwaitw int32 nhaver int32 nhavew int32 // should always be 0 or 1 logOnce sync.Once hmu sync.Mutex holder []byte holdr map[int64]bool // goroutines holding read lock } const stackBufSize = 16 << 20 var stackBuf = make(chan []byte, 8) func getBuf() []byte { select { case b := <-stackBuf: return b[:stackBufSize] default: return make([]byte, stackBufSize) } } func putBuf(b []byte) { select { case stackBuf <- b: default: } } var goroutineSpace = []byte("goroutine ") // GoroutineID returns the current goroutine's ID. // Use of this function is almost always a terrible idea. // It is also very slow. // GoroutineID is intended only for debugging. // In particular, it is used by syncutil. func GoroutineID() int64 { b := getBuf() defer putBuf(b) b = b[:runtime.Stack(b, false)] // Parse the 4707 out of "goroutine 4707 [" b = bytes.TrimPrefix(b, goroutineSpace) i := bytes.IndexByte(b, ' ') if i < 0 { panic(fmt.Sprintf("No space found in %q", b)) } b = b[:i] n, err := strutil.ParseUintBytes(b, 10, 64) if err != nil { panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) } return int64(n) } func (m *RWMutexTracker) startLogger() { go func() { var buf bytes.Buffer for { time.Sleep(1 * time.Second) buf.Reset() m.hmu.Lock() for gid := range m.holdr { fmt.Fprintf(&buf, " [%d]", gid) } m.hmu.Unlock() log.Printf("Mutex %p: waitW %d haveW %d waitR %d haveR %d %s", m, atomic.LoadInt32(&m.nwaitw), atomic.LoadInt32(&m.nhavew), atomic.LoadInt32(&m.nwaitr), atomic.LoadInt32(&m.nhaver), buf.Bytes()) } }() } func (m *RWMutexTracker) Lock() { m.logOnce.Do(m.startLogger) atomic.AddInt32(&m.nwaitw, 1) m.mu.Lock() atomic.AddInt32(&m.nwaitw, -1) atomic.AddInt32(&m.nhavew, 1) m.hmu.Lock() defer m.hmu.Unlock() if len(m.holder) == 0 { m.holder = make([]byte, stackBufSize) } m.holder = m.holder[:runtime.Stack(m.holder[:stackBufSize], false)] log.Printf("Lock at %s", string(m.holder)) } func (m *RWMutexTracker) Unlock() { m.hmu.Lock() m.holder = nil m.hmu.Unlock() atomic.AddInt32(&m.nhavew, -1) m.mu.Unlock() } func (m *RWMutexTracker) RLock() { m.logOnce.Do(m.startLogger) atomic.AddInt32(&m.nwaitr, 1) // Catch read-write-read lock. See if somebody (us? via // another goroutine?) already has a read lock, and then // somebody else is waiting to write, meaning our second read // will deadlock. if atomic.LoadInt32(&m.nhaver) > 0 && atomic.LoadInt32(&m.nwaitw) > 0 { buf := getBuf() buf = buf[:runtime.Stack(buf, false)] log.Printf("Potential R-W-R deadlock at: %s", buf) putBuf(buf) } m.mu.RLock() atomic.AddInt32(&m.nwaitr, -1) atomic.AddInt32(&m.nhaver, 1) gid := GoroutineID() m.hmu.Lock() defer m.hmu.Unlock() if m.holdr == nil { m.holdr = make(map[int64]bool) } if m.holdr[gid] { buf := getBuf() buf = buf[:runtime.Stack(buf, false)] log.Fatalf("Recursive call to RLock: %s", buf) } m.holdr[gid] = true } func stack() []byte { buf := make([]byte, 1024) return buf[:runtime.Stack(buf, false)] } func (m *RWMutexTracker) RUnlock() { atomic.AddInt32(&m.nhaver, -1) gid := GoroutineID() m.hmu.Lock() delete(m.holdr, gid) m.hmu.Unlock() m.mu.RUnlock() } // Holder returns the stack trace of the current exclusive lock holder's stack // when it acquired the lock (with Lock). It returns the empty string if the lock // is not currently held. func (m *RWMutexTracker) Holder() string { m.hmu.Lock() defer m.hmu.Unlock() return string(m.holder) } golang-go4-0.0~git20230225.2148625/syncutil/syncdebug/syncdebug_test.go000066400000000000000000000014731445700726600251110ustar00rootroot00000000000000/* Copyright 2013 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package syncdebug import "testing" func TestGoroutineID(t *testing.T) { c := make(chan int64, 2) c <- GoroutineID() go func() { c <- GoroutineID() }() if a, b := <-c, <-c; a == b { t.Errorf("both goroutine IDs were %d; expected different", a) } } golang-go4-0.0~git20230225.2148625/syncutil/syncutil.go000066400000000000000000000012461445700726600217540ustar00rootroot00000000000000/* Copyright 2014 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package syncutil provides various synchronization utilities. package syncutil // import "go4.org/syncutil" golang-go4-0.0~git20230225.2148625/testing/000077500000000000000000000000001445700726600173535ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/testing/functest/000077500000000000000000000000001445700726600212065ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/testing/functest/functest.go000066400000000000000000000172651445700726600234030ustar00rootroot00000000000000/* Copyright 2016 The go4.org Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package functest contains utilities to ease writing table-driven // tests for pure functions and method. // // Example: // // func square(v int) int { return v * v } // // func TestFunc(t *testing.T) { // f := functest.New(square) // f.Test(t, // f.In(0).Want(0), // f.In(1).Want(1), // f.In(2).Want(4), // f.In(3).Want(9), // ) // } // // It can test whether things panic: // // f := functest.New(condPanic) // f.Test(t, // f.In(false, nil), // f.In(true, "boom").Check(func(res functest.Result) error { // if res.Panic != "boom" { // return fmt.Errorf("panic = %v; want boom", res.Panic) // } // return nil // }), // f.In(true, nil).Check(func(res functest.Result) error { // if res.Panic != nil || res.Paniked { // return fmt.Errorf("expected panic with nil value, got: %+v", res) // } // return nil // }), // ) // // If a test fails, functest does its best to format a useful error message. You can also // name test cases: // // f := functest.New(square) // f.Test(t, // f.In(0).Want(0), // f.In(1).Want(111), // f.In(2).Want(4), // f.Case("three").In(3).Want(999), // ) // // Which would fail like: // // --- FAIL: TestSquare (0.00s) // functest.go:304: square(1) = 1; want 111 // functest.go:304: three: square(3) = 9; want 999 // FAIL // package functest import ( "bytes" "fmt" "reflect" "runtime" "strings" "testing" ) // Func is a wrapper around a func to test. // It must be created with New. type Func struct { // Name is the name of the function to use in error messages. // In most cases it is initialized by New, unless the function // being tested is an anonymous function. Name string f interface{} // the func fv reflect.Value // of f } var removePunc = strings.NewReplacer("(", "", ")", "", "*", "") // New wraps a function for testing. // The provided value f must be a function or method. func New(f interface{}) *Func { fv := reflect.ValueOf(f) if fv.Kind() != reflect.Func { panic("argument to New must be a func") } var name string rf := runtime.FuncForPC(fv.Pointer()) if rf != nil { name = rf.Name() if methType := strings.LastIndex(name, ".("); methType != -1 { name = removePunc.Replace(name[methType+2:]) } else if lastDot := strings.LastIndex(name, "."); lastDot != -1 { name = name[lastDot+1:] if strings.HasPrefix(name, "func") { // Looks like some anonymous function. Prefer naming it "f". name = "f" } } } else { name = "f" } return &Func{ f: f, fv: fv, Name: name, } } // Result is the result of a function call, for use with Check. type Result struct { // Result is the return value(s) of the function. Result []interface{} // Panic is the panic value of the function. Panic interface{} // Panicked is whether the function paniced. // It can be used to determine whether a function // called panic(nil). Panicked bool } // Case is a test case to run. // // Test cases can be either named or unnamed, depending on how they're // created. Naming cases is optional; all failures messages aim to // have useful output and include the input to the function. // // Unless the function's arity is zero, all cases should have their input // set with In. // // The case's expected output can be set with Want and/or Check. type Case struct { f *Func in []interface{} name string // optional want []interface{} // non-nil if we check args checkRes []func(Result) error } // Case returns a new named case. It should be modified before use. func (f *Func) Case(name string) *Case { return &Case{f: f, name: name} } // In returns a new unnamed test case. It will be identified by its arguments // only. func (f *Func) In(args ...interface{}) *Case { return &Case{f: f, in: args} } // In sets the arguments of c used to call f. func (c *Case) In(args ...interface{}) *Case { c.in = args return c } // Want sets the expected result values of the test case. // Want modifies and returns c. // Callers my use both Want and Check. func (c *Case) Want(result ...interface{}) *Case { if c.want != nil { panic("duplicate Want declared on functest.Case") } c.want = result numOut := c.f.fv.Type().NumOut() if len(result) != numOut { // TODO: let caller providing only interesting result values, or // provide matchers. panic(fmt.Sprintf("Want called with %d values; function returns %d values", len(result), numOut)) } return c } // Check adds a function to check the result of the case's function // call. It is a low-level function when Want is insufficient. // For instance, it allows checking whether a function panics. // If no checker functions are registered, function panics are considered // a test failure. // // Check modifies and returns c. // Callers my use both Want and Check, and may use Check multiple times. func (c *Case) Check(checker func(Result) error) *Case { c.checkRes = append(c.checkRes, checker) return c } // Test runs the provided test cases against f. // If any test cases fail, t.Errorf is called. func (f *Func) Test(t testing.TB, cases ...*Case) { for _, tc := range cases { f.testCase(t, tc) } } func (f *Func) checkCall(in []reflect.Value) (out []reflect.Value, didPanic bool, panicValue interface{}) { defer func() { panicValue = recover() }() didPanic = true out = f.fv.Call(in) didPanic = false return } var nilEmptyInterface = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) func (f *Func) testCase(t testing.TB, c *Case) { // Non-variadic: ft := f.fv.Type() inReg := ft.NumIn() if ft.IsVariadic() { inReg-- if len(c.in) < inReg { c.errorf(t, ": input has %d arguments; func requires at least %d", len(c.in), inReg) return } } else if len(c.in) != ft.NumIn() { c.errorf(t, ": input has %d arguments; func takes %d", len(c.in), ft.NumIn()) return } inv := make([]reflect.Value, len(c.in)) for i, v := range c.in { if v == nil { inv[i] = nilEmptyInterface } else { inv[i] = reflect.ValueOf(v) } } got, didPanic, panicValue := f.checkCall(inv) var goti []interface{} if !didPanic { goti = make([]interface{}, len(got)) for i, rv := range got { goti[i] = rv.Interface() } } if c.want != nil { if !reflect.DeepEqual(goti, c.want) { c.errorf(t, " = %v; want %v", formatRes(goti), formatRes(c.want)) } } for _, checkRes := range c.checkRes { err := checkRes(Result{ Result: goti, Panic: panicValue, Panicked: didPanic, }) if err != nil { c.errorf(t, ": %v", err) } } if didPanic && (c.checkRes == nil) { c.errorf(t, ": panicked with %v", panicValue) } } func formatRes(res []interface{}) string { var buf bytes.Buffer if len(res) != 1 { buf.WriteByte('(') } formatValues(&buf, res) if len(res) != 1 { buf.WriteByte(')') } return buf.String() } func formatValues(buf *bytes.Buffer, vals []interface{}) { for i, v := range vals { if i != 0 { buf.WriteString(", ") } fmt.Fprintf(buf, "%#v", v) } } func (c *Case) errorf(t testing.TB, format string, args ...interface{}) { var buf bytes.Buffer if c.name != "" { fmt.Fprintf(&buf, "%s: ", c.name) } buf.WriteString(c.f.Name) buf.WriteString("(") formatValues(&buf, c.in) buf.WriteString(")") fmt.Fprintf(&buf, format, args...) t.Errorf("%s", buf.Bytes()) } golang-go4-0.0~git20230225.2148625/testing/functest/functest_test.go000066400000000000000000000057251445700726600244400ustar00rootroot00000000000000package functest import ( "bytes" "fmt" "testing" ) // trec is a testing.TB which logs Errorf calls to buf type trec struct { testing.TB // crash on unimplemented methods buf bytes.Buffer } func (t *trec) Errorf(format string, args ...interface{}) { t.buf.WriteString("ERR: ") fmt.Fprintf(&t.buf, format, args...) t.buf.WriteByte('\n') } func (t *trec) Logf(format string, args ...interface{}) { t.buf.WriteString("LOG: ") fmt.Fprintf(&t.buf, format, args...) t.buf.WriteByte('\n') } func (t *trec) String() string { return t.buf.String() } func add(a, b int) int { return a + b } func TestBasic(t *testing.T) { f := New(add) trec := new(trec) f.Test(trec, f.In(1, 2).Want(3), f.In(5, 6).Want(100), f.Case("also wrong").In(5, 6).Want(101), ) want := `ERR: add(5, 6) = 11; want 100 ERR: also wrong: add(5, 6) = 11; want 101 ` if got := trec.String(); got != want { t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want) } } func TestBasic_Strings(t *testing.T) { concat := func(a, b string) string { return a + b } f := New(concat) f.Name = "concat" trec := new(trec) f.Test(trec, f.In("a", "b").Want("ab"), f.In("a", "b\x00").Want("ab"), ) want := `ERR: concat("a", "b\x00") = "ab\x00"; want "ab" ` if got := trec.String(); got != want { t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want) } } func TestVariadic(t *testing.T) { sumVar := func(vals ...int) (sum int) { for _, v := range vals { sum += v } return } f := New(sumVar) f.Name = "sumVar" trec := new(trec) f.Test(trec, f.In().Want(0), f.In().Want(100), f.In(1).Want(1), f.In(1).Want(100), f.In(1, 2).Want(3), f.In(1, 2, 3).Want(6), f.In(1, 2, 3).Want(100), ) want := `ERR: sumVar() = 0; want 100 ERR: sumVar(1) = 1; want 100 ERR: sumVar(1, 2, 3) = 6; want 100 ` if got := trec.String(); got != want { t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want) } } func condPanic(doPanic bool, panicValue interface{}) { if doPanic { panic(panicValue) } } func TestPanic(t *testing.T) { f := New(condPanic) f.Name = "condPanic" trec := new(trec) f.Test(trec, f.In(false, nil), f.In(true, "boom").Check(func(res Result) error { trec.Logf("Got res: %+v", res) if res.Panic != "boom" { return fmt.Errorf("panic = %v; want boom", res.Panic) } return nil }), f.Case("panic with nil").In(true, nil), ) want := `LOG: Got res: {Result:[] Panic:boom Panicked:true} ERR: panic with nil: condPanic(true, ): panicked with ` if got := trec.String(); got != want { t.Errorf("Output mismatch.\nGot:\n%v\nWant:\n%v\n", got, want) } } func TestName_AutoFunc(t *testing.T) { testName(t, New(add), "add") } type SomeType struct{} func (t *SomeType) SomeMethod(int) int { return 123 } func TestName_AutoMethod(t *testing.T) { testName(t, New((*SomeType).SomeMethod), "SomeType.SomeMethod") } func testName(t *testing.T, f *Func, want string) { if f.Name != want { t.Errorf("name = %q; want %q", f.Name, want) } } golang-go4-0.0~git20230225.2148625/types/000077500000000000000000000000001445700726600170425ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/types/types.go000066400000000000000000000067011445700726600205410ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package types provides various common types. package types // import "go4.org/types" import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "strings" "sync" "time" ) var null_b = []byte("null") // NopCloser is an io.Closer that does nothing. var NopCloser io.Closer = CloseFunc(func() error { return nil }) // EmptyBody is a ReadCloser that returns EOF on Read and does nothing // on Close. var EmptyBody io.ReadCloser = ioutil.NopCloser(strings.NewReader("")) // Time3339 is a time.Time which encodes to and from JSON // as an RFC 3339 time in UTC. type Time3339 time.Time var ( _ json.Marshaler = Time3339{} _ json.Unmarshaler = (*Time3339)(nil) ) func (t Time3339) String() string { return time.Time(t).UTC().Format(time.RFC3339Nano) } func (t Time3339) MarshalJSON() ([]byte, error) { if t.Time().IsZero() { return null_b, nil } return json.Marshal(t.String()) } func (t *Time3339) UnmarshalJSON(b []byte) error { if bytes.Equal(b, null_b) { *t = Time3339{} return nil } if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) } s := string(b[1 : len(b)-1]) if s == "" { *t = Time3339{} return nil } tm, err := time.Parse(time.RFC3339Nano, s) if err != nil { if strings.HasPrefix(s, "0000-00-00T00:00:00") { *t = Time3339{} return nil } return err } *t = Time3339(tm) return nil } // ParseTime3339OrZero parses a string in RFC3339 format. If it's invalid, // the zero time value is returned instead. func ParseTime3339OrZero(v string) Time3339 { t, err := time.Parse(time.RFC3339Nano, v) if err != nil { return Time3339{} } return Time3339(t) } func ParseTime3339OrNil(v string) *Time3339 { t, err := time.Parse(time.RFC3339Nano, v) if err != nil { return nil } tm := Time3339(t) return &tm } // Time returns the time as a time.Time with slightly less stutter // than a manual conversion. func (t Time3339) Time() time.Time { return time.Time(t) } // IsAnyZero returns whether the time is Go zero or Unix zero. func (t *Time3339) IsAnyZero() bool { return t == nil || time.Time(*t).IsZero() || time.Time(*t).Unix() == 0 } // ByTime sorts times. type ByTime []time.Time func (s ByTime) Len() int { return len(s) } func (s ByTime) Less(i, j int) bool { return s[i].Before(s[j]) } func (s ByTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // NewOnceCloser returns a Closer wrapping c which only calls Close on c // once. Subsequent calls to Close return nil. func NewOnceCloser(c io.Closer) io.Closer { return &onceCloser{c: c} } type onceCloser struct { mu sync.Mutex c io.Closer } func (c *onceCloser) Close() error { c.mu.Lock() defer c.mu.Unlock() if c.c == nil { return nil } err := c.c.Close() c.c = nil return err } // CloseFunc implements io.Closer with a function. type CloseFunc func() error func (fn CloseFunc) Close() error { return fn() } golang-go4-0.0~git20230225.2148625/types/types_test.go000066400000000000000000000051251445700726600215770ustar00rootroot00000000000000/* Copyright 2013 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package types import ( "encoding/json" "strings" "testing" "time" ) func TestTime3339(t *testing.T) { tm := time.Unix(123, 456) t3 := Time3339(tm) type O struct { SomeTime Time3339 `json:"someTime"` } o := &O{SomeTime: t3} got, err := json.Marshal(o) if err != nil { t.Fatal(err) } goodEnc := "{\"someTime\":\"1970-01-01T00:02:03.000000456Z\"}" if string(got) != goodEnc { t.Errorf("Encoding wrong.\n Got: %q\nWant: %q", got, goodEnc) } ogot := &O{} err = json.Unmarshal([]byte(goodEnc), ogot) if err != nil { t.Fatal(err) } if !tm.Equal(ogot.SomeTime.Time()) { t.Errorf("Unmarshal got time %v; want %v", ogot.SomeTime.Time(), tm) } } func TestTime3339_Marshal(t *testing.T) { tests := []struct { in time.Time want string }{ {time.Time{}, "null"}, {time.Unix(1, 0), `"1970-01-01T00:00:01Z"`}, } for i, tt := range tests { got, err := Time3339(tt.in).MarshalJSON() if err != nil { t.Errorf("%d. marshal(%v) got error: %v", i, tt.in, err) continue } if string(got) != tt.want { t.Errorf("%d. marshal(%v) = %q; want %q", i, tt.in, got, tt.want) } } } func TestTime3339_empty(t *testing.T) { tests := []struct { enc string z bool }{ {enc: "null", z: true}, {enc: `""`, z: true}, {enc: "0000-00-00T00:00:00Z", z: true}, {enc: "0001-01-01T00:00:00Z", z: true}, {enc: "1970-01-01T00:00:00Z", z: true}, {enc: "2001-02-03T04:05:06Z", z: false}, {enc: "2001-02-03T04:05:06+06:00", z: false}, {enc: "2001-02-03T04:05:06-06:00", z: false}, {enc: "2001-02-03T04:05:06.123456789Z", z: false}, {enc: "2001-02-03T04:05:06.123456789+06:00", z: false}, {enc: "2001-02-03T04:05:06.123456789-06:00", z: false}, } for _, tt := range tests { var tm Time3339 enc := tt.enc if strings.Contains(enc, "T") { enc = "\"" + enc + "\"" } err := json.Unmarshal([]byte(enc), &tm) if err != nil { t.Errorf("unmarshal %q = %v", enc, err) } if tm.IsAnyZero() != tt.z { t.Errorf("unmarshal %q = %v (%d), %v; zero=%v; want %v", tt.enc, tm.Time(), tm.Time().Unix(), err, !tt.z, tt.z) } } } golang-go4-0.0~git20230225.2148625/wkfs/000077500000000000000000000000001445700726600166505ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/wkfs/gcs/000077500000000000000000000000001445700726600174245ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/wkfs/gcs/gcs.go000066400000000000000000000122031445700726600205250ustar00rootroot00000000000000/* Copyright 2014 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package gcs registers a Google Cloud Storage filesystem at the // well-known /gcs/ filesystem path if the current machine is running // on Google Compute Engine. // // It was initially only meant for small files, and as such, it can only // read files smaller than 1MB for now. package gcs // import "go4.org/wkfs/gcs" import ( "bytes" "fmt" "io" "io/ioutil" "os" "path" "strings" "time" "cloud.google.com/go/compute/metadata" "cloud.google.com/go/storage" "go4.org/wkfs" "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/option" ) // Max size for all files read, because we use a bytes.Reader as our file // reader, instead of storage.NewReader. This is because we get all wkfs.File // methods for free by embedding a bytes.Reader. This filesystem was only supposed // to be for configuration data only, so this is ok for now. const maxSize = 1 << 20 func init() { if !metadata.OnGCE() { return } hc, err := google.DefaultClient(oauth2.NoContext) if err != nil { registerBrokenFS(fmt.Errorf("could not get http client for context: %v", err)) return } ctx := context.Background() sc, err := storage.NewClient(ctx, option.WithHTTPClient(hc)) if err != nil { registerBrokenFS(fmt.Errorf("could not get cloud storage client: %v", err)) return } wkfs.RegisterFS("/gcs/", &gcsFS{ ctx: ctx, sc: sc, }) } type gcsFS struct { ctx context.Context sc *storage.Client err error // sticky error } func registerBrokenFS(err error) { wkfs.RegisterFS("/gcs/", &gcsFS{ err: err, }) } func (fs *gcsFS) parseName(name string) (bucket, fileName string, err error) { if fs.err != nil { return "", "", fs.err } name = strings.TrimPrefix(name, "/gcs/") i := strings.Index(name, "/") if i < 0 { return name, "", nil } return name[:i], name[i+1:], nil } // Open opens the named file for reading. It returns an error if the file size // is larger than 1 << 20. func (fs *gcsFS) Open(name string) (wkfs.File, error) { bucket, fileName, err := fs.parseName(name) if err != nil { return nil, err } obj := fs.sc.Bucket(bucket).Object(fileName) attrs, err := obj.Attrs(fs.ctx) if err != nil { return nil, err } size := attrs.Size if size > maxSize { return nil, fmt.Errorf("file %s too large (%d bytes) for /gcs/ filesystem", name, size) } rc, err := obj.NewReader(fs.ctx) if err != nil { return nil, err } defer rc.Close() slurp, err := ioutil.ReadAll(io.LimitReader(rc, size)) if err != nil { return nil, err } return &file{ name: name, Reader: bytes.NewReader(slurp), }, nil } func (fs *gcsFS) Stat(name string) (os.FileInfo, error) { return fs.Lstat(name) } func (fs *gcsFS) Lstat(name string) (os.FileInfo, error) { bucket, fileName, err := fs.parseName(name) if err != nil { return nil, err } attrs, err := fs.sc.Bucket(bucket).Object(fileName).Attrs(fs.ctx) if err == storage.ErrObjectNotExist { return nil, os.ErrNotExist } if err != nil { return nil, err } return &statInfo{ name: attrs.Name, size: attrs.Size, }, nil } func (fs *gcsFS) MkdirAll(path string, perm os.FileMode) error { return nil } func (fs *gcsFS) OpenFile(name string, flag int, perm os.FileMode) (wkfs.FileWriter, error) { bucket, fileName, err := fs.parseName(name) if err != nil { return nil, err } switch flag { case os.O_WRONLY | os.O_CREATE | os.O_EXCL: case os.O_WRONLY | os.O_CREATE | os.O_TRUNC: default: return nil, fmt.Errorf("Unsupported OpenFlag flag mode %d on Google Cloud Storage", flag) } if flag&os.O_EXCL != 0 { if _, err := fs.Stat(name); err == nil { return nil, os.ErrExist } } // TODO(mpl): consider adding perm to the object's ObjectAttrs.Metadata return fs.sc.Bucket(bucket).Object(fileName).NewWriter(fs.ctx), nil } func (fs *gcsFS) Remove(name string) error { bucket, fileName, err := fs.parseName(name) if err != nil { return err } return fs.sc.Bucket(bucket).Object(fileName).Delete(fs.ctx) } type statInfo struct { name string size int64 isDir bool modtime time.Time } func (si *statInfo) IsDir() bool { return si.isDir } func (si *statInfo) ModTime() time.Time { return si.modtime } func (si *statInfo) Mode() os.FileMode { return 0644 } func (si *statInfo) Name() string { return path.Base(si.name) } func (si *statInfo) Size() int64 { return si.size } func (si *statInfo) Sys() interface{} { return nil } type file struct { name string *bytes.Reader } func (*file) Close() error { return nil } func (f *file) Name() string { return path.Base(f.name) } func (f *file) Stat() (os.FileInfo, error) { panic("Stat not implemented on /gcs/ files yet") } golang-go4-0.0~git20230225.2148625/wkfs/gcs/gcs_test.go000066400000000000000000000044471445700726600215770ustar00rootroot00000000000000/* Copyright 2015 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package gcs import ( "bytes" "flag" "io" "strings" "testing" "cloud.google.com/go/compute/metadata" "cloud.google.com/go/storage" "go4.org/wkfs" "golang.org/x/net/context" "google.golang.org/api/iterator" ) var flagBucket = flag.String("bucket", "", "Google Cloud Storage bucket where to run the tests. It should be empty.") func TestWriteRead(t *testing.T) { if !metadata.OnGCE() { t.Skipf("Not testing on GCE") } if *flagBucket == "" { t.Skipf("No bucket specified") } ctx := context.Background() cl, err := storage.NewClient(ctx) it := cl.Bucket(*flagBucket).Objects(ctx, nil) if _, err := it.Next(); err != iterator.Done { if err == nil { t.Fatalf("Bucket %v is not empty, aborting test.", *flagBucket) } t.Fatalf("unexpected bucket iteration error: %v", err) } // Write to camli-gcs_test.txt filename := "camli-gcs_test.txt" gcsPath := "/gcs/" + *flagBucket + "/" + filename f, err := wkfs.Create(gcsPath) if err != nil { t.Fatalf("error creating %v: %v", gcsPath, err) } defer func() { if err := wkfs.Remove(gcsPath); err != nil { t.Fatalf("error while cleaning up %v: %v", gcsPath, err) } }() data := "Hello World" if _, err := io.Copy(f, strings.NewReader(data)); err != nil { t.Fatalf("error writing to %v: %v", gcsPath, err) } if err := f.Close(); err != nil { t.Fatalf("error closing %v: %v", gcsPath, err) } // Read back from camli-gcs_test.txt g, err := wkfs.Open(gcsPath) if err != nil { t.Fatalf("error opening %v: %v", gcsPath, err) } defer g.Close() var buf bytes.Buffer if _, err := io.Copy(&buf, g); err != nil { t.Fatalf("error reading %v: %v", gcsPath, err) } if buf.String() != data { t.Fatalf("error with %v contents: got %v, wanted %v", gcsPath, buf.String(), data) } } golang-go4-0.0~git20230225.2148625/wkfs/wkfs.go000066400000000000000000000102621445700726600201520ustar00rootroot00000000000000/* Copyright 2014 The Perkeep Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package wkfs implements the pluggable "well-known filesystem" abstraction layer. // // Instead of accessing files directly through the operating system // using os.Open or os.Stat, code should use wkfs.Open or wkfs.Stat, // which first try to intercept paths at well-known top-level // directories representing previously-registered mount types, // otherwise fall through to the operating system paths. // // Example of top-level well-known directories that might be // registered include /gcs/bucket/object for Google Cloud Storage or // /s3/bucket/object for AWS S3. package wkfs // import "go4.org/wkfs" import ( "io" "io/ioutil" "os" "strings" ) type File interface { io.Reader io.ReaderAt io.Closer io.Seeker Name() string Stat() (os.FileInfo, error) } type FileWriter interface { io.Writer io.Closer } func Open(name string) (File, error) { return fs(name).Open(name) } func Stat(name string) (os.FileInfo, error) { return fs(name).Stat(name) } func Lstat(name string) (os.FileInfo, error) { return fs(name).Lstat(name) } func MkdirAll(path string, perm os.FileMode) error { return fs(path).MkdirAll(path, perm) } func OpenFile(name string, flag int, perm os.FileMode) (FileWriter, error) { return fs(name).OpenFile(name, flag, perm) } func Remove(name string) error { return fs(name).Remove(name) } func Create(name string) (FileWriter, error) { // like os.Create but WRONLY instead of RDWR because we don't // expose a Reader here. return OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) } func fs(name string) FileSystem { for pfx, fs := range wkFS { if strings.HasPrefix(name, pfx) { return fs } } return osFS{} } type osFS struct{} func (osFS) Open(name string) (File, error) { return os.Open(name) } func (osFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) } func (osFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) } func (osFS) MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, perm) } func (osFS) OpenFile(name string, flag int, perm os.FileMode) (FileWriter, error) { return os.OpenFile(name, flag, perm) } func (osFS) Remove(name string) error { return os.Remove(name) } type FileSystem interface { Open(name string) (File, error) OpenFile(name string, flag int, perm os.FileMode) (FileWriter, error) Stat(name string) (os.FileInfo, error) Lstat(name string) (os.FileInfo, error) MkdirAll(path string, perm os.FileMode) error Remove(name string) error } // well-known filesystems var wkFS = map[string]FileSystem{} // RegisterFS registers a well-known filesystem. It intercepts // anything beginning with prefix (which must start and end with a // forward slash) and forwards it to fs. func RegisterFS(prefix string, fs FileSystem) { if !strings.HasPrefix(prefix, "/") || !strings.HasSuffix(prefix, "/") { panic("bogus prefix: " + prefix) } if _, dup := wkFS[prefix]; dup { panic("duplication registration of " + prefix) } wkFS[prefix] = fs } // WriteFile writes data to a file named by filename. // If the file does not exist, WriteFile creates it with permissions perm; // otherwise WriteFile truncates it before writing. func WriteFile(filename string, data []byte, perm os.FileMode) error { f, err := OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } n, err := f.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if err1 := f.Close(); err == nil { err = err1 } return err } func ReadFile(filename string) ([]byte, error) { f, err := Open(filename) if err != nil { return nil, err } defer f.Close() return ioutil.ReadAll(f) } golang-go4-0.0~git20230225.2148625/writerutil/000077500000000000000000000000001445700726600201105ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/writerutil/writerutil.go000066400000000000000000000061461445700726600226600ustar00rootroot00000000000000/* Copyright 2016 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package writerutil contains io.Writer types. package writerutil // import "go4.org/writerutil" import ( "bytes" "strconv" ) // PrefixSuffixSaver is an io.Writer which retains the first N bytes // and the last N bytes written to it. The Bytes method reconstructs // it with a pretty error message. // It is copied from os/exec/exec.go of the Go stdlib. type PrefixSuffixSaver struct { N int // max size of prefix or suffix prefix []byte suffix []byte // ring buffer once len(suffix) == N suffixOff int // offset to write into suffix skipped int64 // TODO(bradfitz): we could keep one large []byte and use part of it for // the prefix, reserve space for the '... Omitting N bytes ...' message, // then the ring buffer suffix, and just rearrange the ring buffer // suffix when Bytes() is called, but it doesn't seem worth it for // now just for error messages. It's only ~64KB anyway. } func (w *PrefixSuffixSaver) Write(p []byte) (n int, err error) { lenp := len(p) p = w.fill(&w.prefix, p) // Only keep the last w.N bytes of suffix data. if overage := len(p) - w.N; overage > 0 { p = p[overage:] w.skipped += int64(overage) } p = w.fill(&w.suffix, p) // w.suffix is full now if p is non-empty. Overwrite it in a circle. for len(p) > 0 { // 0, 1, or 2 iterations. n := copy(w.suffix[w.suffixOff:], p) p = p[n:] w.skipped += int64(n) w.suffixOff += n if w.suffixOff == w.N { w.suffixOff = 0 } } return lenp, nil } // fill appends up to len(p) bytes of p to *dst, such that *dst does not // grow larger than w.N. It returns the un-appended suffix of p. func (w *PrefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) { if remain := w.N - len(*dst); remain > 0 { add := minInt(len(p), remain) *dst = append(*dst, p[:add]...) p = p[add:] } return p } // Bytes returns a slice of the bytes, or a copy of the bytes, retained by w. // If more bytes than could be retained were written to w, it returns a // concatenation of the N first bytes, a message for how many bytes were dropped, // and the N last bytes. func (w *PrefixSuffixSaver) Bytes() []byte { if w.suffix == nil { return w.prefix } if w.skipped == 0 { return append(w.prefix, w.suffix...) } var buf bytes.Buffer buf.Grow(len(w.prefix) + len(w.suffix) + 50) buf.Write(w.prefix) buf.WriteString("\n... omitting ") buf.WriteString(strconv.FormatInt(w.skipped, 10)) buf.WriteString(" bytes ...\n") buf.Write(w.suffix[w.suffixOff:]) buf.Write(w.suffix[:w.suffixOff]) return buf.Bytes() } func minInt(a, b int) int { if a < b { return a } return b } golang-go4-0.0~git20230225.2148625/writerutil/writerutil_test.go000066400000000000000000000031641445700726600237140ustar00rootroot00000000000000/* Copyright 2016 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package writerutil import ( "io" "testing" ) func TestPrefixSuffixSaver(t *testing.T) { tests := []struct { N int writes []string want string }{ { N: 2, writes: nil, want: "", }, { N: 2, writes: []string{"a"}, want: "a", }, { N: 2, writes: []string{"abc", "d"}, want: "abcd", }, { N: 2, writes: []string{"abc", "d", "e"}, want: "ab\n... omitting 1 bytes ...\nde", }, { N: 2, writes: []string{"ab______________________yz"}, want: "ab\n... omitting 22 bytes ...\nyz", }, { N: 2, writes: []string{"ab_______________________y", "z"}, want: "ab\n... omitting 23 bytes ...\nyz", }, } for i, tt := range tests { w := &PrefixSuffixSaver{N: tt.N} for _, s := range tt.writes { n, err := io.WriteString(w, s) if err != nil || n != len(s) { t.Errorf("%d. WriteString(%q) = %v, %v; want %v, %v", i, s, n, err, len(s), nil) } } if got := string(w.Bytes()); got != tt.want { t.Errorf("%d. Bytes = %q; want %q", i, got, tt.want) } } } golang-go4-0.0~git20230225.2148625/xdgdir/000077500000000000000000000000001445700726600171575ustar00rootroot00000000000000golang-go4-0.0~git20230225.2148625/xdgdir/example_test.go000066400000000000000000000026121445700726600222010ustar00rootroot00000000000000/* Copyright 2017 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package xdgdir_test import ( "fmt" "os" "go4.org/xdgdir" ) func Example() { // Print the absolute path of the current user's XDG_CONFIG_DIR. fmt.Println(xdgdir.Config.Path()) // Read a file from $XDG_CONFIG_DIR/myconfig.json. // This will search for a file named "myconfig.json" inside // $XDG_CONFIG_DIR and then each entry inside $XDG_CONFIG_DIRS. // It opens and returns the first file it finds, or returns an error. if f, err := xdgdir.Data.Create("myconfig.json"); err == nil { fmt.Fprintln(f, "Hello, World!") if err := f.Close(); err != nil { fmt.Fprintln(os.Stderr, err) } } else { fmt.Fprintln(os.Stderr, err) } // Write a file to $XDG_DATA_DIR/myapp/foo.txt if f, err := xdgdir.Data.Create("myapp/foo.txt"); err == nil { fmt.Fprintln(f, "Hello, World!") f.Close() } else { fmt.Fprintln(os.Stderr, err) } } golang-go4-0.0~git20230225.2148625/xdgdir/xdgdir.go000066400000000000000000000133711445700726600207740ustar00rootroot00000000000000/* Copyright 2017 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package xdgdir implements the Free Desktop Base Directory // specification for locating directories. // // The specification is at // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html package xdgdir // import "go4.org/xdgdir" import ( "errors" "fmt" "os" "os/user" "path/filepath" "syscall" ) // Directories defined by the specification. var ( Data Dir Config Dir Cache Dir Runtime Dir ) func init() { // Placed in init for the sake of readable docs. Data = Dir{ env: "XDG_DATA_HOME", dirsEnv: "XDG_DATA_DIRS", fallback: ".local/share", dirsFallback: []string{"/usr/local/share", "/usr/share"}, } Config = Dir{ env: "XDG_CONFIG_HOME", dirsEnv: "XDG_CONFIG_DIRS", fallback: ".config", dirsFallback: []string{"/etc/xdg"}, } Cache = Dir{ env: "XDG_CACHE_HOME", fallback: ".cache", } Runtime = Dir{ env: "XDG_RUNTIME_DIR", userOwned: true, } } // A Dir is a logical base directory along with additional search // directories. type Dir struct { // env is the name of the environment variable for the base directory // relative to which files should be written. env string // dirsEnv is the name of the environment variable containing // preference-ordered base directories to search for files. dirsEnv string // fallback is the home-relative path to use if the variable named by // env is not set. fallback string // dirsFallback is the list of paths to use if the variable named by // dirsEnv is not set. dirsFallback []string // If userOwned is true, then for the directory to be considered // valid, it must be owned by the user with the mode 700. This is // only used for XDG_RUNTIME_DIR. userOwned bool } // String returns the name of the primary environment variable for the // directory. func (d Dir) String() string { if d.env == "" { panic("xdgdir.Dir.String() on zero Dir") } return d.env } // Path returns the absolute path of the primary directory, or an empty // string if there's no suitable directory present. This is the path // that should be used for writing files. func (d Dir) Path() string { if d.env == "" { panic("xdgdir.Dir.Path() on zero Dir") } p := d.path() if p != "" && d.userOwned { info, err := os.Stat(p) if err != nil { return "" } if !info.IsDir() || info.Mode().Perm() != 0700 { return "" } st, ok := info.Sys().(*syscall.Stat_t) if !ok || int(st.Uid) != geteuid() { return "" } } return p } func (d Dir) path() string { if e := getenv(d.env); isValidPath(e) { return e } if d.fallback == "" { return "" } home := findHome() if home == "" { return "" } p := filepath.Join(home, d.fallback) if !isValidPath(p) { return "" } return p } // SearchPaths returns the list of paths (in descending order of // preference) to search for files. func (d Dir) SearchPaths() []string { if d.env == "" { panic("xdgdir.Dir.SearchPaths() on zero Dir") } var paths []string if p := d.Path(); p != "" { paths = append(paths, p) } if d.dirsEnv == "" { return paths } e := getenv(d.dirsEnv) if e == "" { paths = append(paths, d.dirsFallback...) return paths } epaths := filepath.SplitList(e) n := 0 for _, p := range epaths { if isValidPath(p) { epaths[n] = p n++ } } paths = append(paths, epaths[:n]...) return paths } // Open opens the named file inside the directory for reading. If the // directory has multiple search paths, each path is checked in order // for the file and the first one found is opened. func (d Dir) Open(name string) (*os.File, error) { if d.env == "" { return nil, errors.New("xdgdir: Open on zero Dir") } paths := d.SearchPaths() if len(paths) == 0 { return nil, fmt.Errorf("xdgdir: open %s: %s is invalid or not set", name, d.env) } var firstErr error for _, p := range paths { f, err := os.Open(filepath.Join(p, name)) if err == nil { return f, nil } else if !os.IsNotExist(err) { firstErr = err } } if firstErr != nil { return nil, firstErr } return nil, &os.PathError{ Op: "Open", Path: filepath.Join("$"+d.env, name), Err: os.ErrNotExist, } } // Create creates the named file inside the directory mode 0666 (before // umask), truncating it if it already exists. Parent directories of // the file will be created with mode 0700. func (d Dir) Create(name string) (*os.File, error) { if d.env == "" { return nil, errors.New("xdgdir: Create on zero Dir") } p := d.Path() if p == "" { return nil, fmt.Errorf("xdgdir: create %s: %s is invalid or not set", name, d.env) } fp := filepath.Join(p, name) if err := os.MkdirAll(filepath.Dir(fp), 0700); err != nil { return nil, err } return os.Create(fp) } func isValidPath(path string) bool { return path != "" && filepath.IsAbs(path) } // findHome returns the user's home directory or the empty string if it // can't be found. It can be faked for testing. var findHome = func() string { if h := getenv("HOME"); h != "" { return h } u, err := user.Current() if err != nil { return "" } return u.HomeDir } // getenv retrieves an environment variable. It can be faked for testing. var getenv = os.Getenv // geteuid retrieves the effective user ID of the process. It can be faked for testing. var geteuid = os.Geteuid golang-go4-0.0~git20230225.2148625/xdgdir/xdgdir_test.go000066400000000000000000000271471445700726600220410ustar00rootroot00000000000000/* Copyright 2017 The go4 Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package xdgdir import ( "io/ioutil" "os" "path/filepath" "testing" ) func TestDir_Path(t *testing.T) { td := newTempDir(t) defer td.cleanup() allopenDir := td.mkdir("allopen", 0777) readonlyDir := td.mkdir("readonly", 0400) secureDir := td.mkdir("secure", 0700) tests := []struct { dir Dir env env path string geteuid func() int }{ { dir: Data, env: env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "/foo/data"}, path: "/foo/data", }, { dir: Data, env: env{"HOME": "/xHOMEx/me"}, path: "/xHOMEx/me/.local/share", }, { dir: Data, env: env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "relative/path"}, path: "/xHOMEx/me/.local/share", }, { dir: Data, env: env{}, path: "", }, { dir: Data, env: env{"HOME": "relative/path"}, path: "", }, { dir: Config, env: env{"HOME": "/xHOMEx/me", "XDG_CONFIG_HOME": "/foo/config"}, path: "/foo/config", }, { dir: Config, env: env{"HOME": "/xHOMEx/me"}, path: "/xHOMEx/me/.config", }, { dir: Config, env: env{"HOME": "/xHOMEx/me", "XDG_CONFIG_HOME": "relative/path"}, path: "/xHOMEx/me/.config", }, { dir: Config, env: env{}, path: "", }, { dir: Cache, env: env{"HOME": "/xHOMEx/me", "XDG_CACHE_HOME": "/foo/cache"}, path: "/foo/cache", }, { dir: Cache, env: env{"HOME": "/xHOMEx/me"}, path: "/xHOMEx/me/.cache", }, { dir: Cache, env: env{"HOME": "/xHOMEx/me", "XDG_CACHE_HOME": "relative/path"}, path: "/xHOMEx/me/.cache", }, { dir: Cache, env: env{}, path: "", }, { dir: Runtime, env: env{"XDG_RUNTIME_DIR": secureDir}, path: secureDir, }, { dir: Runtime, env: env{"XDG_RUNTIME_DIR": secureDir}, geteuid: func() int { return os.Geteuid() + 1 }, path: "", }, { dir: Runtime, env: env{"XDG_RUNTIME_DIR": readonlyDir}, path: "", }, { dir: Runtime, env: env{"XDG_RUNTIME_DIR": allopenDir}, path: "", }, { dir: Runtime, env: env{"HOME": secureDir}, path: "", }, { dir: Runtime, env: env{}, path: "", }, } for _, test := range tests { test.env.set() if test.geteuid != nil { geteuid = test.geteuid } else { geteuid = os.Geteuid } if path := test.dir.Path(); path != test.path { var euidMod string if test.geteuid != nil { euidMod = " (euid modified)" } t.Errorf("In environment %v%s, %v.Path() = %q; want %q", test.env, euidMod, test.dir, path, test.path) } } } func TestDir_SearchPaths(t *testing.T) { td := newTempDir(t) defer td.cleanup() allopenDir := td.mkdir("allopen", 0777) secureDir := td.mkdir("secure", 0700) tests := []struct { dir Dir env env paths []string }{ { dir: Data, env: env{}, paths: []string{"/usr/local/share", "/usr/share"}, }, { dir: Data, env: env{"HOME": "/xHOMEx/me"}, paths: []string{"/xHOMEx/me/.local/share", "/usr/local/share", "/usr/share"}, }, { dir: Data, env: env{"HOME": "/xHOMEx/me", "XDG_DATA_HOME": "/foo/data"}, paths: []string{"/foo/data", "/usr/local/share", "/usr/share"}, }, { dir: Data, env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data"}, paths: []string{"/foo/data", "/mybacon/data"}, }, { dir: Data, env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:/eggs/data"}, paths: []string{"/foo/data", "/mybacon/data", "/eggs/data"}, }, { dir: Data, env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:/eggs/data:/woka/woka"}, paths: []string{"/foo/data", "/mybacon/data", "/eggs/data", "/woka/woka"}, }, { dir: Data, env: env{"XDG_DATA_HOME": "/foo/data", "XDG_DATA_DIRS": "/mybacon/data:relative/path:/woka/woka"}, paths: []string{"/foo/data", "/mybacon/data", "/woka/woka"}, }, { dir: Data, env: env{"XDG_DATA_HOME": "relative/path", "XDG_DATA_DIRS": "/mybacon/data:relative/path:/woka/woka"}, paths: []string{"/mybacon/data", "/woka/woka"}, }, { dir: Data, env: env{"XDG_DATA_DIRS": "/mybacon/data:/eggs/data:/woka/woka"}, paths: []string{"/mybacon/data", "/eggs/data", "/woka/woka"}, }, { dir: Config, env: env{"XDG_CONFIG_HOME": "/foo/config", "XDG_CONFIG_DIRS": "/mybacon/config:/eggs/config:/woka/woka"}, paths: []string{"/foo/config", "/mybacon/config", "/eggs/config", "/woka/woka"}, }, { // Cache only has primary dir dir: Cache, env: env{"XDG_CACHE_HOME": "/foo/cache", "XDG_CACHE_DIRS": "/mybacon/config:/eggs/config:/woka/woka"}, paths: []string{"/foo/cache"}, }, { dir: Runtime, env: env{"XDG_RUNTIME_DIR": secureDir}, paths: []string{secureDir}, }, { dir: Runtime, env: env{"XDG_RUNTIME_DIR": allopenDir}, paths: []string{}, }, } for _, test := range tests { test.env.set() paths := test.dir.SearchPaths() if !stringsEqual(paths, test.paths) { t.Errorf("In environment %v, %v.SearchPaths() = %q; want %q", test.env, test.dir, paths, test.paths) } } } func TestDir_Open(t *testing.T) { td := newTempDir(t) defer td.cleanup() junkDir := td.mkdir("junk", 0777) dir1 := td.mkdir("dir1", 0777) dir2 := td.mkdir("dir2", 0777) dir3 := td.mkdir("dir3", 0777) td.newFile("dir1/foo.txt", "foo") td.newFile("dir1/multiple.txt", "1") td.newFile("dir2/bar.txt", "bar") td.newFile("dir2/only2_3.txt", "this is 2") td.newFile("dir2/multiple.txt", "2") td.newFile("dir3/multiple.txt", "3") td.newFile("dir3/only2_3.txt", "this is 3") tests := []struct { dir Dir env env name string path string err bool }{ { dir: Data, env: env{}, name: "foo.txt", err: true, }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": junkDir}, name: "foo.txt", path: filepath.Join(dir1, "foo.txt"), }, { dir: Data, env: env{"XDG_DATA_HOME": junkDir, "XDG_DATA_DIRS": junkDir}, name: "foo.txt", err: true, }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2}, name: "foo.txt", path: filepath.Join(dir1, "foo.txt"), }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2}, name: "bar.txt", path: filepath.Join(dir2, "bar.txt"), }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3}, name: "NOTREAL.txt", err: true, }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3}, name: "foo.txt", path: filepath.Join(dir1, "foo.txt"), }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3}, name: "bar.txt", path: filepath.Join(dir2, "bar.txt"), }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3}, name: "multiple.txt", path: filepath.Join(dir1, "multiple.txt"), }, { dir: Data, env: env{"XDG_DATA_HOME": dir1, "XDG_DATA_DIRS": dir2 + ":" + dir3}, name: "only2_3.txt", path: filepath.Join(dir2, "only2_3.txt"), }, } for _, test := range tests { test.env.set() f, err := test.dir.Open(test.name) switch { case err == nil && test.err: t.Errorf("In environment %v, %v.Open(%q) succeeded; want error", test.env, test.dir, test.name) case err == nil && !test.err && f.Name() != test.path: t.Errorf("In environment %v, %v.Open(%q).Name() = %q; want %q", test.env, test.dir, test.name, f.Name(), test.path) case err != nil && !test.err: t.Errorf("In environment %v, %v.Open(%q) error: %v", test.env, test.dir, test.name, err) } if f != nil { f.Close() } } } func TestDir_Create(t *testing.T) { td := newTempDir(t) defer td.cleanup() junkDir := td.mkdir("junk", 0777) dataDir := td.mkdir("data", 0777) tests := []struct { dir Dir env env name string path string err bool permChecks []permCheck }{ { dir: Data, env: env{"XDG_DATA_HOME": dataDir, "XDG_DATA_DIRS": junkDir}, name: "foo01", path: filepath.Join(dataDir, "foo01"), }, { dir: Data, env: env{}, name: "foo02", err: true, }, { dir: Data, env: env{"XDG_DATA_HOME": dataDir, "XDG_DATA_DIRS": junkDir}, name: filepath.Join("foo03", "bar"), path: filepath.Join(dataDir, "foo03", "bar"), permChecks: []permCheck{ {filepath.Join(dataDir, "foo03"), 0700}, }, }, { dir: Data, env: env{"XDG_DATA_HOME": filepath.Join(td.dir, "NOTREAL"), "XDG_DATA_DIRS": junkDir}, name: filepath.Join("foo04", "bar"), path: filepath.Join(td.dir, "NOTREAL", "foo04", "bar"), permChecks: []permCheck{ {filepath.Join(td.dir, "NOTREAL"), 0700}, {filepath.Join(td.dir, "NOTREAL", "foo04"), 0700}, }, }, } for _, test := range tests { test.env.set() f, err := test.dir.Create(test.name) switch { case err == nil && test.err: t.Errorf("In environment %v, %v.Create(%q) succeeded; want error", test.env, test.dir, test.name) case err == nil && !test.err && f.Name() != test.path: t.Errorf("In environment %v, %v.Create(%q).Name() = %q; want %q", test.env, test.dir, test.name, f.Name(), test.path) case err != nil && !test.err: t.Errorf("In environment %v, %v.Create(%q) error: %v", test.env, test.dir, test.name, err) } if f != nil { f.Close() } for _, pc := range test.permChecks { info, err := os.Stat(pc.name) if err != nil { t.Errorf("In environment %v, %v.Create(%q): stat %s error: %v", test.env, test.dir, test.name, pc.name, err) continue } if perm := info.Mode().Perm(); perm != pc.perm { t.Errorf("In environment %v, %v.Create(%q): %s has permission %v; want %v", test.env, test.dir, test.name, pc.name, perm, pc.perm) } } } } func stringsEqual(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } type tempDir struct { t *testing.T dir string } func newTempDir(t *testing.T) *tempDir { td := &tempDir{t: t} var err error td.dir, err = ioutil.TempDir("", "xdgdir_test") if err != nil { t.Fatal("making temp dir:", err) } return td } // newFile creates a file and returns its path. func (td *tempDir) newFile(name string, data string) string { path := filepath.Join(td.dir, name) f, err := os.Create(path) if err != nil { td.t.Fatalf("newFile(%q, %q) error: %v", name, data, err) } _, werr := f.Write([]byte(data)) cerr := f.Close() if werr != nil { td.t.Errorf("newFile(%q, %q) write error: %v", name, data, err) } if cerr != nil { td.t.Errorf("newFile(%q, %q) close error: %v", name, data, err) } if werr != nil || cerr != nil { td.t.FailNow() } return path } // mkdir creates a directory and returns its path. func (td *tempDir) mkdir(name string, perm os.FileMode) string { path := filepath.Join(td.dir, name) err := os.Mkdir(path, perm) if err != nil { td.t.Fatal(err) } return path } func (td *tempDir) cleanup() { err := os.RemoveAll(td.dir) if err != nil { td.t.Log("failed to clean up temp dir:", err) } } type permCheck struct { name string perm os.FileMode } type env map[string]string func (e env) set() { getenv = func(key string) string { return e[key] } findHome = func() string { return e["HOME"] } }