pax_global_header00006660000000000000000000000064151324301600014505gustar00rootroot0000000000000052 comment=d0366c7e61c91c71decd240fbec6e53f0077ce58 go-util-0.9.5/000077500000000000000000000000001513243016000131005ustar00rootroot00000000000000go-util-0.9.5/.editorconfig000066400000000000000000000002661513243016000155610ustar00rootroot00000000000000root = true [*] indent_style = tab indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{yaml,yml}] indent_style = space go-util-0.9.5/.github/000077500000000000000000000000001513243016000144405ustar00rootroot00000000000000go-util-0.9.5/.github/workflows/000077500000000000000000000000001513243016000164755ustar00rootroot00000000000000go-util-0.9.5/.github/workflows/go.yml000066400000000000000000000023541513243016000176310ustar00rootroot00000000000000name: Go on: [push, pull_request] env: GOTOOLCHAIN: local jobs: lint: runs-on: ubuntu-latest name: Lint (latest) steps: - uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: "1.25" cache: true - name: Install dependencies run: | go install golang.org/x/tools/cmd/goimports@latest go install honnef.co/go/tools/cmd/staticcheck@latest export PATH="$HOME/go/bin:$PATH" - name: Run pre-commit uses: pre-commit/action@v3.0.1 build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: go-version: ["1.24", "1.25"] name: Build ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }} steps: - uses: actions/checkout@v6 - name: Set up Go ${{ matrix.go-version }} uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} cache: true - name: Set up gotestfmt uses: GoTestTools/gotestfmt-action@v2 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Build run: go build -v ./... - name: Test run: go test -json -v ./... 2>&1 | gotestfmt go-util-0.9.5/.gitignore000066400000000000000000000000201513243016000150600ustar00rootroot00000000000000.idea/ .vscode/ go-util-0.9.5/.pre-commit-config.yaml000066400000000000000000000012001513243016000173520ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace exclude_types: [markdown] - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - repo: https://github.com/tekwizely/pre-commit-golang rev: v1.0.0-rc.4 hooks: - id: go-imports-repo args: - "-local" - "go.mau.fi/util" - "-w" - id: go-vet-repo-mod - id: go-staticcheck-repo-mod - repo: https://github.com/beeper/pre-commit-go rev: v0.4.2 hooks: - id: zerolog-ban-msgf types: [ file ] go-util-0.9.5/CHANGELOG.md000066400000000000000000000305041513243016000147130ustar00rootroot00000000000000# v0.9.5 (2026-01-16) * *(exhttp)* Added utility for configuring a HTTP transport. # v0.9.4 (2025-12-16) * *(exsync)* Added methods to swap entire map data and a GetOrSet equivalent with a factory method. * *(ffmpeg/waveform)* Added package for generating waveform data using ffmpeg. * *(cmd/maubuild)* Added tool to replace the `build.sh` script in bridges. * *(lottie)* Fixed animated webp output not being looped properly. # v0.9.3 (2025-11-16) * *(unicodeurls,confusables,emojirunes,variationselector)* Updated to Unicode v17. * *(dbutil)* Added option to log all queries without arguments. * *(exmaps)* Added non-synchronous equivalent of `exsync.Set`. * *(exslices)* Added utilities for deleting items by value. * *(exslices)* Added non-synchronous `Stack` type. * *(exstrings)* Added `LongestCommonPrefix`. * *(shlex)* Added support for line continuations with backslashes. * *(progver)* Fixed linkified version for tags. # v0.9.2 (2025-10-16) * *(progver)* Added program version calculation utility like the one used by mautrix bridges and Meowlnir. * *(dbutil)* Added `sqlite3-fk-wal-fullsync` driver which is otherwise equivalent to `sqlite3-fk-wal`, but sets `PRAGMA synchronous=FULL` for better crash resistance. * *(dbutil)* Added explicit error if comment prefix (`--`) isn't at the start of the line when using dialect filters with the `(lines commented)` modifier. * *(exsync)* Added NewMapWithData, Clear, Len and CopyFrom methods for maps. * *(exsync)* Added iterators for maps and sets. * *(jsontime)* Changed `Unix*()` methods and `jsontime.U*Int()` functions to treat 0 and the zero `time.Time` value as the same. # v0.9.1 (2025-09-16) * *(dbutil)* Added general documentation. * *(random)* Added `StringCharset` for generating a random string with a custom character set and `AppendSequence` for generating a random slice with a completely arbitrary types. * *(exslices)* Added methods for deduplicating a slice by custom key. * *(exsync)* Added `WaitTimeoutCtx` for waiting for an `Event` with both a timeout and a context. # v0.9.0 (2025-08-16) * Bumped minimum Go version to 1.24. * **Breaking change *(exhttp)*** Refactored HandleErrors middleware to take raw response data instead of functions returning response data. * *(requestlog)* Added option to recover and log panics. * *(exhttp)* Added `syscall.EPIPE` to `IsNetworkError` checks. * *(exsync)* Added `Notify` method for waking up all `Event` waiters without setting the flag. This is the atomic equivalent of `Set()` immediately followed by `Clear()`. * *(exbytes)* Added `UnsafeString` method for converting a byte slice to a string without copying. * *(exstrings)* Added `CollapseSpaces` to replace multiple sequential spaces with one. * *(exstrings)* Added `PrefixByteRunLength` to count the number of occurrences of a given byte at the start of a string. * *(base58)* Fixed panic when input contains non-ASCII characters. # v0.8.8 (2025-06-16) * *(requestlog)* Added option to log `X-Forwarded-For` header value. * *(exstrings)* Added `LongestSequenceOfFunc` as a customizable version of `LongestSequenceOf` # v0.8.7 (2025-05-16) * *(jsonbytes)* Added utility for url-safe base64 to complement the existing standard unpadded base64 marshaling utility. * *(exstrings)* Added `LongestSequenceOf` to find the longest sequence of a single character in a string. * *(requestlog)* Implemented `Flush` in `CountingResponseWriter` to fix flushing HTTP response buffer when using request logging. * *(exhttp)* Added utility for checking if a given error is a network error or an http2 stream error. # v0.8.6 (2025-03-16) * *(curl)* Added support for parsing cookies set using the `-b` flag, which recent versions of Chrome use. * *(exstrings)* Added functions for hashing and constant time comparing strings without copying to a byte array. # v0.8.5 (2025-02-16) * Bumped minimum Go version to 1.23. * *(dbutil)* Deprecated `NewRowIter` as it encourages bad error handling. `NewRowIterWithError` and `ConvertRowFn[T].NewRowIter` are recommended instead, as they support bundling an error inside the iterator. * *(exslices)* Added utility to map and filter a slice in one go. * *(confusable)* Fixed skeleton incorrectly including replacement characters for some input strings. * *(exbytes)* Added utility that implements `io.Writer` for byte slices without resizing. * *(glob)* Added `ToRegexPattern` helper which converts a glob to a regex without compiling it. # v0.8.4 (2025-01-16) * *(dbutil)* Added option to retry transaction begin calls. * *(dbutil)* Added `QueryHelper.QueryManyIter` function to get a `RowIter` instead of pre-reading all rows into a list. * *(jsontime)* Added utilities for durations. # v0.8.3 (2024-12-16) * *(exhttp)* Added global flag for disabling automatic CORS headers when using JSON response helper functions. # v0.8.2 (2024-11-16) * *(ffmpeg)* Added wrapper functions for `ffprobe`. * *(emojirunes)* Added method to check if a string is only emojis. * *(unicodeurls)* Updated data sheets used by emojirunes, variationselectors and other packages to Unicode 16. * *(dbutil)* Added support for mass inserts with no static parameters. # v0.8.1 (2024-10-16) * **Breaking change *(lottie)*** Improved interface to take a destination file name rather than returning bytes. The method was internally using a file anyway, so forcing reading it into memory was a waste. * *(ffmpeg)* Added `ConvertPathWithDestination` to specify destination file manually. * *(exhttp)* Added utility for applying middlewares to any HTTP handler. * *(exfmt)* Made duration formatting more customizable. * *(dbutil)* Changed table existence checks during schema upgrades to properly return errors instead of panicking. * *(dbutil)* Fixed `sqlite-fkey-off` transaction mode. # v0.8.0 (2024-09-16) * *(dbutil)* Changed litestream package to allow importing as no-op even when cgo is disabled. * *(ptr)* Added `NonZero` and `NonDefault` helpers to get nil if the value is zero/default or a pointer to the value otherwise. * *(ffmpeg)* Fixed files not being removed if conversion fails. * *(pblite)* Added pblite (protobuf as JSON arrays) en/decoder. * *(exhttp)* Added utilities for JSON responses, CORS headers and other things. * *(glob)* Added utility for parsing Matrix globs into efficient matchers, with a fallback to regex for more complicated patterns. * *(exsync)* Added `Size`, `Pop`, `ReplaceAll` and `AsList` for `Set`. * *(variationselector)* Fixed plain numbers being emojified by `Add`. # v0.7.0 (2024-08-16) * Bumped minimum Go version to 1.22. * *(curl)* Added `Parse` function to parse a curl command exported from browser devtools. * *(exfmt)* Moved `FormatCurl` to `curl` package. * *(exslices)* Added `DeduplicateUnsorted` utility for deduplicating items in a list while preserving order. * *(exsync)* Deprecated `ReturnableOnce` in favor of the standard library's [`sync.OnceValues`]. * *(exsync)* Added `Event` which works similar to Python's [`asyncio.Event`]. * *(confusable)* Added implementation of confusable detection from [UTS #39]. * *(dbutil)* Added deadlock detection option which panics if a database call is made without the appropriate transaction context in a goroutine which previously entered a database transaction. [UTS #39]: https://www.unicode.org/reports/tr39/#Confusable_Detection [`sync.OnceValues`]: https://pkg.go.dev/sync#OnceValues [`asyncio.Event`]: https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event # v0.6.0 (2024-07-16) * *(dbutil)* Added `-- transaction: sqlite-fkey-off` mode to upgrades, which allows safer upgrades that disable foreign keys without having to disable transactions entirely. * **Breaking change:** `UpgradeTable.Register` now takes a TxnMode instead of a bool as the 5th parameter. * **Breaking change:** `Database.DoTxn` now takes `*dbutil.TxnOptions` instead of `*sql.TxOptions`. `nil` is still allowed and the existing fields are still supported, but there's a new field too. * **Breaking change:** `Database.Conn` was renamed to `Execable` to avoid confusion with the new `AcquireConn` method (`Execable` just returns the current transaction or database, `AcquireConn` acquires a connection for exclusive use). * *(dbutil)* Added finalizer for RowIter to panic if the rows aren't iterated. * *(dbutil)* Changed `QueryOne` to return a zero value (usually nil) if the `Scan` method returns an error. * *(progress)* Implemented `io.Seeker` in `Reader`. * *(ptr)* Added new utilities for creating pointers to arbitrary values, as well as safely dereferencing pointers and making shallow clones. * *(exslices)* Added functions to cast slices to different types. * *(gnuzip)* Added wrappers for gzip that operate on `[]byte`s instead of `io.Reader`/`Writer`s. * *(lottie)* Added wrapper for [lottieconverter] similar to ffmpeg. * *(variationselector)* Fixed edge cases where `Add` and `FullyQualify` produced invalid output. [lottieconverter]: https://github.com/sot-tech/LottieConverter # v0.5.0 (2024-06-16) * **Breaking change *(configupgrade)*** Changed `Helper` into an interface. * *(configupgrade)* Added `ProxyHelper` that prepends a given path to all calls to a target `Helper`. * *(dbutil)* Added support for notating line filters as `(line commented)` to indicate that they should be uncommented when the filter matches. * *(dbutil)* Prevented accidentally using the transaction of another database connection by mixing contexts. * *(fallocate)* Added utility for allocating file space on disk. Currently compatible with Linux (including Android) and macOS. * *(requestlog)* Added utility for HTTP access logging. * *(progress)* Added `io.Reader` and `io.Writer` wrappers that support monitoring progress of the reading/writing. # v0.4.2 (2024-04-16) * *(dbutil)* Added utility for building mass insert queries. * *(dbutil)* Added utility for using reflect to build a RowIter. # v0.4.1 (2024-03-16) * *(exfmt)* Added utility for converting HTTP requests to `curl` commands. * *(exmime)* Added hardcoded extension override for `audio/mp4` -> `.m4a`. * *(dbutil)* Added `UnixPtr`, `UnixMilliPtr` and `ConvertedPtr` helpers for converting `time.Time` into `*int64` so that zero times are nil and other times are unix. * *(dbutil)* Added `UntypedNil` utility for avoiding typed nils, and `JSONPtr` for wrapping a struct in the existing `JSON` utility using `UntypedNil`. * *(dbutil)* Added periodic logs to `DoTxn` if the transaction takes more than 5 seconds. # v0.4.0 (2024-02-16) * *(jsonbytes)* Added utilities for en/decoding byte slices as unpadded base64. * *(jsontime)* Fixed serialization of Unix(Micro|Nano)String types. * *(exzerolog)* Added helper function for setting sensible zerolog globals such as CallerMarshalFunc, default loggers and better level colors. * *(dbutil)* Added helper for wrapping a raw slice in a RowIter. * This is useful for interfaces that return RowIters to allow implementing the interface without SQL. * The RowIter interface may be moved to a separate package in the future to further separate it from SQL databases. * *(dbutil)* Added helper for converting RowIter to map. # v0.3.0 (2024-01-16) * **Breaking change *(dbutil)*** Removed all non-context methods. * *(dbutil)* Added query helper to reduce boilerplate with executing database queries and scanning results. * *(exsync)* Added generic `Set` utility that wraps a valueless map with a mutex. * *(exerrors)* Added `Must` helper to turn `(T, error)` returns into `T` or panic. * *(ffmpeg)* Added `Supported` and `SetPath` for checking if ffmpeg is available and overriding the binary path respectively. # v0.2.1 (2023-11-16) * *(dbutil)* Fixed read-only db close error not including actual error message. # v0.2.0 (2023-10-16) * *(jsontime)* Added helpers for unix microseconds and nanoseconds, as well as alternative structs that parse JSON strings instead of ints (all precisions). * *(exzerolog)* Added generic helpers to generate `*zerolog.Array`s out of slices. * *(exslices)* Added helpers for finding the difference between two slices. * `Diff` is a generic implementation using maps which works with any `comparable` types (i.e. types that have the equality operator `==` defined). * `SortedDiff` is a more efficient implementation which can take any types (using the help of a `compare` function), but the input must be sorted and shouldn't have duplicates. # v0.1.0 (2023-09-16) Initial release go-util-0.9.5/LICENSE000066400000000000000000000405251513243016000141130ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. go-util-0.9.5/README.md000066400000000000000000000002421513243016000143550ustar00rootroot00000000000000# go.mau.fi/util This repository contains various Go utilities used by mautrix-go, bridges written in Go, as well as some other related libraries like whatsmeow. go-util-0.9.5/base58/000077500000000000000000000000001513243016000141675ustar00rootroot00000000000000go-util-0.9.5/base58/README.md000066400000000000000000000003031513243016000154420ustar00rootroot00000000000000base58 ========== This is a copy of . ## License Package base58 is licensed under the [copyfree](http://copyfree.org) ISC License. go-util-0.9.5/base58/alphabet.go000066400000000000000000000031561513243016000163030ustar00rootroot00000000000000// Copyright (c) 2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. // AUTOGENERATED by genalphabet.go; do not edit. package base58 const ( // alphabet is the modified base58 alphabet used by Bitcoin. alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" alphabetIdx0 = '1' ) var b58 = [256]byte{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 255, 255, 255, 255, 255, 255, 255, 9, 10, 11, 12, 13, 14, 15, 16, 255, 17, 18, 19, 20, 21, 255, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 255, 255, 255, 255, 255, 255, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 255, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, } go-util-0.9.5/base58/base58.go000066400000000000000000000064531513243016000156150ustar00rootroot00000000000000// Copyright (c) 2013-2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package base58 import ( "math/big" ) //go:generate go run genalphabet.go var bigRadix = [...]*big.Int{ big.NewInt(0), big.NewInt(58), big.NewInt(58 * 58), big.NewInt(58 * 58 * 58), big.NewInt(58 * 58 * 58 * 58), big.NewInt(58 * 58 * 58 * 58 * 58), big.NewInt(58 * 58 * 58 * 58 * 58 * 58), big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58), big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58), big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58), bigRadix10, } var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10 // Decode decodes a modified base58 string to a byte slice. func Decode(b string) []byte { answer := big.NewInt(0) scratch := new(big.Int) // Calculating with big.Int is slow for each iteration. // x += b58[b[i]] * j // j *= 58 // // Instead we can try to do as much calculations on int64. // We can represent a 10 digit base58 number using an int64. // // Hence we'll try to convert 10, base58 digits at a time. // The rough idea is to calculate `t`, such that: // // t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0 // x *= 58^10 // x += t // // Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10. // In that case we'll use the bigRadix[n] lookup for the appropriate power. for t := b; len(t) > 0; { n := len(t) if n > 10 { n = 10 } total := uint64(0) for _, v := range t[:n] { if v > 255 { return []byte("") } tmp := b58[v] if tmp == 255 { return []byte("") } total = total*58 + uint64(tmp) } answer.Mul(answer, bigRadix[n]) scratch.SetUint64(total) answer.Add(answer, scratch) t = t[n:] } tmpval := answer.Bytes() var numZeros int for numZeros = 0; numZeros < len(b); numZeros++ { if b[numZeros] != alphabetIdx0 { break } } flen := numZeros + len(tmpval) val := make([]byte, flen) copy(val[numZeros:], tmpval) return val } // Encode encodes a byte slice to a modified base58 string. func Encode(b []byte) string { x := new(big.Int) x.SetBytes(b) // maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58) maxlen := int(float64(len(b))*1.365658237309761) + 1 answer := make([]byte, 0, maxlen) mod := new(big.Int) for x.Sign() > 0 { // Calculating with big.Int is slow for each iteration. // x, mod = x / 58, x % 58 // // Instead we can try to do as much calculations on int64. // x, mod = x / 58^10, x % 58^10 // // Which will give us mod, which is 10 digit base58 number. // We'll loop that 10 times to convert to the answer. x.DivMod(x, bigRadix10, mod) if x.Sign() == 0 { // When x = 0, we need to ensure we don't add any extra zeros. m := mod.Int64() for m > 0 { answer = append(answer, alphabet[m%58]) m /= 58 } } else { m := mod.Int64() for i := 0; i < 10; i++ { answer = append(answer, alphabet[m%58]) m /= 58 } } } // leading zero bytes for _, i := range b { if i != 0 { break } answer = append(answer, alphabetIdx0) } // reverse alen := len(answer) for i := 0; i < alen/2; i++ { answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] } return string(answer) } go-util-0.9.5/base58/base58_test.go000066400000000000000000000064041513243016000166500ustar00rootroot00000000000000// Copyright (c) 2013-2017 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package base58_test import ( "bytes" "encoding/hex" "testing" "go.mau.fi/util/base58" ) var stringTests = []struct { in string out string }{ {"", ""}, {" ", "Z"}, {"-", "n"}, {"0", "q"}, {"1", "r"}, {"-1", "4SU"}, {"11", "4k8"}, {"abc", "ZiCa"}, {"1234598760", "3mJr7AoUXx2Wqd"}, {"abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"}, {"00000000000000000000000000000000000000000000000000000000000000", "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"}, } var invalidStringTests = []struct { in string out string }{ {"0", ""}, {"O", ""}, {"I", ""}, {"l", ""}, {"3mJr0", ""}, {"O3yxU", ""}, {"3sNI", ""}, {"4kl8", ""}, {"0OIl", ""}, {"!@#$%^&*()-_=+~`", ""}, {"abcd\xd80", ""}, {"abcd\U000020BF", ""}, } var hexTests = []struct { in string out string }{ {"", ""}, {"61", "2g"}, {"626262", "a3gV"}, {"636363", "aPEr"}, {"73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"}, {"00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"}, {"516b6fcd0f", "ABnLTmg"}, {"bf4f89001e670274dd", "3SEo3LWLoPntC"}, {"572e4794", "3EFU7m"}, {"ecac89cad93923c02321", "EJDM8drfXA6uyA"}, {"10c8511e", "Rt5zm"}, {"00000000000000000000", "1111111111"}, {"000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"}, {"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"}, } func TestBase58(t *testing.T) { // Encode tests for x, test := range stringTests { tmp := []byte(test.in) if res := base58.Encode(tmp); res != test.out { t.Errorf("Encode test #%d failed: got: %s want: %s", x, res, test.out) continue } } // Decode tests for x, test := range hexTests { b, err := hex.DecodeString(test.in) if err != nil { t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in) continue } if res := base58.Decode(test.out); !bytes.Equal(res, b) { t.Errorf("Decode test #%d failed: got: %q want: %q", x, res, test.in) continue } } // Decode with invalid input for x, test := range invalidStringTests { if res := base58.Decode(test.in); string(res) != test.out { t.Errorf("Decode invalidString test #%d failed: got: %q want: %q", x, res, test.out) continue } } } go-util-0.9.5/base58/base58bench_test.go000066400000000000000000000017201513243016000176440ustar00rootroot00000000000000// Copyright (c) 2013-2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package base58_test import ( "bytes" "testing" "go.mau.fi/util/base58" ) var ( raw5k = bytes.Repeat([]byte{0xff}, 5000) raw100k = bytes.Repeat([]byte{0xff}, 100*1000) encoded5k = base58.Encode(raw5k) encoded100k = base58.Encode(raw100k) ) func BenchmarkBase58Encode_5K(b *testing.B) { b.SetBytes(int64(len(raw5k))) for i := 0; i < b.N; i++ { base58.Encode(raw5k) } } func BenchmarkBase58Encode_100K(b *testing.B) { b.SetBytes(int64(len(raw100k))) for i := 0; i < b.N; i++ { base58.Encode(raw100k) } } func BenchmarkBase58Decode_5K(b *testing.B) { b.SetBytes(int64(len(encoded5k))) for i := 0; i < b.N; i++ { base58.Decode(encoded5k) } } func BenchmarkBase58Decode_100K(b *testing.B) { b.SetBytes(int64(len(encoded100k))) for i := 0; i < b.N; i++ { base58.Decode(encoded100k) } } go-util-0.9.5/base58/base58check.go000066400000000000000000000027421513243016000166100ustar00rootroot00000000000000// Copyright (c) 2013-2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package base58 import ( "crypto/sha256" "errors" ) // ErrChecksum indicates that the checksum of a check-encoded string does not verify against // the checksum. var ErrChecksum = errors.New("checksum error") // ErrInvalidFormat indicates that the check-encoded string has an invalid format. var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing") // checksum: first four bytes of sha256^2 func checksum(input []byte) (cksum [4]byte) { h := sha256.Sum256(input) h2 := sha256.Sum256(h[:]) copy(cksum[:], h2[:4]) return } // CheckEncode prepends a version byte and appends a four byte checksum. func CheckEncode(input []byte, version byte) string { b := make([]byte, 0, 1+len(input)+4) b = append(b, version) b = append(b, input...) cksum := checksum(b) b = append(b, cksum[:]...) return Encode(b) } // CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum. func CheckDecode(input string) (result []byte, version byte, err error) { decoded := Decode(input) if len(decoded) < 5 { return nil, 0, ErrInvalidFormat } version = decoded[0] var cksum [4]byte copy(cksum[:], decoded[len(decoded)-4:]) if checksum(decoded[:len(decoded)-4]) != cksum { return nil, 0, ErrChecksum } payload := decoded[1 : len(decoded)-4] result = append(result, payload...) return } go-util-0.9.5/base58/base58check_test.go000066400000000000000000000037721513243016000176530ustar00rootroot00000000000000// Copyright (c) 2013-2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package base58_test import ( "testing" "go.mau.fi/util/base58" ) var checkEncodingStringTests = []struct { version byte in string out string }{ {20, "", "3MNQE1X"}, {20, " ", "B2Kr6dBE"}, {20, "-", "B3jv1Aft"}, {20, "0", "B482yuaX"}, {20, "1", "B4CmeGAC"}, {20, "-1", "mM7eUf6kB"}, {20, "11", "mP7BMTDVH"}, {20, "abc", "4QiVtDjUdeq"}, {20, "1234598760", "ZmNb8uQn5zvnUohNCEPP"}, {20, "abcdefghijklmnopqrstuvwxyz", "K2RYDcKfupxwXdWhSAxQPCeiULntKm63UXyx5MvEH2"}, {20, "00000000000000000000000000000000000000000000000000000000000000", "bi1EWXwJay2udZVxLJozuTb8Meg4W9c6xnmJaRDjg6pri5MBAxb9XwrpQXbtnqEoRV5U2pixnFfwyXC8tRAVC8XxnjK"}, } func TestBase58Check(t *testing.T) { for x, test := range checkEncodingStringTests { // test encoding if res := base58.CheckEncode([]byte(test.in), test.version); res != test.out { t.Errorf("CheckEncode test #%d failed: got %s, want: %s", x, res, test.out) } // test decoding res, version, err := base58.CheckDecode(test.out) switch { case err != nil: t.Errorf("CheckDecode test #%d failed with err: %v", x, err) case version != test.version: t.Errorf("CheckDecode test #%d failed: got version: %d want: %d", x, version, test.version) case string(res) != test.in: t.Errorf("CheckDecode test #%d failed: got: %s want: %s", x, res, test.in) } } // test the two decoding failure cases // case 1: checksum error _, _, err := base58.CheckDecode("3MNQE1Y") if err != base58.ErrChecksum { t.Error("Checkdecode test failed, expected ErrChecksum") } // case 2: invalid formats (string lengths below 5 mean the version byte and/or the checksum // bytes are missing). testString := "" for len := 0; len < 4; len++ { testString += "x" _, _, err = base58.CheckDecode(testString) if err != base58.ErrInvalidFormat { t.Error("Checkdecode test failed, expected ErrInvalidFormat") } } } go-util-0.9.5/base58/doc.go000066400000000000000000000023601513243016000152640ustar00rootroot00000000000000// Copyright (c) 2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. /* Package base58 provides an API for working with modified base58 and Base58Check encodings. # Modified Base58 Encoding Standard base58 encoding is similar to standard base64 encoding except, as the name implies, it uses a 58 character alphabet which results in an alphanumeric string and allows some characters which are problematic for humans to be excluded. Due to this, there can be various base58 alphabets. The modified base58 alphabet used by Bitcoin, and hence this package, omits the 0, O, I, and l characters that look the same in many fonts and are therefore hard to humans to distinguish. # Base58Check Encoding Scheme The Base58Check encoding scheme is primarily used for Bitcoin addresses at the time of this writing, however it can be used to generically encode arbitrary byte arrays into human-readable strings along with a version byte that can be used to differentiate the same payload. For Bitcoin addresses, the extra version is used to differentiate the network of otherwise identical public keys which helps prevent using an address intended for one network on another. */ package base58 go-util-0.9.5/base58/example_test.go000066400000000000000000000033501513243016000172110ustar00rootroot00000000000000// Copyright (c) 2014 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package base58_test import ( "fmt" "go.mau.fi/util/base58" ) // This example demonstrates how to decode modified base58 encoded data. func ExampleDecode() { // Decode example modified base58 encoded data. encoded := "25JnwSn7XKfNQ" decoded := base58.Decode(encoded) // Show the decoded data. fmt.Println("Decoded Data:", string(decoded)) // Output: // Decoded Data: Test data } // This example demonstrates how to encode data using the modified base58 // encoding scheme. func ExampleEncode() { // Encode example data with the modified base58 encoding scheme. data := []byte("Test data") encoded := base58.Encode(data) // Show the encoded data. fmt.Println("Encoded Data:", encoded) // Output: // Encoded Data: 25JnwSn7XKfNQ } // This example demonstrates how to decode Base58Check encoded data. func ExampleCheckDecode() { // Decode an example Base58Check encoded data. encoded := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" decoded, version, err := base58.CheckDecode(encoded) if err != nil { fmt.Println(err) return } // Show the decoded data. fmt.Printf("Decoded data: %x\n", decoded) fmt.Println("Version Byte:", version) // Output: // Decoded data: 62e907b15cbf27d5425399ebf6f0fb50ebb88f18 // Version Byte: 0 } // This example demonstrates how to encode data using the Base58Check encoding // scheme. func ExampleCheckEncode() { // Encode example data with the Base58Check encoding scheme. data := []byte("Test data") encoded := base58.CheckEncode(data, 0) // Show the encoded data. fmt.Println("Encoded Data:", encoded) // Output: // Encoded Data: 182iP79GRURMp7oMHDU } go-util-0.9.5/cmd/000077500000000000000000000000001513243016000136435ustar00rootroot00000000000000go-util-0.9.5/cmd/maubuild/000077500000000000000000000000001513243016000154455ustar00rootroot00000000000000go-util-0.9.5/cmd/maubuild/maubuild.go000066400000000000000000000063541513243016000176060ustar00rootroot00000000000000package main import ( "encoding/json" "fmt" "os" "os/exec" "runtime" "slices" "strings" "syscall" "time" "golang.org/x/mod/modfile" "go.mau.fi/util/exerrors" ) const ldflagTemplate = "-s -w -X '%[1]s.Tag=%[2]s' -X '%[1]s.Commit=%[3]s' -X '%[1]s.BuildTime=%[4]s' -X 'maunium.net/go/mautrix.GoModVersion=%[5]s' %[6]s" func main() { versionPackage := os.Getenv("MAU_VERSION_PACKAGE") if versionPackage == "" { versionPackage = "main" } var gitCommit, gitTag string if os.Getenv("CI") == "true" { gitCommit = os.Getenv("CI_COMMIT_SHA") gitTag = os.Getenv("CI_COMMIT_TAG") } else { gitCommit = subcommand("git", "rev-parse", "HEAD") gitTag = subcommand("git", "describe", "--exact-match", "--tags") } extraLDFlags := os.Getenv("GO_LDFLAGS") if os.Getenv("MAU_STATIC_BUILD") == "true" { extraLDFlags += " -linkmode external -extldflags '-static'" } ldflags := fmt.Sprintf( ldflagTemplate, versionPackage, gitTag, gitCommit, time.Now().Format(time.RFC3339), getMautrixGoVersion(), extraLDFlags, ) args := []string{"go", "build", "-ldflags", ldflags} args = append(args, os.Args[1:]...) buildPackage := os.Getenv("MAU_BUILD_PACKAGE_OVERRIDE") if buildPackage == "" { buildPackage = "./cmd/" + os.Getenv("BINARY_NAME") } args = append(args, buildPackage) env := os.Environ() targetGOOS := os.Getenv("TARGET_GOOS") targetGOARCH := os.Getenv("TARGET_GOARCH") if targetGOOS != "" && targetGOARCH != "" { env = slices.DeleteFunc(env, func(s string) bool { return strings.HasPrefix(s, "GOOS=") || strings.HasPrefix(s, "GOARCH=") }) env = append(env, "GOOS="+targetGOOS) env = append(env, "GOARCH="+targetGOARCH) fmt.Printf("Actually building for %s/%s\n", targetGOOS, targetGOARCH) } if runtime.GOOS == "darwin" && os.Getenv("LIBRARY_PATH") == "" { if brewPrefix := subcommand("brew", "--prefix"); brewPrefix != "" { fmt.Println("Mac: Using", brewPrefix, "for LIBRARY_PATH and CPATH") env = append(env, fmt.Sprintf("LIBRARY_PATH=%s/lib", brewPrefix)) env = append(env, fmt.Sprintf("CPATH=%s/include", brewPrefix)) } else if directoryExists("/opt/homebrew") { fmt.Println("Mac: Using /opt/homebrew for LIBRARY_PATH and CPATH") env = append(env, "LIBRARY_PATH=/opt/homebrew/lib") env = append(env, "CPATH=/opt/homebrew/include") } } else if strings.HasSuffix(runtime.GOOS, "bsd") && os.Getenv("LIBRARY_PATH") == "" { fmt.Println("BSD: Using /usr/local for LIBRARY_PATH and CPATH") env = append(env, "LIBRARY_PATH=/usr/local/bin") env = append(env, "CPATH=/usr/local/include") } goBin := exerrors.Must(exec.LookPath("go")) fmt.Println("Running", string(exerrors.Must(json.Marshal(args)))) exerrors.PanicIfNotNil(syscall.Exec(goBin, args, env)) } func directoryExists(dir string) bool { info, err := os.Stat(dir) if os.IsNotExist(err) { return false } return info.IsDir() } func getMautrixGoVersion() string { parsedGoMod := exerrors.Must(modfile.Parse("go.mod", exerrors.Must(os.ReadFile("go.mod")), nil)) for _, req := range parsedGoMod.Require { if req.Mod.Path == "maunium.net/go/mautrix" { return req.Mod.Version } } return "" } func subcommand(command string, args ...string) string { stdout, _ := exec.Command(command, args...).Output() return strings.TrimSpace(string(stdout)) } go-util-0.9.5/configupgrade/000077500000000000000000000000001513243016000157155ustar00rootroot00000000000000go-util-0.9.5/configupgrade/helper.go000066400000000000000000000142371513243016000175320ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package configupgrade import ( "fmt" "os" "strings" "gopkg.in/yaml.v3" ) type YAMLMap map[string]YAMLNode type YAMLList []YAMLNode type YAMLNode struct { *yaml.Node Map YAMLMap List YAMLList Key *yaml.Node } type YAMLType uint32 const ( Null YAMLType = 1 << iota Bool Str Int Float Timestamp List Map Binary ) func (t YAMLType) String() string { switch t { case Null: return NullTag case Bool: return BoolTag case Str: return StrTag case Int: return IntTag case Float: return FloatTag case Timestamp: return TimestampTag case List: return SeqTag case Map: return MapTag case Binary: return BinaryTag default: panic(fmt.Errorf("can't convert type %d to string", t)) } } func tagToType(tag string) YAMLType { switch tag { case NullTag: return Null case BoolTag: return Bool case StrTag: return Str case IntTag: return Int case FloatTag: return Float case TimestampTag: return Timestamp case SeqTag: return List case MapTag: return Map case BinaryTag: return Binary default: return 0 } } const ( NullTag = "!!null" BoolTag = "!!bool" StrTag = "!!str" IntTag = "!!int" FloatTag = "!!float" TimestampTag = "!!timestamp" SeqTag = "!!seq" MapTag = "!!map" BinaryTag = "!!binary" ) func fromNode(node, key *yaml.Node) YAMLNode { switch node.Kind { case yaml.DocumentNode: return fromNode(node.Content[0], nil) case yaml.AliasNode: return fromNode(node.Alias, nil) case yaml.MappingNode: return YAMLNode{ Node: node, Map: parseYAMLMap(node), Key: key, } case yaml.SequenceNode: return YAMLNode{ Node: node, List: parseYAMLList(node), } default: return YAMLNode{Node: node, Key: key} } } func (yn *YAMLNode) toNode() *yaml.Node { yn.UpdateContent() return yn.Node } func (yn *YAMLNode) UpdateContent() { switch { case yn.Map != nil && yn.Node.Kind == yaml.MappingNode: yn.Content = yn.Map.toNodes() case yn.List != nil && yn.Node.Kind == yaml.SequenceNode: yn.Content = yn.List.toNodes() } } func parseYAMLList(node *yaml.Node) YAMLList { data := make(YAMLList, len(node.Content)) for i, item := range node.Content { data[i] = fromNode(item, nil) } return data } func (yl YAMLList) toNodes() []*yaml.Node { nodes := make([]*yaml.Node, len(yl)) for i, item := range yl { nodes[i] = item.toNode() } return nodes } func parseYAMLMap(node *yaml.Node) YAMLMap { if len(node.Content)%2 != 0 { panic(fmt.Errorf("uneven number of items in YAML map (%d)", len(node.Content))) } data := make(YAMLMap, len(node.Content)/2) for i := 0; i < len(node.Content); i += 2 { key := node.Content[i] value := node.Content[i+1] if key.Kind == yaml.ScalarNode { data[key.Value] = fromNode(value, key) } } return data } func (ym YAMLMap) toNodes() []*yaml.Node { nodes := make([]*yaml.Node, len(ym)*2) i := 0 for key, value := range ym { nodes[i] = makeStringNode(key) nodes[i+1] = value.toNode() i += 2 } return nodes } func makeStringNode(val string) *yaml.Node { var node yaml.Node node.SetString(val) return &node } func StringNode(val string) YAMLNode { return YAMLNode{Node: makeStringNode(val)} } type Helper interface { Copy(allowedTypes YAMLType, path ...string) Get(tag YAMLType, path ...string) (string, bool) GetNode(path ...string) *YAMLNode GetBase(path ...string) string GetBaseNode(path ...string) *YAMLNode Set(tag YAMLType, value string, path ...string) SetMap(value YAMLMap, path ...string) AddSpaceBeforeComment(path ...string) } type CopyHelper struct { Base YAMLNode Config YAMLNode } var _ Helper = (*CopyHelper)(nil) func NewHelper(base, cfg *yaml.Node) *CopyHelper { return &CopyHelper{ Base: fromNode(base, nil), Config: fromNode(cfg, nil), } } func (helper *CopyHelper) AddSpaceBeforeComment(path ...string) { node := helper.GetBaseNode(path...) if node == nil || node.Key == nil { panic(fmt.Errorf("didn't find key at %+v", path)) } node.Key.HeadComment = "\n" + node.Key.HeadComment } func (helper *CopyHelper) Copy(allowedTypes YAMLType, path ...string) { base, cfg := helper.Base, helper.Config var ok bool for _, item := range path { cfg, ok = cfg.Map[item] if !ok { return } base, ok = base.Map[item] if !ok { _, _ = fmt.Fprintf(os.Stderr, "Ignoring config field %s which is missing in base config\n", strings.Join(path, "->")) return } } if allowedTypes&tagToType(cfg.Tag) == 0 { _, _ = fmt.Fprintf(os.Stderr, "Ignoring incorrect config field type %s at %s\n", cfg.Tag, strings.Join(path, "->")) return } base.Tag = cfg.Tag base.Style = cfg.Style switch base.Kind { case yaml.ScalarNode: base.Value = cfg.Value case yaml.SequenceNode, yaml.MappingNode: base.Content = cfg.Content } } func getNode(cfg YAMLNode, path []string) *YAMLNode { var ok bool for _, item := range path { cfg, ok = cfg.Map[item] if !ok { return nil } } return &cfg } func (helper *CopyHelper) GetNode(path ...string) *YAMLNode { return getNode(helper.Config, path) } func (helper *CopyHelper) GetBaseNode(path ...string) *YAMLNode { return getNode(helper.Base, path) } func (helper *CopyHelper) Get(tag YAMLType, path ...string) (string, bool) { node := helper.GetNode(path...) if node == nil || node.Kind != yaml.ScalarNode || tag&tagToType(node.Tag) == 0 { return "", false } return node.Value, true } func (helper *CopyHelper) GetBase(path ...string) string { return helper.GetBaseNode(path...).Value } func (helper *CopyHelper) Set(tag YAMLType, value string, path ...string) { base := helper.Base for _, item := range path { base = base.Map[item] } base.Tag = tag.String() base.Value = value } func (helper *CopyHelper) SetMap(value YAMLMap, path ...string) { base := helper.Base for _, item := range path { base = base.Map[item] } if base.Tag != MapTag || base.Kind != yaml.MappingNode { panic(fmt.Errorf("invalid target for SetMap(%+v): tag:%s, kind:%d", path, base.Tag, base.Kind)) } base.Content = value.toNodes() } go-util-0.9.5/configupgrade/proxyhelper.go000066400000000000000000000025101513243016000206230ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package configupgrade type ProxyHelper struct { Prefix []string Target Helper } var _ Helper = (*ProxyHelper)(nil) func (p *ProxyHelper) Copy(allowedTypes YAMLType, path ...string) { p.Target.Copy(allowedTypes, append(p.Prefix, path...)...) } func (p *ProxyHelper) Get(tag YAMLType, path ...string) (string, bool) { return p.Target.Get(tag, append(p.Prefix, path...)...) } func (p *ProxyHelper) GetBase(path ...string) string { return p.Target.GetBase(append(p.Prefix, path...)...) } func (p *ProxyHelper) GetNode(path ...string) *YAMLNode { return p.Target.GetNode(append(p.Prefix, path...)...) } func (p *ProxyHelper) GetBaseNode(path ...string) *YAMLNode { return p.Target.GetBaseNode(append(p.Prefix, path...)...) } func (p *ProxyHelper) Set(tag YAMLType, value string, path ...string) { p.Target.Set(tag, value, append(p.Prefix, path...)...) } func (p *ProxyHelper) SetMap(value YAMLMap, path ...string) { p.Target.SetMap(value, append(p.Prefix, path...)...) } func (p *ProxyHelper) AddSpaceBeforeComment(path ...string) { p.Target.AddSpaceBeforeComment(append(p.Prefix, path...)...) } go-util-0.9.5/configupgrade/upgrade.go000066400000000000000000000072111513243016000176740ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package configupgrade import ( "fmt" "os" "path" "gopkg.in/yaml.v3" ) type Upgrader interface { DoUpgrade(helper Helper) } type noopUpgrader struct{} func (*noopUpgrader) DoUpgrade(helper Helper) {} var NoopUpgrader Upgrader = &noopUpgrader{} type SpacedUpgrader interface { Upgrader SpacedBlocks() [][]string } type BaseUpgrader interface { Upgrader GetBase() string } type StructUpgrader struct { SimpleUpgrader Blocks [][]string Base string } func (su *StructUpgrader) SpacedBlocks() [][]string { return su.Blocks } func (su *StructUpgrader) GetBase() string { return su.Base } type ProxyUpgrader struct { Prefix []string Target Upgrader } var _ SpacedUpgrader = (*ProxyUpgrader)(nil) func (p *ProxyUpgrader) DoUpgrade(helper Helper) { p.Target.DoUpgrade(&ProxyHelper{ Target: helper, Prefix: p.Prefix, }) } func (p *ProxyUpgrader) SpacedBlocks() [][]string { spaced, ok := p.Target.(SpacedUpgrader) if ok { blocks := spaced.SpacedBlocks() newBlocks := make([][]string, len(blocks)) for i, block := range blocks { newBlocks[i] = append(p.Prefix, block...) } return newBlocks } return nil } func MergeUpgraders(base string, upgraders ...Upgrader) *StructUpgrader { var blocks [][]string for _, upgrader := range upgraders { spaced, ok := upgrader.(SpacedUpgrader) if ok { blocks = append(blocks, spaced.SpacedBlocks()...) } } return &StructUpgrader{ SimpleUpgrader: func(helper Helper) { for _, upgrader := range upgraders { upgrader.DoUpgrade(helper) } }, Blocks: blocks, Base: base, } } type SimpleUpgrader func(helper Helper) func (su SimpleUpgrader) DoUpgrade(helper Helper) { su(helper) } func (helper *CopyHelper) apply(upgrader Upgrader) { upgrader.DoUpgrade(helper) helper.addSpaces(upgrader) } func (helper *CopyHelper) addSpaces(upgrader Upgrader) { spaced, ok := upgrader.(SpacedUpgrader) if ok { for _, spacePath := range spaced.SpacedBlocks() { helper.AddSpaceBeforeComment(spacePath...) } } } func Do(configPath string, save bool, upgrader BaseUpgrader, additional ...Upgrader) ([]byte, bool, error) { sourceData, err := os.ReadFile(configPath) if err != nil { return nil, false, fmt.Errorf("failed to read config: %w", err) } var base, cfg yaml.Node err = yaml.Unmarshal([]byte(upgrader.GetBase()), &base) if err != nil { return sourceData, false, fmt.Errorf("failed to unmarshal example config: %w", err) } err = yaml.Unmarshal(sourceData, &cfg) if err != nil { return sourceData, false, fmt.Errorf("failed to unmarshal config: %w", err) } helper := NewHelper(&base, &cfg) helper.apply(upgrader) for _, add := range additional { helper.apply(add) } output, err := yaml.Marshal(&base) if err != nil { return sourceData, false, fmt.Errorf("failed to marshal updated config: %w", err) } if save { var tempFile *os.File tempFile, err = os.CreateTemp(path.Dir(configPath), "mautrix-config-*.yaml") if err != nil { return output, true, fmt.Errorf("failed to create temp file for writing config: %w", err) } _, err = tempFile.Write(output) if err != nil { _ = os.Remove(tempFile.Name()) return output, true, fmt.Errorf("failed to write updated config to temp file: %w", err) } err = os.Rename(tempFile.Name(), configPath) if err != nil { _ = os.Remove(tempFile.Name()) return output, true, fmt.Errorf("failed to override current config with temp file: %w", err) } } return output, true, nil } go-util-0.9.5/confusable/000077500000000000000000000000001513243016000152215ustar00rootroot00000000000000go-util-0.9.5/confusable/confusables.go000066400000000000000000005466001513243016000200670ustar00rootroot00000000000000// Code generated by go generate; DO NOT EDIT. package confusable func GetReplacement(input rune) string { switch input { case 34: return "''" case 37: return "ยบ/โ‚€" case 48: return "O" case 49: return "l" case 73: return "l" case 96: return "'" case 109: return "rn" case 124: return "l" case 160: return " " case 162: return "cฬธ" case 165: return "Yฬต" case 175: return "ห‰" case 180: return "'" case 181: return "ฮผ" case 184: return "," case 198: return "AE" case 199: return "Cฬฆ" case 208: return "Dฬต" case 215: return "x" case 216: return "Oฬธ" case 230: return "ae" case 231: return "cฬฆ" case 240: return "โˆ‚ฬต" case 246: return "ุฉ" case 248: return "oฬธ" case 254: return "p" case 272: return "Dฬต" case 273: return "dฬต" case 282: return "ฤ”" case 283: return "ฤ•" case 294: return "Hฬต" case 295: return "hฬต" case 305: return "i" case 306: return "lJ" case 307: return "ij" case 319: return "lยท" case 320: return "lยท" case 321: return "Lฬธ" case 322: return "lฬธ" case 326: return "ษฒ" case 329: return "'n" case 331: return "nฬฉ" case 336: return "ร–" case 338: return "OE" case 339: return "oe" case 355: return "ฦซ" case 358: return "Tฬต" case 359: return "tฬต" case 383: return "f" case 384: return "bฬต" case 385: return "'B" case 386: return "bฬ„" case 387: return "bฬ„" case 388: return "b" case 391: return "C'" case 393: return "Dฬต" case 394: return "'D" case 396: return "dฬ„" case 397: return "g" case 401: return "Fฬฆ" case 402: return "f" case 403: return "G'" case 406: return "l" case 407: return "lฬต" case 408: return "K'" case 409: return "kฬ”" case 410: return "lฬต" case 411: return "ฮปฬธ" case 413: return "Nฬฆ" case 414: return "nฬฉ" case 415: return "Oฬต" case 416: return "O'" case 417: return "o'" case 420: return "'P" case 421: return "pฬ”" case 422: return "R" case 423: return "2" case 428: return "'T" case 429: return "tฬ”" case 430: return "Tฬจ" case 435: return "'Y" case 436: return "yฬ”" case 437: return "Zฬต" case 438: return "zฬต" case 439: return "3" case 443: return "2ฬต" case 444: return "5" case 445: return "s" case 447: return "p" case 448: return "l" case 449: return "ll" case 451: return "!" case 452: return "Dลฝ" case 453: return "Dลพ" case 454: return "dลพ" case 455: return "LJ" case 456: return "Lj" case 457: return "lj" case 458: return "NJ" case 459: return "Nj" case 460: return "nj" case 461: return "ฤ‚" case 462: return "ฤƒ" case 463: return "ฤฌ" case 464: return "ฤญ" case 465: return "ลŽ" case 466: return "ล" case 467: return "ลฌ" case 468: return "ลญ" case 484: return "Gฬต" case 485: return "gฬต" case 486: return "ฤž" case 487: return "ฤŸ" case 497: return "DZ" case 498: return "Dz" case 499: return "dz" case 501: return "ฤฃ" case 510: return "Oฬธฬ" case 538: return "ลข" case 539: return "ฦซ" case 540: return "3" case 546: return "8" case 547: return "8" case 548: return "Zฬฆ" case 549: return "zฬฆ" case 550: return "ร…" case 551: return "รฅ" case 572: return "cฬธ" case 574: return "Tฬธ" case 577: return "?" case 580: return "Uฬต" case 582: return "Eฬธ" case 583: return "eฬธ" case 584: return "Jฬต" case 585: return "jฬต" case 589: return "rฬต" case 590: return "Yฬต" case 591: return "yฬต" case 593: return "a" case 595: return "bฬ”" case 598: return "dฬจ" case 599: return "dฬ”" case 601: return "ว" case 602: return "วหž" case 603: return "๊ž“" case 608: return "gฬ”" case 609: return "g" case 611: return "y" case 614: return "hฬ”" case 616: return "iฬต" case 617: return "i" case 618: return "i" case 619: return "lฬด" case 621: return "lฬจ" case 622: return "lศ" case 623: return "w" case 625: return "rnฬฆ" case 627: return "nฬจ" case 629: return "oฬต" case 630: return "oแด‡" case 636: return "rฬฉ" case 637: return "rฬจ" case 642: return "sฬจ" case 651: return "u" case 655: return "y" case 656: return "zฬจ" case 658: return "ศ" case 660: return "?" case 661: return "\ua7ce" case 672: return "qฬ”" case 675: return "dz" case 676: return "dศ" case 677: return "dส‘" case 678: return "ts" case 679: return "tสƒ" case 680: return "tษ•" case 681: return "fnฬฉ" case 682: return "ls" case 683: return "lz" case 691: return "แฃด" case 697: return "'" case 698: return "''" case 699: return "'" case 700: return "'" case 701: return "'" case 702: return "'" case 703: return "ี™" case 706: return "<" case 707: return ">" case 708: return "^" case 710: return "^" case 712: return "'" case 714: return "'" case 715: return "'" case 720: return ":" case 723: return "ี™" case 727: return "-" case 728: return "ห‡" case 729: return "เฅฑ" case 730: return "ยฐ" case 731: return "i" case 732: return "~" case 733: return "''" case 737: return "แฃณ" case 738: return "แฃต" case 740: return "ห" case 750: return "''" case 756: return "'" case 758: return "''" case 760: return ":" case 763: return "หช" case 773: return "ฬ„" case 780: return "ฬ†" case 781: return "ูฐ" case 784: return "ฬ†ฬ‡" case 785: return "ฬ‚" case 789: return "ฬ“" case 791: return "ู" case 794: return "\u1ae9" case 800: return "ฬฑ" case 801: return "ฬฆ" case 802: return "ฬจ" case 807: return "ฬฆ" case 822: return "ฬต" case 823: return "ฬธ" case 825: return "ฬฆ" case 832: return "ฬ€" case 833: return "ฬ" case 834: return "ฬƒ" case 835: return "ฬ“" case 837: return "ฬจ" case 839: return "ฬณ" case 840: return "\U00010efa" case 855: return "อ" case 856: return "ฬ‡" case 870: return "ฬŠ" case 878: return "ฬ†" case 880: return "โฑต" case 884: return "'" case 885: return "ห" case 886: return "ะ˜" case 887: return "แดŽ" case 890: return "i" case 891: return "ษ”" case 893: return "๊œฟ" case 894: return ";" case 895: return "J" case 900: return "'" case 903: return "ยท" case 913: return "A" case 914: return "B" case 917: return "E" case 918: return "Z" case 919: return "H" case 920: return "Oฬต" case 921: return "l" case 922: return "K" case 923: return "ษ…" case 924: return "M" case 925: return "N" case 927: return "O" case 929: return "P" case 931: return "ฦฉ" case 932: return "T" case 933: return "Y" case 935: return "X" case 945: return "a" case 946: return "รŸ" case 947: return "y" case 948: return "แบŸ" case 949: return "๊ž“" case 951: return "nฬฉ" case 952: return "Oฬต" case 953: return "i" case 954: return "ฤธ" case 957: return "v" case 959: return "o" case 961: return "p" case 963: return "o" case 964: return "แด›" case 965: return "u" case 966: return "ษธ" case 976: return "รŸ" case 977: return "Oฬต" case 978: return "Y" case 981: return "ษธ" case 982: return "ฯ€" case 987: return "ฯ‚" case 988: return "F" case 996: return "ะง" case 997: return "ั‡" case 1000: return "2" case 1001: return "ฦจ" case 1004: return "6" case 1005: return "o" case 1008: return "ฤธ" case 1009: return "p" case 1010: return "c" case 1011: return "j" case 1012: return "Oฬต" case 1013: return "๊ž“" case 1015: return "รž" case 1016: return "p" case 1017: return "C" case 1018: return "M" case 1021: return "ฦ†" case 1023: return "๊œพ" case 1028: return "๊ž’" case 1029: return "S" case 1030: return "l" case 1032: return "J" case 1040: return "A" case 1041: return "bฬ„" case 1042: return "B" case 1043: return "ฮ“" case 1045: return "E" case 1047: return "3" case 1049: return "ะ" case 1050: return "K" case 1051: return "ษ…" case 1052: return "M" case 1053: return "H" case 1054: return "O" case 1055: return "ฮ " case 1056: return "P" case 1057: return "C" case 1058: return "T" case 1059: return "Y" case 1060: return "ฮฆ" case 1061: return "X" case 1067: return "bl" case 1068: return "b" case 1070: return "lO" case 1072: return "a" case 1073: return "6" case 1074: return "ส™" case 1075: return "r" case 1077: return "e" case 1079: return "ษœ" case 1080: return "แดŽ" case 1082: return "ฤธ" case 1084: return "ส" case 1085: return "สœ" case 1086: return "o" case 1087: return "ฯ€" case 1088: return "p" case 1089: return "c" case 1090: return "แด›" case 1091: return "y" case 1092: return "ษธ" case 1093: return "x" case 1096: return "w" case 1098: return "ห‰b" case 1099: return "ฦ…i" case 1100: return "ฦ…" case 1103: return "แด™" case 1108: return "๊ž“" case 1109: return "s" case 1110: return "i" case 1112: return "j" case 1115: return "hฬต" case 1117: return "ะน" case 1119: return "uฬฉ" case 1121: return "w" case 1122: return "bฬต" case 1123: return "bฬต" case 1136: return "ฮจ" case 1137: return "ฯˆ" case 1138: return "Oฬต" case 1139: return "oฬต" case 1140: return "V" case 1141: return "v" case 1148: return "ั า†า‡" case 1149: return "wา†า‡" case 1162: return "ะฬฆ" case 1163: return "ะนฬฆ" case 1164: return "bฬต" case 1165: return "bฬต" case 1168: return "ฮ“'" case 1169: return "r'" case 1170: return "ฮ“ฬต" case 1171: return "rฬต" case 1174: return "ะ–ฬฉ" case 1175: return "ะถฬฉ" case 1176: return "3ฬฆ" case 1177: return "ษœฬฆ" case 1178: return "Kฬฉ" case 1179: return "ฤธฬฉ" case 1182: return "Kฬต" case 1183: return "ฤธฬต" case 1186: return "Hฬฉ" case 1187: return "สœฬฉ" case 1194: return "Cฬฆ" case 1195: return "cฬฆ" case 1196: return "Tฬฉ" case 1197: return "แด›ฬฉ" case 1198: return "Y" case 1199: return "y" case 1200: return "Yฬต" case 1201: return "yฬต" case 1202: return "Xฬฉ" case 1211: return "h" case 1213: return "e" case 1214: return "าผฬจ" case 1215: return "eฬจ" case 1216: return "l" case 1221: return "ษ…ฬฆ" case 1222: return "ะปฬฆ" case 1223: return "Hฬฆ" case 1224: return "สœฬฆ" case 1225: return "Hฬฆ" case 1226: return "สœฬฆ" case 1227: return "าถ" case 1228: return "าท" case 1229: return "Mฬฆ" case 1230: return "สฬฆ" case 1231: return "l" case 1236: return "AE" case 1237: return "ae" case 1240: return "ฦ" case 1241: return "ว" case 1248: return "3" case 1249: return "ศ" case 1256: return "Oฬต" case 1257: return "oฬต" case 1281: return "d" case 1290: return "วถ" case 1292: return "G" case 1293: return "ษข" case 1296: return "ฦ" case 1297: return "๊ž“" case 1307: return "q" case 1308: return "W" case 1309: return "w" case 1339: return "แŠฎ" case 1348: return "แˆ†" case 1354: return "แŒฃ" case 1356: return "แ‰ก" case 1357: return "U" case 1359: return "S" case 1363: return "ฮฆ" case 1365: return "O" case 1370: return "'" case 1373: return "'" case 1377: return "w" case 1379: return "q" case 1382: return "q" case 1390: return "แบŸ" case 1392: return "h" case 1394: return "nฬฉ" case 1397: return "ศท" case 1400: return "n" case 1402: return "ษฐ" case 1404: return "n" case 1405: return "u" case 1409: return "g" case 1410: return "i" case 1412: return "f" case 1413: return "o" case 1415: return "ีฅi" case 1417: return ":" case 1436: return "ฬ" case 1437: return "ฬ" case 1444: return "ึš" case 1448: return "ึ™" case 1453: return "ึ–" case 1454: return "ึ˜" case 1455: return "ฬŠ" case 1460: return "ฬฃ" case 1465: return "ฬ‡" case 1466: return "ฬ‡" case 1472: return "l" case 1473: return "ฬ‡" case 1474: return "ฬ‡" case 1475: return ":" case 1476: return "ฬ‡" case 1477: return "ฬฃ" case 1493: return "l" case 1496: return "v" case 1497: return "'" case 1503: return "l" case 1505: return "o" case 1520: return "ll" case 1521: return "l'" case 1522: return "''" case 1523: return "'" case 1524: return "''" case 1545: return "ยบ/โ‚€โ‚€" case 1546: return "ยบ/โ‚€โ‚€โ‚€" case 1549: return "," case 1551: return "ุน" case 1560: return "ฬ" case 1561: return "ฬ“" case 1562: return "ู" case 1571: return "lูด" case 1572: return "ูˆูด" case 1573: return "lู•" case 1574: return "ู‰ูด" case 1575: return "l" case 1579: return "ู‰›" case 1588: return "ุณ›" case 1597: return "ู‰ฬ‚" case 1599: return "ู‰›" case 1607: return "o" case 1610: return "ู‰" case 1611: return "ฬ‹" case 1614: return "ฬ" case 1615: return "ฬ“" case 1618: return "ฬŠ" case 1619: return "ฬƒ" case 1622: return "ฬฉ" case 1623: return "ฬ’" case 1624: return "ฬ†" case 1625: return "ฬ„" case 1626: return "ฬ†" case 1627: return "ฬ‚" case 1628: return "ฬฃ" case 1629: return "ฬ”" case 1631: return "ู•" case 1632: return "." case 1633: return "l" case 1637: return "o" case 1639: return "V" case 1640: return "ษ…" case 1642: return "ยบ/โ‚€" case 1643: return "," case 1644: return "ุŒ" case 1645: return "*" case 1646: return "ู‰" case 1647: return "ฺก" case 1650: return "lูด" case 1651: return "lู•" case 1653: return "lูด" case 1654: return "ูˆูด" case 1655: return "ูˆฬ“ูด" case 1656: return "ู‰ูด" case 1657: return "ู‰ุ•" case 1658: return "ุช" case 1662: return "ู‰›" case 1665: return "ุญู”" case 1669: return "ุญ›" case 1672: return "ุฏุ•" case 1675: return "ฺŠุ•" case 1678: return "ุฏ›" case 1679: return "ุฏ›" case 1681: return "ุฑุ•" case 1682: return "ุฑฬ†" case 1688: return "ุฑ›" case 1694: return "ุต›" case 1695: return "ุท›" case 1700: return "ฺก›" case 1703: return "ู" case 1704: return "ฺก›" case 1705: return "ูƒ" case 1706: return "ูƒ" case 1709: return "ูƒ›" case 1716: return "ฺฏ›" case 1717: return "ู„ฬ†" case 1719: return "ู„›" case 1722: return "ู‰" case 1723: return "ู‰ุ•" case 1725: return "ู‰›" case 1726: return "o" case 1729: return "o" case 1730: return "€" case 1731: return "ุฉ" case 1734: return "ูˆฬ†" case 1735: return "ูˆฬ“" case 1736: return "ูˆูฐ" case 1737: return "ูˆฬ‚" case 1739: return "ูˆ›" case 1740: return "ู‰" case 1742: return "ู‰ฬ†" case 1744: return "ูป" case 1745: return "ู‰›" case 1746: return "ู‰" case 1748: return "-" case 1749: return "o" case 1759: return "ฬŠ" case 1768: return "ฬ†ฬ‡" case 1772: return "ฬ‡" case 1774: return "ุฏฬ‚" case 1775: return "ุฑฬ‚" case 1776: return "." case 1777: return "l" case 1778: return "ูข" case 1779: return "ูฃ" case 1780: return "ูค" case 1781: return "o" case 1782: return "ูฆ" case 1783: return "V" case 1784: return "ษ…" case 1785: return "ูฉ" case 1789: return "ุก\U00010efa" case 1790: return "ู…\U00010efa" case 1791: return "oฬ‚" case 1793: return "." case 1794: return "." case 1795: return ":" case 1796: return ":" case 1856: return "ฬ‡" case 1857: return "ฬ‡" case 1858: return "ผ" case 1863: return "ฬ" case 1873: return "ุจ›" case 1874: return "ู‰›" case 1878: return "ู‰ฬ†" case 1890: return "ฺฌ" case 1891: return "ูƒ›" case 1895: return "”" case 1896: return "ู†ุ•" case 1897: return "ู†ฬ†" case 1900: return "ุฑู”" case 1905: return "ฺ—ุ•" case 1906: return "ุญู”" case 1918: return "ุณฬ‚" case 1984: return "O" case 1994: return "l" case 2027: return "ฬ„" case 2029: return "ฬ‡" case 2030: return "ฬ‚" case 2035: return "ฬˆ" case 2036: return "'" case 2037: return "'" case 2042: return "_" case 2191: return "ู‰ฬŠ" case 2209: return "ุจู”" case 2212: return "ฺข›" case 2215: return "ู…›" case 2216: return "ู‰ู”" case 2217: return "”" case 2222: return "ุฏฬคฬฃ" case 2223: return "ุตฬคฬฃ" case 2224: return "ฺฏ" case 2225: return "ูˆ" case 2226: return "ุฒฬ‚" case 2230: return "ุจข" case 2231: return "ู‰›ข" case 2233: return "ุฑฬ†ฬ‡" case 2234: return "ู‰ฬ†ฬ‡" case 2235: return "ฺก" case 2236: return "ฺก" case 2237: return "ู‰" case 2238: return "ู‰›ฬ†" case 2239: return "ุชฬ†" case 2240: return "ู‰ุ•ฬ†" case 2241: return "ฺ†ฬ†" case 2242: return "ูƒฬ†" case 2277: return "ูŒ" case 2280: return "ูŒ" case 2282: return "ฬ‡" case 2283: return "ฬˆ" case 2285: return "ฬฃ" case 2286: return "ฬค" case 2288: return "ฬ‹" case 2289: return "ูŒ" case 2290: return "ู" case 2291: return "ฬ“" case 2296: return "อ" case 2297: return "อ”" case 2298: return "อ•" case 2303: return "อ" case 2304: return "อ’" case 2305: return "ฬ†ฬ‡" case 2306: return "ฬ‡" case 2307: return ":" case 2308: return "เค…เฅ†" case 2310: return "เค…เคพ" case 2312: return "เคฐเฅเค‡" case 2317: return "เคฬ†" case 2318: return "เคเฅ†" case 2320: return "เค\U00011b64" case 2321: return "เค…เคพฬ†" case 2322: return "เค…เคพเฅ†" case 2323: return "เค…เคพ\U00011b64" case 2324: return "เค…เคพเฅˆ" case 2363: return "เคพเคบ" case 2364: return "ฬฃ" case 2367: return "เฆฟ" case 2373: return "ฬ†" case 2375: return "\U00011b64" case 2377: return "เคพฬ†" case 2386: return "ฬฑ" case 2387: return "ฬ€" case 2388: return "ฬ" case 2390: return "\U00011b62" case 2391: return "\U00011b63" case 2405: return "เฅคเฅค" case 2406: return "o" case 2407: return "ูฉ" case 2409: return "3" case 2418: return "เค…ฬ†" case 2419: return "เค…เคบ" case 2420: return "เค…เคพเคบ" case 2421: return "เค…เฅ" case 2429: return "?" case 2433: return "ฬ†ฬ‡" case 2438: return "เฆ…เฆพ" case 2492: return "ฬฃ" case 2528: return "เฆ‹เงƒ" case 2529: return "เฆ‹เงƒ" case 2534: return "o" case 2538: return "8" case 2541: return "9" case 2544: return "เฆฐ" case 2562: return "ฬ‡" case 2563: return "เฆƒ" case 2566: return "เจ…เจพ" case 2567: return "เคชเฅเคŸเฆฟ" case 2568: return "เคชเฅเคŸเฉ€" case 2569: return "เฉณ\U00011b62" case 2570: return "เฉณ\U00011b63" case 2575: return "เคชเฅเคŸ\U00011b64" case 2576: return "เจ…เฅˆ" case 2580: return "เจ…เฉŒ" case 2581: return "เคต" case 2588: return "เคคเฅเคค" case 2591: return "เคŸ" case 2592: return "เค " case 2596: return "เค‰" case 2599: return "เคช" case 2603: return "เคข" case 2606: return "เคญ" case 2613: return "เคน" case 2616: return "เคฎ" case 2620: return "ฬฃ" case 2623: return "เฆฟ" case 2625: return "\U00011b62" case 2626: return "\U00011b63" case 2631: return "\U00011b64" case 2632: return "เฅˆ" case 2635: return "เฅ†" case 2637: return "เฅ" case 2662: return "o" case 2663: return "9" case 2666: return "8" case 2674: return "เคชเฅเคŸ" case 2689: return "ฬ†ฬ‡" case 2690: return "ฬ‡" case 2691: return ":" case 2694: return "เช…เชพ" case 2701: return "เช…เซ…" case 2703: return "เช…เซ‡" case 2704: return "เช…เซˆ" case 2705: return "เช…เชพเซ…" case 2707: return "เช…เชพเซ‡" case 2708: return "เช…เชพเซˆ" case 2730: return "ั‡" case 2736: return "เฅจ" case 2748: return "ฬฃ" case 2749: return "เคฝ" case 2753: return "เฅ" case 2754: return "เฅ‚" case 2765: return "เฅ" case 2790: return "o" case 2792: return "เฅจ" case 2793: return "3" case 2794: return "เฅช" case 2795: return "ั‡" case 2798: return "เฅฎ" case 2800: return "เฅฐ" case 2817: return "ฬ†ฬ‡" case 2819: return "8" case 2822: return "เฌ…เฌพ" case 2848: return "O" case 2876: return "ฬฃ" case 2918: return "o" case 2920: return "9" case 2946: return "ฬŠ" case 2954: return "เฎ‰เฎณ" case 2964: return "เฎ’เฎณ" case 2972: return "เฎ" case 2992: return "เฎˆ" case 3000: return "เฎถ" case 3006: return "เฎˆ" case 3016: return "เฎฉ" case 3018: return "เฏ†เฎˆ" case 3019: return "เฏ‡เฎˆ" case 3020: return "เฏ†เฎณ" case 3021: return "ฬ‡" case 3031: return "เฎณ" case 3046: return "o" case 3047: return "เฎ•" case 3048: return "เฎ‰" case 3050: return "เฎš" case 3051: return "เฎˆเฏ" case 3052: return "เฎšเฏ" case 3053: return "เฎŽ" case 3054: return "เฎ…" case 3056: return "เฎฏ" case 3058: return "เฎšเฏ‚" case 3060: return "เฎฎเฏ€" case 3061: return "เฏณ" case 3063: return "เฎŽเฎต" case 3064: return "เฎท" case 3066: return "เฎจเฏ€" case 3072: return "ฬ†ฬ‡" case 3074: return "o" case 3075: return "เฆƒ" case 3091: return "เฐ’เฑ•" case 3092: return "เฐ’เฑŒ" case 3094: return "เฒ–ฬฃ" case 3104: return "เฐฐึผ" case 3106: return "เฐกฬฃ" case 3109: return "เฐงึผ" case 3117: return "เฐฌฬฃ" case 3118: return "เฐตเฑ" case 3127: return "เฐตฬฃ" case 3129: return "เฐตเฐพ" case 3138: return "เฑเฐพ" case 3140: return "เฑƒเฐพ" case 3168: return "เฐ‹เฐพ" case 3169: return "เฐŒเฐพ" case 3174: return "o" case 3201: return "ฬ†ฬ‡" case 3202: return "o" case 3203: return "เฆƒ" case 3205: return "เฐ…" case 3206: return "เฐ†" case 3207: return "เฐ‡" case 3216: return "เฐ" case 3218: return "เฐ’" case 3219: return "เฐ’เฑ•" case 3220: return "เฐ’เฑŒ" case 3223: return "เฐ—" case 3228: return "เฐœ" case 3229: return "เฐ" case 3230: return "เฐž" case 3231: return "เฐŸ" case 3235: return "เฐฃ" case 3238: return "เฐฆ" case 3240: return "เฐจ" case 3247: return "เฐฏ" case 3248: return "เฐฐ" case 3249: return "เฐฑ" case 3250: return "เฐฒ" case 3251: return "เฐณ" case 3263: return "เฐฟ" case 3265: return "เฑ" case 3267: return "เฑƒ" case 3292: return "\u0c5c" case 3297: return "เฒŒเฒพ" case 3302: return "O" case 3303: return "เฑง" case 3304: return "เฑจ" case 3311: return "เฑฏ" case 3329: return "ฬ†ฬ‡" case 3330: return "o" case 3331: return "เฆƒ" case 3336: return "เด‡เต—" case 3337: return "เฎ‰" case 3338: return "เฎ‰เต—" case 3340: return "เดจเต" case 3344: return "เฏ†เดŽ" case 3347: return "เด’เดพ" case 3348: return "เด’เต—" case 3350: return "เฎต" case 3353: return "เดจเต" case 3356: return "เฎ" case 3359: return "s" case 3360: return "o" case 3363: return "เฎฃ" case 3365: return "เฎฎ" case 3377: return "เดฐ" case 3380: return "เฎด" case 3382: return "เฎถ" case 3386: return "เฎŸเฎฟ" case 3391: return "เฎฟ" case 3392: return "เฎฟ" case 3394: return "เต" case 3395: return "เต" case 3398: return "เฏ†" case 3399: return "เฏ‡" case 3400: return "เฏ†เฏ†" case 3406: return "เฅฑ" case 3418: return "เดจเตเดฎ" case 3423: return "oเดฐo" case 3425: return "เดž" case 3430: return "o" case 3434: return "เดฐเต" case 3435: return "เดฆเตเดฐ" case 3436: return "เดจเตเดจ" case 3437: return "9" case 3438: return "เดตเตเดฐ" case 3439: return "เดจเต" case 3446: return "เดนเตเดฎ" case 3449: return "เดจเต" case 3450: return "เฎฃเต" case 3451: return "เดจเต" case 3452: return "เดฐเต" case 3453: return "เดฒเต" case 3454: return "เดณเต" case 3458: return "o" case 3459: return "เฆƒ" case 3469: return "เทƒเท˜" case 3474: return "เถ‘เทŠ" case 3475: return "เถ‘เท™" case 3509: return "เถ‘" case 3510: return "เถ›" case 3513: return "เถ”" case 3520: return "เถ " case 3524: return "เถท" case 3561: return "เทจเท" case 3562: return "เถข" case 3563: return "เถฏ" case 3567: return "เทจเท“" case 3587: return "เธ‚" case 3595: return "เธŠ" case 3599: return "เธŽ" case 3604: return "เธ„" case 3605: return "เธ„" case 3607: return "เธ‘" case 3617: return "เธ†" case 3622: return "เธ " case 3635: return "ฬŠเธฒ" case 3649: return "เน€เน€" case 3653: return "เธฒ" case 3661: return "ฬŠ" case 3664: return "o" case 3720: return "เธˆ" case 3725: return "เธข" case 3738: return "เธš" case 3739: return "เธ›" case 3741: return "เธ" case 3742: return "เธž" case 3743: return "เธŸ" case 3763: return "ฬŠเบฒ" case 3768: return "เธธ" case 3769: return "เธน" case 3784: return "เนˆ" case 3785: return "เน‰" case 3786: return "เนŠ" case 3787: return "เน‹" case 3789: return "ฬŠ" case 3792: return "o" case 3804: return "เบซเบ™" case 3805: return "เบซเบก" case 3840: return "เฝจเฝผเฝพ" case 3842: return "เฝ เฝดเพ‚เฝฟ" case 3843: return "เฝ เฝดเพ‚เผ”" case 3852: return "เผ‹" case 3854: return "เผเผ" case 3867: return "เผšเผš" case 3870: return "เผเผ" case 3871: return "เผšเผ" case 3895: return "ฬฅ" case 3946: return "เฝข" case 3959: return "เพฒเฝฑเพ€" case 3961: return "เพณเฝฑเพ€" case 3963: return "เฝบเฝบ" case 3965: return "เฝผเฝผ" case 4046: return "เผเผš" case 4053: return "ๅ" case 4054: return "ๅ" case 4096: return "เดฐแ€ฌ" case 4098: return "เดฐ" case 4100: return "c" case 4112: return "oแ€ฌ" case 4125: return "o" case 4127: return "แ€•แ€ฌ" case 4131: return "เดฐแ€ฌแ€นเดฐแ€ฌ" case 4137: return "แ€žแ€ผ" case 4138: return "แ€žแ€ผเญ‡แ€ฌแ€บ" case 4145: return "เญ‡" case 4150: return "ฬŠ" case 4152: return "เฆƒ" case 4160: return "o" case 4171: return "แŠแŠ" case 4186: return "c" case 4193: return "แ€›แ€พ" case 4197: return "แ" case 4198: return "แ€•แ€พ" case 4207: return "แ€•แ€ฌแ€พ" case 4208: return "แ€ƒแ€พ" case 4222: return "แฝแ€พ" case 4225: return "เดฐแ€พ" case 4254: return "แ‚ƒฬŠ" case 4256: return "๊ž†" case 4311: return "oแ€ฌ" case 4312: return "เดฐ" case 4327: return "y" case 4339: return "ศ" case 4351: return "o" case 4353: return "แ„€แ„€" case 4356: return "แ„ƒแ„ƒ" case 4360: return "แ„‡แ„‡" case 4362: return "แ„‰แ„‰" case 4365: return "แ„Œแ„Œ" case 4371: return "แ„‚แ„€" case 4372: return "แ„‚แ„‚" case 4373: return "แ„‚แ„ƒ" case 4374: return "แ„‚แ„‡" case 4375: return "แ„ƒแ„€" case 4376: return "แ„…แ„‚" case 4377: return "แ„…แ„…" case 4378: return "แ„…แ„’" case 4379: return "แ„…แ„‹" case 4380: return "แ„†แ„‡" case 4381: return "แ„†แ„‹" case 4382: return "แ„‡แ„€" case 4383: return "แ„‡แ„‚" case 4384: return "แ„‡แ„ƒ" case 4385: return "แ„‡แ„‰" case 4386: return "แ„‡แ„‰แ„€" case 4387: return "แ„‡แ„‰แ„ƒ" case 4388: return "แ„‡แ„‰แ„‡" case 4389: return "แ„‡แ„‰แ„‰" case 4390: return "แ„‡แ„‰แ„Œ" case 4391: return "แ„‡แ„Œ" case 4392: return "แ„‡แ„Ž" case 4393: return "แ„‡แ„" case 4394: return "แ„‡แ„‘" case 4395: return "แ„‡แ„‹" case 4396: return "แ„‡แ„‡แ„‹" case 4397: return "แ„‰แ„€" case 4398: return "แ„‰แ„‚" case 4399: return "แ„‰แ„ƒ" case 4400: return "แ„‰แ„…" case 4401: return "แ„‰แ„†" case 4402: return "แ„‰แ„‡" case 4403: return "แ„‰แ„‡แ„€" case 4404: return "แ„‰แ„‰แ„‰" case 4405: return "แ„‰แ„‹" case 4406: return "แ„‰แ„Œ" case 4407: return "แ„‰แ„Ž" case 4408: return "แ„‰แ„" case 4409: return "แ„‰แ„" case 4410: return "แ„‰แ„‘" case 4411: return "แ„…แ„’" case 4413: return "แ„ผแ„ผ" case 4415: return "แ„พแ„พ" case 4417: return "แ„‹แ„€" case 4418: return "แ„‹แ„ƒ" case 4419: return "แ„‹แ„†" case 4420: return "แ„‹แ„‡" case 4421: return "แ„‹แ„‰" case 4422: return "แ„‹แ…€" case 4423: return "แ„‹แ„‹" case 4424: return "แ„‹แ„Œ" case 4425: return "แ„‹แ„Ž" case 4426: return "แ„‹แ„" case 4427: return "แ„‹แ„‘" case 4429: return "แ„Œแ„‹" case 4431: return "แ…Žแ…Ž" case 4433: return "แ…แ…" case 4434: return "แ„Žแ„" case 4435: return "แ„Žแ„’" case 4438: return "แ„‘แ„‡" case 4439: return "แ„‘แ„‹" case 4440: return "แ„’แ„’" case 4442: return "แ„€แ„ƒ" case 4443: return "แ„‚แ„‰" case 4444: return "แ„‚แ„Œ" case 4445: return "แ„‚แ„’" case 4446: return "แ„ƒแ„…" case 4450: return "แ…กไธจ" case 4452: return "แ…ฃไธจ" case 4454: return "แ…ฅไธจ" case 4456: return "แ…งไธจ" case 4458: return "แ…ฉแ…ก" case 4459: return "แ…ฉแ…กไธจ" case 4460: return "แ…ฉไธจ" case 4463: return "แ…ฎแ…ฅ" case 4464: return "แ…ฎแ…ฅไธจ" case 4465: return "แ…ฎไธจ" case 4467: return "ใƒผ" case 4468: return "ใƒผไธจ" case 4469: return "ไธจ" case 4470: return "แ…กแ…ฉ" case 4471: return "แ…กแ…ฎ" case 4472: return "แ…ฃแ…ฉ" case 4473: return "แ…ฃแ…ญ" case 4474: return "แ…ฅแ…ฉ" case 4475: return "แ…ฅแ…ฎ" case 4476: return "แ…ฅใƒผ" case 4477: return "แ…งแ…ฉ" case 4478: return "แ…งแ…ฎ" case 4479: return "แ…ฉแ…ฅ" case 4480: return "แ…ฉแ…ฅไธจ" case 4481: return "แ…ฉแ…งไธจ" case 4482: return "แ…ฉแ…ฉ" case 4483: return "แ…ฉแ…ฎ" case 4484: return "แ…ญแ…ฃ" case 4485: return "แ…ญแ…ฃไธจ" case 4486: return "แ…ญแ…ฃ" case 4487: return "แ…ญแ…ฉ" case 4488: return "แ…ญไธจ" case 4489: return "แ…ฎแ…ก" case 4490: return "แ…ฎแ…กไธจ" case 4491: return "แ…ฎแ…ฅใƒผ" case 4492: return "แ…ฎแ…งไธจ" case 4493: return "แ…ฎแ…ฎ" case 4494: return "แ…ฒแ…ก" case 4495: return "แ…ฒแ…ฅ" case 4496: return "แ…ฒแ…ฅไธจ" case 4497: return "แ…ฒแ…ง" case 4498: return "แ…ฒแ…งไธจ" case 4499: return "แ…ฒแ…ฎ" case 4500: return "แ…ฒไธจ" case 4501: return "ใƒผแ…ฎ" case 4502: return "ใƒผใƒผ" case 4503: return "ใƒผไธจแ…ฎ" case 4504: return "ไธจแ…ก" case 4505: return "ไธจแ…ฃ" case 4506: return "ไธจแ…ฉ" case 4507: return "ไธจแ…ฎ" case 4508: return "ไธจใƒผ" case 4509: return "ไธจแ†ž" case 4511: return "แ†žแ…ฅ" case 4512: return "แ†žแ…ฎ" case 4513: return "แ†žไธจ" case 4514: return "แ†žแ†ž" case 4515: return "แ…กใƒผ" case 4516: return "แ…ฃแ…ฎ" case 4517: return "แ…งแ…ฃ" case 4518: return "แ…ฉแ…ฃ" case 4519: return "แ…ฉแ…ฃไธจ" case 4520: return "แ„€" case 4521: return "แ„€แ„€" case 4522: return "แ„€แ„‰" case 4523: return "แ„‚" case 4524: return "แ„‚แ„Œ" case 4525: return "แ„‚แ„’" case 4526: return "แ„ƒ" case 4527: return "แ„…" case 4528: return "แ„…แ„€" case 4529: return "แ„…แ„†" case 4530: return "แ„…แ„‡" case 4531: return "แ„…แ„‰" case 4532: return "แ„…แ„" case 4533: return "แ„…แ„‘" case 4534: return "แ„…แ„’" case 4535: return "แ„†" case 4536: return "แ„‡" case 4537: return "แ„‡แ„‰" case 4538: return "แ„‰" case 4539: return "แ„‰แ„‰" case 4540: return "แ„‹" case 4541: return "แ„Œ" case 4542: return "แ„Ž" case 4543: return "แ„" case 4544: return "แ„" case 4545: return "แ„‘" case 4546: return "แ„’" case 4547: return "แ„€แ„…" case 4548: return "แ„€แ„‰แ„€" case 4549: return "แ„‚แ„€" case 4550: return "แ„‚แ„ƒ" case 4551: return "แ„‚แ„‰" case 4552: return "แ„‚แ…€" case 4553: return "แ„‚แ„" case 4554: return "แ„ƒแ„€" case 4555: return "แ„ƒแ„…" case 4556: return "แ„…แ„€แ„‰" case 4557: return "แ„…แ„‚" case 4558: return "แ„…แ„ƒ" case 4559: return "แ„…แ„ƒแ„’" case 4560: return "แ„…แ„…" case 4561: return "แ„…แ„†แ„€" case 4562: return "แ„…แ„†แ„‰" case 4563: return "แ„…แ„‡แ„‰" case 4564: return "แ„…แ„‡แ„’" case 4565: return "แ„…แ„‡แ„‹" case 4566: return "แ„…แ„‰แ„‰" case 4567: return "แ„…แ…€" case 4568: return "แ„…แ„" case 4569: return "แ„…แ…™" case 4570: return "แ„†แ„€" case 4571: return "แ„†แ„…" case 4572: return "แ„†แ„‡" case 4573: return "แ„†แ„‰" case 4574: return "แ„†แ„‰แ„‰" case 4575: return "แ„†แ…€" case 4576: return "แ„†แ„Ž" case 4577: return "แ„†แ„’" case 4578: return "แ„†แ„‹" case 4579: return "แ„‡แ„…" case 4580: return "แ„‡แ„‘" case 4581: return "แ„‡แ„’" case 4582: return "แ„‡แ„‹" case 4583: return "แ„‰แ„€" case 4584: return "แ„‰แ„ƒ" case 4585: return "แ„‰แ„…" case 4586: return "แ„‰แ„‡" case 4587: return "แ…€" case 4588: return "แ„‹แ„€" case 4589: return "แ„‹แ„€แ„€" case 4590: return "แ„‹แ„‹" case 4591: return "แ„‹แ„" case 4592: return "แ…Œ" case 4593: return "แ„‹แ„‰" case 4594: return "แ„‹แ…€" case 4595: return "แ„‘แ„‡" case 4596: return "แ„‘แ„‹" case 4597: return "แ„’แ„‚" case 4598: return "แ„’แ„…" case 4599: return "แ„’แ„†" case 4600: return "แ„’แ„‡" case 4601: return "แ…™" case 4602: return "แ„€แ„‚" case 4603: return "แ„€แ„‡" case 4604: return "แ„€แ„Ž" case 4605: return "แ„€แ„" case 4606: return "แ„€แ„’" case 4607: return "แ„‚แ„‚" case 4608: return "U" case 4643: return "ษฐ" case 4672: return "ฮฆ" case 4704: return "ีˆ" case 4756: return "ีฑ" case 4816: return "O" case 5024: return "D" case 5025: return "R" case 5026: return "T" case 5028: return "O'" case 5029: return "i" case 5032: return "โฑต" case 5033: return "Y" case 5034: return "A" case 5035: return "J" case 5036: return "E" case 5038: return "?" case 5040: return "โฑต" case 5041: return "ฮ“" case 5043: return "W" case 5047: return "M" case 5051: return "H" case 5053: return "Y" case 5054: return "Oฬต" case 5055: return "ฦซ" case 5056: return "G" case 5058: return "h" case 5059: return "Z" case 5063: return "ั " case 5067: return "ฦ" case 5068: return "Uฬต" case 5070: return "4" case 5071: return "b" case 5074: return "R" case 5076: return "W" case 5077: return "S" case 5081: return "V" case 5082: return "S" case 5086: return "L" case 5087: return "C" case 5090: return "P" case 5094: return "K" case 5095: return "d" case 5099: return "Oฬต" case 5102: return "6" case 5104: return "รŸ" case 5106: return "hฬ”" case 5107: return "G" case 5108: return "B" case 5115: return "ษข" case 5116: return "ส™" case 5120: return "=" case 5123: return "ฮ”" case 5132: return "ยทแ" case 5133: return "แยท" case 5134: return "ยทฮ”" case 5135: return "ฮ”ยท" case 5136: return "ยทแ„" case 5137: return "แ„ยท" case 5138: return "ยทแ…" case 5139: return "แ…ยท" case 5140: return "ยทแ†" case 5141: return "แ†ยท" case 5143: return "ยทแŠ" case 5144: return "แŠยท" case 5145: return "ยทแ‹" case 5146: return "แ‹ยท" case 5159: return "ยท" case 5163: return "แแ " case 5164: return "ฮ”แ " case 5165: return "แ…แ " case 5166: return "แŠแ " case 5167: return "V" case 5169: return "ษ…" case 5171: return ">" case 5175: return "ยท>" case 5176: return "<" case 5178: return "ยทV" case 5179: return "Vยท" case 5180: return "ยทษ…" case 5181: return "ษ…ยท" case 5182: return "ยทแฒ" case 5183: return "แฒยท" case 5184: return "ยท>" case 5185: return ">ยท" case 5186: return "ยทแด" case 5187: return "แดยท" case 5188: return "ยท<" case 5189: return "<ยท" case 5190: return "ยทแน" case 5191: return "แนยท" case 5194: return "'" case 5196: return "U" case 5198: return "ีˆ" case 5204: return "ยทแ‘" case 5207: return "ยทU" case 5208: return "Uยท" case 5209: return "ยทีˆ" case 5210: return "ีˆยท" case 5211: return "ยทแ‘" case 5212: return "แ‘ยท" case 5213: return "ยทแ‘" case 5214: return "แ‘ยท" case 5215: return "ยทแ‘‘" case 5216: return "แ‘‘ยท" case 5217: return "ยทแ‘•" case 5218: return "แ‘•ยท" case 5219: return "ยทแ‘–" case 5220: return "แ‘–ยท" case 5223: return "U'" case 5224: return "ีˆ'" case 5225: return "แ‘'" case 5226: return "แ‘•'" case 5229: return "P" case 5231: return "d" case 5234: return "b" case 5235: return "bฬ‡" case 5236: return "ยทแ‘ซ" case 5237: return "แ‘ซยท" case 5238: return "ยทP" case 5239: return "pยท" case 5240: return "ยทแ‘ฎ" case 5241: return "แ‘ฎยท" case 5242: return "ยทd" case 5243: return "dยท" case 5244: return "ยทแ‘ฐ" case 5245: return "แ‘ฐยท" case 5246: return "ยทb" case 5247: return "bยท" case 5248: return "ยทbฬ‡" case 5249: return "bฬ‡ยท" case 5253: return "แ‘ซ'" case 5254: return "P'" case 5255: return "d'" case 5256: return "b'" case 5261: return "J" case 5266: return "ยทแ’‰" case 5267: return "แ’‰ยท" case 5268: return "ยทแ’‹" case 5269: return "แ’‹ยท" case 5270: return "ยทแ’Œ" case 5271: return "แ’Œยท" case 5272: return "ยทJ" case 5273: return "Jยท" case 5274: return "ยทแ’Ž" case 5275: return "แ’Žยท" case 5276: return "ยทแ’" case 5277: return "แ’ยท" case 5278: return "ยทแ’‘" case 5279: return "แ’‘ยท" case 5285: return "ฮ“" case 5290: return "L" case 5292: return "ยทแ’ฃ" case 5293: return "แ’ฃยท" case 5294: return "ยทฮ“" case 5295: return "ฮ“ยท" case 5296: return "ยทแ’ฆ" case 5297: return "แ’ฆยท" case 5298: return "ยทแ’ง" case 5299: return "แ’งยท" case 5300: return "ยทแ’จ" case 5301: return "แ’จยท" case 5302: return "ยทL" case 5303: return "lยท" case 5304: return "ยทแ’ซ" case 5305: return "แ’ซยท" case 5311: return "2" case 5321: return "ยทแ“€" case 5322: return "แ“€ยท" case 5323: return "ยทแ“‡" case 5324: return "แ“‡ยท" case 5325: return "ยทแ“ˆ" case 5326: return "แ“ˆยท" case 5329: return "แก" case 5340: return "ยทแ““" case 5341: return "แ““ยท" case 5342: return "ยทแ“•" case 5343: return "แ“•ยท" case 5344: return "ยทแ“–" case 5345: return "แ“–ยท" case 5346: return "ยทแ“—" case 5347: return "แ“—ยท" case 5348: return "ยทแ“˜" case 5349: return "แ“˜ยท" case 5350: return "ยทแ“š" case 5351: return "แ“šยท" case 5352: return "ยทแ“›" case 5353: return "แ“›ยท" case 5366: return "ยทแ“ญ" case 5367: return "แ“ญยท" case 5368: return "ยทแ“ฏ" case 5369: return "แ“ฏยท" case 5370: return "ยทแ“ฐ" case 5371: return "แ“ฐยท" case 5372: return "ยทแ“ฑ" case 5373: return "แ“ฑยท" case 5374: return "ยทแ“ฒ" case 5375: return "แ“ฒยท" case 5376: return "ยทแ“ด" case 5377: return "แ“ดยท" case 5378: return "ยทแ“ต" case 5379: return "แ“ตยท" case 5388: return "แ”‹<" case 5389: return "แ”‹แ‘•" case 5390: return "แ”‹b" case 5391: return "แ”‹แ’" case 5399: return "ยทแ”" case 5400: return "แ”ยท" case 5401: return "ยทแ”‘" case 5402: return "แ”‘ยท" case 5403: return "ยทแ”’" case 5404: return "แ”’ยท" case 5405: return "ยทแ”“" case 5406: return "แ”“ยท" case 5407: return "ยทแ””" case 5408: return "แ””ยท" case 5409: return "ยทแ”•" case 5410: return "แ”•ยท" case 5411: return "ยทแ”–" case 5412: return "แ”–ยท" case 5423: return "ยท4" case 5424: return "4ยท" case 5425: return "ยทแ”จ" case 5426: return "แ”จยท" case 5427: return "ยทแ”ฉ" case 5428: return "แ”ฉยท" case 5429: return "ยทแ”ช" case 5430: return "แ”ชยท" case 5431: return "ยทแ”ซ" case 5432: return "แ”ซยท" case 5433: return "ยทแ”ญ" case 5434: return "แ”ญยท" case 5435: return "ยทแ”ฎ" case 5436: return "แ”ฎยท" case 5440: return "แฉ" case 5441: return "x" case 5454: return "ยทแ•Œ" case 5455: return "แ•Œยท" case 5467: return "ยทแ•š" case 5468: return "แ•šยท" case 5480: return "ยทแ•ง" case 5481: return "แ•งยท" case 5495: return "แบŸ" case 5500: return "H" case 5501: return "x" case 5502: return "แ•แ‘ฌ" case 5503: return "แ•P" case 5504: return "แ•แ‘ฎ" case 5505: return "แ•d" case 5506: return "แ•แ‘ฐ" case 5507: return "แ•b" case 5508: return "แ•bฬ‡" case 5509: return "แ•แ’ƒ" case 5511: return "R" case 5518: return "แ–•แ’Š" case 5519: return "แ–•แ’‹" case 5520: return "แ–•แ’Œ" case 5521: return "แ–•J" case 5522: return "แ–•แ’Ž" case 5523: return "แ–•แ’" case 5524: return "แ–•แ’‘" case 5551: return "b" case 5556: return "F" case 5557: return "โ„ฒ" case 5559: return "๊Ÿป" case 5572: return "โฑฏ" case 5573: return "A" case 5598: return "D" case 5610: return "D" case 5615: return "ั " case 5616: return "M" case 5623: return "B" case 5634: return "แ’" case 5635: return "แ’‰" case 5636: return "แ““" case 5639: return "แ“š" case 5666: return "แ•ƒ" case 5667: return "แ•†" case 5668: return "แ•Š" case 5678: return "ฦฑ" case 5679: return "ฮฉ" case 5684: return "ฦฑ" case 5685: return "ฮฉ" case 5741: return "X" case 5742: return "x" case 5743: return "แ•แ‘ซ" case 5744: return "แ–•แ’‰" case 5745: return "แ––แ’‹" case 5746: return "แ––แ’Œ" case 5747: return "แ––J" case 5748: return "แ––แ’Ž" case 5749: return "แ––แ’" case 5750: return "แ––แ’‘" case 5751: return "แ–งยท" case 5752: return "แ–จยท" case 5753: return "แ–ฉยท" case 5754: return "แ–ชยท" case 5755: return "แ–ซยท" case 5756: return "แ–ฌยท" case 5757: return "แ–ญยท" case 5760: return " " case 5810: return "<" case 5815: return "X" case 5825: return "l" case 5826: return "แšฝ" case 5836: return "'" case 5845: return "K" case 5846: return "M" case 5848: return "ฮจ" case 5857: return "แšผ" case 5867: return "ยท" case 5868: return ":" case 5869: return "+" case 5872: return "ฮฆ" case 5940: return "แœ•" case 5941: return "/" case 6031: return "แžŠ" case 6051: return "แžข" case 6071: return "เธด" case 6072: return "เธต" case 6073: return "เธถ" case 6074: return "เธท" case 6086: return "ฬŠ" case 6091: return "เนˆ" case 6099: return "ฬŠ" case 6100: return "เธฏ" case 6101: return "เนš" case 6105: return "เน" case 6106: return "เน›" case 6112: return "o" case 6147: return ":" case 6153: return ":" case 6229: return "แ ต" case 6294: return "แกœ" case 6323: return "ยทแขฑ" case 6326: return "ยทแขด" case 6329: return "ยทแขธ" case 6338: return "ยทแฃ€" case 6342: return "ยทแ“‚" case 6343: return "แ“‚ยท" case 6344: return "ยทแ“ƒ" case 6345: return "แ“ƒยท" case 6346: return "ยทแ“„" case 6347: return "แ“„ยท" case 6348: return "ยทแ“…" case 6349: return "แ“…ยท" case 6350: return "ยทแ•ƒ" case 6351: return "ยทแ•†" case 6352: return "ยทแ•‡" case 6353: return "ยทแ•ˆ" case 6354: return "ยทแ•‰" case 6355: return "ยทแ•‹" case 6363: return "แฃต" case 6364: return "แฃŸแž" case 6365: return "แžแฃŸ" case 6368: return "แ•ƒยท" case 6371: return "แ•žยท" case 6372: return "แ•ฆยท" case 6373: return "แ•ซยท" case 6376: return "แ–†ยท" case 6378: return "แ–—ยท" case 6381: return "ั ยท" case 6384: return "แ—ดยท" case 6386: return "แ˜›ยท" case 6608: return "แฆž" case 6609: return "แฆฑ" case 6784: return "แฉ…" case 6800: return "แฉ…" case 6825: return "แชจแชจ" case 6827: return "แชชแชจ" case 6836: return "›" case 6839: return "ฬจ" case 6873: return "แซ†" case 6882: return "ฬ„" case 6887: return "\u1ae5" case 6888: return "ฬ„ฬ„" case 6994: return "แฌ" case 6995: return "แฌ‘" case 7000: return "แฌจ" case 7004: return "แญ" case 7007: return "แญžแญž" case 7228: return "แฐปแฐป" case 7295: return "แฑพแฑพ" case 7376: return "ฬ‚" case 7378: return "ฬ„" case 7379: return "''" case 7381: return "ฬซ" case 7384: return "ฬฎ" case 7385: return "ฬญ" case 7386: return "ฬŽ" case 7388: return "ฬฉ" case 7389: return "ฬฃ" case 7390: return "ฬค" case 7405: return "ฬ–" case 7428: return "c" case 7432: return "ษœ" case 7435: return "ฤธ" case 7437: return "ส" case 7439: return "o" case 7440: return "ษ”" case 7441: return "o" case 7444: return "วo" case 7452: return "u" case 7456: return "v" case 7457: return "w" case 7458: return "z" case 7460: return "ฦจ" case 7462: return "r" case 7463: return "สŒ" case 7464: return "ฯ€" case 7465: return "แด˜" case 7467: return "ะป" case 7486: return "แฃ–" case 7506: return "ยบ" case 7531: return "ue" case 7534: return "fฬด" case 7535: return "rnฬด" case 7536: return "nฬด" case 7538: return "rฬด" case 7539: return "ษพฬด" case 7540: return "sฬด" case 7541: return "tฬด" case 7542: return "zฬด" case 7544: return "แดด" case 7547: return "iฬต" case 7548: return "iฬต" case 7549: return "pฬต" case 7550: return "uฬต" case 7551: return "สŠฬต" case 7555: return "g" case 7564: return "y" case 7568: return "ษ‹" case 7583: return "แต‹" case 7586: return "แต" case 7610: return "แฃ”" case 7611: return "แ™†" case 7656: return "\u1ada" case 7662: return "โทฌ" case 7747: return "๊ญ‘" case 7834: return "แบฃ" case 7837: return "f" case 7838: return "รŸ" case 7935: return "y" case 8061: return "แฟด" case 8125: return "'" case 8126: return "i" case 8127: return "'" case 8128: return "~" case 8175: return "'" case 8182: return "แฏ" case 8189: return "'" case 8190: return "'" case 8192: return " " case 8193: return " " case 8194: return " " case 8195: return " " case 8196: return " " case 8197: return " " case 8198: return " " case 8199: return " " case 8200: return " " case 8201: return " " case 8202: return " " case 8208: return "-" case 8209: return "-" case 8210: return "-" case 8211: return "-" case 8212: return "ใƒผ" case 8213: return "ใƒผ" case 8214: return "ll" case 8216: return "'" case 8217: return "'" case 8218: return "," case 8219: return "'" case 8220: return "''" case 8221: return "''" case 8223: return "''" case 8226: return "ยท" case 8228: return "." case 8229: return ".." case 8230: return "..." case 8231: return "ยท" case 8232: return " " case 8233: return " " case 8239: return " " case 8240: return "ยบ/โ‚€โ‚€" case 8241: return "ยบ/โ‚€โ‚€โ‚€" case 8242: return "'" case 8243: return "''" case 8244: return "'''" case 8245: return "'" case 8246: return "''" case 8247: return "'''" case 8249: return "<" case 8250: return ">" case 8252: return "!!" case 8254: return "ห‰" case 8257: return "/" case 8259: return "-" case 8260: return "/" case 8263: return "??" case 8264: return "?!" case 8265: return "!?" case 8270: return "*" case 8274: return "ยบ/โ‚€" case 8275: return "~" case 8279: return "''''" case 8282: return ":" case 8285: return "โต—" case 8286: return "โต‚" case 8287: return " " case 8304: return "ยบ" case 8313: return "๊ฐ" case 8353: return "Cโƒซ" case 8356: return "ยฃ" case 8357: return "rnฬธ" case 8360: return "Rs" case 8361: return "Wฬต" case 8363: return "dฬตฬฑ" case 8364: return "๊ž’" case 8365: return "Kฬต" case 8366: return "Tโƒซ" case 8374: return "lt" case 8381: return "ี”" case 8385: return "ุฑู‰lู„" case 8411: return "›" case 8448: return "a/c" case 8449: return "a/s" case 8450: return "C" case 8451: return "ยฐC" case 8453: return "c/o" case 8454: return "c/u" case 8455: return "ฦ" case 8456: return "ะญ" case 8457: return "ยฐF" case 8458: return "g" case 8459: return "H" case 8460: return "H" case 8461: return "H" case 8462: return "h" case 8463: return "hฬต" case 8464: return "l" case 8465: return "l" case 8466: return "L" case 8467: return "l" case 8469: return "N" case 8470: return "No" case 8473: return "P" case 8474: return "Q" case 8475: return "R" case 8476: return "R" case 8477: return "R" case 8481: return "TEL" case 8484: return "Z" case 8486: return "ฮฉ" case 8487: return "ฦฑ" case 8488: return "Z" case 8489: return "ษฟ" case 8490: return "K" case 8492: return "B" case 8493: return "C" case 8494: return "e" case 8495: return "e" case 8496: return "E" case 8497: return "F" case 8499: return "M" case 8500: return "o" case 8501: return "ื" case 8502: return "ื‘" case 8503: return "ื’" case 8504: return "ื“" case 8505: return "i" case 8507: return "FAX" case 8508: return "ฯ€" case 8509: return "y" case 8510: return "ฮ“" case 8511: return "ฮ " case 8512: return "ฦฉ" case 8513: return "๊“จ" case 8514: return "๊“ถ" case 8515: return "๐–ผ€" case 8517: return "D" case 8518: return "d" case 8519: return "e" case 8520: return "i" case 8521: return "j" case 8544: return "l" case 8545: return "ll" case 8546: return "lll" case 8547: return "lV" case 8548: return "V" case 8549: return "Vl" case 8550: return "Vll" case 8551: return "Vlll" case 8552: return "lX" case 8553: return "X" case 8554: return "Xl" case 8555: return "Xll" case 8556: return "L" case 8557: return "C" case 8558: return "D" case 8559: return "M" case 8560: return "i" case 8561: return "ii" case 8562: return "iii" case 8563: return "iv" case 8564: return "v" case 8565: return "vi" case 8566: return "vii" case 8567: return "viii" case 8568: return "ix" case 8569: return "x" case 8570: return "xi" case 8571: return "xii" case 8572: return "l" case 8573: return "c" case 8574: return "d" case 8575: return "rn" case 8579: return "ฦ†" case 8580: return "ษ”" case 8593: return "แ›" case 8597: return "แ›จ" case 8629: return "โ†ฒ" case 8634: return "๐Ÿ„Ž" case 8638: return "แ›š" case 8639: return "แ›" case 8644: return "\U0001f8d0" case 8652: return "\U0001f8d1" case 8704: return "โฑฏ" case 8707: return "ฦŽ" case 8710: return "ฮ”" case 8719: return "ฮ " case 8721: return "ฦฉ" case 8722: return "-" case 8724: return "+ฬ‡" case 8725: return "/" case 8726: return "\\" case 8727: return "*" case 8728: return "ยฐ" case 8729: return "ยท" case 8734: return "oo" case 8739: return "l" case 8741: return "ll" case 8744: return "v" case 8745: return "ีˆ" case 8746: return "U" case 8747: return "สƒ" case 8748: return "สƒสƒ" case 8749: return "สƒสƒสƒ" case 8751: return "โˆฎโˆฎ" case 8752: return "โˆฎโˆฎโˆฎ" case 8758: return ":" case 8760: return "-ฬ‡" case 8764: return "~" case 8784: return "=ฬ‡" case 8785: return "=ฬ‡ฬฃ" case 8791: return "=ฬŠ" case 8793: return "=ฬ‚" case 8794: return "=ฬ†" case 8798: return "=อซ" case 8803: return "โ‰ก" case 8810: return "<<" case 8811: return ">>" case 8834: return "แ‘•" case 8835: return "แ‘" case 8853: return "๐Šจ" case 8854: return "Oฬต" case 8857: return "ส˜" case 8861: return "Oฬต" case 8868: return "T" case 8869: return "๊“•" case 8896: return "โˆง" case 8897: return "v" case 8898: return "ีˆ" case 8899: return "U" case 8900: return "แ›œ" case 8901: return "ยท" case 8904: return "แ›ž" case 8918: return "<ยท" case 8919: return "ยท>" case 8920: return "<<<" case 8921: return ">>>" case 8942: return "โต—" case 8943: return "ยทยทยท" case 8948: return "๊ž“" case 8959: return "E" case 8960: return "โˆ…" case 8997: return "โŒค" case 9001: return "โฌ" case 9002: return "โญ" case 9025: return "ใ€ผ" case 9049: return "ฮ”ฬฒ" case 9050: return "แ›œฬฒ" case 9052: return "ยฐฬฒ" case 9055: return "โŠ›" case 9057: return "Tฬˆ" case 9058: return "โˆ‡ฬˆ" case 9059: return "โ‹†ฬˆ" case 9060: return "ยฐฬˆ" case 9061: return "ุฉ" case 9064: return "~ฬˆ" case 9065: return "แต" case 9067: return "โˆ‡ฬด" case 9068: return "Oฬต" case 9075: return "i" case 9076: return "p" case 9077: return "ฯ‰" case 9078: return "aฬฒ" case 9079: return "๊ž“ฬฒ" case 9080: return "iฬฒ" case 9081: return "ฯ‰ฬฒ" case 9082: return "a" case 9087: return "แšฝ" case 9116: return "ไธจ" case 9119: return "ไธจ" case 9122: return "ไธจ" case 9125: return "ไธจ" case 9130: return "ไธจ" case 9134: return "ไธจ" case 9153: return "โ•" case 9154: return "โŽ" case 9155: return "โ‹" case 9158: return "โญ" case 9192: return "โ‚โ‚€" case 9212: return "โป" case 9213: return "l" case 9214: return "โ˜พ" case 9290: return "\\\\" case 9312: return "โž€" case 9313: return "โž" case 9314: return "โž‚" case 9315: return "โžƒ" case 9316: return "โž„" case 9317: return "โž…" case 9318: return "โž†" case 9319: return "โž‡" case 9320: return "โžˆ" case 9321: return "โž‰" case 9332: return "(l)" case 9333: return "(2)" case 9334: return "(3)" case 9335: return "(4)" case 9336: return "(5)" case 9337: return "(6)" case 9338: return "(7)" case 9339: return "(8)" case 9340: return "(9)" case 9341: return "(lO)" case 9342: return "(ll)" case 9343: return "(l2)" case 9344: return "(l3)" case 9345: return "(l4)" case 9346: return "(l5)" case 9347: return "(l6)" case 9348: return "(l7)" case 9349: return "(l8)" case 9350: return "(l9)" case 9351: return "(2O)" case 9352: return "l." case 9353: return "2." case 9354: return "3." case 9355: return "4." case 9356: return "5." case 9357: return "6." case 9358: return "7." case 9359: return "8." case 9360: return "9." case 9361: return "lO." case 9362: return "ll." case 9363: return "l2." case 9364: return "l3." case 9365: return "l4." case 9366: return "l5." case 9367: return "l6." case 9368: return "l7." case 9369: return "l8." case 9370: return "l9." case 9371: return "2O." case 9372: return "(a)" case 9373: return "(b)" case 9374: return "(c)" case 9375: return "(d)" case 9376: return "(e)" case 9377: return "(f)" case 9378: return "(g)" case 9379: return "(h)" case 9380: return "(i)" case 9381: return "(j)" case 9382: return "(k)" case 9383: return "(l)" case 9384: return "(rn)" case 9385: return "(n)" case 9386: return "(o)" case 9387: return "(p)" case 9388: return "(q)" case 9389: return "(r)" case 9390: return "(s)" case 9391: return "(t)" case 9392: return "(u)" case 9393: return "(v)" case 9394: return "(w)" case 9395: return "(x)" case 9396: return "(y)" case 9397: return "(z)" case 9400: return "ยฉ" case 9413: return "โ„—" case 9415: return "ยฎ" case 9435: return "โ’พ" case 9450: return "๐Ÿ„" case 9472: return "ใƒผ" case 9473: return "ใƒผ" case 9475: return "โ”‚" case 9487: return "โ”Œ" case 9507: return "โ”œ" case 9578: return "ว‚" case 9585: return "/" case 9587: return "X" case 9608: return "โˆŽ" case 9616: return "โ–Œ" case 9620: return "ห‰" case 9623: return "โ––" case 9629: return "โ–˜" case 9632: return "โˆŽ" case 9649: return "โฅ" case 9651: return "ฮ”" case 9655: return "โŠณ" case 9656: return "โ–ถ" case 9658: return "โ–ถ" case 9661: return "๐Šผ" case 9665: return "โŠฒ" case 9671: return "แ›œ" case 9674: return "แ›œ" case 9675: return "ยฐ" case 9678: return "โŒพ" case 9696: return "โŒ’" case 9702: return "ยฐ" case 9737: return "ส˜" case 9744: return "โ–ก" case 9765: return "๐ฆž" case 9776: return "ฮž" case 9784: return "โŽˆ" case 9806: return "โ‰" case 9815: return "\U0001fa55" case 9821: return "\U0001fa57" case 9826: return "แ›œ" case 9833: return "๐…˜๐…ฅ" case 9834: return "๐…˜๐…ฅ๐…ฎ" case 9900: return "เฅฐ" case 10088: return "(" case 10089: return ")" case 10094: return "<" case 10095: return ">" case 10098: return "(" case 10099: return ")" case 10100: return "{" case 10101: return "}" case 10133: return "+" case 10134: return "-" case 10135: return "รท" case 10178: return "๊“•" case 10184: return "\\แ‘•" case 10185: return "แ‘/" case 10187: return "/" case 10189: return "\\" case 10201: return "T" case 10216: return "โฌ" case 10217: return "โญ" case 10495: return "\U0001cee0" case 10539: return "x" case 10540: return "x" case 10595: return "แ›แ›š" case 10597: return "โ‡ƒโ‡‚" case 10606: return "แ›โ‡‚" case 10607: return "โ‡ƒแ›š" case 10649: return "โต‚" case 10672: return "โ‰" case 10677: return "\U0001cef0" case 10686: return "โŒพ" case 10692: return "ใ€ผ" case 10693: return "โ‚" case 10695: return "โŒป" case 10710: return "๐‹€" case 10713: return "โฆš" case 10740: return ":โ†’" case 10741: return "\\" case 10742: return "/ฬ„" case 10744: return "/" case 10745: return "\\" case 10752: return "ส˜" case 10753: return "๐Šจ" case 10754: return "โŠ—" case 10755: return "โŠ" case 10756: return "โŠŽ" case 10757: return "โŠ“" case 10758: return "โŠ”" case 10764: return "สƒสƒสƒสƒ" case 10781: return "แ›ž" case 10784: return ">>" case 10785: return "แ›š" case 10786: return "+ฬŠ" case 10787: return "+ฬ‚" case 10788: return "+ฬƒ" case 10789: return "+ฬฃ" case 10790: return "+ฬฐ" case 10791: return "+โ‚‚" case 10793: return "-ฬ“" case 10794: return "-ฬฃ" case 10799: return "x" case 10800: return "xฬ‡" case 10813: return "โŒ™" case 10814: return "โจŸ" case 10815: return "โˆ" case 10858: return "~ฬ‡" case 10862: return "=โƒฐ" case 10868: return "::=" case 10869: return "==" case 10870: return "===" case 10917: return "><" case 10922: return "แ—•" case 10923: return "แ—’" case 10967: return "แ‘แ‘•" case 11003: return "///" case 11005: return "//" case 11158: return "=แชฒ" case 11244: return "โ†ž" case 11245: return "โ†Ÿ" case 11246: return "โ† " case 11247: return "โ†ก" case 11367: return "Hฬฉ" case 11369: return "Kฬฉ" case 11394: return "B" case 11395: return "ส™" case 11396: return "ฮ“" case 11397: return "r" case 11398: return "ฮ”" case 11400: return "๊ž’" case 11401: return "๊ž“" case 11403: return "ฯ‚" case 11404: return "โฑซ" case 11405: return "โฑฌ" case 11406: return "H" case 11407: return "สœ" case 11408: return "Oฬต" case 11409: return "oฬต" case 11410: return "l" case 11411: return "i" case 11412: return "K" case 11413: return "ฤธ" case 11414: return "ฮป" case 11415: return "สŒ" case 11416: return "M" case 11417: return "ส" case 11418: return "N" case 11419: return "ษด" case 11420: return "3" case 11421: return "ส“" case 11422: return "O" case 11423: return "o" case 11424: return "ฮ " case 11425: return "ฯ€" case 11426: return "P" case 11427: return "p" case 11428: return "C" case 11429: return "c" case 11430: return "T" case 11431: return "แด›" case 11432: return "Y" case 11433: return "y" case 11434: return "ฮฆ" case 11435: return "ษธ" case 11436: return "X" case 11437: return "ฯ‡" case 11438: return "ฮจ" case 11439: return "ฯˆ" case 11440: return "๊™Œ" case 11441: return "ฯ‰" case 11442: return "-ฬ‡" case 11443: return "-ฬ‡" case 11444: return "<ยท" case 11445: return "<ยท" case 11446: return "ฮž" case 11447: return "โ‰ก" case 11450: return "-" case 11451: return "-" case 11452: return "ะจ" case 11453: return "w" case 11456: return "ี”" case 11457: return "ฯผ" case 11460: return "3" case 11461: return "ศ" case 11462: return "/" case 11463: return "/" case 11466: return "9" case 11467: return "9" case 11468: return "3" case 11469: return "ศ" case 11470: return "P" case 11471: return "p" case 11472: return "L" case 11473: return "สŸ" case 11474: return "6" case 11475: return "6" case 11484: return "6" case 11485: return "แบŸ" case 11488: return "ษธ" case 11489: return "ษธ" case 11492: return "ฯ—" case 11496: return "ี”" case 11497: return "โ˜ง" case 11513: return "\\\\" case 11569: return "Oฬต" case 11575: return "ษ…" case 11576: return "V" case 11577: return "E" case 11578: return "ฦŽ" case 11585: return "Oฬธ" case 11592: return "ยทยทยท" case 11593: return "ฦฉ" case 11599: return "l" case 11601: return "!" case 11604: return "O" case 11605: return "Q" case 11609: return "ส˜" case 11613: return "X" case 11616: return "ฮ”" case 11619: return "แ›ฏ" case 11752: return "แทŸ" case 11754: return "ฬŠ" case 11757: return "อจ" case 11758: return "\u1adb" case 11759: return "อฏ" case 11766: return "อฃ" case 11767: return "อค" case 11802: return "-ฬˆ" case 11806: return "~ฬ‡" case 11807: return "~ฬฃ" case 11814: return "แ‘•" case 11815: return "แ‘" case 11816: return "((" case 11817: return "))" case 11818: return "โˆต" case 11819: return "โˆด" case 11820: return "โˆท" case 11822: return "ุŸ" case 11824: return "ยฐ" case 11825: return "ยท" case 11826: return "ุŒ" case 11829: return "ุ›" case 11833: return "แบŸ" case 11837: return "โต‚" case 11839: return "ยถ" case 11840: return "=" case 11906: return "ไน›" case 11907: return "ไนš" case 11909: return "ไบป" case 11913: return "ๅˆ‚" case 11915: return "ใ”พ" case 11918: return "ๅ…€" case 11919: return "ๅฐฃ" case 11920: return "ๅฐข" case 11922: return "ๅทณ" case 11923: return "ๅนบ" case 11924: return "ๅฝ‘" case 11926: return "ๅฟ„" case 11927: return "ใฃบ" case 11928: return "ๆ‰Œ" case 11929: return "ๆ”ต" case 11931: return "ๆ—ก" case 11934: return "ๆญบ" case 11935: return "ๆฏ" case 11936: return "ๆฐ‘" case 11937: return "ๆฐต" case 11938: return "ๆฐบ" case 11939: return "็ฌ" case 11940: return "็ˆซ" case 11942: return "ไธฌ" case 11944: return "็Šญ" case 11947: return "็ฝ’" case 11949: return "็คป" case 11951: return "็ณน" case 11953: return "็ฝ“" case 11954: return "็ฝ’" case 11961: return "่€‚" case 11962: return "่‚€" case 11966: return "่‰น" case 11967: return "่‰น" case 11968: return "่‰น" case 11969: return "่™Ž" case 11970: return "่กค" case 11971: return "่ฆ€" case 11972: return "่ฅฟ" case 11973: return "่ง" case 11976: return "่ฎ " case 11977: return "่ด" case 11979: return "่ฝฆ" case 11980: return "่พถ" case 11981: return "่พถ" case 11983: return "้˜" case 11984: return "้’…" case 11985: return "แ„ใƒผแ„‚แ„Œ" case 11986: return "้•ธ" case 11987: return "้•ฟ" case 11988: return "้—จ" case 11990: return "้˜" case 11992: return "้’" case 11993: return "้Ÿฆ" case 11994: return "้กต" case 11995: return "้ฃŽ" case 11996: return "้ฃž" case 11997: return "้ฃŸ" case 11999: return "้ฃ " case 12000: return "้ฅฃ" case 12002: return "้ฉฌ" case 12004: return "้ฌผ" case 12005: return "้ฑผ" case 12008: return "้บฆ" case 12009: return "้ป„" case 12011: return "ๆ–‰" case 12012: return "้ฝ" case 12013: return "ๆญฏ" case 12014: return "้ฝฟ" case 12015: return "็ซœ" case 12016: return "้พ™" case 12018: return "ไบ€" case 12019: return "้พŸ" case 12032: return "ใƒผ" case 12033: return "ไธจ" case 12034: return "\\" case 12035: return "/" case 12036: return "ไน™" case 12037: return "ไบ…" case 12038: return "ไบŒ" case 12039: return "ไบ " case 12040: return "ไบบ" case 12041: return "ๅ„ฟ" case 12042: return "ๅ…ฅ" case 12043: return "ๅ…ซ" case 12044: return "ๅ†‚" case 12045: return "ๅ†–" case 12046: return "ๅ†ซ" case 12047: return "ๅ‡ " case 12048: return "ๅ‡ต" case 12049: return "ๅˆ€" case 12050: return "ๅŠ›" case 12051: return "ๅ‹น" case 12052: return "ๅŒ•" case 12053: return "ๅŒš" case 12054: return "ๅŒธ" case 12055: return "ๅ" case 12056: return "ๅœ" case 12057: return "ๅฉ" case 12058: return "ๅŽ‚" case 12059: return "ๅŽถ" case 12060: return "ๅˆ" case 12061: return "ๅฃ" case 12062: return "ๅฃ" case 12063: return "ๅœŸ" case 12064: return "ๅœŸ" case 12065: return "ๅค‚" case 12066: return "ๅคŠ" case 12067: return "ๅค•" case 12068: return "ๅคง" case 12069: return "ๅฅณ" case 12070: return "ๅญ" case 12071: return "ๅฎ€" case 12072: return "ๅฏธ" case 12073: return "ๅฐ" case 12074: return "ๅฐข" case 12075: return "ๅฐธ" case 12076: return "ๅฑฎ" case 12077: return "ๅฑฑ" case 12078: return "ๅท›" case 12079: return "ๅทฅ" case 12080: return "ๅทฑ" case 12081: return "ๅทพ" case 12082: return "ๅนฒ" case 12083: return "ๅนบ" case 12084: return "ๅนฟ" case 12085: return "ๅปด" case 12086: return "ๅปพ" case 12087: return "ๅผ‹" case 12088: return "ๅผ“" case 12089: return "ๅฝ" case 12090: return "ๅฝก" case 12091: return "ๅฝณ" case 12092: return "ๅฟƒ" case 12093: return "ๆˆˆ" case 12094: return "ๆˆถ" case 12095: return "ๆ‰‹" case 12096: return "ๆ”ฏ" case 12097: return "ๆ”ด" case 12098: return "ๆ–‡" case 12099: return "ๆ–—" case 12100: return "ๆ–ค" case 12101: return "ๆ–น" case 12102: return "ๆ— " case 12103: return "ๆ—ฅ" case 12104: return "ๆ›ฐ" case 12105: return "ๆœˆ" case 12106: return "ๆœจ" case 12107: return "ๆฌ " case 12108: return "ๆญข" case 12109: return "ๆญน" case 12110: return "ๆฎณ" case 12111: return "ๆฏ‹" case 12112: return "ๆฏ”" case 12113: return "ๆฏ›" case 12114: return "ๆฐ" case 12115: return "ๆฐ”" case 12116: return "ๆฐด" case 12117: return "็ซ" case 12118: return "็ˆช" case 12119: return "็ˆถ" case 12120: return "็ˆป" case 12121: return "แ„‚แ…ฎไธจ" case 12122: return "็‰‡" case 12123: return "็‰™" case 12124: return "็‰›" case 12125: return "็Šฌ" case 12126: return "็Ž„" case 12127: return "็މ" case 12128: return "็“œ" case 12129: return "็“ฆ" case 12130: return "็”˜" case 12131: return "็”Ÿ" case 12132: return "็”จ" case 12133: return "็”ฐ" case 12134: return "็–‹" case 12135: return "็–’" case 12136: return "็™ถ" case 12137: return "็™ฝ" case 12138: return "็šฎ" case 12139: return "็šฟ" case 12140: return "็›ฎ" case 12141: return "็Ÿ›" case 12142: return "็Ÿข" case 12143: return "็Ÿณ" case 12144: return "็คบ" case 12145: return "็ฆธ" case 12146: return "็ฆพ" case 12147: return "็ฉด" case 12148: return "็ซ‹" case 12149: return "็ซน" case 12150: return "็ฑณ" case 12151: return "็ณธ" case 12152: return "็ผถ" case 12153: return "็ฝ‘" case 12154: return "็พŠ" case 12155: return "็พฝ" case 12156: return "่€" case 12157: return "่€Œ" case 12158: return "่€’" case 12159: return "่€ณ" case 12160: return "่ฟ" case 12161: return "่‚‰" case 12162: return "่‡ฃ" case 12163: return "่‡ช" case 12164: return "่‡ณ" case 12165: return "่‡ผ" case 12166: return "่ˆŒ" case 12167: return "่ˆ›" case 12168: return "่ˆŸ" case 12169: return "่‰ฎ" case 12170: return "่‰ฒ" case 12171: return "่‰ธ" case 12172: return "่™" case 12173: return "่™ซ" case 12174: return "่ก€" case 12175: return "่กŒ" case 12176: return "่กฃ" case 12177: return "่ฅพ" case 12178: return "่ฆ‹" case 12179: return "่ง’" case 12180: return "่จ€" case 12181: return "่ฐท" case 12182: return "่ฑ†" case 12183: return "่ฑ•" case 12184: return "่ฑธ" case 12185: return "่ฒ" case 12186: return "่ตค" case 12187: return "่ตฐ" case 12188: return "่ถณ" case 12189: return "่บซ" case 12190: return "่ปŠ" case 12191: return "่พ›" case 12192: return "่พฐ" case 12193: return "่พต" case 12194: return "้‚‘" case 12195: return "้…‰" case 12196: return "้‡†" case 12197: return "้‡Œ" case 12198: return "้‡‘" case 12199: return "แ„ใƒผแ„‚แ„Œ" case 12200: return "้–€" case 12201: return "้˜œ" case 12202: return "้šถ" case 12203: return "้šน" case 12204: return "้›จ" case 12205: return "้‘" case 12206: return "้ž" case 12207: return "้ข" case 12208: return "้ฉ" case 12209: return "้Ÿ‹" case 12210: return "้Ÿญ" case 12211: return "้Ÿณ" case 12212: return "้ " case 12213: return "้ขจ" case 12214: return "้ฃ›" case 12215: return "้ฃŸ" case 12216: return "้ฆ–" case 12217: return "้ฆ™" case 12218: return "้ฆฌ" case 12219: return "้ชจ" case 12220: return "้ซ˜" case 12221: return "้ซŸ" case 12222: return "้ฌฅ" case 12223: return "้ฌฏ" case 12224: return "้ฌฒ" case 12225: return "้ฌผ" case 12226: return "้ญš" case 12227: return "้ณฅ" case 12228: return "้นต" case 12229: return "้นฟ" case 12230: return "้บฅ" case 12231: return "้บป" case 12232: return "้ปƒ" case 12233: return "้ป" case 12234: return "้ป‘" case 12235: return "้ปน" case 12236: return "้ปฝ" case 12237: return "้ผŽ" case 12238: return "้ผ“" case 12239: return "้ผ " case 12240: return "้ผป" case 12241: return "้ฝŠ" case 12242: return "้ฝ’" case 12243: return "้พ" case 12244: return "้พœ" case 12245: return "้พ " case 12290: return "หณ" case 12291: return "''" case 12295: return "O" case 12296: return "โฌ" case 12297: return "โญ" case 12306: return "โ‚ธ" case 12308: return "(" case 12309: return ")" case 12314: return "โŸฆ" case 12315: return "โŸง" case 12332: return "ฬ‰" case 12333: return "ฬฅ" case 12339: return "/" case 12342: return "โ‚ธ" case 12344: return "ๅ" case 12345: return "ๅ„" case 12346: return "ๅ…" case 12367: return "โฌ" case 12442: return "ฬŠ" case 12443: return "๏พž" case 12444: return "๏พŸ" case 12448: return "=" case 12452: return "ไบป" case 12456: return "ๅทฅ" case 12459: return "ๅŠ›" case 12479: return "ๅค•" case 12488: return "ๅœ" case 12491: return "ไบŒ" case 12494: return "/" case 12495: return "ๅ…ซ" case 12504: return "ใธ" case 12525: return "ๅฃ" case 12539: return "ยท" case 12582: return "ๅ„ฟ" case 12593: return "แ„€" case 12594: return "แ„€แ„€" case 12595: return "แ„€แ„‰" case 12596: return "แ„‚" case 12597: return "แ„‚แ„Œ" case 12598: return "แ„‚แ„’" case 12599: return "แ„ƒ" case 12600: return "แ„ƒแ„ƒ" case 12601: return "แ„…" case 12602: return "แ„…แ„€" case 12603: return "แ„…แ„†" case 12604: return "แ„…แ„‡" case 12605: return "แ„…แ„‰" case 12606: return "แ„…แ„" case 12607: return "แ„…แ„‘" case 12608: return "แ„…แ„’" case 12609: return "แ„†" case 12610: return "แ„‡" case 12611: return "แ„‡แ„‡" case 12612: return "แ„‡แ„‰" case 12613: return "แ„‰" case 12614: return "แ„‰แ„‰" case 12615: return "แ„‹" case 12616: return "แ„Œ" case 12617: return "แ„Œแ„Œ" case 12618: return "แ„Ž" case 12619: return "แ„" case 12620: return "แ„" case 12621: return "แ„‘" case 12622: return "แ„’" case 12623: return "แ…ก" case 12624: return "แ…กไธจ" case 12625: return "แ…ฃ" case 12626: return "แ…ฃไธจ" case 12627: return "แ…ฅ" case 12628: return "แ…ฅไธจ" case 12629: return "แ…ง" case 12630: return "แ…งไธจ" case 12631: return "แ…ฉ" case 12632: return "แ…ฉแ…ก" case 12633: return "แ…ฉแ…กไธจ" case 12634: return "แ…ฉไธจ" case 12635: return "แ…ญ" case 12636: return "แ…ฎ" case 12637: return "แ…ฎแ…ฅ" case 12638: return "แ…ฎแ…ฅไธจ" case 12639: return "แ…ฎไธจ" case 12640: return "แ…ฒ" case 12641: return "ใƒผ" case 12642: return "ใƒผไธจ" case 12643: return "ไธจ" case 12644: return "แ… " case 12645: return "แ„‚แ„‚" case 12646: return "แ„‚แ„ƒ" case 12647: return "แ„‚แ„‰" case 12648: return "แ„‚แ…€" case 12649: return "แ„…แ„€แ„‰" case 12650: return "แ„…แ„ƒ" case 12651: return "แ„…แ„‡แ„‰" case 12652: return "แ„…แ…€" case 12653: return "แ„…แ…™" case 12654: return "แ„†แ„‡" case 12655: return "แ„†แ„‰" case 12656: return "แ„†แ…€" case 12657: return "แ„†แ„‹" case 12658: return "แ„‡แ„€" case 12659: return "แ„‡แ„ƒ" case 12660: return "แ„‡แ„‰แ„€" case 12661: return "แ„‡แ„‰แ„ƒ" case 12662: return "แ„‡แ„Œ" case 12663: return "แ„‡แ„" case 12664: return "แ„‡แ„‹" case 12665: return "แ„‡แ„‡แ„‹" case 12666: return "แ„‰แ„€" case 12667: return "แ„‰แ„‚" case 12668: return "แ„‰แ„ƒ" case 12669: return "แ„‰แ„‡" case 12670: return "แ„‰แ„Œ" case 12671: return "แ…€" case 12672: return "แ„‹แ„‹" case 12673: return "แ…Œ" case 12674: return "แ„‹แ„‰" case 12675: return "แ„‹แ…€" case 12676: return "แ„‘แ„‹" case 12677: return "แ„’แ„’" case 12678: return "แ…™" case 12679: return "แ…ญแ…ฃ" case 12680: return "แ…ญแ…ฃไธจ" case 12681: return "แ…ญไธจ" case 12682: return "แ…ฒแ…ง" case 12683: return "แ…ฒแ…งไธจ" case 12684: return "แ…ฒไธจ" case 12685: return "แ†ž" case 12686: return "แ†žไธจ" case 12752: return "ใƒผ" case 12753: return "ไธจ" case 12755: return "/" case 12756: return "\\" case 12758: return "ไน›" case 12762: return "ไบ…" case 12763: return "โฌ" case 12767: return "ไนš" case 12768: return "ไน™" case 12800: return "(แ„€)" case 12801: return "(แ„‚)" case 12802: return "(แ„ƒ)" case 12803: return "(แ„…)" case 12804: return "(แ„†)" case 12805: return "(แ„‡)" case 12806: return "(แ„‰)" case 12807: return "(แ„‹)" case 12808: return "(แ„Œ)" case 12809: return "(แ„Ž)" case 12810: return "(แ„)" case 12811: return "(แ„)" case 12812: return "(แ„‘)" case 12813: return "(แ„’)" case 12814: return "(๊ฐ€)" case 12815: return "(๋‚˜)" case 12816: return "(๋‹ค)" case 12817: return "(๋ผ)" case 12818: return "(๋งˆ)" case 12819: return "(๋ฐ”)" case 12820: return "(์‚ฌ)" case 12821: return "(์•„)" case 12822: return "(์ž)" case 12823: return "(์ฐจ)" case 12824: return "(์นด)" case 12825: return "(ํƒ€)" case 12826: return "(ํŒŒ)" case 12827: return "(ํ•˜)" case 12828: return "(์ฃผ)" case 12829: return "(์˜ค์ „)" case 12830: return "(์˜คํ›„)" case 12832: return "(ใƒผ)" case 12833: return "(ไบŒ)" case 12834: return "(ไธ‰)" case 12835: return "(ๅ››)" case 12836: return "(ไบ”)" case 12837: return "(ๅ…ญ)" case 12838: return "(ไธƒ)" case 12839: return "(ๅ…ซ)" case 12840: return "(ไน)" case 12841: return "(ๅ)" case 12842: return "(ๆœˆ)" case 12843: return "(็ซ)" case 12844: return "(ๆฐด)" case 12845: return "(ๆœจ)" case 12846: return "(้‡‘)" case 12847: return "(ๅœŸ)" case 12848: return "(ๆ—ฅ)" case 12849: return "(ๆ ช)" case 12850: return "(ๆœ‰)" case 12851: return "(็คพ)" case 12852: return "(ๅ)" case 12853: return "(็‰น)" case 12854: return "(่ฒก)" case 12855: return "(็ฅ)" case 12856: return "(ๅŠด)" case 12857: return "(ไปฃ)" case 12858: return "(ๅ‘ผ)" case 12859: return "(ๅญฆ)" case 12860: return "(็›ฃ)" case 12861: return "(ไผ)" case 12862: return "(่ณ‡)" case 12863: return "(ๅ”)" case 12864: return "(็ฅญ)" case 12865: return "(ไผ‘)" case 12866: return "(่‡ช)" case 12867: return "(่‡ณ)" case 12992: return "lๆœˆ" case 12993: return "2ๆœˆ" case 12994: return "3ๆœˆ" case 12995: return "4ๆœˆ" case 12996: return "5ๆœˆ" case 12997: return "6ๆœˆ" case 12998: return "7ๆœˆ" case 12999: return "8ๆœˆ" case 13000: return "9ๆœˆ" case 13001: return "lOๆœˆ" case 13002: return "llๆœˆ" case 13003: return "l2ๆœˆ" case 13144: return "O็‚น" case 13145: return "l็‚น" case 13146: return "2็‚น" case 13147: return "3็‚น" case 13148: return "4็‚น" case 13149: return "5็‚น" case 13150: return "6็‚น" case 13151: return "7็‚น" case 13152: return "8็‚น" case 13153: return "9็‚น" case 13154: return "lO็‚น" case 13155: return "ll็‚น" case 13156: return "l2็‚น" case 13157: return "l3็‚น" case 13158: return "l4็‚น" case 13159: return "l5็‚น" case 13160: return "l6็‚น" case 13161: return "l7็‚น" case 13162: return "l8็‚น" case 13163: return "l9็‚น" case 13164: return "2O็‚น" case 13165: return "2l็‚น" case 13166: return "22็‚น" case 13167: return "23็‚น" case 13168: return "24็‚น" case 13280: return "lๆ—ฅ" case 13281: return "2ๆ—ฅ" case 13282: return "3ๆ—ฅ" case 13283: return "4ๆ—ฅ" case 13284: return "5ๆ—ฅ" case 13285: return "6ๆ—ฅ" case 13286: return "7ๆ—ฅ" case 13287: return "8ๆ—ฅ" case 13288: return "9ๆ—ฅ" case 13289: return "lOๆ—ฅ" case 13290: return "llๆ—ฅ" case 13291: return "l2ๆ—ฅ" case 13292: return "l3ๆ—ฅ" case 13293: return "l4ๆ—ฅ" case 13294: return "l5ๆ—ฅ" case 13295: return "l6ๆ—ฅ" case 13296: return "l7ๆ—ฅ" case 13297: return "l8ๆ—ฅ" case 13298: return "l9ๆ—ฅ" case 13299: return "2Oๆ—ฅ" case 13300: return "2lๆ—ฅ" case 13301: return "22ๆ—ฅ" case 13302: return "23ๆ—ฅ" case 13303: return "24ๆ—ฅ" case 13304: return "25ๆ—ฅ" case 13305: return "26ๆ—ฅ" case 13306: return "27ๆ—ฅ" case 13307: return "28ๆ—ฅ" case 13308: return "29ๆ—ฅ" case 13309: return "3Oๆ—ฅ" case 13310: return "3lๆ—ฅ" case 14771: return "ใ˜ฝ" case 17307: return "ใ–ˆ" case 17440: return "ใฌป" case 18069: return "๐งขฎ" case 19968: return "ใƒผ" case 19989: return "แ„Œแ…ฉ" case 19995: return "แ„‰แ„‰ใƒผ" case 20022: return "\\" case 20031: return "/" case 20110: return "๐›„ข" case 20170: return "แ„‰ใƒผแ„€" case 20482: return "ไฝต" case 20540: return "ๅ€ค" case 20818: return "\U00016ff3" case 21343: return "แ„†แ…ก" case 21512: return "แ„‰ใƒผแ„†" case 21855: return "ๅ•“" case 22231: return "ๅฃ" case 22635: return "ๅกก" case 22763: return "ๅœŸ" case 22783: return "ๅขซ" case 23296: return "ๅชฏ" case 24114: return "ๅธก" case 24144: return "ใฌบ" case 24888: return "\U0002b73f" case 25144: return "ๆˆถ" case 25609: return "ใฉ" case 26211: return "ไ€ฟ" case 26217: return "ๆ™š" case 26358: return "ใซš" case 26406: return "ไ‘ƒ" case 26623: return "ๆฎ" case 27113: return "ใฎฃ" case 27175: return "ๆฆ" case 28505: return "ๆบˆ" case 29247: return "แ„‚แ…ฎไธจ" case 30799: return "็ ”" case 32118: return "็ต•" case 32934: return "ๆœŒ" case 32970: return "ๆœ" case 32976: return "ๆœ" case 33014: return "ใฌต" case 33025: return "ๆœ“" case 33063: return "ๆœ˜" case 33089: return "่ƒผ" case 33191: return "ๆœฃ" case 34111: return "่’" case 34369: return "่˜ท" case 35358: return "ไšถ" case 35453: return "่จฎ" case 35727: return "่ฎ†" case 35939: return "่ฑœ" case 36230: return "่ตฟ" case 36346: return "่ทฅ" case 36507: return "่บ—" case 36647: return "่ปฟ" case 37086: return "้ƒŽ" case 37806: return "้Žญ" case 38263: return "แ„ใƒผแ„‚แ„Œ" case 38584: return "้šท" case 40515: return "้น‚" case 40658: return "้ป‘" case 40899: return "ไ€น" case 42132: return "๊‹" case 42140: return "๊ƒ€" case 42142: return "๊Š" case 42151: return "๊‘˜" case 42152: return "๊„ฒ" case 42156: return "๊" case 42160: return "๊‚" case 42170: return "๊Žฟ" case 42174: return "๊Šฑ" case 42175: return "๊‰™" case 42176: return "๊Žซ" case 42178: return "๊Žต" case 42192: return "B" case 42193: return "P" case 42194: return "d" case 42195: return "D" case 42196: return "T" case 42198: return "G" case 42199: return "K" case 42201: return "J" case 42202: return "C" case 42203: return "ฦ†" case 42204: return "Z" case 42205: return "F" case 42206: return "โ„ฒ" case 42207: return "M" case 42208: return "N" case 42209: return "L" case 42210: return "S" case 42211: return "R" case 42213: return "ษ…" case 42214: return "V" case 42215: return "H" case 42218: return "W" case 42219: return "X" case 42220: return "Y" case 42221: return "แ™ " case 42222: return "A" case 42223: return "โฑฏ" case 42224: return "E" case 42225: return "ฦŽ" case 42226: return "l" case 42227: return "O" case 42228: return "U" case 42229: return "ีˆ" case 42231: return "แ—ก" case 42232: return "." case 42233: return "," case 42234: return ".." case 42235: return ".," case 42237: return ":" case 42238: return "-." case 42239: return "=" case 42510: return "." case 42564: return "2" case 42565: return "ฦจ" case 42567: return "i" case 42573: return "ฯ‰" case 42576: return "ะชl" case 42577: return "ห‰bi" case 42600: return "ส˜" case 42607: return "โƒฉ" case 42620: return "ฬ†" case 42622: return "ห‡" case 42645: return "hฬ”" case 42648: return "OO" case 42649: return "oo" case 42650: return "๐Šจ" case 42657: return "ะ˜" case 42672: return "แšน" case 42673: return "โฑต" case 42701: return "สก" case 42702: return "ษ…" case 42715: return "ฮ " case 42719: return "V" case 42731: return "?" case 42735: return "2" case 42736: return "ฬ‚" case 42737: return "ฬ„" case 42740: return "๊›ณ๊›ณ" case 42772: return "หซ" case 42774: return "หช" case 42792: return "T3" case 42793: return "tศ" case 42801: return "s" case 42802: return "AA" case 42803: return "aa" case 42804: return "AO" case 42805: return "ao" case 42806: return "AU" case 42807: return "au" case 42808: return "AV" case 42809: return "av" case 42810: return "AV" case 42811: return "av" case 42812: return "AY" case 42813: return "ay" case 42816: return "Kฬต" case 42826: return "Oฬต" case 42827: return "oฬต" case 42830: return "OO" case 42831: return "oo" case 42842: return "2" case 42849: return "wฬฆ" case 42858: return "3" case 42859: return "ศ" case 42862: return "9" case 42871: return "tf" case 42872: return "&" case 42874: return "๊น" case 42889: return ":" case 42892: return "'" case 42895: return "ยท" case 42901: return "๊œง" case 42904: return "F" case 42905: return "f" case 42906: return "๐’" case 42907: return "๐บ" case 42909: return "สš" case 42910: return "๊“ค" case 42911: return "u" case 42923: return "3" case 42929: return "๊“•" case 42930: return "J" case 42931: return "X" case 42932: return "B" case 42933: return "รŸ" case 42934: return "๊™Œ" case 42935: return "ฯ‰" case 42959: return "\ua7ce" case 42962: return "๊Ÿ“" case 42964: return "๊Ÿ•" case 42966: return "รŸ" case 42970: return "ษ…" case 42971: return "ฮป" case 42972: return "ษ…ฬธ" case 42993: return "แฃต" case 42999: return "ใƒผ" case 43056: return "เฅค" case 43360: return "แ„ƒแ„†" case 43361: return "แ„ƒแ„‡" case 43362: return "แ„ƒแ„‰" case 43363: return "แ„ƒแ„Œ" case 43364: return "แ„…แ„€" case 43365: return "แ„…แ„€แ„€" case 43366: return "แ„…แ„ƒ" case 43367: return "แ„…แ„ƒแ„ƒ" case 43368: return "แ„…แ„†" case 43369: return "แ„…แ„‡" case 43370: return "แ„…แ„‡แ„‡" case 43371: return "แ„…แ„‡แ„‹" case 43372: return "แ„…แ„‰" case 43373: return "แ„…แ„Œ" case 43374: return "แ„…แ„" case 43375: return "แ„†แ„€" case 43376: return "แ„†แ„ƒ" case 43377: return "แ„†แ„‰" case 43378: return "แ„‡แ„‰แ„" case 43379: return "แ„‡แ„" case 43380: return "แ„‡แ„’" case 43381: return "แ„‰แ„‰แ„‡" case 43382: return "แ„‹แ„…" case 43383: return "แ„‹แ„’" case 43384: return "แ„Œแ„Œแ„’" case 43385: return "แ„แ„" case 43386: return "แ„‘แ„’" case 43387: return "แ„’แ„‰" case 43388: return "แ…™แ…™" case 43410: return "โฐฟ" case 43427: return "๊ฆ" case 43462: return "๊ง" case 43471: return "ูข" case 43603: return "๊จ" case 43606: return "๊จฃ" case 43826: return "e" case 43829: return "f" case 43837: return "o" case 43838: return "oฬธ" case 43839: return "ษ”ฬธ" case 43841: return "วoฬธ" case 43842: return "วoฬต" case 43847: return "r" case 43848: return "r" case 43853: return "สƒ" case 43854: return "u" case 43858: return "u" case 43859: return "ฯ‡" case 43861: return "ฯ‡" case 43866: return "y" case 43872: return "ั™" case 43874: return "ษ”e" case 43875: return "uo" case 43888: return "แด…" case 43889: return "ส€" case 43890: return "แด›" case 43892: return "oฬ›" case 43893: return "i" case 43898: return "แด€" case 43899: return "แดŠ" case 43900: return "แด‡" case 43902: return "ษ‚" case 43904: return "โฑถ" case 43905: return "r" case 43907: return "w" case 43911: return "ส" case 43915: return "สœ" case 43918: return "oฬต" case 43920: return "ษข" case 43923: return "z" case 43931: return "๊ž“" case 43932: return "uฬต" case 43935: return "ฦ…" case 43938: return "ส€" case 43945: return "v" case 43946: return "s" case 43950: return "สŸ" case 43951: return "c" case 43954: return "แด˜" case 43958: return "ฤธ" case 43963: return "oฬต" case 55216: return "แ…ฉแ…ง" case 55217: return "แ…ฉแ…ฉไธจ" case 55218: return "แ…ญแ…ก" case 55219: return "แ…ญแ…กไธจ" case 55220: return "แ…ญแ…ฅ" case 55221: return "แ…ฎแ…ง" case 55222: return "แ…ฎไธจไธจ" case 55223: return "แ…ฒแ…กไธจ" case 55224: return "แ…ฒแ…ฉ" case 55225: return "ใƒผแ…ก" case 55226: return "ใƒผแ…ฅ" case 55227: return "ใƒผแ…ฅไธจ" case 55228: return "ใƒผแ…ฉ" case 55229: return "ไธจแ…ฃแ…ฉ" case 55230: return "ไธจแ…ฃไธจ" case 55231: return "ไธจแ…ง" case 55232: return "ไธจแ…งไธจ" case 55233: return "ไธจแ…ฉไธจ" case 55234: return "ไธจแ…ญ" case 55235: return "ไธจแ…ฒ" case 55236: return "ไธจไธจ" case 55237: return "แ†žแ…ก" case 55238: return "แ†žแ…ฅไธจ" case 55243: return "แ„‚แ„…" case 55244: return "แ„‚แ„Ž" case 55245: return "แ„ƒแ„ƒ" case 55246: return "แ„ƒแ„ƒแ„‡" case 55247: return "แ„ƒแ„‡" case 55248: return "แ„ƒแ„‰" case 55249: return "แ„ƒแ„‰แ„€" case 55250: return "แ„ƒแ„Œ" case 55251: return "แ„ƒแ„Ž" case 55252: return "แ„ƒแ„" case 55253: return "แ„…แ„€แ„€" case 55254: return "แ„…แ„€แ„’" case 55255: return "แ„…แ„…แ„" case 55256: return "แ„…แ„†แ„’" case 55257: return "แ„…แ„‡แ„ƒ" case 55258: return "แ„…แ„‡แ„‘" case 55259: return "แ„…แ…Œ" case 55260: return "แ„…แ…™แ„’" case 55261: return "แ„…แ„‹" case 55262: return "แ„†แ„‚" case 55263: return "แ„†แ„‚แ„‚" case 55264: return "แ„†แ„†" case 55265: return "แ„†แ„‡แ„‰" case 55266: return "แ„†แ„Œ" case 55267: return "แ„‡แ„ƒ" case 55268: return "แ„‡แ„…แ„‘" case 55269: return "แ„‡แ„†" case 55270: return "แ„‡แ„‡" case 55271: return "แ„‡แ„‰แ„ƒ" case 55272: return "แ„‡แ„Œ" case 55273: return "แ„‡แ„Ž" case 55274: return "แ„‰แ„†" case 55275: return "แ„‰แ„‡แ„‹" case 55276: return "แ„‰แ„‰แ„€" case 55277: return "แ„‰แ„‰แ„ƒ" case 55278: return "แ„‰แ…€" case 55279: return "แ„‰แ„Œ" case 55280: return "แ„‰แ„Ž" case 55281: return "แ„‰แ„" case 55282: return "แ„…แ„’" case 55283: return "แ…€แ„‡" case 55284: return "แ…€แ„‡แ„‹" case 55285: return "แ…Œแ„†" case 55286: return "แ…Œแ„’" case 55287: return "แ„Œแ„‡" case 55288: return "แ„Œแ„‡แ„‡" case 55289: return "แ„Œแ„Œ" case 55290: return "แ„‘แ„‰" case 55291: return "แ„‘แ„" case 63744: return "่ฑˆ" case 63745: return "ๆ›ด" case 63746: return "่ปŠ" case 63747: return "่ณˆ" case 63748: return "ๆป‘" case 63749: return "ไธฒ" case 63750: return "ๅฅ" case 63751: return "้พœ" case 63752: return "้พœ" case 63753: return "ๅฅ‘" case 63754: return "้‡‘" case 63755: return "ๅ–‡" case 63756: return "ๅฅˆ" case 63757: return "ๆ‡ถ" case 63758: return "็™ฉ" case 63759: return "็พ…" case 63760: return "่˜ฟ" case 63761: return "่žบ" case 63762: return "่ฃธ" case 63763: return "้‚" case 63764: return "ๆจ‚" case 63765: return "ๆด›" case 63766: return "็ƒ™" case 63767: return "็ž" case 63768: return "่ฝ" case 63769: return "้…ช" case 63770: return "้งฑ" case 63771: return "ไบ‚" case 63772: return "ๅต" case 63773: return "ๆฌ„" case 63774: return "็ˆ›" case 63775: return "่˜ญ" case 63776: return "้ธž" case 63777: return "ๅต" case 63778: return "ๆฟซ" case 63779: return "่—" case 63780: return "่ฅค" case 63781: return "ๆ‹‰" case 63782: return "่‡˜" case 63783: return "่ Ÿ" case 63784: return "ๅปŠ" case 63785: return "ๆœ—" case 63786: return "ๆตช" case 63787: return "็‹ผ" case 63788: return "้ƒŽ" case 63789: return "ไพ†" case 63790: return "ๅ†ท" case 63791: return "ๅ‹ž" case 63792: return "ๆ“„" case 63793: return "ๆซ“" case 63794: return "็ˆ" case 63795: return "็›ง" case 63796: return "่€" case 63797: return "่˜†" case 63798: return "่™œ" case 63799: return "่ทฏ" case 63800: return "้œฒ" case 63801: return "้ญฏ" case 63802: return "้ทบ" case 63803: return "็ขŒ" case 63804: return "็ฅฟ" case 63805: return "็ถ " case 63806: return "่‰" case 63807: return "้Œ„" case 63808: return "้นฟ" case 63809: return "่ซ–" case 63810: return "ๅฃŸ" case 63811: return "ๅผ„" case 63812: return "็ฑ " case 63813: return "่พ" case 63814: return "็‰ข" case 63815: return "็ฃŠ" case 63816: return "่ณ‚" case 63817: return "้›ท" case 63818: return "ๅฃ˜" case 63819: return "ๅฑข" case 63820: return "ๆจ“" case 63821: return "ๆทš" case 63822: return "ๆผ" case 63823: return "็ดฏ" case 63824: return "็ธท" case 63825: return "้™‹" case 63826: return "ๅ‹’" case 63827: return "่‚‹" case 63828: return "ๅ‡œ" case 63829: return "ๅ‡Œ" case 63830: return "็จœ" case 63831: return "็ถพ" case 63832: return "่ฑ" case 63833: return "้™ต" case 63834: return "่ฎ€" case 63835: return "ๆ‹" case 63836: return "ๆจ‚" case 63837: return "่ซพ" case 63838: return "ไธน" case 63839: return "ๅฏง" case 63840: return "ๆ€’" case 63841: return "็އ" case 63842: return "็•ฐ" case 63843: return "ๅŒ—" case 63844: return "็ฃป" case 63845: return "ไพฟ" case 63846: return "ๅพฉ" case 63847: return "ไธ" case 63848: return "ๆณŒ" case 63849: return "ๆ•ธ" case 63850: return "็ดข" case 63851: return "ๅƒ" case 63852: return "ๅกž" case 63853: return "็œ" case 63854: return "่‘‰" case 63855: return "่ชช" case 63856: return "ๆฎบ" case 63857: return "่พฐ" case 63858: return "ๆฒˆ" case 63859: return "ๆ‹พ" case 63860: return "่‹ฅ" case 63861: return "ๆŽ " case 63862: return "็•ฅ" case 63863: return "ไบฎ" case 63864: return "ๅ…ฉ" case 63865: return "ๅ‡‰" case 63866: return "ๆข" case 63867: return "็ณง" case 63868: return "่‰ฏ" case 63869: return "่ซ’" case 63870: return "้‡" case 63871: return "ๅ‹ต" case 63872: return "ๅ‘‚" case 63873: return "ๅฅณ" case 63874: return "ๅปฌ" case 63875: return "ๆ—…" case 63876: return "ๆฟพ" case 63877: return "็คช" case 63878: return "้–ญ" case 63879: return "้ฉช" case 63880: return "้บ—" case 63881: return "้ปŽ" case 63882: return "ๅŠ›" case 63883: return "ๆ›†" case 63884: return "ๆญท" case 63885: return "่ฝข" case 63886: return "ๅนด" case 63887: return "ๆ†" case 63888: return "ๆˆ€" case 63889: return "ๆ’š" case 63890: return "ๆผฃ" case 63891: return "็…‰" case 63892: return "็’‰" case 63893: return "็งŠ" case 63894: return "็ทด" case 63895: return "่ฏ" case 63896: return "่ผฆ" case 63897: return "่“ฎ" case 63898: return "้€ฃ" case 63899: return "้Š" case 63900: return "ๅˆ—" case 63901: return "ๅŠฃ" case 63902: return "ๅ’ฝ" case 63903: return "็ƒˆ" case 63904: return "่ฃ‚" case 63905: return "่ชช" case 63906: return "ๅป‰" case 63907: return "ๅฟต" case 63908: return "ๆป" case 63909: return "ๆฎฎ" case 63910: return "็ฐพ" case 63911: return "็ต" case 63912: return "ไปค" case 63913: return "ๅ›น" case 63914: return "ๅฏง" case 63915: return "ๅถบ" case 63916: return "ๆ€œ" case 63917: return "็Žฒ" case 63918: return "็‘ฉ" case 63919: return "็พš" case 63920: return "่†" case 63921: return "้ˆด" case 63922: return "้›ถ" case 63923: return "้ˆ" case 63924: return "้ ˜" case 63925: return "ไพ‹" case 63926: return "็ฆฎ" case 63927: return "้†ด" case 63928: return "้šท" case 63929: return "ๆƒก" case 63930: return "ไบ†" case 63931: return "ๅƒš" case 63932: return "ๅฏฎ" case 63933: return "ๅฐฟ" case 63934: return "ๆ–™" case 63935: return "ๆจ‚" case 63936: return "็‡Ž" case 63937: return "็™‚" case 63938: return "่“ผ" case 63939: return "้ผ" case 63940: return "้พ" case 63941: return "ๆšˆ" case 63942: return "้˜ฎ" case 63943: return "ๅЉ" case 63944: return "ๆป" case 63945: return "ๆŸณ" case 63946: return "ๆต" case 63947: return "ๆบœ" case 63948: return "็‰" case 63949: return "็•™" case 63950: return "็กซ" case 63951: return "็ด" case 63952: return "้กž" case 63953: return "ๅ…ญ" case 63954: return "ๆˆฎ" case 63955: return "้™ธ" case 63956: return "ๅ€ซ" case 63957: return "ๅด™" case 63958: return "ๆทช" case 63959: return "่ผช" case 63960: return "ๅพ‹" case 63961: return "ๆ…„" case 63962: return "ๆ —" case 63963: return "็އ" case 63964: return "้š†" case 63965: return "ๅˆฉ" case 63966: return "ๅ" case 63967: return "ๅฑฅ" case 63968: return "ๆ˜“" case 63969: return "ๆŽ" case 63970: return "ๆขจ" case 63971: return "ๆณฅ" case 63972: return "็†" case 63973: return "็—ข" case 63974: return "็ฝน" case 63975: return "่ฃ" case 63976: return "่ฃก" case 63977: return "้‡Œ" case 63978: return "้›ข" case 63979: return "ๅŒฟ" case 63980: return "ๆบบ" case 63981: return "ๅ" case 63982: return "็‡" case 63983: return "็’˜" case 63984: return "่—บ" case 63985: return "้šฃ" case 63986: return "้ฑ—" case 63987: return "้บŸ" case 63988: return "ๆž—" case 63989: return "ๆท‹" case 63990: return "่‡จ" case 63991: return "็ซ‹" case 63992: return "็ฌ " case 63993: return "็ฒ’" case 63994: return "็‹€" case 63995: return "็‚™" case 63996: return "่ญ˜" case 63997: return "ไป€" case 63998: return "่Œถ" case 63999: return "ๅˆบ" case 64000: return "ๅˆ‡" case 64001: return "ๅบฆ" case 64002: return "ๆ‹“" case 64003: return "็ณ–" case 64004: return "ๅฎ…" case 64005: return "ๆดž" case 64006: return "ๆšด" case 64007: return "่ผป" case 64008: return "่กŒ" case 64009: return "้™" case 64010: return "่ฆ‹" case 64011: return "ๅป“" case 64012: return "ๅ…€" case 64013: return "ๅ—€" case 64016: return "ๅกš" case 64018: return "ๆ™ด" case 64021: return "ๅ‡ž" case 64022: return "็Œช" case 64023: return "็›Š" case 64024: return "็คผ" case 64025: return "็ฅž" case 64026: return "็ฅฅ" case 64027: return "็ฆ" case 64028: return "้–" case 64029: return "็ฒพ" case 64030: return "็พฝ" case 64032: return "่˜’" case 64034: return "่ซธ" case 64037: return "้€ธ" case 64038: return "้ƒฝ" case 64042: return "้ฃฏ" case 64043: return "้ฃผ" case 64044: return "้คจ" case 64045: return "้ถด" case 64046: return "้ƒŽ" case 64047: return "้šท" case 64048: return "ไพฎ" case 64049: return "ๅƒง" case 64050: return "ๅ…" case 64051: return "ๅ‹‰" case 64052: return "ๅ‹ค" case 64053: return "ๅ‘" case 64054: return "ๅ–" case 64055: return "ๅ˜†" case 64056: return "ๅ™จ" case 64057: return "ๅก€" case 64058: return "ๅขจ" case 64059: return "ๅฑค" case 64060: return "ๅฑฎ" case 64061: return "ๆ‚”" case 64062: return "ๆ…จ" case 64063: return "ๆ†Ž" case 64064: return "ๆ‡ฒ" case 64065: return "ๆ•" case 64066: return "ๆ—ข" case 64067: return "ๆš‘" case 64068: return "ๆข…" case 64069: return "ๆตท" case 64070: return "ๆธš" case 64071: return "ๆผข" case 64072: return "็…ฎ" case 64073: return "็ˆซ" case 64074: return "็ข" case 64075: return "็ข‘" case 64076: return "็คพ" case 64077: return "็ฅ‰" case 64078: return "็ฅˆ" case 64079: return "็ฅ" case 64080: return "็ฅ–" case 64081: return "็ฅ" case 64082: return "็ฆ" case 64083: return "็ฆŽ" case 64084: return "็ฉ€" case 64085: return "็ช" case 64086: return "็ฏ€" case 64087: return "็ทด" case 64088: return "็ธ‰" case 64089: return "็น" case 64090: return "็ฝฒ" case 64091: return "่€…" case 64092: return "่‡ญ" case 64093: return "่‰น" case 64094: return "่‰น" case 64095: return "่‘—" case 64096: return "่ค" case 64097: return "่ฆ–" case 64098: return "่ฌ" case 64099: return "่ฌน" case 64100: return "่ณ“" case 64101: return "่ดˆ" case 64102: return "่พถ" case 64103: return "้€ธ" case 64104: return "้›ฃ" case 64105: return "้Ÿฟ" case 64106: return "้ ป" case 64107: return "ๆต" case 64108: return "๐ค‹ฎ" case 64109: return "่ˆ˜" case 64112: return "ไธฆ" case 64113: return "ๅ†ต" case 64114: return "ๅ…จ" case 64115: return "ไพ€" case 64116: return "ๅ……" case 64117: return "ๅ†€" case 64118: return "ๅ‹‡" case 64119: return "ๅ‹บ" case 64120: return "ๅ–" case 64121: return "ๅ••" case 64122: return "ๅ–™" case 64123: return "ๅ—ข" case 64124: return "ๅกš" case 64125: return "ๅขณ" case 64126: return "ๅฅ„" case 64127: return "ๅฅ”" case 64128: return "ๅฉข" case 64129: return "ๅฌจ" case 64130: return "ๅป’" case 64131: return "ๅป™" case 64132: return "ๅฝฉ" case 64133: return "ๅพญ" case 64134: return "ๆƒ˜" case 64135: return "ๆ…Ž" case 64136: return "ๆ„ˆ" case 64137: return "ๆ†Ž" case 64138: return "ๆ… " case 64139: return "ๆ‡ฒ" case 64140: return "ๆˆด" case 64141: return "ๆ„" case 64142: return "ๆœ" case 64143: return "ๆ‘’" case 64144: return "ๆ•–" case 64145: return "ๆ™ด" case 64146: return "ๆœ—" case 64147: return "ๆœ›" case 64148: return "ๆ–" case 64149: return "ๆญน" case 64150: return "ๆฎบ" case 64151: return "ๆต" case 64152: return "ๆป›" case 64153: return "ๆป‹" case 64154: return "ๆผข" case 64155: return "็€ž" case 64156: return "็…ฎ" case 64157: return "็žง" case 64158: return "็ˆต" case 64159: return "็Šฏ" case 64160: return "็Œช" case 64161: return "็‘ฑ" case 64162: return "็”†" case 64163: return "็”ป" case 64164: return "็˜" case 64165: return "็˜Ÿ" case 64166: return "็›Š" case 64167: return "็››" case 64168: return "็›ด" case 64169: return "็Š" case 64170: return "็€" case 64171: return "็ฃŒ" case 64172: return "็ชฑ" case 64173: return "็ฏ€" case 64174: return "็ฑป" case 64175: return "็ต›" case 64176: return "็ทด" case 64177: return "็ผพ" case 64178: return "่€…" case 64179: return "่’" case 64180: return "่ฏ" case 64181: return "่น" case 64182: return "่ฅ" case 64183: return "่ฆ†" case 64184: return "่ฆ–" case 64185: return "่ชฟ" case 64186: return "่ซธ" case 64187: return "่ซ‹" case 64188: return "่ฌ" case 64189: return "่ซพ" case 64190: return "่ซญ" case 64191: return "่ฌน" case 64192: return "่ฎŠ" case 64193: return "่ดˆ" case 64194: return "่ผธ" case 64195: return "้ฒ" case 64196: return "้†™" case 64197: return "้‰ถ" case 64198: return "้™ผ" case 64199: return "้›ฃ" case 64200: return "้–" case 64201: return "้Ÿ›" case 64202: return "้Ÿฟ" case 64203: return "้ ‹" case 64204: return "้ ป" case 64205: return "้ฌ’" case 64206: return "้พœ" case 64207: return "๐ขกŠ" case 64208: return "๐ขก„" case 64209: return "๐ฃ•" case 64210: return "ใฎ" case 64211: return "ไ€˜" case 64212: return "ไ€น" case 64213: return "๐ฅ‰‰" case 64214: return "๐ฅณ" case 64215: return "๐งป“" case 64216: return "้ฝƒ" case 64217: return "้พŽ" case 64256: return "ff" case 64257: return "fi" case 64258: return "fl" case 64259: return "ffi" case 64260: return "ffl" case 64262: return "st" case 64275: return "ีดีถ" case 64276: return "ีดีฅ" case 64277: return "ีดีซ" case 64278: return "ีพีถ" case 64279: return "ีดีญ" case 64288: return "ืข" case 64289: return "ื" case 64290: return "ื“" case 64291: return "ื”" case 64292: return "ื›" case 64293: return "ืœ" case 64294: return "ื" case 64295: return "ืจ" case 64296: return "ืช" case 64297: return "-ฬ‡" case 64299: return "๏ฌช" case 64301: return "๏ฌฌ" case 64303: return "๏ฌฎ" case 64304: return "๏ฌฎ" case 64313: return "๏ฌ" case 64329: return "๏ฌช" case 64335: return "ืืœ" case 64336: return "ูฑ" case 64337: return "ูฑ" case 64338: return "ูป" case 64339: return "ูป" case 64340: return "ูป" case 64341: return "ูป" case 64342: return "ู‰›" case 64343: return "ู‰›" case 64344: return "ู‰›" case 64345: return "ู‰›" case 64346: return "ฺ€" case 64347: return "ฺ€" case 64348: return "ฺ€" case 64349: return "ฺ€" case 64350: return "ุช" case 64351: return "ุช" case 64352: return "ุช" case 64353: return "ุช" case 64354: return "ูฟ" case 64355: return "ูฟ" case 64356: return "ูฟ" case 64357: return "ูฟ" case 64358: return "ู‰ุ•" case 64359: return "ู‰ุ•" case 64360: return "ู‰ุ•" case 64361: return "ู‰ุ•" case 64362: return "ฺก›" case 64363: return "ฺก›" case 64364: return "ฺก›" case 64365: return "ฺก›" case 64366: return "ฺฆ" case 64367: return "ฺฆ" case 64368: return "ฺฆ" case 64369: return "ฺฆ" case 64370: return "ฺ„" case 64371: return "ฺ„" case 64372: return "ฺ„" case 64373: return "ฺ„" case 64374: return "ฺƒ" case 64375: return "ฺƒ" case 64376: return "ฺƒ" case 64377: return "ฺƒ" case 64378: return "ฺ†" case 64379: return "ฺ†" case 64380: return "ฺ†" case 64381: return "ฺ†" case 64382: return "ฺ‡" case 64383: return "ฺ‡" case 64384: return "ฺ‡" case 64385: return "ฺ‡" case 64386: return "ฺ" case 64387: return "ฺ" case 64388: return "ฺŒ" case 64389: return "ฺŒ" case 64390: return "ุฏ›" case 64391: return "ุฏ›" case 64392: return "ุฏุ•" case 64393: return "ุฏุ•" case 64394: return "ุฑ›" case 64395: return "ุฑ›" case 64396: return "ุฑุ•" case 64397: return "ุฑุ•" case 64398: return "ูƒ" case 64399: return "ูƒ" case 64400: return "ูƒ" case 64401: return "ูƒ" case 64402: return "ฺฏ" case 64403: return "ฺฏ" case 64404: return "ฺฏ" case 64405: return "ฺฏ" case 64406: return "ฺณ" case 64407: return "ฺณ" case 64408: return "ฺณ" case 64409: return "ฺณ" case 64410: return "ฺฑ" case 64411: return "ฺฑ" case 64412: return "ฺฑ" case 64413: return "ฺฑ" case 64414: return "ู‰" case 64415: return "ู‰" case 64416: return "ู‰ุ•" case 64417: return "ู‰ุ•" case 64418: return "ู‰ุ•" case 64419: return "ู‰ุ•" case 64420: return "€" case 64421: return "€" case 64422: return "o" case 64423: return "o" case 64424: return "o" case 64425: return "o" case 64426: return "o" case 64427: return "o" case 64428: return "o" case 64429: return "o" case 64430: return "ู‰" case 64431: return "ู‰" case 64432: return "“" case 64433: return "“" case 64467: return "ูƒ›" case 64468: return "ูƒ›" case 64469: return "ูƒ›" case 64470: return "ูƒ›" case 64471: return "ูˆฬ“" case 64472: return "ูˆฬ“" case 64473: return "ูˆฬ†" case 64474: return "ูˆฬ†" case 64475: return "ูˆูฐ" case 64476: return "ูˆูฐ" case 64477: return "ูˆฬ“ูด" case 64478: return "ูˆ›" case 64479: return "ูˆ›" case 64480: return "…" case 64481: return "…" case 64482: return "ูˆฬ‚" case 64483: return "ูˆฬ‚" case 64484: return "ูป" case 64485: return "ูป" case 64486: return "ูป" case 64487: return "ูป" case 64488: return "ู‰" case 64489: return "ู‰" case 64490: return "ู‰ูดl" case 64491: return "ู‰ูดl" case 64492: return "ู‰ูดo" case 64493: return "ู‰ูดo" case 64494: return "ู‰ูดูˆ" case 64495: return "ู‰ูดูˆ" case 64496: return "ู‰ูดูˆฬ“" case 64497: return "ู‰ูดูˆฬ“" case 64498: return "ู‰ูดูˆฬ†" case 64499: return "ู‰ูดูˆฬ†" case 64500: return "ู‰ูดูˆูฐ" case 64501: return "ู‰ูดูˆูฐ" case 64502: return "ู‰ูดูป" case 64503: return "ู‰ูดูป" case 64504: return "ู‰ูดูป" case 64505: return "ู‰ูดู‰" case 64506: return "ู‰ูดู‰" case 64507: return "ู‰ูดู‰" case 64508: return "ู‰" case 64509: return "ู‰" case 64510: return "ู‰" case 64511: return "ู‰" case 64512: return "ู‰ูดุฌ" case 64513: return "ู‰ูดุญ" case 64514: return "ู‰ูดู…" case 64515: return "ู‰ูดู‰" case 64516: return "ู‰ูดู‰" case 64517: return "ุจุฌ" case 64518: return "ุจุญ" case 64519: return "ุจุฎ" case 64520: return "ุจู…" case 64521: return "ุจู‰" case 64522: return "ุจู‰" case 64523: return "ุชุฌ" case 64524: return "ุชุญ" case 64525: return "ุชุฎ" case 64526: return "ุชู…" case 64527: return "ุชู‰" case 64528: return "ุชู‰" case 64529: return "ู‰›ุฌ" case 64530: return "ู‰›ู…" case 64531: return "ู‰›ู‰" case 64532: return "ู‰›ู‰" case 64533: return "ุฌุญ" case 64534: return "ุฌู…" case 64535: return "ุญุฌ" case 64536: return "ุญู…" case 64537: return "ุฎุฌ" case 64538: return "ุฎุญ" case 64539: return "ุฎู…" case 64540: return "ุณุฌ" case 64541: return "ุณุญ" case 64542: return "ุณุฎ" case 64543: return "ุณู…" case 64544: return "ุตุญ" case 64545: return "ุตู…" case 64546: return "ุถุฌ" case 64547: return "ุถุญ" case 64548: return "ุถุฎ" case 64549: return "ุถู…" case 64550: return "ุทุญ" case 64551: return "ุทู…" case 64552: return "ุธู…" case 64553: return "ุนุฌ" case 64554: return "ุนู…" case 64555: return "ุบุฌ" case 64556: return "ุบู…" case 64557: return "ูุฌ" case 64558: return "ูุญ" case 64559: return "ูุฎ" case 64560: return "ูู…" case 64561: return "ูู‰" case 64562: return "ูู‰" case 64563: return "ู‚ุญ" case 64564: return "ู‚ู…" case 64565: return "ู‚ู‰" case 64566: return "ู‚ู‰" case 64567: return "ูƒl" case 64568: return "ูƒุฌ" case 64569: return "ูƒุญ" case 64570: return "ูƒุฎ" case 64571: return "ูƒู„" case 64572: return "ูƒู…" case 64573: return "ูƒู‰" case 64574: return "ูƒู‰" case 64575: return "ู„ุฌ" case 64576: return "ู„ุญ" case 64577: return "ู„ุฎ" case 64578: return "ู„ู…" case 64579: return "ู„ู‰" case 64580: return "ู„ู‰" case 64581: return "ู…ุฌ" case 64582: return "ู…ุญ" case 64583: return "ู…ุฎ" case 64584: return "ู…ู…" case 64585: return "ู…ู‰" case 64586: return "ู…ู‰" case 64587: return "ุจุฎ" case 64588: return "ู†ุญ" case 64589: return "ู†ุฎ" case 64590: return "ู†ู…" case 64591: return "ู†ู‰" case 64592: return "ู†ู‰" case 64593: return "oุฌ" case 64594: return "oู…" case 64595: return "oู‰" case 64596: return "oู‰" case 64597: return "ู‰ุฌ" case 64598: return "ู‰ุญ" case 64599: return "ู‰ุฎ" case 64600: return "ู‰ู…" case 64601: return "ู‰ู‰" case 64602: return "ู‰ู‰" case 64603: return "ุฐูฐ" case 64604: return "ุฑูฐ" case 64605: return "ู‰ูฐ" case 64606: return "๏นฒู‘" case 64607: return "๏นดู‘" case 64608: return "๏นถู‘" case 64609: return "๏นธู‘" case 64610: return "๏นบู‘" case 64611: return "๏นผูฐ" case 64612: return "ู‰ูดุฑ" case 64613: return "ู‰ูดุฒ" case 64614: return "ู‰ูดู…" case 64615: return "ู‰ูดู†" case 64616: return "ู‰ูดู‰" case 64617: return "ู‰ูดู‰" case 64618: return "ุจุฑ" case 64619: return "ุจุฒ" case 64620: return "ุจู…" case 64621: return "ุจู†" case 64622: return "ุจู‰" case 64623: return "ุจู‰" case 64624: return "ุชุฑ" case 64625: return "ุชุฒ" case 64626: return "ุชู…" case 64627: return "ุชู†" case 64628: return "ุชู‰" case 64629: return "ุชู‰" case 64630: return "ู‰›ุฑ" case 64631: return "ู‰›ุฒ" case 64632: return "ู‰›ู…" case 64633: return "ู‰›ู†" case 64634: return "ู‰›ู‰" case 64635: return "ู‰›ู‰" case 64636: return "ูู‰" case 64637: return "ูู‰" case 64638: return "ู‚ู‰" case 64639: return "ู‚ู‰" case 64640: return "ูƒl" case 64641: return "ูƒู„" case 64642: return "ูƒู…" case 64643: return "ูƒู‰" case 64644: return "ูƒู‰" case 64645: return "ู„ู…" case 64646: return "ู„ู‰" case 64647: return "ู„ู‰" case 64648: return "ู…l" case 64649: return "ู…ู…" case 64650: return "ู†ุฑ" case 64651: return "ู†ุฒ" case 64652: return "ู†ู…" case 64653: return "ู†ู†" case 64654: return "ู†ู‰" case 64655: return "ู†ู‰" case 64656: return "ู‰ูฐ" case 64657: return "ู‰ุฑ" case 64658: return "ู‰ุฒ" case 64659: return "ู‰ู…" case 64660: return "ู‰ู†" case 64661: return "ู‰ู‰" case 64662: return "ู‰ู‰" case 64663: return "ู‰ูดุฌ" case 64664: return "ู‰ูดุญ" case 64665: return "ู‰ูดุฎ" case 64666: return "ู‰ูดู…" case 64667: return "ู‰ูดo" case 64668: return "ุจุฌ" case 64669: return "ุจุญ" case 64670: return "ุจุฎ" case 64671: return "ุจู…" case 64672: return "ุจo" case 64673: return "ุชุฌ" case 64674: return "ุชุญ" case 64675: return "ุชุฎ" case 64676: return "ุชู…" case 64677: return "ุชo" case 64678: return "ู‰›ู…" case 64679: return "ุฌุญ" case 64680: return "ุฌู…" case 64681: return "ุญุฌ" case 64682: return "ุญู…" case 64683: return "ุฎุฌ" case 64684: return "ุฎู…" case 64685: return "ุณุฌ" case 64686: return "ุณุญ" case 64687: return "ุณุฎ" case 64688: return "ุณู…" case 64689: return "ุตุญ" case 64690: return "ุตุฎ" case 64691: return "ุตู…" case 64692: return "ุถุฌ" case 64693: return "ุถุญ" case 64694: return "ุถุฎ" case 64695: return "ุถู…" case 64696: return "ุทุญ" case 64697: return "ุธู…" case 64698: return "ุนุฌ" case 64699: return "ุนู…" case 64700: return "ุบุฌ" case 64701: return "ุบู…" case 64702: return "ูุฌ" case 64703: return "ูุญ" case 64704: return "ูุฎ" case 64705: return "ูู…" case 64706: return "ู‚ุญ" case 64707: return "ู‚ู…" case 64708: return "ูƒุฌ" case 64709: return "ูƒุญ" case 64710: return "ูƒุฎ" case 64711: return "ูƒู„" case 64712: return "ูƒู…" case 64713: return "ู„ุฌ" case 64714: return "ู„ุญ" case 64715: return "ู„ุฎ" case 64716: return "ู„ู…" case 64717: return "ู„o" case 64718: return "ู…ุฌ" case 64719: return "ู…ุญ" case 64720: return "ู…ุฎ" case 64721: return "ู…ู…" case 64722: return "ุจุฎ" case 64723: return "ู†ุญ" case 64724: return "ู†ุฎ" case 64725: return "ู†ู…" case 64726: return "ู†o" case 64727: return "oุฌ" case 64728: return "oู…" case 64729: return "oูฐ" case 64730: return "ู‰ุฌ" case 64731: return "ู‰ุญ" case 64732: return "ู‰ุฎ" case 64733: return "ู‰ู…" case 64734: return "ู‰o" case 64735: return "ู‰ูดู…" case 64736: return "ู‰ูดo" case 64737: return "ุจู…" case 64738: return "ุจo" case 64739: return "ุชู…" case 64740: return "ุชo" case 64741: return "ู‰›ู…" case 64742: return "ู‰›o" case 64743: return "ุณู…" case 64744: return "ุณo" case 64745: return "ุณ›ู…" case 64746: return "ุณ›o" case 64747: return "ูƒู„" case 64748: return "ูƒู…" case 64749: return "ู„ู…" case 64750: return "ู†ู…" case 64751: return "ู†o" case 64752: return "ู‰ู…" case 64753: return "ู‰o" case 64754: return "๏นทู‘" case 64755: return "๏นนู‘" case 64756: return "๏นปู‘" case 64757: return "ุทู‰" case 64758: return "ุทู‰" case 64759: return "ุนู‰" case 64760: return "ุนู‰" case 64761: return "ุบู‰" case 64762: return "ุบู‰" case 64763: return "ุณู‰" case 64764: return "ุณู‰" case 64765: return "ุณ›ู‰" case 64766: return "ุณ›ู‰" case 64767: return "ุญู‰" case 64768: return "ุญู‰" case 64769: return "ุฌู‰" case 64770: return "ุฌู‰" case 64771: return "ุฎู‰" case 64772: return "ุฎู‰" case 64773: return "ุตู‰" case 64774: return "ุตู‰" case 64775: return "ุถู‰" case 64776: return "ุถู‰" case 64777: return "ุณ›ุฌ" case 64778: return "ุณ›ุญ" case 64779: return "ุณ›ุฎ" case 64780: return "ุณ›ู…" case 64781: return "ุณ›ุฑ" case 64782: return "ุณุฑ" case 64783: return "ุตุฑ" case 64784: return "ุถุฑ" case 64785: return "ุทู‰" case 64786: return "ุทู‰" case 64787: return "ุนู‰" case 64788: return "ุนู‰" case 64789: return "ุบู‰" case 64790: return "ุบู‰" case 64791: return "ุณู‰" case 64792: return "ุณู‰" case 64793: return "ุณ›ู‰" case 64794: return "ุณ›ู‰" case 64795: return "ุญู‰" case 64796: return "ุญู‰" case 64797: return "ุฌู‰" case 64798: return "ุฌู‰" case 64799: return "ุฎู‰" case 64800: return "ุฎู‰" case 64801: return "ุตู‰" case 64802: return "ุตู‰" case 64803: return "ุถู‰" case 64804: return "ุถู‰" case 64805: return "ุณ›ุฌ" case 64806: return "ุณ›ุญ" case 64807: return "ุณ›ุฎ" case 64808: return "ุณ›ู…" case 64809: return "ุณ›ุฑ" case 64810: return "ุณุฑ" case 64811: return "ุตุฑ" case 64812: return "ุถุฑ" case 64813: return "ุณ›ุฌ" case 64814: return "ุณ›ุญ" case 64815: return "ุณ›ุฎ" case 64816: return "ุณ›ู…" case 64817: return "ุณo" case 64818: return "ุณ›o" case 64819: return "ุทู…" case 64820: return "ุณุฌ" case 64821: return "ุณุญ" case 64822: return "ุณุฎ" case 64823: return "ุณ›ุฌ" case 64824: return "ุณ›ุญ" case 64825: return "ุณ›ุฎ" case 64826: return "ุทู…" case 64827: return "ุธู…" case 64828: return "lฬ‹" case 64829: return "lฬ‹" case 64830: return "(" case 64831: return ")" case 64848: return "ุชุฌู…" case 64849: return "ุชุญุฌ" case 64850: return "ุชุญุฌ" case 64851: return "ุชุญู…" case 64852: return "ุชุฎู…" case 64853: return "ุชู…ุฌ" case 64854: return "ุชู…ุญ" case 64855: return "ุชู…ุฎ" case 64856: return "ุฌู…ุญ" case 64857: return "ุฌู…ุญ" case 64858: return "ุญู…ู‰" case 64859: return "ุญู…ู‰" case 64860: return "ุณุญุฌ" case 64861: return "ุณุฌุญ" case 64862: return "ุณุฌู‰" case 64863: return "ุณู…ุญ" case 64864: return "ุณู…ุญ" case 64865: return "ุณู…ุฌ" case 64866: return "ุณู…ู…" case 64867: return "ุณู…ู…" case 64868: return "ุตุญุญ" case 64869: return "ุตุญุญ" case 64870: return "ุตู…ู…" case 64871: return "ุณ›ุญู…" case 64872: return "ุณ›ุญู…" case 64873: return "ุณ›ุฌู‰" case 64874: return "ุณ›ู…ุฎ" case 64875: return "ุณ›ู…ุฎ" case 64876: return "ุณ›ู…ู…" case 64877: return "ุณ›ู…ู…" case 64878: return "ุถุญู‰" case 64879: return "ุถุฎู…" case 64880: return "ุถุฎู…" case 64881: return "ุทู…ุญ" case 64882: return "ุทู…ุญ" case 64883: return "ุทู…ู…" case 64884: return "ุทู…ู‰" case 64885: return "ุนุฌู…" case 64886: return "ุนู…ู…" case 64887: return "ุนู…ู…" case 64888: return "ุนู…ู‰" case 64889: return "ุบู…ู…" case 64890: return "ุบู…ู‰" case 64891: return "ุบู…ู‰" case 64892: return "ูุฎู…" case 64893: return "ูุฎู…" case 64894: return "ู‚ู…ุญ" case 64895: return "ู‚ู…ู…" case 64896: return "ู„ุญู…" case 64897: return "ู„ุญู‰" case 64898: return "ู„ุญู‰" case 64899: return "ู„ุฌุฌ" case 64900: return "ู„ุฌุฌ" case 64901: return "ู„ุฎู…" case 64902: return "ู„ุฎู…" case 64903: return "ู„ู…ุญ" case 64904: return "ู„ู…ุญ" case 64905: return "ู…ุญุฌ" case 64906: return "ู…ุญู…" case 64907: return "ู…ุญู‰" case 64908: return "ู…ุฌุญ" case 64909: return "ู…ุฌู…" case 64910: return "ู…ุฎุฌ" case 64911: return "ู…ุฎู…" case 64914: return "ู…ุฌุฎ" case 64915: return "oู…ุฌ" case 64916: return "oู…ู…" case 64917: return "ู†ุญู…" case 64918: return "ู†ุญู‰" case 64919: return "ู†ุฌู…" case 64920: return "ู†ุฌู…" case 64921: return "ู†ุฌู‰" case 64922: return "ู†ู…ู‰" case 64923: return "ู†ู…ู‰" case 64924: return "ู‰ู…ู…" case 64925: return "ู‰ู…ู…" case 64926: return "ุจุฎู‰" case 64927: return "ุชุฌู‰" case 64928: return "ุชุฌู‰" case 64929: return "ุชุฎู‰" case 64930: return "ุชุฎู‰" case 64931: return "ุชู…ู‰" case 64932: return "ุชู…ู‰" case 64933: return "ุฌู…ู‰" case 64934: return "ุฌุญู‰" case 64935: return "ุฌู…ู‰" case 64936: return "ุณุฎู‰" case 64937: return "ุตุญู‰" case 64938: return "ุณ›ุญู‰" case 64939: return "ุถุญู‰" case 64940: return "ู„ุฌู‰" case 64941: return "ู„ู…ู‰" case 64942: return "ู‰ุญู‰" case 64943: return "ู‰ุฌู‰" case 64944: return "ู‰ู…ู‰" case 64945: return "ู…ู…ู‰" case 64946: return "ู‚ู…ู‰" case 64947: return "ู†ุญู‰" case 64948: return "ู‚ู…ุญ" case 64949: return "ู„ุญู…" case 64950: return "ุนู…ู‰" case 64951: return "ูƒู…ู‰" case 64952: return "ู†ุฌุญ" case 64953: return "ู…ุฎู‰" case 64954: return "ู„ุฌู…" case 64955: return "ูƒู…ู…" case 64956: return "ู„ุฌู…" case 64957: return "ู†ุฌุญ" case 64958: return "ุฌุญู‰" case 64959: return "ุญุฌู‰" case 64960: return "ู…ุฌู‰" case 64961: return "ูู…ู‰" case 64962: return "ุจุญู‰" case 64963: return "ูƒู…ู…" case 64964: return "ุนุฌู…" case 64965: return "ุตู…ู…" case 64966: return "ุณุฎู‰" case 64967: return "ู†ุฌู‰" case 65008: return "ุตู„ู‰" case 65009: return "ู‚ู„ู‰" case 65010: return "lู„ู„ู‘ูฐo" case 65011: return "lูƒุจุฑ" case 65012: return "ู…ุญู…ุฏ" case 65013: return "ุตู„ุนู…" case 65014: return "ุฑุณูˆู„" case 65015: return "ุนู„ู‰o" case 65016: return "ูˆุณู„ู…" case 65017: return "ุตู„ู‰" case 65018: return "ุตู„ู‰ lู„ู„o ุนู„ู‰o ูˆุณู„ู…" case 65019: return "ุฌู„ ุฌู„lู„o" case 65020: return "ุฑู‰lู„" case 65049: return "โต—" case 65072: return ":" case 65073: return "โ”‚" case 65076: return "โŒ‡" case 65077: return "โœ" case 65078: return "โ" case 65079: return "โž" case 65080: return "โŸ" case 65081: return "โ " case 65082: return "โก" case 65097: return "ห‰" case 65098: return "ห‰" case 65099: return "ห‰" case 65100: return "ห‰" case 65101: return "_" case 65102: return "_" case 65103: return "_" case 65112: return "-" case 65128: return "\\" case 65152: return "ุก" case 65153: return "ุข" case 65154: return "ุข" case 65155: return "lูด" case 65156: return "lูด" case 65157: return "ูˆูด" case 65158: return "ูˆูด" case 65159: return "lู•" case 65160: return "lู•" case 65161: return "ู‰ูด" case 65162: return "ู‰ูด" case 65163: return "ู‰ูด" case 65164: return "ู‰ูด" case 65165: return "l" case 65166: return "l" case 65167: return "ุจ" case 65168: return "ุจ" case 65169: return "ุจ" case 65170: return "ุจ" case 65171: return "ุฉ" case 65172: return "ุฉ" case 65173: return "ุช" case 65174: return "ุช" case 65175: return "ุช" case 65176: return "ุช" case 65177: return "ู‰›" case 65178: return "ู‰›" case 65179: return "ู‰›" case 65180: return "ู‰›" case 65181: return "ุฌ" case 65182: return "ุฌ" case 65183: return "ุฌ" case 65184: return "ุฌ" case 65185: return "ุญ" case 65186: return "ุญ" case 65187: return "ุญ" case 65188: return "ุญ" case 65189: return "ุฎ" case 65190: return "ุฎ" case 65191: return "ุฎ" case 65192: return "ุฎ" case 65193: return "ุฏ" case 65194: return "ุฏ" case 65195: return "ุฐ" case 65196: return "ุฐ" case 65197: return "ุฑ" case 65198: return "ุฑ" case 65199: return "ุฒ" case 65200: return "ุฒ" case 65201: return "ุณ" case 65202: return "ุณ" case 65203: return "ุณ" case 65204: return "ุณ" case 65205: return "ุณ›" case 65206: return "ุณ›" case 65207: return "ุณ›" case 65208: return "ุณ›" case 65209: return "ุต" case 65210: return "ุต" case 65211: return "ุต" case 65212: return "ุต" case 65213: return "ุถ" case 65214: return "ุถ" case 65215: return "ุถ" case 65216: return "ุถ" case 65217: return "ุท" case 65218: return "ุท" case 65219: return "ุท" case 65220: return "ุท" case 65221: return "ุธ" case 65222: return "ุธ" case 65223: return "ุธ" case 65224: return "ุธ" case 65225: return "ุน" case 65226: return "ุน" case 65227: return "ุน" case 65228: return "ุน" case 65229: return "ุบ" case 65230: return "ุบ" case 65231: return "ุบ" case 65232: return "ุบ" case 65233: return "ู" case 65234: return "ู" case 65235: return "ู" case 65236: return "ู" case 65237: return "ู‚" case 65238: return "ู‚" case 65239: return "ู‚" case 65240: return "ู‚" case 65241: return "ูƒ" case 65242: return "ูƒ" case 65243: return "ูƒ" case 65244: return "ูƒ" case 65245: return "ู„" case 65246: return "ู„" case 65247: return "ู„" case 65248: return "ู„" case 65249: return "ู…" case 65250: return "ู…" case 65251: return "ู…" case 65252: return "ู…" case 65253: return "ู†" case 65254: return "ู†" case 65255: return "ู†" case 65256: return "ู†" case 65257: return "o" case 65258: return "o" case 65259: return "o" case 65260: return "o" case 65261: return "ูˆ" case 65262: return "ูˆ" case 65263: return "ู‰" case 65264: return "ู‰" case 65265: return "ู‰" case 65266: return "ู‰" case 65267: return "ู‰" case 65268: return "ู‰" case 65269: return "ู„ุข" case 65270: return "ู„ุข" case 65271: return "ู„lูด" case 65272: return "ู„lูด" case 65273: return "ู„lู•" case 65274: return "ู„lู•" case 65275: return "ู„l" case 65276: return "ู„l" case 65281: return "!" case 65282: return "''" case 65287: return "'" case 65293: return "ใƒผ" case 65306: return ":" case 65313: return "A" case 65314: return "B" case 65315: return "C" case 65317: return "E" case 65320: return "H" case 65321: return "l" case 65322: return "J" case 65323: return "K" case 65325: return "M" case 65326: return "N" case 65327: return "O" case 65328: return "P" case 65331: return "S" case 65332: return "T" case 65336: return "X" case 65337: return "Y" case 65338: return "Z" case 65339: return "(" case 65340: return "\\" case 65341: return ")" case 65342: return "๏ธฟ" case 65344: return "'" case 65345: return "a" case 65347: return "c" case 65349: return "e" case 65351: return "g" case 65352: return "h" case 65353: return "i" case 65354: return "j" case 65356: return "l" case 65359: return "o" case 65360: return "p" case 65363: return "s" case 65366: return "v" case 65368: return "x" case 65369: return "y" case 65372: return "โ”‚" case 65374: return "ใ€œ" case 65381: return "ยท" case 65507: return "ห‰" case 65512: return "l" case 65517: return "โ–ช" case 65793: return "ยท" case 65934: return "NฬŠ" case 65942: return "Xฬต" case 65943: return "Vฬต" case 65944: return "lฬตlฬตSฬต" case 65945: return "lฬตlฬต" case 65952: return "ี”" case 66178: return "B" case 66181: return "ฮ”" case 66182: return "E" case 66183: return "F" case 66186: return "l" case 66189: return "ษ…" case 66192: return "X" case 66194: return "O" case 66196: return "แ›œ" case 66197: return "P" case 66198: return "S" case 66199: return "T" case 66203: return "+" case 66208: return "A" case 66209: return "B" case 66210: return "C" case 66211: return "ฮ”" case 66213: return "F" case 66219: return "O" case 66221: return "ฯ˜" case 66224: return "M" case 66225: return "T" case 66226: return "Y" case 66227: return "ฮฆ" case 66228: return "X" case 66229: return "ฮจ" case 66230: return "ฮฉ" case 66232: return "โต€" case 66255: return "H" case 66273: return "ุฏ" case 66276: return "ูˆ" case 66280: return "ุท" case 66290: return "ุต" case 66293: return "Z" case 66305: return "B" case 66306: return "C" case 66313: return "l" case 66321: return "M" case 66322: return "ฯ˜" case 66325: return "T" case 66327: return "X" case 66330: return "8" case 66335: return "*" case 66336: return "l" case 66338: return "X" case 66513: return "๐Ž‚" case 66515: return "๐Ž“" case 66561: return "ฦ" case 66564: return "O" case 66577: return "๊“ถ" case 66581: return "C" case 66587: return "L" case 66591: return "โฑฐ" case 66592: return "S" case 66595: return "ฦ†" case 66597: return "ะ˜" case 66601: return "๊ž“" case 66602: return "สš" case 66604: return "o" case 66621: return "c" case 66623: return "ษท" case 66626: return "ษž" case 66627: return "สŸ" case 66632: return "s" case 66635: return "ษ”" case 66637: return "แดŽ" case 66720: return "๐’†" case 66736: return "ษ…" case 66740: return "R" case 66748: return "ำƒ" case 66754: return "O" case 66755: return "ส˜" case 66756: return "รž" case 66765: return "ะ‹" case 66766: return "U" case 66768: return "แ›ฆ" case 66769: return "ฮจ" case 66770: return "7" case 66776: return "สŒ" case 66779: return "ฮป" case 66794: return "o" case 66795: return "๊™ฉ" case 66806: return "u" case 66809: return "ฯˆ" case 66835: return "N" case 66838: return "O" case 66840: return "K" case 66844: return "C" case 66845: return "V" case 66853: return "F" case 66854: return "L" case 66855: return "X" case 68154: return "ฬฃ" case 68176: return "." case 68183: return "๐ฉ–๐ฉ–" case 68858: return "๐ฒฅ" case 68860: return "๐ฒ‚" case 69318: return "ู†" case 69319: return "ฺ€" case 69328: return "ยฐฬฒ" case 69819: return "เฅฐ" case 70087: return "เฅฐ" case 70090: return "ฬฃ" case 70091: return "เคบ" case 70107: return "๊ฃผ" case 70108: return "๊ฃป" case 70110: return "โ‰ˆ" case 70400: return "ฬŠ" case 70675: return "๐‘ด๐‘‘‚๐‘’" case 70681: return "๐‘ด๐‘‘‚๐‘˜" case 70692: return "๐‘ด๐‘‘‚๐‘ฃ" case 70698: return "๐‘ด๐‘‘‚๐‘ฉ" case 70701: return "๐‘ด๐‘‘‚๐‘ฌ" case 70703: return "๐‘ด๐‘‘‚๐‘ฎ" case 70732: return "๐‘‘‹๐‘‘‹" case 70802: return "เฆ˜" case 70804: return "เฆš" case 70806: return "เฆœ" case 70808: return "เฆž" case 70809: return "เฆŸ" case 70811: return "เฆก" case 70813: return "เฆฒ" case 70814: return "เฆค" case 70815: return "เฆฅ" case 70816: return "เฆฆ" case 70817: return "เฆง" case 70818: return "เฆจ" case 70819: return "เฆช" case 70823: return "เฆฎ" case 70824: return "เฆฏ" case 70825: return "เฆฌ" case 70826: return "เฆฃ" case 70827: return "เฆฐ" case 70829: return "เฆท" case 70830: return "เฆธ" case 70832: return "เฆพ" case 70833: return "เฆฟ" case 70841: return "เง‡" case 70844: return "เง‹" case 70845: return "เง—" case 70846: return "เงŒ" case 70847: return "ฬ†ฬ‡" case 70849: return "เฆƒ" case 70850: return "เง" case 70851: return "ฬฃ" case 70852: return "เฆฝ" case 70853: return "wฬ‡" case 70864: return "o" case 70865: return "เงง" case 70866: return "เงจ" case 70870: return "เงฌ" case 71128: return "๐‘–‚" case 71129: return "๐‘–‚" case 71130: return "๐‘–ƒ" case 71131: return "๐‘–„" case 71132: return "๐‘–ฒ" case 71133: return "๐‘–ณ" case 71234: return "๐‘™๐‘™" case 71424: return "rn" case 71430: return "v" case 71434: return "w" case 71438: return "w" case 71439: return "w" case 71840: return "V" case 71842: return "F" case 71843: return "L" case 71844: return "Y" case 71846: return "E" case 71848: return "โˆ‡" case 71849: return "Z" case 71852: return "9" case 71854: return "E" case 71855: return "4" case 71858: return "L" case 71861: return "O" case 71863: return "แ›œ" case 71864: return "U" case 71867: return "5" case 71868: return "T" case 71872: return "v" case 71873: return "s" case 71874: return "F" case 71875: return "i" case 71876: return "z" case 71878: return "7" case 71880: return "o" case 71882: return "3" case 71884: return "9" case 71886: return "๊ž“" case 71893: return "6" case 71894: return "9" case 71895: return "o" case 71896: return "u" case 71900: return "y" case 71904: return "O" case 71907: return "rn" case 71908: return "ูฉ" case 71909: return "Z" case 71910: return "W" case 71913: return "C" case 71916: return "X" case 71919: return "W" case 71922: return "C" case 72422: return "๐‘ซฅ๐‘ซฏ" case 72423: return "๐‘ซฅ๐‘ซฐ" case 72424: return "๐‘ซฅ๐‘ซฅ" case 72425: return "๐‘ซฅ๐‘ซฅ๐‘ซฏ" case 72426: return "๐‘ซฅ๐‘ซฅ๐‘ซฐ" case 72428: return "๐‘ซซ๐‘ซฏ" case 72429: return "๐‘ซซ๐‘ซซ" case 72430: return "๐‘ซซ๐‘ซซ๐‘ซฏ" case 72436: return "๐‘ซณ๐‘ซฏ" case 72437: return "๐‘ซณ๐‘ซฐ" case 72438: return "๐‘ซณ๐‘ซณ" case 72439: return "๐‘ซณ๐‘ซณ๐‘ซฏ" case 72440: return "๐‘ซณ๐‘ซณ๐‘ซฐ" case 72544: return "เคบ" case 72550: return "ฬ†" case 72770: return "๐‘ฑ๐‘ฑ" case 72882: return "๐‘ฒช" case 73177: return ":" case 73178: return "l" case 73184: return "O" case 73185: return "l" case 73784: return "๐Žš" case 78585: return "๐ฆž" case 93862: return "ฮ " case 93866: return "l" case 93878: return "b" case 93889: return "ฯ€" case 93905: return "ฦ…" case 93959: return "ฮ“" case 93960: return "V" case 93962: return "T" case 93974: return "L" case 93978: return "ฮ”" case 93980: return "๊™˜" case 93990: return "๊“ถ" case 93992: return "l" case 93997: return "ฦ" case 94005: return "R" case 94010: return "S" case 94011: return "3" case 94013: return "ษ…" case 94015: return ">" case 94016: return "A" case 94018: return "U" case 94019: return "Y" case 94033: return "'" case 94034: return "'" case 94194: return "ๅ„ฟ" case 117974: return "A" case 117975: return "B" case 117976: return "C" case 117977: return "D" case 117978: return "E" case 117979: return "F" case 117980: return "G" case 117981: return "H" case 117982: return "l" case 117983: return "J" case 117984: return "K" case 117985: return "L" case 117986: return "M" case 117987: return "N" case 117988: return "O" case 117989: return "P" case 117990: return "Q" case 117991: return "R" case 117992: return "S" case 117993: return "T" case 117994: return "U" case 117995: return "V" case 117996: return "W" case 117997: return "X" case 117998: return "Y" case 117999: return "Z" case 118000: return "O" case 118001: return "l" case 118002: return "2" case 118003: return "3" case 118004: return "4" case 118005: return "5" case 118006: return "6" case 118007: return "7" case 118008: return "8" case 118009: return "9" case 118011: return "๐Ÿ›ธ" case 118511: return "โต‚" case 119060: return "{" case 119149: return "." case 119298: return "ำพ" case 119302: return "3" case 119307: return "ะ˜" case 119309: return "V" case 119311: return "\\" case 119314: return "7" case 119315: return "F" case 119316: return "๐Šผ" case 119317: return "๊“ถ" case 119318: return "R" case 119319: return "โฑฏ" case 119322: return "Oฬต" case 119323: return "โ…„" case 119324: return "๊“•" case 119329: return "ฦ" case 119330: return "ั " case 119338: return "L" case 119339: return "๊“ถ" case 119344: return "๊Ÿป" case 119350: return "<" case 119351: return ">" case 119352: return "โŠ" case 119353: return "โА" case 119354: return "/" case 119355: return "\\" case 119359: return "แ›‹" case 119365: return "ีˆ" case 119808: return "A" case 119809: return "B" case 119810: return "C" case 119811: return "D" case 119812: return "E" case 119813: return "F" case 119814: return "G" case 119815: return "H" case 119816: return "l" case 119817: return "J" case 119818: return "K" case 119819: return "L" case 119820: return "M" case 119821: return "N" case 119822: return "O" case 119823: return "P" case 119824: return "Q" case 119825: return "R" case 119826: return "S" case 119827: return "T" case 119828: return "U" case 119829: return "V" case 119830: return "W" case 119831: return "X" case 119832: return "Y" case 119833: return "Z" case 119834: return "a" case 119835: return "b" case 119836: return "c" case 119837: return "d" case 119838: return "e" case 119839: return "f" case 119840: return "g" case 119841: return "h" case 119842: return "i" case 119843: return "j" case 119844: return "k" case 119845: return "l" case 119846: return "rn" case 119847: return "n" case 119848: return "o" case 119849: return "p" case 119850: return "q" case 119851: return "r" case 119852: return "s" case 119853: return "t" case 119854: return "u" case 119855: return "v" case 119856: return "w" case 119857: return "x" case 119858: return "y" case 119859: return "z" case 119860: return "A" case 119861: return "B" case 119862: return "C" case 119863: return "D" case 119864: return "E" case 119865: return "F" case 119866: return "G" case 119867: return "H" case 119868: return "l" case 119869: return "J" case 119870: return "K" case 119871: return "L" case 119872: return "M" case 119873: return "N" case 119874: return "O" case 119875: return "P" case 119876: return "Q" case 119877: return "R" case 119878: return "S" case 119879: return "T" case 119880: return "U" case 119881: return "V" case 119882: return "W" case 119883: return "X" case 119884: return "Y" case 119885: return "Z" case 119886: return "a" case 119887: return "b" case 119888: return "c" case 119889: return "d" case 119890: return "e" case 119891: return "f" case 119892: return "g" case 119894: return "i" case 119895: return "j" case 119896: return "k" case 119897: return "l" case 119898: return "rn" case 119899: return "n" case 119900: return "o" case 119901: return "p" case 119902: return "q" case 119903: return "r" case 119904: return "s" case 119905: return "t" case 119906: return "u" case 119907: return "v" case 119908: return "w" case 119909: return "x" case 119910: return "y" case 119911: return "z" case 119912: return "A" case 119913: return "B" case 119914: return "C" case 119915: return "D" case 119916: return "E" case 119917: return "F" case 119918: return "G" case 119919: return "H" case 119920: return "l" case 119921: return "J" case 119922: return "K" case 119923: return "L" case 119924: return "M" case 119925: return "N" case 119926: return "O" case 119927: return "P" case 119928: return "Q" case 119929: return "R" case 119930: return "S" case 119931: return "T" case 119932: return "U" case 119933: return "V" case 119934: return "W" case 119935: return "X" case 119936: return "Y" case 119937: return "Z" case 119938: return "a" case 119939: return "b" case 119940: return "c" case 119941: return "d" case 119942: return "e" case 119943: return "f" case 119944: return "g" case 119945: return "h" case 119946: return "i" case 119947: return "j" case 119948: return "k" case 119949: return "l" case 119950: return "rn" case 119951: return "n" case 119952: return "o" case 119953: return "p" case 119954: return "q" case 119955: return "r" case 119956: return "s" case 119957: return "t" case 119958: return "u" case 119959: return "v" case 119960: return "w" case 119961: return "x" case 119962: return "y" case 119963: return "z" case 119964: return "A" case 119966: return "C" case 119967: return "D" case 119970: return "G" case 119973: return "J" case 119974: return "K" case 119977: return "N" case 119978: return "O" case 119979: return "P" case 119980: return "Q" case 119982: return "S" case 119983: return "T" case 119984: return "U" case 119985: return "V" case 119986: return "W" case 119987: return "X" case 119988: return "Y" case 119989: return "Z" case 119990: return "a" case 119991: return "b" case 119992: return "c" case 119993: return "d" case 119995: return "f" case 119997: return "h" case 119998: return "i" case 119999: return "j" case 120000: return "k" case 120001: return "l" case 120002: return "rn" case 120003: return "n" case 120005: return "p" case 120006: return "q" case 120007: return "r" case 120008: return "s" case 120009: return "t" case 120010: return "u" case 120011: return "v" case 120012: return "w" case 120013: return "x" case 120014: return "y" case 120015: return "z" case 120016: return "A" case 120017: return "B" case 120018: return "C" case 120019: return "D" case 120020: return "E" case 120021: return "F" case 120022: return "G" case 120023: return "H" case 120024: return "l" case 120025: return "J" case 120026: return "K" case 120027: return "L" case 120028: return "M" case 120029: return "N" case 120030: return "O" case 120031: return "P" case 120032: return "Q" case 120033: return "R" case 120034: return "S" case 120035: return "T" case 120036: return "U" case 120037: return "V" case 120038: return "W" case 120039: return "X" case 120040: return "Y" case 120041: return "Z" case 120042: return "a" case 120043: return "b" case 120044: return "c" case 120045: return "d" case 120046: return "e" case 120047: return "f" case 120048: return "g" case 120049: return "h" case 120050: return "i" case 120051: return "j" case 120052: return "k" case 120053: return "l" case 120054: return "rn" case 120055: return "n" case 120056: return "o" case 120057: return "p" case 120058: return "q" case 120059: return "r" case 120060: return "s" case 120061: return "t" case 120062: return "u" case 120063: return "v" case 120064: return "w" case 120065: return "x" case 120066: return "y" case 120067: return "z" case 120068: return "A" case 120069: return "B" case 120071: return "D" case 120072: return "E" case 120073: return "F" case 120074: return "G" case 120077: return "J" case 120078: return "K" case 120079: return "L" case 120080: return "M" case 120081: return "N" case 120082: return "O" case 120083: return "P" case 120084: return "Q" case 120086: return "S" case 120087: return "T" case 120088: return "U" case 120089: return "V" case 120090: return "W" case 120091: return "X" case 120092: return "Y" case 120094: return "a" case 120095: return "b" case 120096: return "c" case 120097: return "d" case 120098: return "e" case 120099: return "f" case 120100: return "g" case 120101: return "h" case 120102: return "i" case 120103: return "j" case 120104: return "k" case 120105: return "l" case 120106: return "rn" case 120107: return "n" case 120108: return "o" case 120109: return "p" case 120110: return "q" case 120111: return "r" case 120112: return "s" case 120113: return "t" case 120114: return "u" case 120115: return "v" case 120116: return "w" case 120117: return "x" case 120118: return "y" case 120119: return "z" case 120120: return "A" case 120121: return "B" case 120123: return "D" case 120124: return "E" case 120125: return "F" case 120126: return "G" case 120128: return "l" case 120129: return "J" case 120130: return "K" case 120131: return "L" case 120132: return "M" case 120134: return "O" case 120138: return "S" case 120139: return "T" case 120140: return "U" case 120141: return "V" case 120142: return "W" case 120143: return "X" case 120144: return "Y" case 120146: return "a" case 120147: return "b" case 120148: return "c" case 120149: return "d" case 120150: return "e" case 120151: return "f" case 120152: return "g" case 120153: return "h" case 120154: return "i" case 120155: return "j" case 120156: return "k" case 120157: return "l" case 120158: return "rn" case 120159: return "n" case 120160: return "o" case 120161: return "p" case 120162: return "q" case 120163: return "r" case 120164: return "s" case 120165: return "t" case 120166: return "u" case 120167: return "v" case 120168: return "w" case 120169: return "x" case 120170: return "y" case 120171: return "z" case 120172: return "A" case 120173: return "B" case 120174: return "C" case 120175: return "D" case 120176: return "E" case 120177: return "F" case 120178: return "G" case 120179: return "H" case 120180: return "l" case 120181: return "J" case 120182: return "K" case 120183: return "L" case 120184: return "M" case 120185: return "N" case 120186: return "O" case 120187: return "P" case 120188: return "Q" case 120189: return "R" case 120190: return "S" case 120191: return "T" case 120192: return "U" case 120193: return "V" case 120194: return "W" case 120195: return "X" case 120196: return "Y" case 120197: return "Z" case 120198: return "a" case 120199: return "b" case 120200: return "c" case 120201: return "d" case 120202: return "e" case 120203: return "f" case 120204: return "g" case 120205: return "h" case 120206: return "i" case 120207: return "j" case 120208: return "k" case 120209: return "l" case 120210: return "rn" case 120211: return "n" case 120212: return "o" case 120213: return "p" case 120214: return "q" case 120215: return "r" case 120216: return "s" case 120217: return "t" case 120218: return "u" case 120219: return "v" case 120220: return "w" case 120221: return "x" case 120222: return "y" case 120223: return "z" case 120224: return "A" case 120225: return "B" case 120226: return "C" case 120227: return "D" case 120228: return "E" case 120229: return "F" case 120230: return "G" case 120231: return "H" case 120232: return "l" case 120233: return "J" case 120234: return "K" case 120235: return "L" case 120236: return "M" case 120237: return "N" case 120238: return "O" case 120239: return "P" case 120240: return "Q" case 120241: return "R" case 120242: return "S" case 120243: return "T" case 120244: return "U" case 120245: return "V" case 120246: return "W" case 120247: return "X" case 120248: return "Y" case 120249: return "Z" case 120250: return "a" case 120251: return "b" case 120252: return "c" case 120253: return "d" case 120254: return "e" case 120255: return "f" case 120256: return "g" case 120257: return "h" case 120258: return "i" case 120259: return "j" case 120260: return "k" case 120261: return "l" case 120262: return "rn" case 120263: return "n" case 120264: return "o" case 120265: return "p" case 120266: return "q" case 120267: return "r" case 120268: return "s" case 120269: return "t" case 120270: return "u" case 120271: return "v" case 120272: return "w" case 120273: return "x" case 120274: return "y" case 120275: return "z" case 120276: return "A" case 120277: return "B" case 120278: return "C" case 120279: return "D" case 120280: return "E" case 120281: return "F" case 120282: return "G" case 120283: return "H" case 120284: return "l" case 120285: return "J" case 120286: return "K" case 120287: return "L" case 120288: return "M" case 120289: return "N" case 120290: return "O" case 120291: return "P" case 120292: return "Q" case 120293: return "R" case 120294: return "S" case 120295: return "T" case 120296: return "U" case 120297: return "V" case 120298: return "W" case 120299: return "X" case 120300: return "Y" case 120301: return "Z" case 120302: return "a" case 120303: return "b" case 120304: return "c" case 120305: return "d" case 120306: return "e" case 120307: return "f" case 120308: return "g" case 120309: return "h" case 120310: return "i" case 120311: return "j" case 120312: return "k" case 120313: return "l" case 120314: return "rn" case 120315: return "n" case 120316: return "o" case 120317: return "p" case 120318: return "q" case 120319: return "r" case 120320: return "s" case 120321: return "t" case 120322: return "u" case 120323: return "v" case 120324: return "w" case 120325: return "x" case 120326: return "y" case 120327: return "z" case 120328: return "A" case 120329: return "B" case 120330: return "C" case 120331: return "D" case 120332: return "E" case 120333: return "F" case 120334: return "G" case 120335: return "H" case 120336: return "l" case 120337: return "J" case 120338: return "K" case 120339: return "L" case 120340: return "M" case 120341: return "N" case 120342: return "O" case 120343: return "P" case 120344: return "Q" case 120345: return "R" case 120346: return "S" case 120347: return "T" case 120348: return "U" case 120349: return "V" case 120350: return "W" case 120351: return "X" case 120352: return "Y" case 120353: return "Z" case 120354: return "a" case 120355: return "b" case 120356: return "c" case 120357: return "d" case 120358: return "e" case 120359: return "f" case 120360: return "g" case 120361: return "h" case 120362: return "i" case 120363: return "j" case 120364: return "k" case 120365: return "l" case 120366: return "rn" case 120367: return "n" case 120368: return "o" case 120369: return "p" case 120370: return "q" case 120371: return "r" case 120372: return "s" case 120373: return "t" case 120374: return "u" case 120375: return "v" case 120376: return "w" case 120377: return "x" case 120378: return "y" case 120379: return "z" case 120380: return "A" case 120381: return "B" case 120382: return "C" case 120383: return "D" case 120384: return "E" case 120385: return "F" case 120386: return "G" case 120387: return "H" case 120388: return "l" case 120389: return "J" case 120390: return "K" case 120391: return "L" case 120392: return "M" case 120393: return "N" case 120394: return "O" case 120395: return "P" case 120396: return "Q" case 120397: return "R" case 120398: return "S" case 120399: return "T" case 120400: return "U" case 120401: return "V" case 120402: return "W" case 120403: return "X" case 120404: return "Y" case 120405: return "Z" case 120406: return "a" case 120407: return "b" case 120408: return "c" case 120409: return "d" case 120410: return "e" case 120411: return "f" case 120412: return "g" case 120413: return "h" case 120414: return "i" case 120415: return "j" case 120416: return "k" case 120417: return "l" case 120418: return "rn" case 120419: return "n" case 120420: return "o" case 120421: return "p" case 120422: return "q" case 120423: return "r" case 120424: return "s" case 120425: return "t" case 120426: return "u" case 120427: return "v" case 120428: return "w" case 120429: return "x" case 120430: return "y" case 120431: return "z" case 120432: return "A" case 120433: return "B" case 120434: return "C" case 120435: return "D" case 120436: return "E" case 120437: return "F" case 120438: return "G" case 120439: return "H" case 120440: return "l" case 120441: return "J" case 120442: return "K" case 120443: return "L" case 120444: return "M" case 120445: return "N" case 120446: return "O" case 120447: return "P" case 120448: return "Q" case 120449: return "R" case 120450: return "S" case 120451: return "T" case 120452: return "U" case 120453: return "V" case 120454: return "W" case 120455: return "X" case 120456: return "Y" case 120457: return "Z" case 120458: return "a" case 120459: return "b" case 120460: return "c" case 120461: return "d" case 120462: return "e" case 120463: return "f" case 120464: return "g" case 120465: return "h" case 120466: return "i" case 120467: return "j" case 120468: return "k" case 120469: return "l" case 120470: return "rn" case 120471: return "n" case 120472: return "o" case 120473: return "p" case 120474: return "q" case 120475: return "r" case 120476: return "s" case 120477: return "t" case 120478: return "u" case 120479: return "v" case 120480: return "w" case 120481: return "x" case 120482: return "y" case 120483: return "z" case 120484: return "i" case 120485: return "ศท" case 120488: return "A" case 120489: return "B" case 120490: return "ฮ“" case 120491: return "ฮ”" case 120492: return "E" case 120493: return "Z" case 120494: return "H" case 120495: return "Oฬต" case 120496: return "l" case 120497: return "K" case 120498: return "ษ…" case 120499: return "M" case 120500: return "N" case 120501: return "ฮž" case 120502: return "O" case 120503: return "ฮ " case 120504: return "P" case 120505: return "Oฬต" case 120506: return "ฦฉ" case 120507: return "T" case 120508: return "Y" case 120509: return "ฮฆ" case 120510: return "X" case 120511: return "ฮจ" case 120512: return "ฮฉ" case 120513: return "โˆ‡" case 120514: return "a" case 120515: return "รŸ" case 120516: return "y" case 120517: return "แบŸ" case 120518: return "๊ž“" case 120519: return "ฮถ" case 120520: return "nฬฉ" case 120521: return "Oฬต" case 120522: return "i" case 120523: return "ฤธ" case 120524: return "ฮป" case 120525: return "ฮผ" case 120526: return "v" case 120527: return "ฮพ" case 120528: return "o" case 120529: return "ฯ€" case 120530: return "p" case 120531: return "ฯ‚" case 120532: return "o" case 120533: return "แด›" case 120534: return "u" case 120535: return "ษธ" case 120536: return "ฯ‡" case 120537: return "ฯˆ" case 120538: return "ฯ‰" case 120539: return "โˆ‚" case 120540: return "๊ž“" case 120541: return "Oฬต" case 120542: return "ฤธ" case 120543: return "ษธ" case 120544: return "p" case 120545: return "ฯ€" case 120546: return "A" case 120547: return "B" case 120548: return "ฮ“" case 120549: return "ฮ”" case 120550: return "E" case 120551: return "Z" case 120552: return "H" case 120553: return "Oฬต" case 120554: return "l" case 120555: return "K" case 120556: return "ษ…" case 120557: return "M" case 120558: return "N" case 120559: return "ฮž" case 120560: return "O" case 120561: return "ฮ " case 120562: return "P" case 120563: return "Oฬต" case 120564: return "ฦฉ" case 120565: return "T" case 120566: return "Y" case 120567: return "ฮฆ" case 120568: return "X" case 120569: return "ฮจ" case 120570: return "ฮฉ" case 120571: return "โˆ‡" case 120572: return "a" case 120573: return "รŸ" case 120574: return "y" case 120575: return "แบŸ" case 120576: return "๊ž“" case 120577: return "ฮถ" case 120578: return "nฬฉ" case 120579: return "Oฬต" case 120580: return "i" case 120581: return "ฤธ" case 120582: return "ฮป" case 120583: return "ฮผ" case 120584: return "v" case 120585: return "ฮพ" case 120586: return "o" case 120587: return "ฯ€" case 120588: return "p" case 120589: return "ฯ‚" case 120590: return "o" case 120591: return "แด›" case 120592: return "u" case 120593: return "ษธ" case 120594: return "ฯ‡" case 120595: return "ฯˆ" case 120596: return "ฯ‰" case 120597: return "โˆ‚" case 120598: return "๊ž“" case 120599: return "Oฬต" case 120600: return "ฤธ" case 120601: return "ษธ" case 120602: return "p" case 120603: return "ฯ€" case 120604: return "A" case 120605: return "B" case 120606: return "ฮ“" case 120607: return "ฮ”" case 120608: return "E" case 120609: return "Z" case 120610: return "H" case 120611: return "Oฬต" case 120612: return "l" case 120613: return "K" case 120614: return "ษ…" case 120615: return "M" case 120616: return "N" case 120617: return "ฮž" case 120618: return "O" case 120619: return "ฮ " case 120620: return "P" case 120621: return "Oฬต" case 120622: return "ฦฉ" case 120623: return "T" case 120624: return "Y" case 120625: return "ฮฆ" case 120626: return "X" case 120627: return "ฮจ" case 120628: return "ฮฉ" case 120629: return "โˆ‡" case 120630: return "a" case 120631: return "รŸ" case 120632: return "y" case 120633: return "แบŸ" case 120634: return "๊ž“" case 120635: return "ฮถ" case 120636: return "nฬฉ" case 120637: return "Oฬต" case 120638: return "i" case 120639: return "ฤธ" case 120640: return "ฮป" case 120641: return "ฮผ" case 120642: return "v" case 120643: return "ฮพ" case 120644: return "o" case 120645: return "ฯ€" case 120646: return "p" case 120647: return "ฯ‚" case 120648: return "o" case 120649: return "แด›" case 120650: return "u" case 120651: return "ษธ" case 120652: return "ฯ‡" case 120653: return "ฯˆ" case 120654: return "ฯ‰" case 120655: return "โˆ‚" case 120656: return "๊ž“" case 120657: return "Oฬต" case 120658: return "ฤธ" case 120659: return "ษธ" case 120660: return "p" case 120661: return "ฯ€" case 120662: return "A" case 120663: return "B" case 120664: return "ฮ“" case 120665: return "ฮ”" case 120666: return "E" case 120667: return "Z" case 120668: return "H" case 120669: return "Oฬต" case 120670: return "l" case 120671: return "K" case 120672: return "ษ…" case 120673: return "M" case 120674: return "N" case 120675: return "ฮž" case 120676: return "O" case 120677: return "ฮ " case 120678: return "P" case 120679: return "Oฬต" case 120680: return "ฦฉ" case 120681: return "T" case 120682: return "Y" case 120683: return "ฮฆ" case 120684: return "X" case 120685: return "ฮจ" case 120686: return "ฮฉ" case 120687: return "โˆ‡" case 120688: return "a" case 120689: return "รŸ" case 120690: return "y" case 120691: return "แบŸ" case 120692: return "๊ž“" case 120693: return "ฮถ" case 120694: return "nฬฉ" case 120695: return "Oฬต" case 120696: return "i" case 120697: return "ฤธ" case 120698: return "ฮป" case 120699: return "ฮผ" case 120700: return "v" case 120701: return "ฮพ" case 120702: return "o" case 120703: return "ฯ€" case 120704: return "p" case 120705: return "ฯ‚" case 120706: return "o" case 120707: return "แด›" case 120708: return "u" case 120709: return "ษธ" case 120710: return "ฯ‡" case 120711: return "ฯˆ" case 120712: return "ฯ‰" case 120713: return "โˆ‚" case 120714: return "๊ž“" case 120715: return "Oฬต" case 120716: return "ฤธ" case 120717: return "ษธ" case 120718: return "p" case 120719: return "ฯ€" case 120720: return "A" case 120721: return "B" case 120722: return "ฮ“" case 120723: return "ฮ”" case 120724: return "E" case 120725: return "Z" case 120726: return "H" case 120727: return "Oฬต" case 120728: return "l" case 120729: return "K" case 120730: return "ษ…" case 120731: return "M" case 120732: return "N" case 120733: return "ฮž" case 120734: return "O" case 120735: return "ฮ " case 120736: return "P" case 120737: return "Oฬต" case 120738: return "ฦฉ" case 120739: return "T" case 120740: return "Y" case 120741: return "ฮฆ" case 120742: return "X" case 120743: return "ฮจ" case 120744: return "ฮฉ" case 120745: return "โˆ‡" case 120746: return "a" case 120747: return "รŸ" case 120748: return "y" case 120749: return "แบŸ" case 120750: return "๊ž“" case 120751: return "ฮถ" case 120752: return "nฬฉ" case 120753: return "Oฬต" case 120754: return "i" case 120755: return "ฤธ" case 120756: return "ฮป" case 120757: return "ฮผ" case 120758: return "v" case 120759: return "ฮพ" case 120760: return "o" case 120761: return "ฯ€" case 120762: return "p" case 120763: return "ฯ‚" case 120764: return "o" case 120765: return "แด›" case 120766: return "u" case 120767: return "ษธ" case 120768: return "ฯ‡" case 120769: return "ฯˆ" case 120770: return "ฯ‰" case 120771: return "โˆ‚" case 120772: return "๊ž“" case 120773: return "Oฬต" case 120774: return "ฤธ" case 120775: return "ษธ" case 120776: return "p" case 120777: return "ฯ€" case 120778: return "F" case 120779: return "ฯ" case 120782: return "O" case 120783: return "l" case 120784: return "2" case 120785: return "3" case 120786: return "4" case 120787: return "5" case 120788: return "6" case 120789: return "7" case 120790: return "8" case 120791: return "9" case 120792: return "O" case 120793: return "l" case 120794: return "2" case 120795: return "3" case 120796: return "4" case 120797: return "5" case 120798: return "6" case 120799: return "7" case 120800: return "8" case 120801: return "9" case 120802: return "O" case 120803: return "l" case 120804: return "2" case 120805: return "3" case 120806: return "4" case 120807: return "5" case 120808: return "6" case 120809: return "7" case 120810: return "8" case 120811: return "9" case 120812: return "O" case 120813: return "l" case 120814: return "2" case 120815: return "3" case 120816: return "4" case 120817: return "5" case 120818: return "6" case 120819: return "7" case 120820: return "8" case 120821: return "9" case 120822: return "O" case 120823: return "l" case 120824: return "2" case 120825: return "3" case 120826: return "4" case 120827: return "5" case 120828: return "6" case 120829: return "7" case 120830: return "8" case 120831: return "9" case 124649: return "+" case 124654: return "แซˆ" case 125127: return "l" case 125128: return "โˆ " case 125129: return "ูฃ" case 125131: return "8" case 125132: return "โˆ‚" case 125133: return "โˆ‚ฬต" case 126464: return "l" case 126465: return "ุจ" case 126466: return "ุฌ" case 126467: return "ุฏ" case 126469: return "ูˆ" case 126470: return "ุฒ" case 126471: return "ุญ" case 126472: return "ุท" case 126473: return "ู‰" case 126474: return "ูƒ" case 126475: return "ู„" case 126476: return "ู…" case 126477: return "ู†" case 126478: return "ุณ" case 126479: return "ุน" case 126480: return "ู" case 126481: return "ุต" case 126482: return "ู‚" case 126483: return "ุฑ" case 126484: return "ุณ›" case 126485: return "ุช" case 126486: return "ู‰›" case 126487: return "ุฎ" case 126488: return "ุฐ" case 126489: return "ุถ" case 126490: return "ุธ" case 126491: return "ุบ" case 126492: return "ู‰" case 126493: return "ู‰" case 126494: return "ฺก" case 126495: return "ฺก" case 126497: return "ุจ" case 126498: return "ุฌ" case 126500: return "o" case 126503: return "ุญ" case 126505: return "ู‰" case 126506: return "ูƒ" case 126507: return "ู„" case 126508: return "ู…" case 126509: return "ู†" case 126510: return "ุณ" case 126511: return "ุน" case 126512: return "ู" case 126513: return "ุต" case 126514: return "ู‚" case 126516: return "ุณ›" case 126517: return "ุช" case 126518: return "ู‰›" case 126519: return "ุฎ" case 126521: return "ุถ" case 126523: return "ุบ" case 126530: return "ุฌ" case 126535: return "ุญ" case 126537: return "ู‰" case 126539: return "ู„" case 126541: return "ู†" case 126542: return "ุณ" case 126543: return "ุน" case 126545: return "ุต" case 126546: return "ู‚" case 126548: return "ุณ›" case 126551: return "ุฎ" case 126553: return "ุถ" case 126555: return "ุบ" case 126557: return "ู‰" case 126559: return "ฺก" case 126561: return "ุจ" case 126562: return "ุฌ" case 126564: return "o" case 126567: return "ุญ" case 126568: return "ุท" case 126569: return "ู‰" case 126570: return "ูƒ" case 126572: return "ู…" case 126573: return "ู†" case 126574: return "ุณ" case 126575: return "ุน" case 126576: return "ู" case 126577: return "ุต" case 126578: return "ู‚" case 126580: return "ุณ›" case 126581: return "ุช" case 126582: return "ู‰›" case 126583: return "ุฎ" case 126585: return "ุถ" case 126586: return "ุธ" case 126587: return "ุบ" case 126588: return "ู‰" case 126590: return "ฺก" case 126592: return "l" case 126593: return "ุจ" case 126594: return "ุฌ" case 126595: return "ุฏ" case 126596: return "o" case 126597: return "ูˆ" case 126598: return "ุฒ" case 126599: return "ุญ" case 126600: return "ุท" case 126601: return "ู‰" case 126603: return "ู„" case 126604: return "ู…" case 126605: return "ู†" case 126606: return "ุณ" case 126607: return "ุน" case 126608: return "ู" case 126609: return "ุต" case 126610: return "ู‚" case 126611: return "ุฑ" case 126612: return "ุณ›" case 126613: return "ุช" case 126614: return "ู‰›" case 126615: return "ุฎ" case 126616: return "ุฐ" case 126617: return "ุถ" case 126618: return "ุธ" case 126619: return "ุบ" case 126625: return "ุจ" case 126626: return "ุฌ" case 126627: return "ุฏ" case 126629: return "ูˆ" case 126630: return "ุฒ" case 126631: return "ุญ" case 126632: return "ุท" case 126633: return "ู‰" case 126635: return "ู„" case 126636: return "ู…" case 126637: return "ู†" case 126638: return "ุณ" case 126639: return "ุน" case 126640: return "ู" case 126641: return "ุต" case 126642: return "ู‚" case 126643: return "ุฑ" case 126644: return "ุณ›" case 126645: return "ุช" case 126646: return "ู‰›" case 126647: return "ุฎ" case 126648: return "ุฐ" case 126649: return "ุถ" case 126650: return "ุธ" case 126651: return "ุบ" case 127232: return "O." case 127233: return "O," case 127234: return "l," case 127235: return "2," case 127236: return "3," case 127237: return "4," case 127238: return "5," case 127239: return "6," case 127240: return "7," case 127241: return "8," case 127242: return "9," case 127247: return "$โƒ " case 127248: return "(A)" case 127249: return "(B)" case 127250: return "(C)" case 127251: return "(D)" case 127252: return "(E)" case 127253: return "(F)" case 127254: return "(G)" case 127255: return "(H)" case 127256: return "(l)" case 127257: return "(J)" case 127258: return "(K)" case 127259: return "(L)" case 127260: return "(M)" case 127261: return "(N)" case 127262: return "(O)" case 127263: return "(P)" case 127264: return "(Q)" case 127265: return "(R)" case 127266: return "(S)" case 127267: return "(T)" case 127268: return "(U)" case 127269: return "(V)" case 127270: return "(W)" case 127271: return "(X)" case 127272: return "(Y)" case 127273: return "(Z)" case 127274: return "(S)" case 127341: return "ใ„\tโƒ" case 127342: return "Cโƒ " case 127552: return "(ๆœฌ)" case 127553: return "(ไธ‰)" case 127554: return "(ไบŒ)" case 127555: return "(ๅฎ‰)" case 127556: return "(็‚น)" case 127557: return "(ๆ‰“)" case 127558: return "(็›—)" case 127559: return "(ๅ‹)" case 127560: return "(ๆ•—)" case 127762: return "โ˜ฝ" case 127768: return "โ˜พ" case 127769: return "โ˜ฝ" case 127795: return "\U0001cebc" case 127822: return "\U0001cebd" case 127823: return "\U0001cebd" case 127826: return "\U0001cebe" case 127827: return "\U0001cebf" case 127863: return "\U0001ceba" case 127970: return "\U0001cebb" case 128013: return "\U0001ccfa" case 128067: return "\U0001ccfc" case 128276: return "\U0001fbfa" case 128768: return "QE" case 128769: return "๊™˜" case 128770: return "ฮ”" case 128772: return "๐Šผ" case 128775: return "AR" case 128776: return "Vแทค" case 128778: return "โ˜ฉ" case 128788: return "Oฬต" case 128808: return "๐Šจ" case 128826: return "โงŸ" case 128844: return "C" case 128852: return "แ›œ" case 128853: return "โŠก" case 128860: return "sss" case 128862: return "โ‰" case 128872: return "T" case 128875: return "MB" case 128876: return "VB" case 128881: return "โŠ " case 130032: return "O" case 130033: return "l" case 130034: return "2" case 130035: return "3" case 130036: return "4" case 130037: return "5" case 130038: return "6" case 130039: return "7" case 130040: return "8" case 130041: return "9" case 132724: return "ๅ‡ต" case 136499: return "ๅฃท" case 136583: return "ๅคš" case 136871: return "๐กšจ" case 139240: return "โฌ" case 140549: return "ๅพš" case 146752: return "ๆถ…" case 149757: return "็Žฅ" case 151834: return "๐ฅ„™" case 154324: return "่ด›" case 157085: return "๐ฆ–จ" case 158982: return "๐ฆฐถ" case 175813: return "๐ค ”" case 177978: return "ๅณ€" case 177982: return "๐ฃŸ" case 184673: return "ๅ‘" case 194560: return "ไธฝ" case 194561: return "ไธธ" case 194562: return "ไน" case 194563: return "๐ „ข" case 194564: return "ไฝ " case 194565: return "ไพฎ" case 194566: return "ไพป" case 194567: return "ไฝต" case 194568: return "ๅบ" case 194569: return "ๅ‚™" case 194570: return "ๅƒง" case 194571: return "ๅƒ" case 194572: return "ใ’ž" case 194573: return "๐ ˜บ" case 194574: return "ๅ…" case 194575: return "ๅ…”" case 194576: return "ๅ…ค" case 194577: return "ๅ…ท" case 194578: return "๐ ”œ" case 194579: return "ใ’น" case 194580: return "ๅ…ง" case 194581: return "ๅ†" case 194582: return "๐ •‹" case 194583: return "ๅ†—" case 194584: return "ๅ†ค" case 194585: return "ไปŒ" case 194586: return "ๅ†ฌ" case 194587: return "ๅ†ต" case 194588: return "๐ฉ‡Ÿ" case 194589: return "ๅ‡ต" case 194590: return "ๅˆƒ" case 194591: return "ใ“Ÿ" case 194592: return "ๅˆป" case 194593: return "ๅ‰†" case 194594: return "ๅ‰ฒ" case 194595: return "ๅ‰ท" case 194596: return "ใ”•" case 194597: return "ๅ‹‡" case 194598: return "ๅ‹‰" case 194599: return "ๅ‹ค" case 194600: return "ๅ‹บ" case 194601: return "ๅŒ…" case 194602: return "ๅŒ†" case 194603: return "ๅŒ—" case 194604: return "ๅ‰" case 194605: return "ๅ‘" case 194606: return "ๅš" case 194607: return "ๅณ" case 194608: return "ๅฝ" case 194609: return "ๅฟ" case 194610: return "ๅฟ" case 194611: return "ๅฟ" case 194612: return "๐ จฌ" case 194613: return "็ฐ" case 194614: return "ๅŠ" case 194615: return "ๅŸ" case 194616: return "๐ ญฃ" case 194617: return "ๅซ" case 194618: return "ๅฑ" case 194619: return "ๅ†" case 194620: return "ๅ’ž" case 194621: return "ๅธ" case 194622: return "ๅ‘ˆ" case 194623: return "ๅ‘จ" case 194624: return "ๅ’ข" case 194625: return "ๅ“ถ" case 194626: return "ๅ”" case 194627: return "ๅ•“" case 194628: return "ๅ•ฃ" case 194629: return "ๅ–„" case 194630: return "ๅ–„" case 194631: return "ๅ–™" case 194632: return "ๅ–ซ" case 194633: return "ๅ–ณ" case 194634: return "ๅ—‚" case 194635: return "ๅœ–" case 194636: return "ๅ˜†" case 194637: return "ๅœ—" case 194638: return "ๅ™‘" case 194639: return "ๅ™ด" case 194640: return "ๅˆ‡" case 194641: return "ๅฃฎ" case 194642: return "ๅŸŽ" case 194643: return "ๅŸด" case 194644: return "ๅ " case 194645: return "ๅž‹" case 194646: return "ๅ ฒ" case 194647: return "ๅ ฑ" case 194648: return "ๅขฌ" case 194649: return "๐ก“ค" case 194650: return "ๅฃฒ" case 194651: return "ๅฃท" case 194652: return "ๅค†" case 194653: return "ๅคš" case 194654: return "ๅคข" case 194655: return "ๅฅข" case 194656: return "๐กšจ" case 194657: return "๐ก›ช" case 194658: return "ๅงฌ" case 194659: return "ๅจ›" case 194660: return "ๅจง" case 194661: return "ๅง˜" case 194662: return "ๅฉฆ" case 194663: return "ใ›ฎ" case 194664: return "ใ›ผ" case 194665: return "ๅฌˆ" case 194666: return "ๅฌพ" case 194667: return "ๅฌพ" case 194668: return "๐กงˆ" case 194669: return "ๅฏƒ" case 194670: return "ๅฏ˜" case 194671: return "ๅฏง" case 194672: return "ๅฏณ" case 194673: return "๐กฌ˜" case 194674: return "ๅฏฟ" case 194675: return "ๅฐ†" case 194676: return "ๅฝ“" case 194677: return "ๅฐข" case 194678: return "ใž" case 194679: return "ๅฑ " case 194680: return "ๅฑฎ" case 194681: return "ๅณ€" case 194682: return "ๅฒ" case 194683: return "๐กทค" case 194684: return "ๅตƒ" case 194685: return "๐กทฆ" case 194686: return "ๅตฎ" case 194687: return "ๅตซ" case 194688: return "ๅตผ" case 194689: return "ๅทก" case 194690: return "ๅทข" case 194691: return "ใ ฏ" case 194692: return "ๅทฝ" case 194693: return "ๅธจ" case 194694: return "ๅธฝ" case 194695: return "ๅนฉ" case 194696: return "ใกข" case 194697: return "๐ข†ƒ" case 194698: return "ใกผ" case 194699: return "ๅบฐ" case 194700: return "ๅบณ" case 194701: return "ๅบถ" case 194702: return "ๅปŠ" case 194703: return "๐ชŽ’" case 194704: return "ๅปพ" case 194705: return "๐ขŒฑ" case 194706: return "๐ขŒฑ" case 194707: return "่ˆ" case 194708: return "ๅผข" case 194709: return "ๅผข" case 194710: return "ใฃ‡" case 194711: return "๐ฃŠธ" case 194712: return "๐ฆ‡š" case 194713: return "ๅฝข" case 194714: return "ๅฝซ" case 194715: return "ใฃฃ" case 194716: return "ๅพš" case 194717: return "ๅฟ" case 194718: return "ๅฟ—" case 194719: return "ๅฟน" case 194720: return "ๆ‚" case 194721: return "ใคบ" case 194722: return "ใคœ" case 194723: return "ๆ‚”" case 194724: return "๐ข›”" case 194725: return "ๆƒ‡" case 194726: return "ๆ…ˆ" case 194727: return "ๆ…Œ" case 194728: return "ๆ…Ž" case 194729: return "ๆ…Œ" case 194730: return "ๆ…บ" case 194731: return "ๆ†Ž" case 194732: return "ๆ†ฒ" case 194733: return "ๆ†ค" case 194734: return "ๆ†ฏ" case 194735: return "ๆ‡ž" case 194736: return "ๆ‡ฒ" case 194737: return "ๆ‡ถ" case 194738: return "ๆˆ" case 194739: return "ๆˆ›" case 194740: return "ๆ‰" case 194741: return "ๆŠฑ" case 194742: return "ๆ‹”" case 194743: return "ๆ" case 194744: return "๐ขฌŒ" case 194745: return "ๆŒฝ" case 194746: return "ๆ‹ผ" case 194747: return "ๆจ" case 194748: return "ๆŽƒ" case 194749: return "ๆค" case 194750: return "๐ขฏฑ" case 194751: return "ๆข" case 194752: return "ๆ…" case 194753: return "ๆŽฉ" case 194754: return "ใจฎ" case 194755: return "ๆ‘ฉ" case 194756: return "ๆ‘พ" case 194757: return "ๆ’" case 194758: return "ๆ‘ท" case 194759: return "ใฉฌ" case 194760: return "ๆ•" case 194761: return "ๆ•ฌ" case 194762: return "๐ฃ€Š" case 194763: return "ๆ—ฃ" case 194764: return "ๆ›ธ" case 194765: return "ๆ™‰" case 194766: return "ใฌ™" case 194767: return "ๆš‘" case 194768: return "ใฌˆ" case 194769: return "ใซค" case 194770: return "ๅ†’" case 194771: return "ๅ†•" case 194772: return "ๆœ€" case 194773: return "ๆšœ" case 194774: return "่‚ญ" case 194775: return "ไ™" case 194776: return "ๆœ—" case 194777: return "ๆœ›" case 194778: return "ๆœก" case 194779: return "ๆž" case 194780: return "ๆ“" case 194781: return "๐ฃƒ" case 194782: return "ใญ‰" case 194783: return "ๆŸบ" case 194784: return "ๆž…" case 194785: return "ๆก’" case 194786: return "ๆข…" case 194787: return "๐ฃ‘ญ" case 194788: return "ๆขŽ" case 194789: return "ๆ Ÿ" case 194790: return "ๆค”" case 194791: return "ใฎ" case 194792: return "ๆฅ‚" case 194793: return "ๆฆฃ" case 194794: return "ๆงช" case 194795: return "ๆชจ" case 194796: return "๐ฃšฃ" case 194797: return "ๆซ›" case 194798: return "ใฐ˜" case 194799: return "ๆฌก" case 194800: return "๐ฃขง" case 194801: return "ๆญ”" case 194802: return "ใฑŽ" case 194803: return "ๆญฒ" case 194804: return "ๆฎŸ" case 194805: return "ๆฎบ" case 194806: return "ๆฎป" case 194807: return "๐ฃช" case 194808: return "๐กด‹" case 194809: return "๐ฃซบ" case 194810: return "ๆฑŽ" case 194811: return "๐ฃฒผ" case 194812: return "ๆฒฟ" case 194813: return "ๆณ" case 194814: return "ๆฑง" case 194815: return "ๆด–" case 194816: return "ๆดพ" case 194817: return "ๆตท" case 194818: return "ๆต" case 194819: return "ๆตฉ" case 194820: return "ๆตธ" case 194821: return "ๆถ…" case 194822: return "๐ฃดž" case 194823: return "ๆดด" case 194824: return "ๆธฏ" case 194825: return "ๆนฎ" case 194826: return "ใดณ" case 194827: return "ๆป‹" case 194828: return "ๆป‡" case 194829: return "๐ฃป‘" case 194830: return "ๆทน" case 194831: return "ๆฝฎ" case 194832: return "๐ฃฝž" case 194833: return "๐ฃพŽ" case 194834: return "ๆฟ†" case 194835: return "็€น" case 194836: return "็€ž" case 194837: return "็€›" case 194838: return "ใถ–" case 194839: return "็Š" case 194840: return "็ฝ" case 194841: return "็ท" case 194842: return "็‚ญ" case 194843: return "๐ ”ฅ" case 194844: return "็……" case 194845: return "๐ค‰ฃ" case 194846: return "็†œ" case 194847: return "๐คŽซ" case 194848: return "็ˆจ" case 194849: return "็ˆต" case 194850: return "็‰" case 194851: return "๐ค˜ˆ" case 194852: return "็Š€" case 194853: return "็Š•" case 194854: return "๐คœต" case 194855: return "๐ค ”" case 194856: return "็บ" case 194857: return "็Ž‹" case 194858: return "ใบฌ" case 194859: return "็Žฅ" case 194860: return "ใบธ" case 194861: return "ใบธ" case 194862: return "็‘‡" case 194863: return "็‘œ" case 194864: return "็‘ฑ" case 194865: return "็’…" case 194866: return "็“Š" case 194867: return "ใผ›" case 194868: return "็”ค" case 194869: return "๐คฐถ" case 194870: return "็”พ" case 194871: return "๐คฒ’" case 194872: return "็•ฐ" case 194873: return "๐ข†Ÿ" case 194874: return "็˜" case 194875: return "๐คพก" case 194876: return "๐คพธ" case 194877: return "๐ฅ„" case 194878: return "ใฟผ" case 194879: return "ไ€ˆ" case 194880: return "็›ด" case 194881: return "๐ฅƒณ" case 194882: return "๐ฅƒฒ" case 194883: return "๐ฅ„™" case 194884: return "๐ฅ„ณ" case 194885: return "็œž" case 194886: return "็œŸ" case 194887: return "็œŸ" case 194888: return "็Š" case 194889: return "ไ€น" case 194890: return "็ž‹" case 194891: return "ไ†" case 194892: return "ไ‚–" case 194893: return "๐ฅ" case 194894: return "็กŽ" case 194895: return "็ขŒ" case 194896: return "็ฃŒ" case 194897: return "ไƒฃ" case 194898: return "๐ฅ˜ฆ" case 194899: return "็ฅ–" case 194900: return "๐ฅšš" case 194901: return "๐ฅ›…" case 194902: return "็ฆ" case 194903: return "็งซ" case 194904: return "ไ„ฏ" case 194905: return "็ฉ€" case 194906: return "็ฉŠ" case 194907: return "็ฉ" case 194908: return "๐ฅฅผ" case 194909: return "๐ฅชง" case 194910: return "๐ฅชง" case 194911: return "็ซฎ" case 194912: return "ไˆ‚" case 194913: return "๐ฅฎซ" case 194914: return "็ฏ†" case 194915: return "็ฏ‰" case 194916: return "ไˆง" case 194917: return "๐ฅฒ€" case 194918: return "็ณ’" case 194919: return "ไŠ " case 194920: return "็ณจ" case 194921: return "็ณฃ" case 194922: return "็ด€" case 194923: return "๐ฅพ†" case 194924: return "็ตฃ" case 194925: return "ไŒ" case 194926: return "็ท‡" case 194927: return "็ธ‚" case 194928: return "็น…" case 194929: return "ไŒด" case 194930: return "๐ฆˆจ" case 194931: return "๐ฆ‰‡" case 194932: return "ไ™" case 194933: return "๐ฆ‹™" case 194934: return "็ฝบ" case 194935: return "๐ฆŒพ" case 194936: return "็พ•" case 194937: return "็ฟบ" case 194938: return "่€…" case 194939: return "๐ฆ“š" case 194940: return "๐ฆ”ฃ" case 194941: return "่ " case 194942: return "๐ฆ–จ" case 194943: return "่ฐ" case 194944: return "๐ฃŸ" case 194945: return "ไ•" case 194946: return "่‚ฒ" case 194947: return "่„ƒ" case 194948: return "ไ‹" case 194949: return "่„พ" case 194950: return "ๅชต" case 194951: return "๐ฆžง" case 194952: return "๐ฆžต" case 194953: return "๐ฃŽ“" case 194954: return "๐ฃŽœ" case 194955: return "่ˆ" case 194956: return "่ˆ„" case 194957: return "่พž" case 194958: return "ไ‘ซ" case 194959: return "่Š‘" case 194960: return "่Š‹" case 194961: return "่Š" case 194962: return "ๅŠณ" case 194963: return "่Šฑ" case 194964: return "่Šณ" case 194965: return "่Šฝ" case 194966: return "่‹ฆ" case 194967: return "๐ฆฌผ" case 194968: return "่‹ฅ" case 194969: return "่Œ" case 194970: return "่ฃ" case 194971: return "่Žญ" case 194972: return "่Œฃ" case 194973: return "่Žฝ" case 194974: return "่ง" case 194975: return "่‘—" case 194976: return "่“" case 194977: return "่Š" case 194978: return "่Œ" case 194979: return "่œ" case 194980: return "๐ฆฐถ" case 194981: return "๐ฆตซ" case 194982: return "๐ฆณ•" case 194983: return "ไ”ซ" case 194984: return "่“ฑ" case 194985: return "่“ณ" case 194986: return "่”–" case 194987: return "๐งŠ" case 194988: return "่•ค" case 194989: return "๐ฆผฌ" case 194990: return "ไ•" case 194991: return "ไ•ก" case 194992: return "๐ฆพฑ" case 194993: return "๐งƒ’" case 194994: return "ไ•ซ" case 194995: return "่™" case 194996: return "่™œ" case 194997: return "่™ง" case 194998: return "่™ฉ" case 194999: return "่šฉ" case 195000: return "่šˆ" case 195001: return "่œŽ" case 195002: return "่›ข" case 195003: return "่น" case 195004: return "่œจ" case 195005: return "่ซ" case 195006: return "่ž†" case 195007: return "ไ——" case 195008: return "่Ÿก" case 195009: return "่ " case 195010: return "ไ—น" case 195011: return "่ก " case 195012: return "่กฃ" case 195013: return "๐ง™ง" case 195014: return "่ฃ—" case 195015: return "่ฃž" case 195016: return "ไ˜ต" case 195017: return "่ฃบ" case 195018: return "ใ’ป" case 195019: return "๐งขฎ" case 195020: return "๐งฅฆ" case 195021: return "ไšพ" case 195022: return "ไ›‡" case 195023: return "่ช " case 195024: return "่ซญ" case 195025: return "่ฎŠ" case 195026: return "่ฑ•" case 195027: return "๐งฒจ" case 195028: return "่ฒซ" case 195029: return "่ณ" case 195030: return "่ด›" case 195031: return "่ตท" case 195032: return "๐งผฏ" case 195033: return "๐  „" case 195034: return "่ท‹" case 195035: return "่ถผ" case 195036: return "่ทฐ" case 195037: return "๐ ฃž" case 195038: return "่ป”" case 195039: return "่ผธ" case 195040: return "๐จ—’" case 195041: return "๐จ—ญ" case 195042: return "้‚”" case 195043: return "้ƒฑ" case 195044: return "้„‘" case 195045: return "๐จœฎ" case 195046: return "้„›" case 195047: return "้ˆธ" case 195048: return "้‹—" case 195049: return "้‹˜" case 195050: return "้‰ผ" case 195051: return "้น" case 195052: return "้•" case 195053: return "๐จฏบ" case 195054: return "้–‹" case 195055: return "ไฆ•" case 195056: return "้–ท" case 195057: return "๐จตท" case 195058: return "ไงฆ" case 195059: return "้›ƒ" case 195060: return "ๅถฒ" case 195061: return "้œฃ" case 195062: return "๐ฉ……" case 195063: return "๐ฉˆš" case 195064: return "ไฉฎ" case 195065: return "ไฉถ" case 195066: return "้Ÿ " case 195067: return "๐ฉŠ" case 195068: return "ไชฒ" case 195069: return "๐ฉ’–" case 195070: return "้ ‹" case 195071: return "้ ‹" case 195072: return "้ ฉ" case 195073: return "๐ฉ–ถ" case 195074: return "้ฃข" case 195075: return "ไฌณ" case 195076: return "้คฉ" case 195077: return "้ฆง" case 195078: return "้ง‚" case 195079: return "้งพ" case 195080: return "ไฏŽ" case 195081: return "๐ฉฌฐ" case 195082: return "้ฌ’" case 195083: return "้ฑ€" case 195084: return "้ณฝ" case 195085: return "ไณŽ" case 195086: return "ไณญ" case 195087: return "้ตง" case 195088: return "๐ชƒŽ" case 195089: return "ไณธ" case 195090: return "๐ช„…" case 195091: return "๐ชˆŽ" case 195092: return "๐ชŠ‘" case 195093: return "้บป" case 195094: return "ไต–" case 195095: return "้ปน" case 195096: return "้ปพ" case 195097: return "้ผ…" case 195098: return "้ผ" case 195099: return "้ผ–" case 195100: return "้ผป" case 195101: return "๐ช˜€" case 204412: return "็ท‡" default: return "" } } go-util-0.9.5/confusable/generate.go000066400000000000000000000032261513243016000173450ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build ignore package main import ( "cmp" "fmt" "os" "slices" "strings" "go.mau.fi/util/exerrors" "go.mau.fi/util/unicodeurls" ) type Confusable struct { From rune To string } func loadConfusables() []Confusable { return unicodeurls.ReadDataFileList(unicodeurls.Confusables, func(line string) (Confusable, bool) { parts := strings.Split(line, ";") if len(parts) < 3 { return Confusable{}, false } fromStr := unicodeurls.ParseHex(strings.Split(strings.TrimSpace(parts[0]), " ")) fromRunes := []rune(fromStr) if len(fromRunes) != 1 { panic(fmt.Errorf("unexpected source rune %s", strings.TrimSpace(parts[0]))) } return Confusable{ From: fromRunes[0], To: unicodeurls.ParseHex(strings.Split(strings.TrimSpace(parts[1]), " ")), }, true }) } func main() { confusables := loadConfusables() slices.SortFunc(confusables, func(a, b Confusable) int { return cmp.Compare(a.From, b.From) }) file := exerrors.Must(os.OpenFile("confusables.go", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)) exerrors.Must(file.WriteString(`// Code generated by go generate; DO NOT EDIT. package confusable func GetReplacement(input rune) string { switch input { `)) for _, confusable := range confusables { exerrors.Must(fmt.Fprintf(file, "\tcase %d:\n\t\treturn %q\n", confusable.From, confusable.To)) } exerrors.Must(file.WriteString(` default: return "" } } `)) exerrors.PanicIfNotNil(file.Close()) } go-util-0.9.5/confusable/skeleton.go000066400000000000000000000035161513243016000174010ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package confusable implements confusable detection according to UTS #39. package confusable import ( "bytes" "crypto/sha256" "strings" "unicode" "golang.org/x/text/unicode/norm" ) //go:generate go run ./generate.go // Skeleton is the skeleton function defined in UTS #39. // // If two strings have the same skeleton, they're considered confusable. // The skeleton strings are not intended for displaying to users. // // See https://www.unicode.org/reports/tr39/#Confusable_Detection for more info. func Skeleton(input string) string { input = norm.NFD.String(input) var builder strings.Builder builder.Grow(len(input)) for _, r := range input { if !unicode.IsGraphic(r) && r != 0xfffd { continue } repl := GetReplacement(r) if repl != "" { builder.WriteString(repl) } else { builder.WriteRune(r) } } return norm.NFD.String(builder.String()) } // SkeletonBytes is the same as Skeleton, but it returns the skeleton as a byte slice. func SkeletonBytes(input string) []byte { input = norm.NFD.String(input) var builder bytes.Buffer builder.Grow(len(input)) for _, r := range input { if !unicode.IsGraphic(r) && r != 0xfffd { continue } repl := GetReplacement(r) if repl != "" { builder.WriteString(repl) } else { builder.WriteRune(r) } } return norm.NFD.Bytes(builder.Bytes()) } // SkeletonHash returns the sha256 hash of the skeleton of the string. func SkeletonHash(input string) [32]byte { return sha256.Sum256(SkeletonBytes(input)) } // Confusable checks if two strings are confusable. func Confusable(x, y string) bool { return Skeleton(x) == Skeleton(y) } go-util-0.9.5/confusable/skeleton_test.go000066400000000000000000000015661513243016000204430ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package confusable_test import ( "testing" "github.com/stretchr/testify/assert" "go.mau.fi/util/confusable" ) func TestSkeleton(t *testing.T) { assert.Equal(t, "MEOW MEOW", confusable.Skeleton("MEOW ๐— ๐”ผ๐‘ถ๐“ฆ")) } func TestConfusable(t *testing.T) { assert.True(t, confusable.Confusable("MEOW", "๐— ๐”ผ๐‘ถ๐“ฆ")) } func BenchmarkSkeleton(b *testing.B) { for i := 0; i < b.N; i++ { confusable.Skeleton("MEOW โ‹˜ ๐— ๐”ผ๐‘ถ๐“ฆ MEOW โ‹˜ ๐— ๐”ผ๐‘ถ๐“ฆ") } } func BenchmarkSkeletonBytes(b *testing.B) { for i := 0; i < b.N; i++ { confusable.SkeletonBytes("MEOW โ‹˜ ๐— ๐”ผ๐‘ถ๐“ฆ MEOW โ‹˜ ๐— ๐”ผ๐‘ถ๐“ฆ") } } go-util-0.9.5/curl/000077500000000000000000000000001513243016000140455ustar00rootroot00000000000000go-util-0.9.5/curl/format.go000066400000000000000000000040251513243016000156650ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package curl import ( "encoding/base64" "fmt" "io" "net/http" "strings" ) // Format formats the given HTTP request as a curl command. // // This will include all headers, and also the request body if GetBody is set. Notes: // // * Header names are quoted using fmt.Sprintf, so it may not always be correct for shell quoting. // * The URL is only quoted and not escaped, so URLs with single quotes will not currently work. // // The client parameter is optional and is used to find cookies from the cookie jar. func Format(cli *http.Client, req *http.Request) string { var curl []string hasBody := false if req.GetBody != nil { body, _ := req.GetBody() if body != http.NoBody { b, _ := io.ReadAll(body) curl = []string{"echo", base64.StdEncoding.EncodeToString(b), "|", "base64", "-d", "|"} hasBody = true } } curl = append(curl, "curl", "-v") switch req.Method { case http.MethodGet: // Don't add -X case http.MethodHead: curl = append(curl, "-I") default: curl = append(curl, "-X", req.Method) } for key, vals := range req.Header { kv := fmt.Sprintf("%s: %s", key, vals[0]) curl = append(curl, "-H", fmt.Sprintf("%q", kv)) } if cli != nil && cli.Jar != nil { cookies := cli.Jar.Cookies(req.URL) if len(cookies) > 0 { cookieStrings := make([]string, len(cookies)) for i, cookie := range cookies { if strings.ContainsAny(cookie.Value, " ,") { cookieStrings[i] = fmt.Sprintf(`%s="%s"`, cookie.Name, cookie.Value) } else { cookieStrings[i] = fmt.Sprintf("%s=%s", cookie.Name, cookie.Value) } } curl = append(curl, "-H", fmt.Sprintf("%q", "Cookie: "+strings.Join(cookieStrings, "; "))) } } if hasBody { curl = append(curl, "--data-binary", "@-") } curl = append(curl, fmt.Sprintf("'%s'", req.URL.String())) return strings.Join(curl, " ") } go-util-0.9.5/curl/parse.go000066400000000000000000000046351513243016000155160ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package curl import ( "encoding/json" "fmt" "io" "mime" "mime/multipart" "net/http" "net/url" "strings" "go.mau.fi/util/shlex" ) type Parsed struct { *http.Request ParsedJSON map[string]any } var ansiCQuoteReplacer = strings.NewReplacer( `\n`, "\n", `\r`, "\r", `\t`, "\t", `\v`, "\v", `\a`, "\a", `\b`, "\b", `\f`, "\f", `\f`, "\f", `\f`, "\f", `\?`, "?", `\'`, "'", `\"`, `"`, `\\`, `\`, ) func Parse(curl string) (*Parsed, error) { parts, err := shlex.Split(curl) if err != nil { return nil, fmt.Errorf("failed to split command: %w", err) } else if parts[0] != "curl" { return nil, fmt.Errorf("expected command to start with curl, got %q", parts[0]) } req := &http.Request{ Header: make(http.Header), } var body string for i := 1; i < len(parts); i++ { val := parts[i] if req.URL == nil && strings.HasPrefix(val, "https://") { req.URL, err = url.Parse(val) if err != nil { return nil, fmt.Errorf("failed to parse url: %w", err) } } switch val { case "-H": i++ hdrParts := strings.SplitN(parts[i], ": ", 2) req.Header.Add(hdrParts[0], hdrParts[1]) case "--data-raw", "--data-binary": i++ body = parts[i] if strings.HasPrefix(body, "$") { body = ansiCQuoteReplacer.Replace(body[1:]) } case "-X": i++ req.Method = parts[i] case "-b": i++ req.Header.Add("Cookie", parts[i]) } } req.Body = io.NopCloser(strings.NewReader(body)) contentType := req.Header.Get("Content-Type") var jsonBody map[string]any if contentType != "" { var params map[string]string contentType, params, err = mime.ParseMediaType(contentType) if err != nil { return nil, fmt.Errorf("failed to parse content type: %w", err) } switch contentType { case "application/json": err = json.Unmarshal([]byte(body), &jsonBody) if err != nil { return nil, fmt.Errorf("failed to parse JSON body: %w", err) } case "multipart/form-data": req.MultipartForm, err = multipart.NewReader(strings.NewReader(body), params["boundary"]).ReadForm(1024 * 1024) if err != nil { return nil, fmt.Errorf("failed to parse form data: %w", err) } } } return &Parsed{ Request: req, ParsedJSON: jsonBody, }, nil } go-util-0.9.5/dbutil/000077500000000000000000000000001513243016000143635ustar00rootroot00000000000000go-util-0.9.5/dbutil/connlog.go000066400000000000000000000140541513243016000163550ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "context" "database/sql" "errors" "fmt" "strconv" "strings" "time" ) // LoggingExecable is a wrapper for anything with database Exec methods (i.e. sql.Conn, sql.DB and sql.Tx) // that can preprocess queries (e.g. replacing $ with ? on SQLite) and log query durations. type LoggingExecable struct { UnderlyingExecable UnderlyingExecable db *Database } type pqError interface { Get(k byte) string } type PQErrorWithLine struct { Underlying error Line string } func (pqe *PQErrorWithLine) Error() string { return pqe.Underlying.Error() } func (pqe *PQErrorWithLine) Unwrap() error { return pqe.Underlying } func addErrorLine(query string, err error) error { if err == nil { return err } var pqe pqError if !errors.As(err, &pqe) { return err } pos, _ := strconv.Atoi(pqe.Get('P')) pos-- if pos <= 0 { return err } lines := strings.Split(query, "\n") for _, line := range lines { lineRunes := []rune(line) if pos < len(lineRunes)+1 { return &PQErrorWithLine{Underlying: err, Line: line} } pos -= len(lineRunes) + 1 } return err } func (le *LoggingExecable) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) { start := time.Now() query = le.db.mutateQuery(query) res, err := le.UnderlyingExecable.ExecContext(ctx, query, args...) err = addErrorLine(query, err) le.db.Log.QueryTiming(ctx, "Exec", query, args, -1, time.Since(start), err) return res, err } func (le *LoggingExecable) QueryContext(ctx context.Context, query string, args ...any) (Rows, error) { start := time.Now() query = le.db.mutateQuery(query) rows, err := le.UnderlyingExecable.QueryContext(ctx, query, args...) err = addErrorLine(query, err) le.db.Log.QueryTiming(ctx, "Query", query, args, -1, time.Since(start), err) return &LoggingRows{ ctx: ctx, db: le.db, query: query, args: args, rs: rows, start: start, }, err } func (le *LoggingExecable) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row { start := time.Now() query = le.db.mutateQuery(query) row := le.UnderlyingExecable.QueryRowContext(ctx, query, args...) le.db.Log.QueryTiming(ctx, "QueryRow", query, args, -1, time.Since(start), nil) return row } func (le *LoggingExecable) beginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { txBeginner, ok := le.UnderlyingExecable.(UnderlyingExecutableWithTx) if !ok { return nil, fmt.Errorf("can't start transaction with a %T", le.UnderlyingExecable) } return txBeginner.BeginTx(ctx, opts) } // loggingDB is a wrapper for LoggingExecable that allows access to BeginTx. // // While LoggingExecable has a pointer to the database and could use BeginTx, it's not technically safe since // the LoggingExecable could be for a transaction (where BeginTx wouldn't make sense). type loggingDB struct { LoggingExecable } type internalTxnStarter interface { beginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) } type TxnOptions struct { Isolation sql.IsolationLevel ReadOnly bool Conn Conn RetryBegin func(error, int) bool } func (ld *loggingDB) BeginTx(ctx context.Context, opts *TxnOptions) (*LoggingTxn, error) { if opts == nil { opts = &TxnOptions{} } sqlOpts := &sql.TxOptions{ Isolation: opts.Isolation, ReadOnly: opts.ReadOnly, } var tx *sql.Tx var err error start := time.Now() for i := 0; ; i++ { if opts.Conn != nil { tx, err = opts.Conn.beginTx(ctx, sqlOpts) } else { targetDB := ld.db.RawDB if opts.ReadOnly && ld.db.ReadOnlyDB != nil { targetDB = ld.db.ReadOnlyDB } tx, err = targetDB.BeginTx(ctx, sqlOpts) } if opts.RetryBegin == nil || err == nil || !opts.RetryBegin(err, i) { break } } ld.db.Log.QueryTiming(ctx, "Begin", "", nil, -1, time.Since(start), err) if err != nil { return nil, err } return &LoggingTxn{ LoggingExecable: LoggingExecable{UnderlyingExecable: tx, db: ld.db}, UnderlyingTx: tx, ctx: ctx, StartTime: start, }, nil } type LoggingTxn struct { LoggingExecable UnderlyingTx *sql.Tx ctx context.Context StartTime time.Time EndTime time.Time noTotalLog bool } func (lt *LoggingTxn) Commit() error { start := time.Now() err := lt.UnderlyingTx.Commit() lt.EndTime = time.Now() if !lt.noTotalLog { lt.db.Log.QueryTiming(lt.ctx, "", "", nil, -1, lt.EndTime.Sub(lt.StartTime), nil) } lt.db.Log.QueryTiming(lt.ctx, "Commit", "", nil, -1, time.Since(start), err) return err } func (lt *LoggingTxn) Rollback() error { start := time.Now() err := lt.UnderlyingTx.Rollback() lt.EndTime = time.Now() if !lt.noTotalLog { lt.db.Log.QueryTiming(lt.ctx, "", "", nil, -1, lt.EndTime.Sub(lt.StartTime), nil) } lt.db.Log.QueryTiming(lt.ctx, "Rollback", "", nil, -1, time.Since(start), err) return err } type LoggingRows struct { ctx context.Context db *Database query string args []any rs Rows start time.Time nrows int } func (lrs *LoggingRows) stopTiming() { if !lrs.start.IsZero() { lrs.db.Log.QueryTiming(lrs.ctx, "EndRows", lrs.query, lrs.args, lrs.nrows, time.Since(lrs.start), lrs.rs.Err()) lrs.start = time.Time{} } } func (lrs *LoggingRows) Close() error { err := lrs.rs.Close() lrs.stopTiming() return err } func (lrs *LoggingRows) ColumnTypes() ([]*sql.ColumnType, error) { return lrs.rs.ColumnTypes() } func (lrs *LoggingRows) Columns() ([]string, error) { return lrs.rs.Columns() } func (lrs *LoggingRows) Err() error { return lrs.rs.Err() } func (lrs *LoggingRows) Next() bool { hasNext := lrs.rs.Next() if !hasNext { lrs.stopTiming() } else { lrs.nrows++ } return hasNext } func (lrs *LoggingRows) NextResultSet() bool { hasNext := lrs.rs.NextResultSet() if !hasNext { lrs.stopTiming() } else { lrs.nrows++ } return hasNext } func (lrs *LoggingRows) Scan(dest ...any) error { return lrs.rs.Scan(dest...) } go-util-0.9.5/dbutil/database.go000066400000000000000000000156411513243016000164650ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "context" "database/sql" "fmt" "net/url" "regexp" "strings" "time" "go.mau.fi/util/exsync" ) type Dialect int const ( DialectUnknown Dialect = iota Postgres SQLite ) func (dialect Dialect) String() string { switch dialect { case Postgres: return "postgres" case SQLite: return "sqlite3" default: return "" } } func ParseDialect(engine string) (Dialect, error) { engine = strings.ToLower(engine) if strings.HasPrefix(engine, "postgres") || engine == "pgx" { return Postgres, nil } else if strings.HasPrefix(engine, "sqlite") || strings.HasPrefix(engine, "litestream") { return SQLite, nil } else { return DialectUnknown, fmt.Errorf("unknown dialect '%s'", engine) } } type Rows interface { Close() error ColumnTypes() ([]*sql.ColumnType, error) Columns() ([]string, error) Err() error Next() bool NextResultSet() bool Scan(...any) error } type Scannable interface { Scan(...any) error } // Expected implementations of Scannable var ( _ Scannable = (*sql.Row)(nil) _ Scannable = (Rows)(nil) ) type UnderlyingExecable interface { ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } type UnderlyingExecutableWithTx interface { UnderlyingExecable BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) } type Execable interface { ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) QueryContext(ctx context.Context, query string, args ...any) (Rows, error) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } type Conn interface { Execable internalTxnStarter } type Transaction interface { Execable Commit() error Rollback() error } // Expected implementations of Execable var ( _ UnderlyingExecable = (*sql.Tx)(nil) _ UnderlyingExecutableWithTx = (*sql.DB)(nil) _ UnderlyingExecutableWithTx = (*sql.Conn)(nil) _ Execable = (*LoggingExecable)(nil) _ Transaction = (*LoggingTxn)(nil) ) type Database struct { LoggingDB loggingDB RawDB *sql.DB ReadOnlyDB *sql.DB Owner string VersionTable string Log DatabaseLogger Dialect Dialect UpgradeTable UpgradeTable txnCtxKey contextKey txnDeadlockMap *exsync.Set[int64] IgnoreForeignTables bool IgnoreUnsupportedDatabase bool DeadlockDetection bool } var ForceDeadlockDetection bool var positionalParamPattern = regexp.MustCompile(`\$(\d+)`) func (db *Database) mutateQuery(query string) string { switch db.Dialect { case SQLite: return positionalParamPattern.ReplaceAllString(query, "?$1") default: return query } } func (db *Database) Child(versionTable string, upgradeTable UpgradeTable, log DatabaseLogger) *Database { if log == nil { log = db.Log } return &Database{ RawDB: db.RawDB, LoggingDB: db.LoggingDB, Owner: "", VersionTable: versionTable, UpgradeTable: upgradeTable, Log: log, Dialect: db.Dialect, txnCtxKey: db.txnCtxKey, txnDeadlockMap: db.txnDeadlockMap, IgnoreForeignTables: true, IgnoreUnsupportedDatabase: db.IgnoreUnsupportedDatabase, DeadlockDetection: db.DeadlockDetection, } } func NewWithDB(db *sql.DB, rawDialect string) (*Database, error) { dialect, err := ParseDialect(rawDialect) if err != nil { return nil, err } wrappedDB := &Database{ RawDB: db, Dialect: dialect, Log: NoopLogger, IgnoreForeignTables: true, VersionTable: "version", txnCtxKey: contextKey(nextContextKeyDatabaseTransaction.Add(1)), txnDeadlockMap: exsync.NewSet[int64](), DeadlockDetection: ForceDeadlockDetection, } wrappedDB.LoggingDB.UnderlyingExecable = db wrappedDB.LoggingDB.db = wrappedDB return wrappedDB, nil } func NewWithDialect(uri, rawDialect string) (*Database, error) { db, err := sql.Open(rawDialect, uri) if err != nil { return nil, err } return NewWithDB(db, rawDialect) } type PoolConfig struct { Type string `yaml:"type"` URI string `yaml:"uri"` MaxOpenConns int `yaml:"max_open_conns"` MaxIdleConns int `yaml:"max_idle_conns"` ConnMaxIdleTime string `yaml:"conn_max_idle_time"` ConnMaxLifetime string `yaml:"conn_max_lifetime"` } type Config struct { PoolConfig `yaml:",inline"` ReadOnlyPool PoolConfig `yaml:"ro_pool"` DeadlockDetection bool `yaml:"deadlock_detection"` } func (db *Database) Close() error { err := db.RawDB.Close() if db.ReadOnlyDB != nil { if err2 := db.ReadOnlyDB.Close(); err2 != nil { if err == nil { err = fmt.Errorf("closing read-only db failed: %w", err2) } else { err = fmt.Errorf("%w (closing read-only db also failed: %v)", err, err2) } } } return err } func (db *Database) Configure(cfg Config) error { db.DeadlockDetection = cfg.DeadlockDetection || ForceDeadlockDetection if err := db.configure(db.ReadOnlyDB, cfg.ReadOnlyPool); err != nil { return err } return db.configure(db.RawDB, cfg.PoolConfig) } func (db *Database) configure(rawDB *sql.DB, cfg PoolConfig) error { if rawDB == nil { return nil } rawDB.SetMaxOpenConns(cfg.MaxOpenConns) rawDB.SetMaxIdleConns(cfg.MaxIdleConns) if len(cfg.ConnMaxIdleTime) > 0 { maxIdleTimeDuration, err := time.ParseDuration(cfg.ConnMaxIdleTime) if err != nil { return fmt.Errorf("failed to parse max_conn_idle_time: %w", err) } rawDB.SetConnMaxIdleTime(maxIdleTimeDuration) } if len(cfg.ConnMaxLifetime) > 0 { maxLifetimeDuration, err := time.ParseDuration(cfg.ConnMaxLifetime) if err != nil { return fmt.Errorf("failed to parse max_conn_idle_time: %w", err) } rawDB.SetConnMaxLifetime(maxLifetimeDuration) } return nil } func NewFromConfig(owner string, cfg Config, logger DatabaseLogger) (*Database, error) { wrappedDB, err := NewWithDialect(cfg.URI, cfg.Type) if err != nil { return nil, err } wrappedDB.Owner = owner if logger != nil { wrappedDB.Log = logger } if cfg.ReadOnlyPool.MaxOpenConns > 0 { if cfg.ReadOnlyPool.Type == "" { cfg.ReadOnlyPool.Type = cfg.Type } roUri := cfg.ReadOnlyPool.URI if roUri == "" { uriParts := strings.Split(cfg.URI, "?") qs := url.Values{} if len(uriParts) == 2 { var err error qs, err = url.ParseQuery(uriParts[1]) if err != nil { return nil, err } qs.Del("_txlock") qs.Del("_auto_vacuum") qs.Del("_vacuum") } qs.Set("_query_only", "true") roUri = uriParts[0] + "?" + qs.Encode() } wrappedDB.ReadOnlyDB, err = sql.Open(cfg.ReadOnlyPool.Type, roUri) if err != nil { return nil, err } } err = wrappedDB.Configure(cfg) if err != nil { return nil, err } return wrappedDB, nil } go-util-0.9.5/dbutil/deadlock_test.go000066400000000000000000000101021513243016000175110ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil_test import ( "context" "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mau.fi/util/dbutil" _ "go.mau.fi/util/dbutil/litestream" ) func initTestDB(t *testing.T) *dbutil.Database { db, err := dbutil.NewFromConfig("", dbutil.Config{ PoolConfig: dbutil.PoolConfig{ Type: "sqlite3-fk-wal", URI: ":memory:?_txlock=immediate", MaxOpenConns: 1, MaxIdleConns: 1, }, DeadlockDetection: true, }, nil) require.NoError(t, err) ctx := context.Background() _, err = db.Exec(ctx, ` CREATE TABLE meow (id INTEGER PRIMARY KEY, value TEXT); INSERT INTO meow (id, value) VALUES (1, 'meow'); INSERT INTO meow (id, value) VALUES (2, 'meow 2'); INSERT INTO meow (value) VALUES ('meow 3'); `) require.NoError(t, err) return db } func getMeow(ctx context.Context, db dbutil.Execable, id int) (value string, err error) { err = db.QueryRowContext(ctx, "SELECT value FROM meow WHERE id = ?", id).Scan(&value) return } func TestDatabase_NoDeadlock(t *testing.T) { db := initTestDB(t) ctx := context.Background() require.NoError(t, db.DoTxn(ctx, nil, func(ctx context.Context) error { _, err := db.Exec(ctx, "INSERT INTO meow (value) VALUES ('meow 4');") require.NoError(t, err) return nil })) val, err := getMeow(ctx, db.Execable(ctx), 4) require.NoError(t, err) require.Equal(t, "meow 4", val) } func TestDatabase_NoDeadlock_Goroutine(t *testing.T) { db := initTestDB(t) ctx := context.Background() require.NoError(t, db.DoTxn(ctx, nil, func(ctx context.Context) error { _, err := db.Exec(ctx, "INSERT INTO meow (value) VALUES ('meow 4');") require.NoError(t, err) go func() { _, err := db.Exec(context.Background(), "INSERT INTO meow (value) VALUES ('meow 5');") require.NoError(t, err) }() time.Sleep(50 * time.Millisecond) return nil })) val, err := getMeow(ctx, db.Execable(ctx), 4) require.NoError(t, err) require.Equal(t, "meow 4", val) val, err = getMeow(ctx, db.Execable(ctx), 5) require.NoError(t, err) require.Equal(t, "meow 5", val) } func TestDatabase_Deadlock(t *testing.T) { db := initTestDB(t) ctx := context.Background() _ = db.DoTxn(ctx, nil, func(ctx context.Context) error { assert.PanicsWithError(t, dbutil.ErrQueryDeadlock.Error(), func() { _, _ = db.Exec(context.Background(), "INSERT INTO meow (value) VALUES ('meow 4');") }) return fmt.Errorf("meow") }) } func TestDatabase_Deadlock_Acquire(t *testing.T) { db := initTestDB(t) ctx := context.Background() _ = db.DoTxn(ctx, nil, func(ctx context.Context) error { assert.PanicsWithError(t, dbutil.ErrAcquireDeadlock.Error(), func() { _, _ = db.AcquireConn(context.Background()) }) return fmt.Errorf("meow") }) } func TestDatabase_Deadlock_Txn(t *testing.T) { db := initTestDB(t) ctx := context.Background() _ = db.DoTxn(ctx, nil, func(ctx context.Context) error { assert.PanicsWithError(t, dbutil.ErrTransactionDeadlock.Error(), func() { _ = db.DoTxn(context.Background(), nil, func(ctx context.Context) error { return nil }) }) return fmt.Errorf("meow") }) } func TestDatabase_Deadlock_Child(t *testing.T) { db := initTestDB(t) ctx := context.Background() childDB := db.Child("", nil, nil) _ = db.DoTxn(ctx, nil, func(ctx context.Context) error { assert.PanicsWithError(t, dbutil.ErrQueryDeadlock.Error(), func() { _, _ = childDB.Exec(context.Background(), "INSERT INTO meow (value) VALUES ('meow 4');") }) return fmt.Errorf("meow") }) } func TestDatabase_Deadlock_Child2(t *testing.T) { db := initTestDB(t) ctx := context.Background() childDB := db.Child("", nil, nil) _ = childDB.DoTxn(ctx, nil, func(ctx context.Context) error { assert.PanicsWithError(t, dbutil.ErrQueryDeadlock.Error(), func() { _, _ = db.Exec(context.Background(), "INSERT INTO meow (value) VALUES ('meow 4');") }) return fmt.Errorf("meow") }) } go-util-0.9.5/dbutil/iter.go000066400000000000000000000127021513243016000156570ustar00rootroot00000000000000// Copyright (c) 2023 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "errors" "fmt" "runtime" "go.mau.fi/util/exzerolog" ) var ErrAlreadyIterated = errors.New("this iterator has been already iterated") // RowIter is a wrapper for [Rows] that allows conveniently iterating over rows // with a predefined scanner function. type RowIter[T any] interface { // Iter iterates over the rows and calls the given function for each row. // // If the function returns false, the iteration is stopped. // If the function returns an error, the iteration is stopped and the error is // returned. Iter(func(T) (bool, error)) error // AsList collects all rows into a slice. AsList() ([]T, error) } type ConvertRowFn[T any] func(Scannable) (T, error) // NewRowIter is a proxy for NewRowIterWithError for more convenient usage. // // For example: // // func exampleConvertRowFn(rows Scannable) (*YourType, error) { // ... // } // func exampleFunction() { // iter := dbutil.ConvertRowFn[*YourType](exampleConvertRowFn).NewRowIter( // db.Query("SELECT ..."), // ) // } func (crf ConvertRowFn[T]) NewRowIter(rows Rows, err error) RowIter[T] { return newRowIterWithError(rows, crf, err) } type rowIterImpl[T any] struct { Rows ConvertRow ConvertRowFn[T] iterated bool caller string err error } // NewRowIter creates a new RowIter from the given Rows and scanner function. // // Deprecated: use NewRowIterWithError instead to avoid an unnecessary separate error check on the Query result. // // Instead of // // func DoQuery(...) (dbutil.RowIter, error) { // rows, err := db.Query(...) // if err != nil { // return nil, err // } // return dbutil.NewRowIter(rows, convertFn), nil // } // // you should use // // func DoQuery(...) dbutil.RowIter { // rows, err := db.Query(...) // return dbutil.NewRowIterWithError(rows, convertFn, err) // } // // or alternatively pre-wrap the convertFn // // var converter = dbutil.ConvertRowFn(convertFn) // func DoQuery(...) dbutil.RowIter { // return converter.NewRowIter(db.Query(...)) // } // // Embedding the error in the iterator allows the caller to do only one error check instead of two: // // iter, err := DoQuery(...) // if err != nil { ... } // result, err := iter.Iter(...) // if err != nil { ... } // // vs // // result, err := DoQuery(...).Iter(...) // if err != nil { ... } func NewRowIter[T any](rows Rows, convertFn ConvertRowFn[T]) RowIter[T] { return newRowIterWithError(rows, convertFn, nil) } // NewRowIterWithError creates a new RowIter from the given Rows and scanner function with default error. If not nil, it will be returned without calling iterator function. func NewRowIterWithError[T any](rows Rows, convertFn ConvertRowFn[T], err error) RowIter[T] { return newRowIterWithError(rows, convertFn, err) } func newRowIterWithError[T any](rows Rows, convertFn ConvertRowFn[T], err error) RowIter[T] { ri := &rowIterImpl[T]{Rows: rows, ConvertRow: convertFn, err: err} if err == nil { callerSkip := 2 if pc, file, line, ok := runtime.Caller(callerSkip); ok { ri.caller = exzerolog.CallerWithFunctionName(pc, file, line) } runtime.SetFinalizer(ri, (*rowIterImpl[T]).destroy) } return ri } func ScanSingleColumn[T any](rows Scannable) (val T, err error) { err = rows.Scan(&val) return } type NewableDataStruct[T any] interface { DataStruct[T] New() T } func ScanDataStruct[T NewableDataStruct[T]](rows Scannable) (T, error) { var val T return val.New().Scan(rows) } func (i *rowIterImpl[T]) destroy() { if !i.iterated { panic(fmt.Errorf("RowIter created at %s wasn't iterated", i.caller)) } } func (i *rowIterImpl[T]) Iter(fn func(T) (bool, error)) error { if i == nil { return nil } else if i.Rows == nil || i.err != nil { return i.err } defer func() { _ = i.Rows.Close() i.iterated = true }() for i.Rows.Next() { if item, err := i.ConvertRow(i.Rows); err != nil { i.err = err return err } else if cont, err := fn(item); err != nil { i.err = err return err } else if !cont { break } } err := i.Rows.Err() if err != nil { i.err = err } else { i.err = ErrAlreadyIterated } return err } func (i *rowIterImpl[T]) AsList() (list []T, err error) { err = i.Iter(func(item T) (bool, error) { list = append(list, item) return true, nil }) return } func RowIterAsMap[T any, Key comparable, Value any](ri RowIter[T], getKeyValue func(T) (Key, Value)) (map[Key]Value, error) { m := make(map[Key]Value) err := ri.Iter(func(item T) (bool, error) { k, v := getKeyValue(item) m[k] = v return true, nil }) return m, err } type sliceIterImpl[T any] struct { items []T err error } func NewSliceIter[T any](items []T) RowIter[T] { return &sliceIterImpl[T]{items: items} } func NewSliceIterWithError[T any](items []T, err error) RowIter[T] { return &sliceIterImpl[T]{items: items, err: err} } func (i *sliceIterImpl[T]) Iter(fn func(T) (bool, error)) error { if i == nil { return nil } else if i.err != nil { return i.err } for _, item := range i.items { if cont, err := fn(item); err != nil { i.err = err return err } else if !cont { break } } i.err = ErrAlreadyIterated return nil } func (i *sliceIterImpl[T]) AsList() ([]T, error) { if i == nil { return nil, nil } else if i.err != nil { return nil, i.err } i.err = ErrAlreadyIterated return i.items, nil } go-util-0.9.5/dbutil/json.go000066400000000000000000000020021513243016000156550ustar00rootroot00000000000000package dbutil import ( "database/sql/driver" "encoding/json" "fmt" "unsafe" ) // JSON is a utility type for using arbitrary JSON data as values in database Exec and Scan calls. type JSON struct { Data any } func (j JSON) Scan(i any) error { switch value := i.(type) { case nil: return nil case string: return json.Unmarshal([]byte(value), j.Data) case []byte: return json.Unmarshal(value, j.Data) default: return fmt.Errorf("invalid type %T for dbutil.JSON.Scan", i) } } func (j JSON) Value() (driver.Value, error) { if j.Data == nil { return nil, nil } v, err := json.Marshal(j.Data) return unsafe.String(unsafe.SliceData(v), len(v)), err } // JSONPtr is a convenience function for wrapping a pointer to a value in the JSON utility, but removing typed nils // (i.e. preventing nils from turning into the string "null" in the database). func JSONPtr[T any](val *T) JSON { return JSON{Data: UntypedNil(val)} } func UntypedNil[T any](val *T) any { if val == nil { return nil } return val } go-util-0.9.5/dbutil/litestream/000077500000000000000000000000001513243016000165345ustar00rootroot00000000000000go-util-0.9.5/dbutil/litestream/nocgo.go000066400000000000000000000000441513243016000201660ustar00rootroot00000000000000//go:build !cgo package litestream go-util-0.9.5/dbutil/litestream/register.go000066400000000000000000000035221513243016000207110ustar00rootroot00000000000000//go:build cgo package litestream import ( "database/sql" "database/sql/driver" "github.com/mattn/go-sqlite3" ) var Functions = make(map[string]any) func setPragmas(conn *sqlite3.SQLiteConn, pragmas ...string) (err error) { for _, pragma := range pragmas { if _, err = conn.Exec(pragma, []driver.Value{}); err != nil { return } } return } func registerFuncsAndSetTrace(conn *sqlite3.SQLiteConn) (err error) { for name, fn := range Functions { err = conn.RegisterFunc(name, fn, true) if err != nil { return } } err = DoSetTrace(conn) return } func init() { sql.Register("litestream", &sqlite3.SQLiteDriver{ ConnectHook: func(conn *sqlite3.SQLiteConn) (err error) { if err = registerFuncsAndSetTrace(conn); err != nil { return } if err = conn.SetFileControlInt("main", sqlite3.SQLITE_FCNTL_PERSIST_WAL, 1); err != nil { return } err = setPragmas( conn, "PRAGMA foreign_keys = ON", "PRAGMA journal_mode = WAL", "PRAGMA wal_autocheckpoint = 0", "PRAGMA synchronous = NORMAL", "PRAGMA busy_timeout = 5000", ) return }, }) sql.Register("sqlite3-fk-wal", &sqlite3.SQLiteDriver{ ConnectHook: func(conn *sqlite3.SQLiteConn) (err error) { if err = registerFuncsAndSetTrace(conn); err != nil { return } err = setPragmas( conn, "PRAGMA foreign_keys = ON", "PRAGMA journal_mode = WAL", "PRAGMA synchronous = NORMAL", "PRAGMA busy_timeout = 5000", ) return }, }) sql.Register("sqlite3-fk-wal-fullsync", &sqlite3.SQLiteDriver{ ConnectHook: func(conn *sqlite3.SQLiteConn) (err error) { if err = registerFuncsAndSetTrace(conn); err != nil { return } err = setPragmas( conn, "PRAGMA foreign_keys = ON", "PRAGMA journal_mode = WAL", "PRAGMA synchronous = FULL", "PRAGMA busy_timeout = 5000", ) return }, }) } go-util-0.9.5/dbutil/litestream/register_notrace.go000066400000000000000000000002401513243016000224160ustar00rootroot00000000000000//go:build cgo && !sqlite_trace package litestream import ( "github.com/mattn/go-sqlite3" ) func DoSetTrace(conn *sqlite3.SQLiteConn) error { return nil } go-util-0.9.5/dbutil/litestream/register_trace.go000066400000000000000000000016111513243016000220640ustar00rootroot00000000000000//go:build cgo && sqlite_trace package litestream import ( "fmt" "time" "github.com/mattn/go-sqlite3" ) func traceCallback(info sqlite3.TraceInfo) int { switch info.EventCode { case sqlite3.TraceStmt: fmt.Println(info.ExpandedSQL) fmt.Println("-------------------------------------------------------------") default: fmt.Print("exectime: ", time.Duration(info.RunTimeNanosec).String()) if info.AutoCommit { fmt.Print(" - autocommit") } else { fmt.Print(" - transaction") } if info.DBError.Code != 0 || info.DBError.ExtendedCode != 0 { fmt.Printf(" - error %#v", info.DBError) } fmt.Println() } return 0 } func DoSetTrace(conn *sqlite3.SQLiteConn) error { return conn.SetTrace(&sqlite3.TraceConfig{ Callback: traceCallback, EventMask: sqlite3.TraceStmt | sqlite3.TraceProfile | sqlite3.TraceRow | sqlite3.TraceClose, WantExpandedSQL: true, }) } go-util-0.9.5/dbutil/log.go000066400000000000000000000101201513243016000154650ustar00rootroot00000000000000package dbutil import ( "context" "regexp" "runtime" "strings" "time" "github.com/rs/zerolog" ) type DatabaseLogger interface { QueryTiming(ctx context.Context, method, query string, args []any, nrows int, duration time.Duration, err error) WarnUnsupportedVersion(current, compat, latest int) PrepareUpgrade(current, compat, latest int) DoUpgrade(from, to int, message string, txn TxnMode) // Deprecated: legacy warning method, return errors instead Warn(msg string, args ...any) } type noopLogger struct{} var NoopLogger DatabaseLogger = &noopLogger{} func (n noopLogger) WarnUnsupportedVersion(_, _, _ int) {} func (n noopLogger) PrepareUpgrade(_, _, _ int) {} func (n noopLogger) DoUpgrade(_, _ int, _ string, _ TxnMode) {} func (n noopLogger) Warn(msg string, args ...any) {} func (n noopLogger) QueryTiming(_ context.Context, _, _ string, _ []any, _ int, _ time.Duration, _ error) { } type zeroLogger struct { l *zerolog.Logger ZeroLogSettings } type ZeroLogSettings struct { CallerSkipFrame int Caller bool // TraceLogAllQueries specifies whether or not all queries should be logged // at the TRACE level. TraceLogAllQueries bool } func ZeroLogger(log zerolog.Logger, cfg ...ZeroLogSettings) DatabaseLogger { return ZeroLoggerPtr(&log, cfg...) } func ZeroLoggerPtr(log *zerolog.Logger, cfg ...ZeroLogSettings) DatabaseLogger { wrapped := &zeroLogger{l: log} if len(cfg) > 0 { wrapped.ZeroLogSettings = cfg[0] } else { wrapped.ZeroLogSettings = ZeroLogSettings{ CallerSkipFrame: 2, // Skip LoggingExecable.ExecContext and zeroLogger.QueryTiming Caller: true, } } return wrapped } func (z zeroLogger) WarnUnsupportedVersion(current, compat, latest int) { z.l.Warn(). Int("current_version", current). Int("oldest_compatible_version", compat). Int("latest_known_version", latest). Msg("Unsupported database schema version, continuing anyway") } func (z zeroLogger) PrepareUpgrade(current, compat, latest int) { evt := z.l.Info(). Int("current_version", current). Int("oldest_compatible_version", compat). Int("latest_known_version", latest) if current >= latest { evt.Msg("Database is up to date") } else { evt.Msg("Preparing to update database schema") } } func (z zeroLogger) DoUpgrade(from, to int, message string, txn TxnMode) { z.l.Info(). Int("from", from). Int("to", to). Str("txn_mode", string(txn)). Str("description", message). Msg("Upgrading database") } var whitespaceRegex = regexp.MustCompile(`\s+`) var GlobalSafeQueryLog bool func (z zeroLogger) QueryTiming(ctx context.Context, method, query string, args []any, nrows int, duration time.Duration, err error) { log := zerolog.Ctx(ctx) if log.GetLevel() == zerolog.Disabled || log == zerolog.DefaultContextLogger { log = z.l } if (!z.TraceLogAllQueries || log.GetLevel() != zerolog.TraceLevel) && !GlobalSafeQueryLog && duration < 1*time.Second { return } if nrows > -1 { rowLog := log.With().Int("rows", nrows).Logger() log = &rowLog } query = strings.TrimSpace(whitespaceRegex.ReplaceAllLiteralString(query, " ")) callerSkipFrame := z.CallerSkipFrame if GlobalSafeQueryLog || duration > 1*time.Second { for ; callerSkipFrame < 10; callerSkipFrame++ { _, filename, _, _ := runtime.Caller(callerSkipFrame) if !strings.Contains(filename, "/dbutil/") { break } } } if GlobalSafeQueryLog { log.Debug(). Err(err). Int64("duration_ยตs", duration.Microseconds()). Str("method", method). Str("query", query). Caller(callerSkipFrame). Msg("Query") } else { log.Trace(). Err(err). Int64("duration_ยตs", duration.Microseconds()). Str("method", method). Str("query", query). Interface("query_args", args). Msg("Query") } if duration >= 1*time.Second { evt := log.Warn(). Float64("duration_seconds", duration.Seconds()). AnErr("result_error", err). Str("method", method). Str("query", query) if z.Caller { evt = evt.Caller(callerSkipFrame) } evt.Msg("Query took long") } } func (z zeroLogger) Warn(msg string, args ...any) { z.l.Warn().Msgf(msg, args...) // zerolog-allow-msgf } go-util-0.9.5/dbutil/massinsert.go000066400000000000000000000155521513243016000171120ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "fmt" "regexp" "strings" ) // Array is an interface for small fixed-size arrays. // It exists because generics can't specify array sizes: https://github.com/golang/go/issues/44253 type Array interface { [0]any | [1]any | [2]any | [3]any | [4]any | [5]any | [6]any | [7]any | [8]any | [9]any | [10]any | [11]any | [12]any | [13]any | [14]any | [15]any | [16]any | [17]any | [18]any | [19]any | [20]any | [21]any | [22]any | [23]any | [24]any | [25]any | [26]any | [27]any | [28]any | [29]any } // MassInsertable represents a struct that contains dynamic values for a mass insert query. type MassInsertable[T Array] interface { GetMassInsertValues() T } // MassInsertBuilder contains pre-validated templates for building mass insert SQL queries. type MassInsertBuilder[Item MassInsertable[DynamicParams], StaticParams Array, DynamicParams Array] struct { queryTemplate string placeholderTemplate string } // NewMassInsertBuilder creates a new MassInsertBuilder that can build mass insert database queries. // // Parameters in mass insert queries are split into two types: static parameters // and dynamic parameters. Static parameters are the same for all items being // inserted, while dynamic parameters are different for each item. // // The given query should be a normal INSERT query for a single row. It can also // have ON CONFLICT clauses, as long as the clause uses `excluded` instead of // positional parameters. // // The placeholder template is used to replace the `VALUES` part of the given // query. It should contain a positional placeholder ($1, $2, ...) for each // static placeholder, and a fmt directive (`$%d`) for each dynamic placeholder. // // The given query and placeholder template are validated here and the function // will panic if they're invalid (e.g. if the `VALUES` part of the insert query // can't be found, or if the placeholder template doesn't have the right things). // The idea is to use this function to populate a global variable with the mass // insert builder, so the panic will happen at startup if the query or // placeholder template are invalid (instead of returning an error when trying // to use the query later). // // Example: // // type Message struct { // ChatID int // RemoteID string // MXID id.EventID // Timestamp time.Time // } // // func (msg *Message) GetMassInsertValues() [3]any { // return [3]any{msg.RemoteID, msg.MXID, msg.Timestamp.UnixMilli()} // } // // const insertMessageQuery = `INSERT INTO message (chat_id, remote_id, mxid, timestamp) VALUES ($1, $2, $3, $4)` // var massInsertMessageBuilder = dbutil.NewMassInsertBuilder[Message, [2]any](insertMessageQuery, "($1, $%d, $%d, $%d, $%d)") // // func DoMassInsert(ctx context.Context, messages []*Message) error { // query, params := massInsertMessageBuilder.Build([1]any{messages[0].ChatID}, messages) // return db.Exec(ctx, query, params...) // } func NewMassInsertBuilder[Item MassInsertable[DynamicParams], StaticParams Array, DynamicParams Array]( singleInsertQuery, placeholderTemplate string, ) *MassInsertBuilder[Item, StaticParams, DynamicParams] { var dyn DynamicParams var stat StaticParams totalParams := len(dyn) + len(stat) mainQueryVariablePlaceholderParts := make([]string, totalParams) for i := 0; i < totalParams; i++ { mainQueryVariablePlaceholderParts[i] = fmt.Sprintf(`\$%d`, i+1) } mainQueryVariablePlaceholderRegex := regexp.MustCompile(fmt.Sprintf(`\(\s*%s\s*\)`, strings.Join(mainQueryVariablePlaceholderParts, `\s*,\s*`))) queryPlaceholders := mainQueryVariablePlaceholderRegex.FindAllString(singleInsertQuery, -1) if len(queryPlaceholders) == 0 { panic(fmt.Errorf("invalid insert query: placeholders not found")) } else if len(queryPlaceholders) > 1 { panic(fmt.Errorf("invalid insert query: multiple placeholders found")) } for i := 0; i < len(stat); i++ { if !strings.Contains(placeholderTemplate, fmt.Sprintf("$%d", i+1)) { panic(fmt.Errorf("invalid placeholder template: static placeholder $%d not found", i+1)) } } if strings.Contains(placeholderTemplate, fmt.Sprintf("$%d", len(stat)+1)) { panic(fmt.Errorf("invalid placeholder template: non-static placeholder $%d found", len(stat)+1)) } fmtParams := make([]any, len(dyn)) for i := 0; i < len(dyn); i++ { fmtParams[i] = fmt.Sprintf("$%d", len(stat)+i+1) } formattedPlaceholder := fmt.Sprintf(placeholderTemplate, fmtParams...) if strings.Contains(formattedPlaceholder, "!(EXTRA string=") { panic(fmt.Errorf("invalid placeholder template: extra string found")) } for i := 0; i < len(dyn); i++ { if !strings.Contains(formattedPlaceholder, fmt.Sprintf("$%d", len(stat)+i+1)) { panic(fmt.Errorf("invalid placeholder template: dynamic placeholder $%d not found", len(stat)+i+1)) } } return &MassInsertBuilder[Item, StaticParams, DynamicParams]{ queryTemplate: strings.Replace(singleInsertQuery, queryPlaceholders[0], "%s", 1), placeholderTemplate: placeholderTemplate, } } // Build constructs a ready-to-use mass insert SQL query using the prepared templates in this builder. // // This method always only produces one query. If there are lots of items, // chunking them beforehand may be required to avoid query parameter limits. // For example, SQLite (3.32+) has a limit of 32766 parameters by default, // while Postgres allows up to 65535. To find out if there are too many items, // divide the maximum number of parameters by the number of dynamic columns in // your data and subtract the number of static columns. // // Example of chunking input data: // // var mib dbutil.MassInsertBuilder // var db *dbutil.Database // func MassInsert(ctx context.Context, ..., data []T) error { // return db.DoTxn(ctx, nil, func(ctx context.Context) error { // for _, chunk := range exslices.Chunk(data, 100) { // query, params := mib.Build(staticParams) // _, err := db.Exec(ctx, query, params...) // if err != nil { // return err // } // } // return nil // } // } func (mib *MassInsertBuilder[Item, StaticParams, DynamicParams]) Build(static StaticParams, data []Item) (query string, params []any) { var itemValues DynamicParams params = make([]any, len(static)+len(itemValues)*len(data)) placeholders := make([]string, len(data)) for i := 0; i < len(static); i++ { params[i] = static[i] } fmtParams := make([]any, len(itemValues)) for i, item := range data { baseIndex := len(static) + len(itemValues)*i itemValues = item.GetMassInsertValues() for j := 0; j < len(itemValues); j++ { params[baseIndex+j] = itemValues[j] fmtParams[j] = baseIndex + j + 1 } placeholders[i] = fmt.Sprintf(mib.placeholderTemplate, fmtParams...) } query = fmt.Sprintf(mib.queryTemplate, strings.Join(placeholders, ", ")) return } go-util-0.9.5/dbutil/massinsert_test.go000066400000000000000000000120111513243016000201340ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil_test import ( "fmt" "math/rand" "strings" "testing" "time" "github.com/stretchr/testify/assert" "go.mau.fi/util/dbutil" "go.mau.fi/util/random" ) type AbstractMassInsertable[T dbutil.Array] struct { Data T } func (a AbstractMassInsertable[T]) GetMassInsertValues() T { return a.Data } type OneParamMassInsertable = AbstractMassInsertable[[1]any] func TestNewMassInsertBuilder_InvalidParams(t *testing.T) { assert.PanicsWithError(t, "invalid insert query: placeholders not found", func() { dbutil.NewMassInsertBuilder[OneParamMassInsertable, [1]any]("", "") }) assert.PanicsWithError(t, "invalid placeholder template: static placeholder $1 not found", func() { dbutil.NewMassInsertBuilder[OneParamMassInsertable, [1]any]("INSERT INTO foo VALUES ($1, $2)", "") }) assert.PanicsWithError(t, "invalid placeholder template: non-static placeholder $2 found", func() { dbutil.NewMassInsertBuilder[OneParamMassInsertable, [1]any]("INSERT INTO foo VALUES ($1, $2)", "($1, $2)") }) assert.PanicsWithError(t, "invalid placeholder template: extra string found", func() { dbutil.NewMassInsertBuilder[OneParamMassInsertable, [1]any]("INSERT INTO foo VALUES ($1, $2)", "($1)") }) } func TestMassInsertBuilder_Build(t *testing.T) { builder := dbutil.NewMassInsertBuilder[OneParamMassInsertable, [1]any]("INSERT INTO foo VALUES ($1, $2)", "($1, $%d)") query, values := builder.Build([1]any{"hi"}, []OneParamMassInsertable{{[1]any{"hmm"}}, {[1]any{"meow"}}, {[1]any{"third"}}}) assert.Equal(t, "INSERT INTO foo VALUES ($1, $2), ($1, $3), ($1, $4)", query) assert.Equal(t, []any{"hi", "hmm", "meow", "third"}, values) } func TestMassInsertBuilder_Build_MultiValue(t *testing.T) { ts := time.Now().UnixMilli() builder := dbutil.NewMassInsertBuilder[AbstractMassInsertable[[5]any], [3]any]("INSERT INTO foo VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", "($1, $2, $%d, $%d, $3, $%d, $%d, $%d)") query, values := builder.Build([3]any{"first", "second", 3}, []AbstractMassInsertable[[5]any]{ {[5]any{"foo1", 123, true, "meow", ts}}, {[5]any{"foo2", 666, false, "meow", ts + 1}}, {[5]any{"foo3", 999, true, "no meow", ts + 2}}, {[5]any{"foo4", 0, true, "meow!", 0}}, }) assert.Equal(t, "INSERT INTO foo VALUES ($1, $2, $4, $5, $3, $6, $7, $8), ($1, $2, $9, $10, $3, $11, $12, $13), ($1, $2, $14, $15, $3, $16, $17, $18), ($1, $2, $19, $20, $3, $21, $22, $23)", query) assert.Equal(t, []any{"first", "second", 3, "foo1", 123, true, "meow", ts, "foo2", 666, false, "meow", ts + 1, "foo3", 999, true, "no meow", ts + 2, "foo4", 0, true, "meow!", 0}, values) } func TestMassInsertBuilder_Build_CompareWithManual(t *testing.T) { builder := dbutil.NewMassInsertBuilder[AbstractMassInsertable[[5]any], [3]any]("INSERT INTO foo VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", "($1, $2, $%d, $%d, $3, $%d, $%d, $%d)") data := makeBenchmarkData[[5]any](100) manualQuery, manualParams := buildMassInsertManual(data) query, params := builder.Build([3]any{"first", "second", 3}, data) assert.Equal(t, manualQuery, query) assert.Equal(t, manualParams, params) } func makeBenchmarkData[T dbutil.Array](n int) []AbstractMassInsertable[T] { outArr := make([]AbstractMassInsertable[T], n) dataLen := len(outArr[0].Data) for i := 0; i < dataLen; i++ { var val any switch rand.Intn(4) { case 0: val = rand.Intn(1000) case 1: val = rand.Intn(2) == 0 case 2: val = time.Now().UnixMilli() case 3: val = random.String(16) } for j := 0; j < len(outArr); j++ { outArr[j].Data[i] = val } } return outArr } func BenchmarkMassInsertBuilder_Build5x100(b *testing.B) { builder := dbutil.NewMassInsertBuilder[AbstractMassInsertable[[5]any], [3]any]("INSERT INTO foo VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", "($1, $2, $%d, $%d, $3, $%d, $%d, $%d)") data := makeBenchmarkData[[5]any](100) for i := 0; i < b.N; i++ { builder.Build([3]any{"first", "second", 3}, data) } } func buildMassInsertManual(data []AbstractMassInsertable[[5]any]) (string, []any) { const queryTemplate = `INSERT INTO foo VALUES %s` const placeholderTemplate = "($1, $2, $%d, $%d, $3, $%d, $%d, $%d)" placeholders := make([]string, len(data)) params := make([]any, 3+len(data)*5) params[0] = "first" params[1] = "second" params[2] = 3 for j, item := range data { baseIndex := j*5 + 3 params[baseIndex] = item.Data[0] params[baseIndex+1] = item.Data[1] params[baseIndex+2] = item.Data[2] params[baseIndex+3] = item.Data[3] params[baseIndex+4] = item.Data[4] placeholders[j] = fmt.Sprintf(placeholderTemplate, baseIndex+1, baseIndex+2, baseIndex+3, baseIndex+4, baseIndex+5) } query := fmt.Sprintf(queryTemplate, strings.Join(placeholders, ", ")) return query, params } func BenchmarkMassInsertBuilder_Build5x100_Manual(b *testing.B) { data := makeBenchmarkData[[5]any](100) for i := 0; i < b.N; i++ { buildMassInsertManual(data) } } go-util-0.9.5/dbutil/module.go000066400000000000000000000101551513243016000162010ustar00rootroot00000000000000// Package dbutil provides a simple framework for in-process database // migrations. You provide the SQL files and they are run to upgrade // the database. A versions table is automatically created in the // database to track which migrations have been applied. There is // support for multiple migration pathways, for example v0->v2 versus // v0->v1->v2, and the shorter one is prioritized if both are // provided. // // Example usage from Go: // // package main // // import ( // "context" // "database/sql" // "embed" // // "go.mau.fi/util/dbutil" // ) // // //go:embed *.sql // var upgrades embed.FS // // func mainE() error { // ctx := context.Background() // rawDB, err := sql.Open("sqlite3", "./hotdogs.db") // if err != nil { // return err // } // db, err := dbutil.NewWithDB(rawDB, "sqlite3") // if err != nil { // return err // } // table := dbutil.UpgradeTable{} // table.RegisterFS(upgrades) // err = db.Upgrade(ctx) // if err != nil { // return err // } // // db has been upgraded to latest version // return nil // } // // In dbutil, the database is understood to have a monotonic integer // sequence of versions starting at v0, v1, v2, etc. By providing // migrations you define a directed acyclic graph (DAG) that allows // dbutil to find a path from the current recorded database version to // the latest version available. // // Each SQL migration file has a mandatory comment header that // identifies which database versions it upgrades between. For example // this is a migration that upgrades from v0 to v2: // // -- v0 -> v2: Do some things // // You can omit the first version for the common case of upgrading to // a version from the previous version. For example this is a // migration that upgrades from v1 to v2: // // -- v2: Do fewer things // // By providing "v1" and "v2" migrations, a v0 database would be // upgraded to v1 and then v2, while by providing an additional "v0 -> // v2" migration a v0 database would be upgraded directly to v2 as it // is a more direct path. With that migration provided the "v1" // migration is no longer needed. // // By default, when running migrations, if a more recent database // version is live than the current code knows about (for example, // from running a previous version of the application), dbutil will // error out. However, many database migrations are backwards // compatible. You can therefore indicate this when writing a // migration, and previous versions of the application will accept a // database with that migration applied, even if they are unaware of // its contents. For example, if the migration from v1 to v2 was // backwards compatible, you could provide this migration: // // -- v2 (compatible with v1+): Do fewer things // // When applying the migration, the compatibility level (v1) is saved // to the versions table in the database, so that older versions of // the application which only know about v1 will see that v2 of the // database is still OK to use. If the compatibility level is not set, // then it defaults to the same as the target version for the // migration, which achieves the default behavior described in the // previous paragraph. // // You can provide additional flags immediately following the header // line. To disable wrapping the upgrade in a single transaction, put // "transaction: off" on the second line. // // -- v5: Upgrade without transaction // -- transaction: off // // do dangerous stuff // // Within migrations, there is special syntax that can be used to // filter parts of the SQL to apply only with specific dialects. To // limit the next line to one dialect: // // -- only: postgres // // To limit the next N lines: // // -- only: sqlite for next 123 lines // // To limit a block of code, fenced by another directive: // // -- only: sqlite until "end only" // QUERY; // ANOTHER QUERY; // -- end only sqlite // // If the single-line limit is on the second line of the file, the // whole file is limited to that dialect. // // If the filter ends with `(lines commented)`, then ALL lines chosen // by the filter will be uncommented. The `--` comment prefix must be // at the beginning of the line with no whitespace ahead of it. package dbutil go-util-0.9.5/dbutil/queryhelper.go000066400000000000000000000104051513243016000172570ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "context" "database/sql" "errors" "time" "golang.org/x/exp/constraints" ) // DataStruct is an interface for structs that represent a single database row. type DataStruct[T any] interface { Scan(row Scannable) (T, error) } // QueryHelper is a generic helper struct for SQL query execution boilerplate. // // After implementing the Scan and Init methods in a data struct, the query // helper allows writing query functions in a single line. type QueryHelper[T DataStruct[T]] struct { db *Database newFunc func(qh *QueryHelper[T]) T } func MakeQueryHelper[T DataStruct[T]](db *Database, new func(qh *QueryHelper[T]) T) *QueryHelper[T] { return &QueryHelper[T]{db: db, newFunc: new} } // ValueOrErr is a helper function that returns the value if err is nil, or // returns nil and the error if err is not nil. It can be used to avoid // `if err != nil { return nil, err }` boilerplate in certain cases like // DataStruct.Scan implementations. func ValueOrErr[T any](val *T, err error) (*T, error) { if err != nil { return nil, err } return val, nil } // StrPtr returns a pointer to the given string, or nil if the string is empty. func StrPtr[T ~string](val T) *string { if val == "" { return nil } strVal := string(val) return &strVal } // NumPtr returns a pointer to the given number, or nil if the number is zero. func NumPtr[T constraints.Integer | constraints.Float](val T) *T { if val == 0 { return nil } return &val } // UnixPtr returns a pointer to the given time as unix seconds, or nil if the time is zero. func UnixPtr(val time.Time) *int64 { return ConvertedPtr(val, time.Time.Unix) } // UnixMilliPtr returns a pointer to the given time as unix milliseconds, or nil if the time is zero. func UnixMilliPtr(val time.Time) *int64 { return ConvertedPtr(val, time.Time.UnixMilli) } type Zeroable interface { IsZero() bool } // ConvertedPtr returns a pointer to the converted version of the given value, or nil if the input is zero. // // This is primarily meant for time.Time, but it can be used with any type that has implements `IsZero() bool`. // // yourTime := time.Now() // unixMSPtr := dbutil.TimePtr(yourTime, time.Time.UnixMilli) func ConvertedPtr[Input Zeroable, Output any](val Input, converter func(Input) Output) *Output { if val.IsZero() { return nil } converted := converter(val) return &converted } func (qh *QueryHelper[T]) GetDB() *Database { return qh.db } func (qh *QueryHelper[T]) New() T { return qh.newFunc(qh) } // Exec executes a query with ExecContext and returns the error. // // It omits the sql.Result return value, as it is rarely used. When the result // is wanted, use `qh.GetDB().Exec(...)` instead, which is // otherwise equivalent. func (qh *QueryHelper[T]) Exec(ctx context.Context, query string, args ...any) error { _, err := qh.db.Exec(ctx, query, args...) return err } func (qh *QueryHelper[T]) scanNew(row Scannable) (T, error) { return qh.New().Scan(row) } // QueryOne executes a query with QueryRowContext, uses the associated DataStruct // to scan it, and returns the value. If the query returns no rows, it returns nil // and no error. func (qh *QueryHelper[T]) QueryOne(ctx context.Context, query string, args ...any) (val T, err error) { val, err = qh.scanNew(qh.db.QueryRow(ctx, query, args...)) if errors.Is(err, sql.ErrNoRows) { return *new(T), nil } return val, err } // QueryMany executes a query with QueryContext, uses the associated DataStruct // to scan each row, and returns the values. If the query returns no rows, it // returns a non-nil zero-length slice and no error. func (qh *QueryHelper[T]) QueryMany(ctx context.Context, query string, args ...any) ([]T, error) { return qh.QueryManyIter(ctx, query, args...).AsList() } // QueryManyIter executes a query with QueryContext and returns a RowIter // that will use the associated DataStruct to scan each row. func (qh *QueryHelper[T]) QueryManyIter(ctx context.Context, query string, args ...any) RowIter[T] { rows, err := qh.db.Query(ctx, query, args...) return NewRowIterWithError(rows, qh.scanNew, err) } go-util-0.9.5/dbutil/reflectscan.go000066400000000000000000000017231513243016000172060ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "reflect" ) func reflectScan[T any](row Scannable) (*T, error) { t := new(T) val := reflect.ValueOf(t).Elem() fields := reflect.VisibleFields(val.Type()) scanInto := make([]any, len(fields)) for i, field := range fields { scanInto[i] = val.FieldByIndex(field.Index).Addr().Interface() } err := row.Scan(scanInto...) return t, err } // NewSimpleReflectRowIter creates a new RowIter that uses reflection to scan rows into the given type. // // This is a simplified implementation that always scans to all struct fields. It does not support any kind of struct tags. func NewSimpleReflectRowIter[T any](rows Rows, err error) RowIter[*T] { return ConvertRowFn[*T](reflectScan[T]).NewRowIter(rows, err) } go-util-0.9.5/dbutil/samples/000077500000000000000000000000001513243016000160275ustar00rootroot00000000000000go-util-0.9.5/dbutil/samples/01-sample.sql000066400000000000000000000011431513243016000202460ustar00rootroot00000000000000-- v0 -> v3: Sample revision jump CREATE TABLE foo ( -- only: postgres key BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, -- only: sqlite (line commented) -- key INTEGER PRIMARY KEY, data JSONB NOT NULL ); -- only: sqlite until "end only" CREATE TRIGGER test AFTER INSERT ON foo WHEN NEW.data->>'action' = 'delete' BEGIN DELETE FROM test WHERE key <= NEW.data->>'index'; END; -- end only sqlite -- only: postgres until "end only" CREATE FUNCTION delete_data() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN DELETE FROM test WHERE key <= NEW.data->>'index'; RETURN NEW; END $$; -- end only postgres go-util-0.9.5/dbutil/samples/04-notxn.sql000066400000000000000000000001361513243016000201370ustar00rootroot00000000000000-- v4: Sample outside transaction -- transaction: off INSERT INTO foo VALUES ('meow', '{}'); go-util-0.9.5/dbutil/samples/05-compat.sql000066400000000000000000000001531513243016000202540ustar00rootroot00000000000000-- v5 (compatible with v3+): Sample backwards-compatible upgrade INSERT INTO foo VALUES ('meow 2', '{}'); go-util-0.9.5/dbutil/samples/output/000077500000000000000000000000001513243016000173675ustar00rootroot00000000000000go-util-0.9.5/dbutil/samples/output/01-postgres.sql000066400000000000000000000003721513243016000221760ustar00rootroot00000000000000CREATE TABLE foo ( key BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, data JSONB NOT NULL ); CREATE FUNCTION delete_data() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN DELETE FROM test WHERE key <= NEW.data->>'index'; RETURN NEW; END $$; go-util-0.9.5/dbutil/samples/output/01-sqlite3.sql000066400000000000000000000003211513243016000217060ustar00rootroot00000000000000CREATE TABLE foo ( key INTEGER PRIMARY KEY, data JSONB NOT NULL ); CREATE TRIGGER test AFTER INSERT ON foo WHEN NEW.data->>'action' = 'delete' BEGIN DELETE FROM test WHERE key <= NEW.data->>'index'; END; go-util-0.9.5/dbutil/samples/output/04-postgres.sql000066400000000000000000000000471513243016000222000ustar00rootroot00000000000000INSERT INTO foo VALUES ('meow', '{}'); go-util-0.9.5/dbutil/samples/output/04-sqlite3.sql000066400000000000000000000000471513243016000217160ustar00rootroot00000000000000INSERT INTO foo VALUES ('meow', '{}'); go-util-0.9.5/dbutil/samples/output/05-postgres.sql000066400000000000000000000000511513243016000221740ustar00rootroot00000000000000INSERT INTO foo VALUES ('meow 2', '{}'); go-util-0.9.5/dbutil/samples/output/05-sqlite3.sql000066400000000000000000000000511513243016000217120ustar00rootroot00000000000000INSERT INTO foo VALUES ('meow 2', '{}'); go-util-0.9.5/dbutil/transaction.go000066400000000000000000000115621513243016000172440ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "context" "database/sql" "errors" "fmt" "runtime" "sync/atomic" "time" "github.com/petermattis/goid" "github.com/rs/zerolog" "go.mau.fi/util/exerrors" "go.mau.fi/util/random" ) var ( ErrTxn = errors.New("transaction") ErrTxnBegin = fmt.Errorf("%w: begin", ErrTxn) ErrTxnCommit = fmt.Errorf("%w: commit", ErrTxn) ) type contextKey int64 const ( ContextKeyDoTxnCallerSkip contextKey = 1 ) var nextContextKeyDatabaseTransaction atomic.Uint64 func init() { nextContextKeyDatabaseTransaction.Store(1 << 61) } func (db *Database) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) { return db.Execable(ctx).ExecContext(ctx, query, args...) } func (db *Database) Query(ctx context.Context, query string, args ...any) (Rows, error) { return db.Execable(ctx).QueryContext(ctx, query, args...) } func (db *Database) QueryRow(ctx context.Context, query string, args ...any) *sql.Row { return db.Execable(ctx).QueryRowContext(ctx, query, args...) } var ErrTransactionDeadlock = errors.New("attempt to start new transaction in goroutine with transaction") var ErrQueryDeadlock = errors.New("attempt to query without context in goroutine with transaction") var ErrAcquireDeadlock = errors.New("attempt to acquire connection without context in goroutine with transaction") func (db *Database) BeginTx(ctx context.Context, opts *TxnOptions) (*LoggingTxn, error) { if ctx == nil { panic("BeginTx() called with nil ctx") } return db.LoggingDB.BeginTx(ctx, opts) } func (db *Database) DoTxn(ctx context.Context, opts *TxnOptions, fn func(ctx context.Context) error) error { if ctx == nil { panic("DoTxn() called with nil ctx") } if ctx.Value(db.txnCtxKey) != nil { zerolog.Ctx(ctx).Trace().Msg("Already in a transaction, not creating a new one") return fn(ctx) } else if db.DeadlockDetection { goroutineID := goid.Get() if !db.txnDeadlockMap.Add(goroutineID) { panic(ErrTransactionDeadlock) } defer db.txnDeadlockMap.Remove(goroutineID) } log := zerolog.Ctx(ctx).With().Str("db_txn_id", random.String(12)).Logger() slowLog := log callerSkip := 1 if val := ctx.Value(ContextKeyDoTxnCallerSkip); val != nil { callerSkip += val.(int) } if pc, file, line, ok := runtime.Caller(callerSkip); ok { slowLog = log.With().Str(zerolog.CallerFieldName, zerolog.CallerMarshalFunc(pc, file, line)).Logger() } start := time.Now() deadlockCh := make(chan struct{}) go func() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: slowLog.Warn(). Float64("duration_seconds", time.Since(start).Seconds()). Msg("Transaction still running") case <-deadlockCh: return } } }() defer func() { close(deadlockCh) dur := time.Since(start) if dur > time.Second { slowLog.Warn(). Float64("duration_seconds", dur.Seconds()). Msg("Transaction took long") } }() tx, err := db.BeginTx(ctx, opts) if err != nil { log.Trace().Err(err).Msg("Failed to begin transaction") return exerrors.NewDualError(ErrTxnBegin, err) } logLevel := zerolog.TraceLevel if GlobalSafeQueryLog { logLevel = zerolog.DebugLevel } log.WithLevel(logLevel).Msg("Transaction started") tx.noTotalLog = true ctx = log.WithContext(ctx) ctx = context.WithValue(ctx, db.txnCtxKey, tx) err = fn(ctx) if err != nil { log.Trace().Err(err).Msg("Database transaction failed, rolling back") rollbackErr := tx.Rollback() if rollbackErr != nil { log.Warn().Err(rollbackErr).Msg("Rollback after transaction error failed") } else { log.WithLevel(logLevel).Msg("Rollback successful") } return err } err = tx.Commit() if err != nil { log.WithLevel(logLevel).Err(err).Msg("Commit failed") return exerrors.NewDualError(ErrTxnCommit, err) } log.WithLevel(logLevel).Msg("Commit successful") return nil } func (db *Database) Execable(ctx context.Context) Execable { if ctx == nil { panic("Conn() called with nil ctx") } txn, ok := ctx.Value(db.txnCtxKey).(Transaction) if ok { return txn } if db.DeadlockDetection && db.txnDeadlockMap.Has(goid.Get()) { panic(ErrQueryDeadlock) } return &db.LoggingDB } func (db *Database) AcquireConn(ctx context.Context) (Conn, error) { if ctx == nil { return nil, fmt.Errorf("AcquireConn() called with nil ctx") } _, ok := ctx.Value(db.txnCtxKey).(Transaction) if ok { return nil, fmt.Errorf("cannot acquire connection while in a transaction") } if db.DeadlockDetection && db.txnDeadlockMap.Has(goid.Get()) { panic(ErrAcquireDeadlock) } conn, err := db.RawDB.Conn(ctx) if err != nil { return nil, err } return &LoggingExecable{ UnderlyingExecable: conn, db: db, }, nil } go-util-0.9.5/dbutil/upgrades.go000066400000000000000000000176601513243016000165360ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "context" "database/sql" "errors" "fmt" ) type upgradeFunc func(context.Context, *Database) error type upgrade struct { message string fn upgradeFunc upgradesTo int compatVersion int transaction TxnMode } func (u *upgrade) DangerouslyRun(ctx context.Context, db *Database) (upgradesTo, compat int, err error) { return u.upgradesTo, u.compatVersion, u.fn(ctx, db) } var ErrUnsupportedDatabaseVersion = errors.New("unsupported database schema version") var ErrForeignTables = errors.New("the database contains foreign tables") var ErrNotOwned = errors.New("the database is owned by") var ErrUnsupportedDialect = errors.New("unsupported database dialect") type NotOwnedError struct { Owner string } func (e NotOwnedError) Error() string { return fmt.Sprintf("%v %s", ErrNotOwned, e.Owner) } func (e NotOwnedError) Unwrap() error { return ErrNotOwned } func DangerousInternalUpgradeVersionTable(ctx context.Context, db *Database) error { return db.upgradeVersionTable(ctx) } func (db *Database) upgradeVersionTable(ctx context.Context) error { if compatColumnExists, err := db.ColumnExists(ctx, db.VersionTable, "compat"); err != nil { return fmt.Errorf("failed to check if version table is up to date: %w", err) } else if !compatColumnExists { if tableExists, err := db.TableExists(ctx, db.VersionTable); err != nil { return fmt.Errorf("failed to check if version table exists: %w", err) } else if !tableExists { _, err = db.Exec(ctx, fmt.Sprintf("CREATE TABLE %s (version INTEGER, compat INTEGER)", db.VersionTable)) if err != nil { return fmt.Errorf("failed to create version table: %w", err) } } else { _, err = db.Exec(ctx, fmt.Sprintf("ALTER TABLE %s ADD COLUMN compat INTEGER", db.VersionTable)) if err != nil { return fmt.Errorf("failed to add compat column to version table: %w", err) } } } return nil } func (db *Database) getVersion(ctx context.Context) (version, compat int, err error) { if err = db.upgradeVersionTable(ctx); err != nil { return } var compatNull sql.NullInt32 err = db.QueryRow(ctx, fmt.Sprintf("SELECT version, compat FROM %s LIMIT 1", db.VersionTable)).Scan(&version, &compatNull) if errors.Is(err, sql.ErrNoRows) { err = nil } if compatNull.Valid && compatNull.Int32 != 0 { compat = int(compatNull.Int32) } else { compat = version } return } const ( tableExistsPostgres = "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name=$1)" tableExistsSQLite = "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND tbl_name=?1)" ) func (db *Database) TableExists(ctx context.Context, table string) (exists bool, err error) { switch db.Dialect { case SQLite: err = db.QueryRow(ctx, tableExistsSQLite, table).Scan(&exists) case Postgres: err = db.QueryRow(ctx, tableExistsPostgres, table).Scan(&exists) default: err = ErrUnsupportedDialect } return } const ( columnExistsPostgres = "SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name=$1 AND column_name=$2)" columnExistsSQLite = "SELECT EXISTS(SELECT 1 FROM pragma_table_info(?1) WHERE name=?2)" ) func (db *Database) ColumnExists(ctx context.Context, table, column string) (exists bool, err error) { switch db.Dialect { case SQLite: err = db.QueryRow(ctx, columnExistsSQLite, table, column).Scan(&exists) case Postgres: err = db.QueryRow(ctx, columnExistsPostgres, table, column).Scan(&exists) default: err = ErrUnsupportedDialect } return } const createOwnerTable = ` CREATE TABLE IF NOT EXISTS database_owner ( key INTEGER PRIMARY KEY DEFAULT 0, owner TEXT NOT NULL ) ` func (db *Database) checkDatabaseOwner(ctx context.Context) error { var owner string if !db.IgnoreForeignTables { if exists, err := db.TableExists(ctx, "state_groups_state"); err != nil { return fmt.Errorf("failed to check if state_groups_state exists: %w", err) } else if exists { return fmt.Errorf("%w (found state_groups_state, likely belonging to Synapse)", ErrForeignTables) } else if exists, err = db.TableExists(ctx, "roomserver_rooms"); err != nil { return fmt.Errorf("failed to check if roomserver_rooms exists: %w", err) } else if exists { return fmt.Errorf("%w (found roomserver_rooms, likely belonging to Dendrite)", ErrForeignTables) } } if db.Owner == "" { return nil } if _, err := db.Exec(ctx, createOwnerTable); err != nil { return fmt.Errorf("failed to ensure database owner table exists: %w", err) } else if err = db.QueryRow(ctx, "SELECT owner FROM database_owner WHERE key=0").Scan(&owner); errors.Is(err, sql.ErrNoRows) { _, err = db.Exec(ctx, "INSERT INTO database_owner (key, owner) VALUES (0, $1)", db.Owner) if err != nil { return fmt.Errorf("failed to insert database owner: %w", err) } } else if err != nil { return fmt.Errorf("failed to check database owner: %w", err) } else if owner != db.Owner { return NotOwnedError{owner} } return nil } func (db *Database) setVersion(ctx context.Context, version, compat int) error { _, err := db.Exec(ctx, fmt.Sprintf("DELETE FROM %s", db.VersionTable)) if err != nil { return err } _, err = db.Exec(ctx, fmt.Sprintf("INSERT INTO %s (version, compat) VALUES ($1, $2)", db.VersionTable), version, compat) return err } func (db *Database) DoSQLiteTransactionWithoutForeignKeys(ctx context.Context, doUpgrade func(context.Context) error) error { conn, err := db.AcquireConn(ctx) if err != nil { return fmt.Errorf("failed to acquire connection: %w", err) } _, err = conn.ExecContext(ctx, "PRAGMA foreign_keys=OFF") if err != nil { return fmt.Errorf("failed to disable foreign keys: %w", err) } err = db.DoTxn(ctx, &TxnOptions{Conn: conn}, func(ctx context.Context) error { err := doUpgrade(ctx) if err != nil { return err } _, err = conn.ExecContext(ctx, "PRAGMA foreign_key_check") if err != nil { return fmt.Errorf("failed to check foreign keys after upgrade: %w", err) } return nil }) if err != nil { _, _ = conn.ExecContext(ctx, "PRAGMA foreign_keys=ON") return err } _, err = conn.ExecContext(ctx, "PRAGMA foreign_keys=ON") if err != nil { return fmt.Errorf("failed to enable foreign keys: %w", err) } return nil } func (db *Database) Upgrade(ctx context.Context) error { err := db.checkDatabaseOwner(ctx) if err != nil { return err } version, compat, err := db.getVersion(ctx) if err != nil { return err } if compat > len(db.UpgradeTable) { if db.IgnoreUnsupportedDatabase { db.Log.WarnUnsupportedVersion(version, compat, len(db.UpgradeTable)) return nil } return fmt.Errorf("%w: currently on v%d (compatible down to v%d), latest known: v%d", ErrUnsupportedDatabaseVersion, version, compat, len(db.UpgradeTable)) } db.Log.PrepareUpgrade(version, compat, len(db.UpgradeTable)) logVersion := version for version < len(db.UpgradeTable) { upgradeItem := db.UpgradeTable[version] if upgradeItem.fn == nil { version++ continue } doUpgrade := func(ctx context.Context) error { err = upgradeItem.fn(ctx, db) if err != nil { return fmt.Errorf("failed to run upgrade v%d->v%d: %w", version, upgradeItem.upgradesTo, err) } version = upgradeItem.upgradesTo logVersion = version err = db.setVersion(ctx, version, upgradeItem.compatVersion) if err != nil { return err } return nil } db.Log.DoUpgrade(logVersion, upgradeItem.upgradesTo, upgradeItem.message, upgradeItem.transaction) switch upgradeItem.transaction { case TxnModeOff: err = doUpgrade(ctx) case TxnModeOn: err = db.DoTxn(ctx, nil, doUpgrade) case TxnModeSQLiteForeignKeysOff: switch db.Dialect { case SQLite: err = db.DoSQLiteTransactionWithoutForeignKeys(ctx, doUpgrade) default: err = db.DoTxn(ctx, nil, doUpgrade) } } if err != nil { return err } } return nil } go-util-0.9.5/dbutil/upgrades_test.go000066400000000000000000000106151513243016000175660ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "context" "embed" "fmt" "strings" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/require" ) //go:embed samples/*.sql samples/output/*.sql var rawUpgrades embed.FS func makeTable() (tbl UpgradeTable) { tbl.RegisterFSPath(rawUpgrades, "samples") return } func expectVersionCheck(dialect Dialect, mock sqlmock.Sqlmock, returnVersion, returnCompat int) { if dialect == Postgres { mock.ExpectQuery(columnExistsPostgres). WithArgs("version", "compat"). WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true)) } else if dialect == SQLite { mock.ExpectQuery(columnExistsSQLite). WithArgs("version", "compat"). WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true)) } mock.ExpectQuery("SELECT version, compat FROM version LIMIT 1"). WillReturnRows(sqlmock.NewRows([]string{"version", "compat"}).AddRow(returnVersion, returnCompat)) } func expectVersionBump(dialect Dialect, mock sqlmock.Sqlmock, toVersion, toCompat int) { mock.ExpectExec("DELETE FROM version"). WillReturnResult(sqlmock.NewResult(0, 1)) q := "INSERT INTO version (version, compat) VALUES ($1, $2)" if dialect == SQLite { q = strings.ReplaceAll(q, "$1", "?1") q = strings.ReplaceAll(q, "$2", "?2") } mock.ExpectExec(q). WithArgs(toVersion, toCompat). WillReturnResult(sqlmock.NewResult(0, 0)) } func testUpgrade(dialect Dialect) func(t *testing.T) { return func(t *testing.T) { conn, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) expectedUpgrade1, err := rawUpgrades.ReadFile(fmt.Sprintf("samples/output/01-%s.sql", dialect.String())) require.NoError(t, err) expectedUpgrade2, err := rawUpgrades.ReadFile(fmt.Sprintf("samples/output/04-%s.sql", dialect.String())) require.NoError(t, err) expectedUpgrade3, err := rawUpgrades.ReadFile(fmt.Sprintf("samples/output/05-%s.sql", dialect.String())) require.NoError(t, err) db := &Database{ RawDB: conn, Log: NoopLogger, VersionTable: "version", Dialect: dialect, UpgradeTable: makeTable(), txnCtxKey: contextKey(nextContextKeyDatabaseTransaction.Add(1)), IgnoreForeignTables: true, } db.LoggingDB.UnderlyingExecable = conn db.LoggingDB.db = db expectVersionCheck(db.Dialect, mock, 0, 0) mock.ExpectBegin() mock.ExpectExec(string(expectedUpgrade1)). WillReturnResult(sqlmock.NewResult(0, 0)) expectVersionBump(db.Dialect, mock, 3, 3) mock.ExpectCommit() mock.ExpectExec(string(expectedUpgrade2)). WillReturnResult(sqlmock.NewResult(0, 0)) expectVersionBump(db.Dialect, mock, 4, 4) mock.ExpectBegin() mock.ExpectExec(string(expectedUpgrade3)). WillReturnResult(sqlmock.NewResult(0, 0)) expectVersionBump(db.Dialect, mock, 5, 3) mock.ExpectCommit() err = db.Upgrade(context.TODO()) require.NoError(t, err) require.NoError(t, mock.ExpectationsWereMet()) } } func testCompatCheck(dialect Dialect) func(t *testing.T) { return func(t *testing.T) { conn, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) db := &Database{ RawDB: conn, Log: NoopLogger, VersionTable: "version", Dialect: dialect, UpgradeTable: makeTable(), txnCtxKey: contextKey(nextContextKeyDatabaseTransaction.Add(1)), IgnoreForeignTables: true, } db.LoggingDB.UnderlyingExecable = conn db.LoggingDB.db = db expectVersionCheck(db.Dialect, mock, 10, 5) err = db.Upgrade(context.TODO()) require.NoError(t, err) require.NoError(t, mock.ExpectationsWereMet()) expectVersionCheck(db.Dialect, mock, 10, 6) err = db.Upgrade(context.TODO()) require.ErrorIs(t, err, ErrUnsupportedDatabaseVersion) require.NoError(t, mock.ExpectationsWereMet()) expectVersionCheck(db.Dialect, mock, 5, 3) err = db.Upgrade(context.TODO()) require.NoError(t, err) require.NoError(t, mock.ExpectationsWereMet()) } } func TestDatabase_Upgrade(t *testing.T) { t.Run("SQLite", testUpgrade(SQLite)) t.Run("Postgres", testUpgrade(Postgres)) } func TestDatabase_Upgrade_CompatCheck(t *testing.T) { t.Run("SQLite", testCompatCheck(SQLite)) t.Run("Postgres", testCompatCheck(Postgres)) } go-util-0.9.5/dbutil/upgradetable.go000066400000000000000000000223371513243016000173600ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "bytes" "context" "errors" "fmt" "io/fs" "path/filepath" "regexp" "strconv" "strings" ) type TxnMode string const ( TxnModeOn TxnMode = "on" TxnModeOff TxnMode = "off" TxnModeSQLiteForeignKeysOff TxnMode = "sqlite-fkey-off" ) type UpgradeTable []upgrade func (ut *UpgradeTable) extend(toSize int) { if cap(*ut) >= toSize { *ut = (*ut)[:toSize] } else { resized := make([]upgrade, toSize) copy(resized, *ut) *ut = resized } } func (ut *UpgradeTable) Register(from, to, compat int, message string, txn TxnMode, fn upgradeFunc) { if from < 0 { from += to } if from < 0 { panic("invalid from value in UpgradeTable.Register() call") } if compat <= 0 { compat = to } upg := upgrade{message: message, fn: fn, upgradesTo: to, compatVersion: compat, transaction: txn} if len(*ut) == from { *ut = append(*ut, upg) return } else if len(*ut) < from { ut.extend(from + 1) } else if (*ut)[from].fn != nil { panic(fmt.Errorf("tried to override upgrade at %d ('%s') with '%s'", from, (*ut)[from].message, upg.message)) } (*ut)[from] = upg } var upgradeHeaderRegex = regexp.MustCompile(`^-- (?:v(\d+) -> )?v(\d+)(?: \(compatible with v(\d+)\+\))?: (.+)$`) var transactionDisableRegex = regexp.MustCompile(`^-- transaction: ([a-z-]*)`) func parseFileHeader(file []byte) (from, to, compat int, message string, txn TxnMode, lines [][]byte, err error) { lines = bytes.Split(file, []byte("\n")) if len(lines) < 2 { err = errors.New("upgrade file too short") return } var maybeFrom int match := upgradeHeaderRegex.FindSubmatch(lines[0]) lines = lines[1:] if match == nil { err = errors.New("header not found") } else if len(match) != 5 { err = errors.New("unexpected number of items in regex match") } else if maybeFrom, err = strconv.Atoi(string(match[1])); len(match[1]) > 0 && err != nil { err = fmt.Errorf("invalid source version: %w", err) } else if to, err = strconv.Atoi(string(match[2])); err != nil { err = fmt.Errorf("invalid target version: %w", err) } else if compat, err = strconv.Atoi(string(match[3])); len(match[3]) > 0 && err != nil { err = fmt.Errorf("invalid compatible version: %w", err) } else { err = nil if len(match[1]) > 0 { from = maybeFrom } else { from = -1 } message = string(match[4]) txn = "on" match = transactionDisableRegex.FindSubmatch(lines[0]) if match != nil { lines = lines[1:] txn = TxnMode(match[1]) switch txn { case TxnModeOff, TxnModeOn, TxnModeSQLiteForeignKeysOff: // ok default: err = fmt.Errorf("invalid value %q for transaction flag", match[1]) } } } return } var dialectLineFilter = regexp.MustCompile(`^\s*-- only: (postgres|sqlite)(?: for next (\d+) lines| until "(end) only")?(?: \(lines? (commented)\))?`) // Constants used to make parseDialectFilter clearer const ( skipUntilEndTag = -1 skipNothing = 0 skipNextLine = 1 ) func (db *Database) parseDialectFilter(line []byte) (dialect Dialect, lineCount int, uncomment bool, err error) { match := dialectLineFilter.FindSubmatch(line) if match == nil { return } dialect, err = ParseDialect(string(match[1])) if err != nil { return } uncomment = bytes.Equal(match[4], []byte("commented")) if bytes.Equal(match[3], []byte("end")) { lineCount = skipUntilEndTag } else if len(match[2]) == 0 { lineCount = skipNextLine } else { lineCount, err = strconv.Atoi(string(match[2])) if err != nil { err = fmt.Errorf("invalid line count %q: %w", match[2], err) } } return } var endLineFilter = regexp.MustCompile(`^\s*-- end only (postgres|sqlite)$`) func (db *Database) Internals() *publishDatabaseInternals { return (*publishDatabaseInternals)(db) } type publishDatabaseInternals Database func (di *publishDatabaseInternals) FilterSQLUpgrade(lines [][]byte) (string, error) { return (*Database)(di).filterSQLUpgrade(lines) } func (db *Database) filterSQLUpgrade(lines [][]byte) (string, error) { output := make([][]byte, 0, len(lines)) for i := 0; i < len(lines); i++ { dialect, lineCount, uncomment, err := db.parseDialectFilter(lines[i]) if err != nil { return "", err } else if lineCount == skipNothing { output = append(output, lines[i]) } else if lineCount == skipUntilEndTag { startedAt := i startedAtMatch := dialectLineFilter.FindSubmatch(lines[startedAt]) // Skip filter start line i++ for ; i < len(lines); i++ { if match := endLineFilter.FindSubmatch(lines[i]); match != nil { if !bytes.Equal(match[1], startedAtMatch[1]) { return "", fmt.Errorf(`unexpected end tag %q for %q start at line %d`, string(match[0]), string(startedAtMatch[1]), startedAt) } break } if dialect == db.Dialect { if uncomment { if !bytes.HasPrefix(lines[i], []byte("--")) { return "", fmt.Errorf("line %d isn't commented even though the dialect filter claimed it is (-- must be at beginning of line)", i+1) } output = append(output, bytes.TrimPrefix(lines[i], []byte("--"))) } else { output = append(output, lines[i]) } } } if i == len(lines) { return "", fmt.Errorf(`didn't get end tag matching start %q at line %d`, string(startedAtMatch[1]), startedAt) } } else if dialect != db.Dialect { i += lineCount } else { // Skip current line, uncomment the specified number of lines i++ targetI := i + lineCount for ; i < targetI; i++ { if uncomment { output = append(output, bytes.TrimPrefix(lines[i], []byte("--"))) } else { output = append(output, lines[i]) } } // Decrement counter to avoid skipping the next line i-- } } return string(bytes.Join(output, []byte("\n"))), nil } func sqlUpgradeFunc(fileName string, lines [][]byte) upgradeFunc { return func(ctx context.Context, db *Database) error { if dialect, skip, _, err := db.parseDialectFilter(lines[0]); err == nil && skip == skipNextLine && dialect != db.Dialect { return nil } else if upgradeSQL, err := db.filterSQLUpgrade(lines); err != nil { panic(fmt.Errorf("failed to parse upgrade %s: %w", fileName, err)) } else { _, err = db.Exec(ctx, upgradeSQL) return err } } } func splitSQLUpgradeFunc(sqliteData, postgresData string) upgradeFunc { return func(ctx context.Context, db *Database) (err error) { switch db.Dialect { case SQLite: _, err = db.Exec(ctx, sqliteData) case Postgres: _, err = db.Exec(ctx, postgresData) default: err = fmt.Errorf("unknown dialect %s", db.Dialect) } return } } func parseSplitSQLUpgrade(name string, fs fullFS, skipNames map[string]struct{}) (from, to, compat int, message string, txn TxnMode, fn upgradeFunc) { postgresName := fmt.Sprintf("%s.postgres.sql", name) sqliteName := fmt.Sprintf("%s.sqlite.sql", name) skipNames[postgresName] = struct{}{} skipNames[sqliteName] = struct{}{} postgresData, err := fs.ReadFile(postgresName) if err != nil { panic(err) } sqliteData, err := fs.ReadFile(sqliteName) if err != nil { panic(err) } from, to, compat, message, txn, _, err = parseFileHeader(postgresData) if err != nil { panic(fmt.Errorf("failed to parse header in %s: %w", postgresName, err)) } sqliteFrom, sqliteTo, sqliteCompat, sqliteMessage, sqliteTxn, _, err := parseFileHeader(sqliteData) if err != nil { panic(fmt.Errorf("failed to parse header in %s: %w", sqliteName, err)) } if from != sqliteFrom || to != sqliteTo || compat != sqliteCompat { panic(fmt.Errorf("mismatching versions in postgres and sqlite versions of %s: %d/%d -> %d/%d", name, from, sqliteFrom, to, sqliteTo)) } else if message != sqliteMessage { panic(fmt.Errorf("mismatching message in postgres and sqlite versions of %s: %q != %q", name, message, sqliteMessage)) } else if txn != sqliteTxn { panic(fmt.Errorf("mismatching transaction flag in postgres and sqlite versions of %s: %s != %s", name, txn, sqliteTxn)) } fn = splitSQLUpgradeFunc(string(sqliteData), string(postgresData)) return } type fullFS interface { fs.ReadFileFS fs.ReadDirFS } var splitFileNameRegex = regexp.MustCompile(`^(.+)\.(postgres|sqlite)\.sql$`) func (ut *UpgradeTable) RegisterFS(fs fullFS) { ut.RegisterFSPath(fs, ".") } func (ut *UpgradeTable) RegisterFSPath(fs fullFS, dir string) { files, err := fs.ReadDir(dir) if err != nil { panic(err) } skipNames := map[string]struct{}{} for _, file := range files { if file.IsDir() || !strings.HasSuffix(file.Name(), ".sql") { // do nothing } else if _, skip := skipNames[file.Name()]; skip { // also do nothing } else if splitName := splitFileNameRegex.FindStringSubmatch(file.Name()); splitName != nil { from, to, compat, message, txn, fn := parseSplitSQLUpgrade(splitName[1], fs, skipNames) ut.Register(from, to, compat, message, txn, fn) } else if data, err := fs.ReadFile(filepath.Join(dir, file.Name())); err != nil { panic(err) } else if from, to, compat, message, txn, lines, err := parseFileHeader(data); err != nil { panic(fmt.Errorf("failed to parse header in %s: %w", file.Name(), err)) } else { ut.Register(from, to, compat, message, txn, sqlUpgradeFunc(file.Name(), lines)) } } } go-util-0.9.5/dbutil/upgradetable_test.go000066400000000000000000000062441513243016000204160ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package dbutil import ( "bytes" "testing" "github.com/stretchr/testify/assert" ) type dialectFilterTest struct { name string line string dialect Dialect count int uncomment bool } func TestParseDialectFilter(t *testing.T) { db := &Database{Dialect: SQLite} tests := []dialectFilterTest{ {"Own dialect: single line", `-- only: sqlite`, SQLite, 1, false}, {"Own dialect: multiple lines", `-- only: sqlite for next 5 lines`, SQLite, 5, false}, {"Own dialect: fenced", `-- only: sqlite until "end only"`, SQLite, -1, false}, {"Own dialect: single line, commented", `-- only: sqlite (line commented)`, SQLite, 1, true}, {"Own dialect: multiple lines, commented", `-- only: sqlite for next 5 lines (lines commented)`, SQLite, 5, true}, {"Own dialect: fenced, commented", `-- only: sqlite until "end only" (lines commented)`, SQLite, -1, true}, {"Other dialect: single line", `-- only: postgres`, Postgres, 1, false}, {"Other dialect: multiple lines", `-- only: postgres for next 5 lines`, Postgres, 5, false}, {"Other dialect: fenced", `-- only: postgres until "end only"`, Postgres, -1, false}, {"Other dialect: single line, commented", `-- only: postgres (line commented)`, Postgres, 1, true}, {"Other dialect: multiple lines, commented", `-- only: postgres for next 5 lines (lines commented)`, Postgres, 5, true}, {"Other dialect: fenced, commented", `-- only: postgres until "end only" (lines commented)`, Postgres, -1, true}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { dialect, lines, uncomment, err := db.parseDialectFilter([]byte(test.line)) assert.NoError(t, err) assert.Equal(t, test.dialect, dialect) assert.Equal(t, test.count, lines) assert.Equal(t, test.uncomment, uncomment) }) } } type filterOutputTest struct { name string input string outputPostgres string outputSQLite string } func TestFilterSQLUpgrade(t *testing.T) { pg := &Database{Dialect: Postgres} lite := &Database{Dialect: SQLite} tests := []filterOutputTest{ {"Single line, commented", ` -- only: postgres meowgres -- only: sqlite (lines commented) -- meowlite `, ` meowgres `, ` meowlite `, }, {"Fenced, commented", ` -- only: postgres until "end only" meowgres line 2 -- end only postgres shared line -- only: sqlite until "end only" (lines commented) -- meowlite -- line 2.5 -- end only sqlite shared line 2 `, ` meowgres line 2 shared line shared line 2 `, ` shared line meowlite line 2.5 shared line 2 `, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { out, err := pg.filterSQLUpgrade(bytes.Split([]byte(test.input), []byte("\n"))) assert.NoError(t, err) assert.Equal(t, test.outputPostgres, out) out, err = lite.filterSQLUpgrade(bytes.Split([]byte(test.input), []byte("\n"))) assert.NoError(t, err) assert.Equal(t, test.outputSQLite, out) }) } } go-util-0.9.5/emojirunes/000077500000000000000000000000001513243016000152605ustar00rootroot00000000000000go-util-0.9.5/emojirunes/data.go000066400000000000000000000337261513243016000165330ustar00rootroot00000000000000// Code generated by go generate; DO NOT EDIT. package emojirunes var EmojiRunes = []rune{ 0x200d, 0x203c, 0x2049, 0x20e3, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x231a, 0x231b, 0x2328, 0x23cf, 0x23e9, 0x23ea, 0x23eb, 0x23ec, 0x23ed, 0x23ee, 0x23ef, 0x23f0, 0x23f1, 0x23f2, 0x23f3, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x25fd, 0x25fe, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2614, 0x2615, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f, 0x2650, 0x2651, 0x2652, 0x2653, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x267f, 0x2692, 0x2693, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a1, 0x26a7, 0x26aa, 0x26ab, 0x26b0, 0x26b1, 0x26bd, 0x26be, 0x26c4, 0x26c5, 0x26c8, 0x26ce, 0x26cf, 0x26d1, 0x26d3, 0x26d4, 0x26e9, 0x26ea, 0x26f0, 0x26f1, 0x26f2, 0x26f3, 0x26f4, 0x26f5, 0x26f7, 0x26f8, 0x26f9, 0x26fa, 0x26fd, 0x2702, 0x2705, 0x2708, 0x2709, 0x270a, 0x270b, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2728, 0x2733, 0x2734, 0x2744, 0x2747, 0x274c, 0x274e, 0x2753, 0x2754, 0x2755, 0x2757, 0x2763, 0x2764, 0x2795, 0x2796, 0x2797, 0x27a1, 0x27b0, 0x27bf, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x2b1b, 0x2b1c, 0x2b50, 0x2b55, 0x3030, 0x303d, 0x3297, 0x3299, 0xfe0f, 0x1f004, 0x1f0cf, 0x1f170, 0x1f171, 0x1f17e, 0x1f17f, 0x1f18e, 0x1f191, 0x1f192, 0x1f193, 0x1f194, 0x1f195, 0x1f196, 0x1f197, 0x1f198, 0x1f199, 0x1f19a, 0x1f1e6, 0x1f1e7, 0x1f1e8, 0x1f1e9, 0x1f1ea, 0x1f1eb, 0x1f1ec, 0x1f1ed, 0x1f1ee, 0x1f1ef, 0x1f1f0, 0x1f1f1, 0x1f1f2, 0x1f1f3, 0x1f1f4, 0x1f1f5, 0x1f1f6, 0x1f1f7, 0x1f1f8, 0x1f1f9, 0x1f1fa, 0x1f1fb, 0x1f1fc, 0x1f1fd, 0x1f1fe, 0x1f1ff, 0x1f201, 0x1f202, 0x1f21a, 0x1f22f, 0x1f232, 0x1f233, 0x1f234, 0x1f235, 0x1f236, 0x1f237, 0x1f238, 0x1f239, 0x1f23a, 0x1f250, 0x1f251, 0x1f300, 0x1f301, 0x1f302, 0x1f303, 0x1f304, 0x1f305, 0x1f306, 0x1f307, 0x1f308, 0x1f309, 0x1f30a, 0x1f30b, 0x1f30c, 0x1f30d, 0x1f30e, 0x1f30f, 0x1f310, 0x1f311, 0x1f312, 0x1f313, 0x1f314, 0x1f315, 0x1f316, 0x1f317, 0x1f318, 0x1f319, 0x1f31a, 0x1f31b, 0x1f31c, 0x1f31d, 0x1f31e, 0x1f31f, 0x1f320, 0x1f321, 0x1f324, 0x1f325, 0x1f326, 0x1f327, 0x1f328, 0x1f329, 0x1f32a, 0x1f32b, 0x1f32c, 0x1f32d, 0x1f32e, 0x1f32f, 0x1f330, 0x1f331, 0x1f332, 0x1f333, 0x1f334, 0x1f335, 0x1f336, 0x1f337, 0x1f338, 0x1f339, 0x1f33a, 0x1f33b, 0x1f33c, 0x1f33d, 0x1f33e, 0x1f33f, 0x1f340, 0x1f341, 0x1f342, 0x1f343, 0x1f344, 0x1f345, 0x1f346, 0x1f347, 0x1f348, 0x1f349, 0x1f34a, 0x1f34b, 0x1f34c, 0x1f34d, 0x1f34e, 0x1f34f, 0x1f350, 0x1f351, 0x1f352, 0x1f353, 0x1f354, 0x1f355, 0x1f356, 0x1f357, 0x1f358, 0x1f359, 0x1f35a, 0x1f35b, 0x1f35c, 0x1f35d, 0x1f35e, 0x1f35f, 0x1f360, 0x1f361, 0x1f362, 0x1f363, 0x1f364, 0x1f365, 0x1f366, 0x1f367, 0x1f368, 0x1f369, 0x1f36a, 0x1f36b, 0x1f36c, 0x1f36d, 0x1f36e, 0x1f36f, 0x1f370, 0x1f371, 0x1f372, 0x1f373, 0x1f374, 0x1f375, 0x1f376, 0x1f377, 0x1f378, 0x1f379, 0x1f37a, 0x1f37b, 0x1f37c, 0x1f37d, 0x1f37e, 0x1f37f, 0x1f380, 0x1f381, 0x1f382, 0x1f383, 0x1f384, 0x1f385, 0x1f386, 0x1f387, 0x1f388, 0x1f389, 0x1f38a, 0x1f38b, 0x1f38c, 0x1f38d, 0x1f38e, 0x1f38f, 0x1f390, 0x1f391, 0x1f392, 0x1f393, 0x1f396, 0x1f397, 0x1f399, 0x1f39a, 0x1f39b, 0x1f39e, 0x1f39f, 0x1f3a0, 0x1f3a1, 0x1f3a2, 0x1f3a3, 0x1f3a4, 0x1f3a5, 0x1f3a6, 0x1f3a7, 0x1f3a8, 0x1f3a9, 0x1f3aa, 0x1f3ab, 0x1f3ac, 0x1f3ad, 0x1f3ae, 0x1f3af, 0x1f3b0, 0x1f3b1, 0x1f3b2, 0x1f3b3, 0x1f3b4, 0x1f3b5, 0x1f3b6, 0x1f3b7, 0x1f3b8, 0x1f3b9, 0x1f3ba, 0x1f3bb, 0x1f3bc, 0x1f3bd, 0x1f3be, 0x1f3bf, 0x1f3c0, 0x1f3c1, 0x1f3c2, 0x1f3c3, 0x1f3c4, 0x1f3c5, 0x1f3c6, 0x1f3c7, 0x1f3c8, 0x1f3c9, 0x1f3ca, 0x1f3cb, 0x1f3cc, 0x1f3cd, 0x1f3ce, 0x1f3cf, 0x1f3d0, 0x1f3d1, 0x1f3d2, 0x1f3d3, 0x1f3d4, 0x1f3d5, 0x1f3d6, 0x1f3d7, 0x1f3d8, 0x1f3d9, 0x1f3da, 0x1f3db, 0x1f3dc, 0x1f3dd, 0x1f3de, 0x1f3df, 0x1f3e0, 0x1f3e1, 0x1f3e2, 0x1f3e3, 0x1f3e4, 0x1f3e5, 0x1f3e6, 0x1f3e7, 0x1f3e8, 0x1f3e9, 0x1f3ea, 0x1f3eb, 0x1f3ec, 0x1f3ed, 0x1f3ee, 0x1f3ef, 0x1f3f0, 0x1f3f3, 0x1f3f4, 0x1f3f5, 0x1f3f7, 0x1f3f8, 0x1f3f9, 0x1f3fa, 0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff, 0x1f400, 0x1f401, 0x1f402, 0x1f403, 0x1f404, 0x1f405, 0x1f406, 0x1f407, 0x1f408, 0x1f409, 0x1f40a, 0x1f40b, 0x1f40c, 0x1f40d, 0x1f40e, 0x1f40f, 0x1f410, 0x1f411, 0x1f412, 0x1f413, 0x1f414, 0x1f415, 0x1f416, 0x1f417, 0x1f418, 0x1f419, 0x1f41a, 0x1f41b, 0x1f41c, 0x1f41d, 0x1f41e, 0x1f41f, 0x1f420, 0x1f421, 0x1f422, 0x1f423, 0x1f424, 0x1f425, 0x1f426, 0x1f427, 0x1f428, 0x1f429, 0x1f42a, 0x1f42b, 0x1f42c, 0x1f42d, 0x1f42e, 0x1f42f, 0x1f430, 0x1f431, 0x1f432, 0x1f433, 0x1f434, 0x1f435, 0x1f436, 0x1f437, 0x1f438, 0x1f439, 0x1f43a, 0x1f43b, 0x1f43c, 0x1f43d, 0x1f43e, 0x1f43f, 0x1f440, 0x1f441, 0x1f442, 0x1f443, 0x1f444, 0x1f445, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44a, 0x1f44b, 0x1f44c, 0x1f44d, 0x1f44e, 0x1f44f, 0x1f450, 0x1f451, 0x1f452, 0x1f453, 0x1f454, 0x1f455, 0x1f456, 0x1f457, 0x1f458, 0x1f459, 0x1f45a, 0x1f45b, 0x1f45c, 0x1f45d, 0x1f45e, 0x1f45f, 0x1f460, 0x1f461, 0x1f462, 0x1f463, 0x1f464, 0x1f465, 0x1f466, 0x1f467, 0x1f468, 0x1f469, 0x1f46a, 0x1f46b, 0x1f46c, 0x1f46d, 0x1f46e, 0x1f46f, 0x1f470, 0x1f471, 0x1f472, 0x1f473, 0x1f474, 0x1f475, 0x1f476, 0x1f477, 0x1f478, 0x1f479, 0x1f47a, 0x1f47b, 0x1f47c, 0x1f47d, 0x1f47e, 0x1f47f, 0x1f480, 0x1f481, 0x1f482, 0x1f483, 0x1f484, 0x1f485, 0x1f486, 0x1f487, 0x1f488, 0x1f489, 0x1f48a, 0x1f48b, 0x1f48c, 0x1f48d, 0x1f48e, 0x1f48f, 0x1f490, 0x1f491, 0x1f492, 0x1f493, 0x1f494, 0x1f495, 0x1f496, 0x1f497, 0x1f498, 0x1f499, 0x1f49a, 0x1f49b, 0x1f49c, 0x1f49d, 0x1f49e, 0x1f49f, 0x1f4a0, 0x1f4a1, 0x1f4a2, 0x1f4a3, 0x1f4a4, 0x1f4a5, 0x1f4a6, 0x1f4a7, 0x1f4a8, 0x1f4a9, 0x1f4aa, 0x1f4ab, 0x1f4ac, 0x1f4ad, 0x1f4ae, 0x1f4af, 0x1f4b0, 0x1f4b1, 0x1f4b2, 0x1f4b3, 0x1f4b4, 0x1f4b5, 0x1f4b6, 0x1f4b7, 0x1f4b8, 0x1f4b9, 0x1f4ba, 0x1f4bb, 0x1f4bc, 0x1f4bd, 0x1f4be, 0x1f4bf, 0x1f4c0, 0x1f4c1, 0x1f4c2, 0x1f4c3, 0x1f4c4, 0x1f4c5, 0x1f4c6, 0x1f4c7, 0x1f4c8, 0x1f4c9, 0x1f4ca, 0x1f4cb, 0x1f4cc, 0x1f4cd, 0x1f4ce, 0x1f4cf, 0x1f4d0, 0x1f4d1, 0x1f4d2, 0x1f4d3, 0x1f4d4, 0x1f4d5, 0x1f4d6, 0x1f4d7, 0x1f4d8, 0x1f4d9, 0x1f4da, 0x1f4db, 0x1f4dc, 0x1f4dd, 0x1f4de, 0x1f4df, 0x1f4e0, 0x1f4e1, 0x1f4e2, 0x1f4e3, 0x1f4e4, 0x1f4e5, 0x1f4e6, 0x1f4e7, 0x1f4e8, 0x1f4e9, 0x1f4ea, 0x1f4eb, 0x1f4ec, 0x1f4ed, 0x1f4ee, 0x1f4ef, 0x1f4f0, 0x1f4f1, 0x1f4f2, 0x1f4f3, 0x1f4f4, 0x1f4f5, 0x1f4f6, 0x1f4f7, 0x1f4f8, 0x1f4f9, 0x1f4fa, 0x1f4fb, 0x1f4fc, 0x1f4fd, 0x1f4ff, 0x1f500, 0x1f501, 0x1f502, 0x1f503, 0x1f504, 0x1f505, 0x1f506, 0x1f507, 0x1f508, 0x1f509, 0x1f50a, 0x1f50b, 0x1f50c, 0x1f50d, 0x1f50e, 0x1f50f, 0x1f510, 0x1f511, 0x1f512, 0x1f513, 0x1f514, 0x1f515, 0x1f516, 0x1f517, 0x1f518, 0x1f519, 0x1f51a, 0x1f51b, 0x1f51c, 0x1f51d, 0x1f51e, 0x1f51f, 0x1f520, 0x1f521, 0x1f522, 0x1f523, 0x1f524, 0x1f525, 0x1f526, 0x1f527, 0x1f528, 0x1f529, 0x1f52a, 0x1f52b, 0x1f52c, 0x1f52d, 0x1f52e, 0x1f52f, 0x1f530, 0x1f531, 0x1f532, 0x1f533, 0x1f534, 0x1f535, 0x1f536, 0x1f537, 0x1f538, 0x1f539, 0x1f53a, 0x1f53b, 0x1f53c, 0x1f53d, 0x1f549, 0x1f54a, 0x1f54b, 0x1f54c, 0x1f54d, 0x1f54e, 0x1f550, 0x1f551, 0x1f552, 0x1f553, 0x1f554, 0x1f555, 0x1f556, 0x1f557, 0x1f558, 0x1f559, 0x1f55a, 0x1f55b, 0x1f55c, 0x1f55d, 0x1f55e, 0x1f55f, 0x1f560, 0x1f561, 0x1f562, 0x1f563, 0x1f564, 0x1f565, 0x1f566, 0x1f567, 0x1f56f, 0x1f570, 0x1f573, 0x1f574, 0x1f575, 0x1f576, 0x1f577, 0x1f578, 0x1f579, 0x1f57a, 0x1f587, 0x1f58a, 0x1f58b, 0x1f58c, 0x1f58d, 0x1f590, 0x1f595, 0x1f596, 0x1f5a4, 0x1f5a5, 0x1f5a8, 0x1f5b1, 0x1f5b2, 0x1f5bc, 0x1f5c2, 0x1f5c3, 0x1f5c4, 0x1f5d1, 0x1f5d2, 0x1f5d3, 0x1f5dc, 0x1f5dd, 0x1f5de, 0x1f5e1, 0x1f5e3, 0x1f5e8, 0x1f5ef, 0x1f5f3, 0x1f5fa, 0x1f5fb, 0x1f5fc, 0x1f5fd, 0x1f5fe, 0x1f5ff, 0x1f600, 0x1f601, 0x1f602, 0x1f603, 0x1f604, 0x1f605, 0x1f606, 0x1f607, 0x1f608, 0x1f609, 0x1f60a, 0x1f60b, 0x1f60c, 0x1f60d, 0x1f60e, 0x1f60f, 0x1f610, 0x1f611, 0x1f612, 0x1f613, 0x1f614, 0x1f615, 0x1f616, 0x1f617, 0x1f618, 0x1f619, 0x1f61a, 0x1f61b, 0x1f61c, 0x1f61d, 0x1f61e, 0x1f61f, 0x1f620, 0x1f621, 0x1f622, 0x1f623, 0x1f624, 0x1f625, 0x1f626, 0x1f627, 0x1f628, 0x1f629, 0x1f62a, 0x1f62b, 0x1f62c, 0x1f62d, 0x1f62e, 0x1f62f, 0x1f630, 0x1f631, 0x1f632, 0x1f633, 0x1f634, 0x1f635, 0x1f636, 0x1f637, 0x1f638, 0x1f639, 0x1f63a, 0x1f63b, 0x1f63c, 0x1f63d, 0x1f63e, 0x1f63f, 0x1f640, 0x1f641, 0x1f642, 0x1f643, 0x1f644, 0x1f645, 0x1f646, 0x1f647, 0x1f648, 0x1f649, 0x1f64a, 0x1f64b, 0x1f64c, 0x1f64d, 0x1f64e, 0x1f64f, 0x1f680, 0x1f681, 0x1f682, 0x1f683, 0x1f684, 0x1f685, 0x1f686, 0x1f687, 0x1f688, 0x1f689, 0x1f68a, 0x1f68b, 0x1f68c, 0x1f68d, 0x1f68e, 0x1f68f, 0x1f690, 0x1f691, 0x1f692, 0x1f693, 0x1f694, 0x1f695, 0x1f696, 0x1f697, 0x1f698, 0x1f699, 0x1f69a, 0x1f69b, 0x1f69c, 0x1f69d, 0x1f69e, 0x1f69f, 0x1f6a0, 0x1f6a1, 0x1f6a2, 0x1f6a3, 0x1f6a4, 0x1f6a5, 0x1f6a6, 0x1f6a7, 0x1f6a8, 0x1f6a9, 0x1f6aa, 0x1f6ab, 0x1f6ac, 0x1f6ad, 0x1f6ae, 0x1f6af, 0x1f6b0, 0x1f6b1, 0x1f6b2, 0x1f6b3, 0x1f6b4, 0x1f6b5, 0x1f6b6, 0x1f6b7, 0x1f6b8, 0x1f6b9, 0x1f6ba, 0x1f6bb, 0x1f6bc, 0x1f6bd, 0x1f6be, 0x1f6bf, 0x1f6c0, 0x1f6c1, 0x1f6c2, 0x1f6c3, 0x1f6c4, 0x1f6c5, 0x1f6cb, 0x1f6cc, 0x1f6cd, 0x1f6ce, 0x1f6cf, 0x1f6d0, 0x1f6d1, 0x1f6d2, 0x1f6d5, 0x1f6d6, 0x1f6d7, 0x1f6d8, 0x1f6dc, 0x1f6dd, 0x1f6de, 0x1f6df, 0x1f6e0, 0x1f6e1, 0x1f6e2, 0x1f6e3, 0x1f6e4, 0x1f6e5, 0x1f6e9, 0x1f6eb, 0x1f6ec, 0x1f6f0, 0x1f6f3, 0x1f6f4, 0x1f6f5, 0x1f6f6, 0x1f6f7, 0x1f6f8, 0x1f6f9, 0x1f6fa, 0x1f6fb, 0x1f6fc, 0x1f7e0, 0x1f7e1, 0x1f7e2, 0x1f7e3, 0x1f7e4, 0x1f7e5, 0x1f7e6, 0x1f7e7, 0x1f7e8, 0x1f7e9, 0x1f7ea, 0x1f7eb, 0x1f7f0, 0x1f90c, 0x1f90d, 0x1f90e, 0x1f90f, 0x1f910, 0x1f911, 0x1f912, 0x1f913, 0x1f914, 0x1f915, 0x1f916, 0x1f917, 0x1f918, 0x1f919, 0x1f91a, 0x1f91b, 0x1f91c, 0x1f91d, 0x1f91e, 0x1f91f, 0x1f920, 0x1f921, 0x1f922, 0x1f923, 0x1f924, 0x1f925, 0x1f926, 0x1f927, 0x1f928, 0x1f929, 0x1f92a, 0x1f92b, 0x1f92c, 0x1f92d, 0x1f92e, 0x1f92f, 0x1f930, 0x1f931, 0x1f932, 0x1f933, 0x1f934, 0x1f935, 0x1f936, 0x1f937, 0x1f938, 0x1f939, 0x1f93a, 0x1f93c, 0x1f93d, 0x1f93e, 0x1f93f, 0x1f940, 0x1f941, 0x1f942, 0x1f943, 0x1f944, 0x1f945, 0x1f947, 0x1f948, 0x1f949, 0x1f94a, 0x1f94b, 0x1f94c, 0x1f94d, 0x1f94e, 0x1f94f, 0x1f950, 0x1f951, 0x1f952, 0x1f953, 0x1f954, 0x1f955, 0x1f956, 0x1f957, 0x1f958, 0x1f959, 0x1f95a, 0x1f95b, 0x1f95c, 0x1f95d, 0x1f95e, 0x1f95f, 0x1f960, 0x1f961, 0x1f962, 0x1f963, 0x1f964, 0x1f965, 0x1f966, 0x1f967, 0x1f968, 0x1f969, 0x1f96a, 0x1f96b, 0x1f96c, 0x1f96d, 0x1f96e, 0x1f96f, 0x1f970, 0x1f971, 0x1f972, 0x1f973, 0x1f974, 0x1f975, 0x1f976, 0x1f977, 0x1f978, 0x1f979, 0x1f97a, 0x1f97b, 0x1f97c, 0x1f97d, 0x1f97e, 0x1f97f, 0x1f980, 0x1f981, 0x1f982, 0x1f983, 0x1f984, 0x1f985, 0x1f986, 0x1f987, 0x1f988, 0x1f989, 0x1f98a, 0x1f98b, 0x1f98c, 0x1f98d, 0x1f98e, 0x1f98f, 0x1f990, 0x1f991, 0x1f992, 0x1f993, 0x1f994, 0x1f995, 0x1f996, 0x1f997, 0x1f998, 0x1f999, 0x1f99a, 0x1f99b, 0x1f99c, 0x1f99d, 0x1f99e, 0x1f99f, 0x1f9a0, 0x1f9a1, 0x1f9a2, 0x1f9a3, 0x1f9a4, 0x1f9a5, 0x1f9a6, 0x1f9a7, 0x1f9a8, 0x1f9a9, 0x1f9aa, 0x1f9ab, 0x1f9ac, 0x1f9ad, 0x1f9ae, 0x1f9af, 0x1f9b0, 0x1f9b1, 0x1f9b2, 0x1f9b3, 0x1f9b4, 0x1f9b5, 0x1f9b6, 0x1f9b7, 0x1f9b8, 0x1f9b9, 0x1f9ba, 0x1f9bb, 0x1f9bc, 0x1f9bd, 0x1f9be, 0x1f9bf, 0x1f9c0, 0x1f9c1, 0x1f9c2, 0x1f9c3, 0x1f9c4, 0x1f9c5, 0x1f9c6, 0x1f9c7, 0x1f9c8, 0x1f9c9, 0x1f9ca, 0x1f9cb, 0x1f9cc, 0x1f9cd, 0x1f9ce, 0x1f9cf, 0x1f9d0, 0x1f9d1, 0x1f9d2, 0x1f9d3, 0x1f9d4, 0x1f9d5, 0x1f9d6, 0x1f9d7, 0x1f9d8, 0x1f9d9, 0x1f9da, 0x1f9db, 0x1f9dc, 0x1f9dd, 0x1f9de, 0x1f9df, 0x1f9e0, 0x1f9e1, 0x1f9e2, 0x1f9e3, 0x1f9e4, 0x1f9e5, 0x1f9e6, 0x1f9e7, 0x1f9e8, 0x1f9e9, 0x1f9ea, 0x1f9eb, 0x1f9ec, 0x1f9ed, 0x1f9ee, 0x1f9ef, 0x1f9f0, 0x1f9f1, 0x1f9f2, 0x1f9f3, 0x1f9f4, 0x1f9f5, 0x1f9f6, 0x1f9f7, 0x1f9f8, 0x1f9f9, 0x1f9fa, 0x1f9fb, 0x1f9fc, 0x1f9fd, 0x1f9fe, 0x1f9ff, 0x1fa70, 0x1fa71, 0x1fa72, 0x1fa73, 0x1fa74, 0x1fa75, 0x1fa76, 0x1fa77, 0x1fa78, 0x1fa79, 0x1fa7a, 0x1fa7b, 0x1fa7c, 0x1fa80, 0x1fa81, 0x1fa82, 0x1fa83, 0x1fa84, 0x1fa85, 0x1fa86, 0x1fa87, 0x1fa88, 0x1fa89, 0x1fa8a, 0x1fa8e, 0x1fa8f, 0x1fa90, 0x1fa91, 0x1fa92, 0x1fa93, 0x1fa94, 0x1fa95, 0x1fa96, 0x1fa97, 0x1fa98, 0x1fa99, 0x1fa9a, 0x1fa9b, 0x1fa9c, 0x1fa9d, 0x1fa9e, 0x1fa9f, 0x1faa0, 0x1faa1, 0x1faa2, 0x1faa3, 0x1faa4, 0x1faa5, 0x1faa6, 0x1faa7, 0x1faa8, 0x1faa9, 0x1faaa, 0x1faab, 0x1faac, 0x1faad, 0x1faae, 0x1faaf, 0x1fab0, 0x1fab1, 0x1fab2, 0x1fab3, 0x1fab4, 0x1fab5, 0x1fab6, 0x1fab7, 0x1fab8, 0x1fab9, 0x1faba, 0x1fabb, 0x1fabc, 0x1fabd, 0x1fabe, 0x1fabf, 0x1fac0, 0x1fac1, 0x1fac2, 0x1fac3, 0x1fac4, 0x1fac5, 0x1fac6, 0x1fac8, 0x1facd, 0x1face, 0x1facf, 0x1fad0, 0x1fad1, 0x1fad2, 0x1fad3, 0x1fad4, 0x1fad5, 0x1fad6, 0x1fad7, 0x1fad8, 0x1fad9, 0x1fada, 0x1fadb, 0x1fadc, 0x1fadf, 0x1fae0, 0x1fae1, 0x1fae2, 0x1fae3, 0x1fae4, 0x1fae5, 0x1fae6, 0x1fae7, 0x1fae8, 0x1fae9, 0x1faea, 0x1faef, 0x1faf0, 0x1faf1, 0x1faf2, 0x1faf3, 0x1faf4, 0x1faf5, 0x1faf6, 0x1faf7, 0x1faf8, 0xe0062, 0xe0063, 0xe0065, 0xe0067, 0xe006c, 0xe006e, 0xe0073, 0xe0074, 0xe0077, 0xe007f, } go-util-0.9.5/emojirunes/emoji.go000066400000000000000000000020421513243016000167100ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package emojirunes import ( "slices" ) //go:generate go run generate.go // Is reports whether r is an emoji rune, a zero-width joiner (\u200D), or variation selector 16 (\uFE0F). func Is(r rune) bool { _, found := slices.BinarySearch(EmojiRunes, r) return found } func isNumberEmoji(r rune, s string, i int) bool { return ((r >= '0' && r <= '9') || r == '#' || r == '*') && len(s) > i+3 && s[i+1] == 0xef && s[i+2] == 0xb8 && s[i+3] == 0x8f } // IsOnlyEmojis reports whether s is a string containing only emoji runes as defined by [Is]. // Additionally, it accepts numbers (0-9) followed by variation selector 16 (\uFE0F) as emojis. func IsOnlyEmojis(s string) bool { if len(s) == 0 { return false } for i, r := range s { if !Is(r) && !isNumberEmoji(r, s, i) { return false } } return true } go-util-0.9.5/emojirunes/emoji_test.go000066400000000000000000000007061513243016000177540ustar00rootroot00000000000000package emojirunes_test import ( "testing" "github.com/stretchr/testify/assert" "go.mau.fi/util/emojirunes" ) func TestIsOnlyEmojis(t *testing.T) { assert.True(t, emojirunes.IsOnlyEmojis("๐Ÿค”")) assert.True(t, emojirunes.IsOnlyEmojis("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ")) } func TestIsOnlyEmojis_Keycaps(t *testing.T) { assert.True(t, emojirunes.IsOnlyEmojis("#๏ธโƒฃ*๏ธโƒฃ1๏ธโƒฃ2๏ธโƒฃ3๏ธโƒฃ4๏ธโƒฃ5๏ธโƒฃ6๏ธโƒฃ7๏ธโƒฃ8๏ธโƒฃ9๏ธโƒฃ0๏ธโƒฃ")) } go-util-0.9.5/emojirunes/generate.go000066400000000000000000000025611513243016000174050ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build ignore package main import ( "fmt" "os" "strconv" "strings" "golang.org/x/exp/slices" "go.mau.fi/util/exerrors" "go.mau.fi/util/unicodeurls" ) func main() { allEmojiRelatedCodepoints := make(map[string]struct{}, 10000) unicodeurls.ReadDataFile(unicodeurls.EmojiTest, func(line string) { parts := strings.Split(line, "; ") if len(parts) < 2 { return } for _, codepoint := range strings.Split(strings.TrimSpace(parts[0]), " ") { if !strings.HasPrefix(codepoint, "00") { allEmojiRelatedCodepoints[codepoint] = struct{}{} } } }) emojiRunes := make([]rune, 0, len(allEmojiRelatedCodepoints)) for runeHex := range allEmojiRelatedCodepoints { emojiRunes = append(emojiRunes, rune(exerrors.Must(strconv.ParseInt(runeHex, 16, 32)))) } slices.Sort(emojiRunes) file := exerrors.Must(os.OpenFile("data.go", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)) exerrors.Must(file.WriteString(`// Code generated by go generate; DO NOT EDIT. package emojirunes var EmojiRunes = []rune{ `)) for _, r := range emojiRunes { exerrors.Must(fmt.Fprintf(file, "\t0x%x,\n", r)) } exerrors.Must(file.WriteString("}\n")) } go-util-0.9.5/exbytes/000077500000000000000000000000001513243016000145635ustar00rootroot00000000000000go-util-0.9.5/exbytes/string.go000066400000000000000000000011461513243016000164220ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exbytes import ( "unsafe" ) // UnsafeString returns a string that points to the same memory as the input byte slice. // // The input byte slice must not be modified after this function is called. // // See [go.mau.fi/util/exstrings.UnsafeBytes] for the reverse operation. func UnsafeString(b []byte) string { return unsafe.String(unsafe.SliceData(b), len(b)) } go-util-0.9.5/exbytes/writer.go000066400000000000000000000032611513243016000164300ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exbytes import ( "errors" "fmt" "io" ) // Writer is a simple byte writer that does not allow extending the buffer. // // Writes always go after the current len() and will fail if the slice capacity is too low. // // The correct way to use this is to create a slice with sufficient capacity and zero length: // // x := make([]byte, 0, 11) // w := (*Writer)(&x) // w.Write([]byte("hello")) // w.WriteByte(' ') // w.WriteString("world") // fmt.Println(string(x)) // "hello world" type Writer []byte var ErrWriterBufferFull = errors.New("exbytes.Writer: buffer full") var ( _ io.Writer = (*Writer)(nil) _ io.StringWriter = (*Writer)(nil) _ io.ByteWriter = (*Writer)(nil) ) func (w *Writer) extendLen(n int) (int, error) { ptr := len(*w) if ptr+n > cap(*w) { return 0, fmt.Errorf("%w (%d + %d > %d)", ErrWriterBufferFull, ptr, n, cap(*w)) } *w = (*w)[:ptr+n] return ptr, nil } func (w *Writer) Write(b []byte) (n int, err error) { ptr, err := w.extendLen(len(b)) if err != nil { return 0, err } copy((*w)[ptr:], b) return len(b), nil } func (w *Writer) WriteString(s string) (n int, err error) { ptr, err := w.extendLen(len(s)) if err != nil { return 0, err } copy((*w)[ptr:], s) return len(s), nil } func (w *Writer) WriteByte(r byte) error { ptr, err := w.extendLen(1) if err != nil { return err } (*w)[ptr] = r return nil } func (w *Writer) String() string { if w == nil { return "" } return string(*w) } go-util-0.9.5/exbytes/writer_test.go000066400000000000000000000015161513243016000174700ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exbytes_test import ( "fmt" "testing" "github.com/stretchr/testify/assert" "go.mau.fi/util/exbytes" "go.mau.fi/util/exerrors" ) func ExampleWriter() { x := make([]byte, 0, 11) w := (*exbytes.Writer)(&x) w.Write([]byte("hello")) w.WriteByte(' ') w.WriteString("world") fmt.Println(string(x)) // Output: hello world } func TestWriter_HelloWorld(t *testing.T) { x := make([]byte, 0, 11) w := (*exbytes.Writer)(&x) exerrors.Must(w.Write([]byte("hello"))) exerrors.PanicIfNotNil(w.WriteByte(' ')) exerrors.Must(w.WriteString("world")) assert.Equal(t, "hello world", string(x)) } go-util-0.9.5/exerrors/000077500000000000000000000000001513243016000147515ustar00rootroot00000000000000go-util-0.9.5/exerrors/dualerror.go000066400000000000000000000012431513243016000172770ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exerrors import ( "errors" "fmt" ) type DualError struct { High error Low error } func NewDualError(high, low error) DualError { return DualError{high, low} } func (err DualError) Is(other error) bool { return errors.Is(other, err.High) || errors.Is(other, err.Low) } func (err DualError) Unwrap() error { return err.Low } func (err DualError) Error() string { return fmt.Sprintf("%v: %v", err.High, err.Low) } go-util-0.9.5/exerrors/must.go000066400000000000000000000007701513243016000162740ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exerrors func Must[T any](val T, err error) T { PanicIfNotNil(err) return val } func Must2[T any, T2 any](val T, val2 T2, err error) (T, T2) { PanicIfNotNil(err) return val, val2 } func PanicIfNotNil(err error) { if err != nil { panic(err) } } go-util-0.9.5/exfmt/000077500000000000000000000000001513243016000142235ustar00rootroot00000000000000go-util-0.9.5/exfmt/duration.go000066400000000000000000000042461513243016000164050ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exfmt import ( "errors" "fmt" "strings" "time" ) var Day = 24 * time.Hour var Week = 7 * Day type Pluralizer func(int) string func Pluralizable(unit string) Pluralizer { return func(value int) string { if value == 1 { return "1 " + unit } return fmt.Sprintf("%d %ss", value, unit) } } func NonPluralizable(unit string) Pluralizer { return func(value int) string { return fmt.Sprintf("%d %s", value, unit) } } func Duration(d time.Duration) string { return DurationCustom(d, nil, Week, Day, time.Hour, time.Minute, time.Second) } func appendDurationPart(time, unit time.Duration, name Pluralizer, parts *[]string) (remainder time.Duration) { if time < unit { return time } value := int(time / unit) remainder = time % unit *parts = append(*parts, name(value)) return } var DefaultDurationUnitNames = map[time.Duration]Pluralizer{ Week: Pluralizable("week"), Day: Pluralizable("day"), time.Hour: Pluralizable("hour"), time.Minute: Pluralizable("minute"), time.Second: Pluralizable("second"), time.Millisecond: NonPluralizable("ms"), time.Microsecond: NonPluralizable("ยตs"), time.Nanosecond: NonPluralizable("ns"), } func DurationCustom(d time.Duration, names map[time.Duration]Pluralizer, units ...time.Duration) string { if d < 0 { panic(errors.New("exfmt.Duration: negative duration")) } else if len(units) == 0 { panic(errors.New("exfmt.Duration: no units provided")) } else if d < units[len(units)-1] { return "now" } if names == nil { names = DefaultDurationUnitNames } parts := make([]string, 0, 2) for _, unit := range units { name, ok := names[unit] if !ok { panic(fmt.Errorf("exfmt.Duration: no name for unit %q", unit)) } d = appendDurationPart(d, unit, name, &parts) } if len(parts) > 2 { parts[0] = strings.Join(parts[:len(parts)-1], ", ") parts[1] = parts[len(parts)-1] parts = parts[:2] } return strings.Join(parts, " and ") } go-util-0.9.5/exfmt/duration_test.go000066400000000000000000000045041513243016000174410ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exfmt_test import ( "testing" "time" "github.com/stretchr/testify/assert" "go.mau.fi/util/exfmt" ) func TestFormatDuration(t *testing.T) { tests := []struct { input time.Duration expected string }{ {0, "now"}, {500 * time.Millisecond, "now"}, {999*time.Millisecond + 999*time.Microsecond + 999*time.Nanosecond, "now"}, {999*time.Millisecond + 999*time.Microsecond + 999*time.Nanosecond + 1, "1 second"}, {time.Second, "1 second"}, {2 * time.Second, "2 seconds"}, {59 * time.Second, "59 seconds"}, {time.Minute, "1 minute"}, {2 * time.Minute, "2 minutes"}, {time.Hour, "1 hour"}, {2 * time.Hour, "2 hours"}, {exfmt.Day, "1 day"}, {2 * exfmt.Day, "2 days"}, {exfmt.Week, "1 week"}, {2 * exfmt.Week, "2 weeks"}, {8 * exfmt.Day, "1 week and 1 day"}, {16 * exfmt.Day, "2 weeks and 2 days"}, {time.Minute + time.Second, "1 minute and 1 second"}, {2*time.Minute + 2*time.Second, "2 minutes and 2 seconds"}, {time.Hour + time.Second, "1 hour and 1 second"}, {2*time.Hour + 2*time.Second, "2 hours and 2 seconds"}, {2*time.Hour + time.Minute, "2 hours and 1 minute"}, {time.Hour + time.Minute + time.Second, "1 hour, 1 minute and 1 second"}, {2*time.Hour + 2*time.Minute + 2*time.Second, "2 hours, 2 minutes and 2 seconds"}, {987654 * time.Second, "1 week, 4 days, 10 hours, 20 minutes and 54 seconds"}, {694861 * time.Second, "1 week, 1 day, 1 hour, 1 minute and 1 second"}, {1234 * time.Second, "20 minutes and 34 seconds"}, } for _, test := range tests { assert.Equal(t, test.expected, exfmt.Duration(test.input)) } } func TestFormatDuration_PanicNegative(t *testing.T) { assert.Panics(t, func() { exfmt.Duration(-1) }) assert.Panics(t, func() { exfmt.Duration(-time.Second) }) assert.Panics(t, func() { exfmt.Duration(-exfmt.Week) }) } func TestFormatDuration_Custom(t *testing.T) { assert.Equal(t, "90 days", exfmt.DurationCustom(90*exfmt.Day, nil, exfmt.Day)) assert.Equal(t, "2160 hours", exfmt.DurationCustom(90*exfmt.Day, nil, time.Hour)) assert.Equal(t, "2160 hours", exfmt.DurationCustom(90*exfmt.Day+59*time.Minute, nil, time.Hour)) } go-util-0.9.5/exgjson/000077500000000000000000000000001513243016000145555ustar00rootroot00000000000000go-util-0.9.5/exgjson/gjson.go000066400000000000000000000014001513243016000162170ustar00rootroot00000000000000// Copyright (c) 2022 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exgjson import ( "strings" ) // Escaper escapes a string for use in a GJSON path. var Escaper = strings.NewReplacer( `\`, `\\`, ".", `\.`, "|", `\|`, "#", `\#`, "@", `\@`, "*", `\*`, "?", `\?`) // Path returns a GJSON path pointing at a nested object, with each provided string being a key. func Path(path ...string) string { var result strings.Builder for i, part := range path { _, _ = Escaper.WriteString(&result, part) if i < len(path)-1 { result.WriteRune('.') } } return result.String() } go-util-0.9.5/exhttp/000077500000000000000000000000001513243016000144145ustar00rootroot00000000000000go-util-0.9.5/exhttp/config.go000066400000000000000000000105621513243016000162140ustar00rootroot00000000000000package exhttp import ( "context" "crypto/tls" "fmt" "net" "net/http" "net/url" "time" "golang.org/x/net/proxy" ) type DialerFunc func(ctx context.Context, network, addr string) (net.Conn, error) func (df DialerFunc) Dial(network, addr string) (net.Conn, error) { return df(context.Background(), network, addr) } func (df DialerFunc) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { return df(ctx, network, addr) } type ClientSettings struct { Dial DialerFunc innerDialer DialerFunc HTTPProxy func(*http.Request) (*url.URL, error) ProxyAddress string GlobalTimeout time.Duration TLSHandshakeTimeout time.Duration ResponseHeaderTimeout time.Duration IdleConnTimeout time.Duration TLSConfig *tls.Config InsecureTLS bool DisableHTTP2 bool TransportOverride func(ClientSettings) http.RoundTripper } var SensibleClientSettings = ClientSettings{ GlobalTimeout: 5 * time.Minute, TLSHandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 10 * time.Second, IdleConnTimeout: 90 * time.Second, }.WithDialTimeout(10 * time.Second) // WithDial sets a custom dialer function for the HTTP client settings. func (cs ClientSettings) WithDial(dial DialerFunc) ClientSettings { cs.Dial = dial cs.innerDialer = dial cs, _ = cs.WithProxy(cs.ProxyAddress) return cs } // WithDialTimeout sets a TCP dial timeout for the HTTP client. Resets any custom dialer. func (cs ClientSettings) WithDialTimeout(timeout time.Duration) ClientSettings { return cs.WithDial((&net.Dialer{Timeout: timeout}).DialContext) } func (cs ClientSettings) WithTLSHandshakeTimeout(timeout time.Duration) ClientSettings { cs.TLSHandshakeTimeout = timeout return cs } func (cs ClientSettings) WithResponseHeaderTimeout(timeout time.Duration) ClientSettings { cs.ResponseHeaderTimeout = timeout return cs } func (cs ClientSettings) WithIdleConnTimeout(timeout time.Duration) ClientSettings { cs.IdleConnTimeout = timeout return cs } func (cs ClientSettings) WithGlobalTimeout(timeout time.Duration) ClientSettings { cs.GlobalTimeout = timeout return cs } func (cs ClientSettings) WithoutHTTP2() ClientSettings { cs.DisableHTTP2 = true return cs } func (cs ClientSettings) WithTLSConfig(tlsConfig *tls.Config) ClientSettings { cs.TLSConfig = tlsConfig return cs } // WithProxy sets a proxy for the HTTP client. This will preserve any custom dialer set previously. func (cs ClientSettings) WithProxy(addr string) (ClientSettings, error) { if addr == "" { cs.ProxyAddress = addr cs.HTTPProxy = nil cs.Dial = cs.innerDialer return cs, nil } parsedAddr, err := url.Parse(addr) if err != nil { return cs, err } switch parsedAddr.Scheme { case "http", "https": cs.HTTPProxy = http.ProxyURL(parsedAddr) cs.Dial = cs.innerDialer cs.ProxyAddress = addr case "socks5", "socks5h": socksProxy, err := proxy.FromURL(parsedAddr, cs.innerDialer) if err != nil { return cs, err } ctxDialer := socksProxy.(proxy.ContextDialer) cs.Dial = ctxDialer.DialContext cs.HTTPProxy = nil cs.ProxyAddress = addr default: return cs, fmt.Errorf("unsupported proxy scheme %q", parsedAddr.Scheme) } return cs, nil } // Configure configures the given HTTP transport with the settings in this struct. // Note that the GlobalTimeout field is not applied on the transport level and needs to be set in the http.Client. func (cs ClientSettings) Configure(transport *http.Transport) *http.Transport { if cs.Dial != nil { transport.DialContext = cs.Dial } if cs.HTTPProxy != nil { transport.Proxy = cs.HTTPProxy } if cs.TLSHandshakeTimeout != 0 { transport.TLSHandshakeTimeout = cs.TLSHandshakeTimeout } if cs.ResponseHeaderTimeout != 0 { transport.ResponseHeaderTimeout = cs.ResponseHeaderTimeout } if cs.IdleConnTimeout != 0 { transport.IdleConnTimeout = cs.IdleConnTimeout } if !cs.DisableHTTP2 { transport.ForceAttemptHTTP2 = true } if cs.TLSConfig != nil { transport.TLSClientConfig = cs.TLSConfig } if cs.InsecureTLS { if transport.TLSClientConfig == nil { transport.TLSClientConfig = &tls.Config{} } transport.TLSClientConfig.InsecureSkipVerify = true } return transport } func (cs ClientSettings) Compile() (c *http.Client) { c = &http.Client{ Timeout: cs.GlobalTimeout, } if cs.TransportOverride != nil { c.Transport = cs.TransportOverride(cs) } else { c.Transport = cs.Configure(&http.Transport{}) } return } go-util-0.9.5/exhttp/cors.go000066400000000000000000000024361513243016000157160ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exhttp import "net/http" func AddCORSHeaders(w http.ResponseWriter) { // Recommended CORS headers can be found in https://spec.matrix.org/v1.3/client-server-api/#web-browser-clients w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization") w.Header().Set("Content-Security-Policy", "sandbox; default-src 'none'; script-src 'none'; plugin-types application/pdf; style-src 'unsafe-inline'; object-src 'self';") // Allow browsers to cache above for 1 day w.Header().Set("Access-Control-Max-Age", "86400") } // CORSMiddleware adds CORS headers to the response and handles OPTIONS // requests by returning 200 OK immediately. func CORSMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { AddCORSHeaders(w) if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } go-util-0.9.5/exhttp/handleerrors.go000066400000000000000000000043621513243016000174400ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exhttp import ( "bufio" "encoding/json" "fmt" "net" "net/http" ) type ErrorBodies struct { NotFound json.RawMessage MethodNotAllowed json.RawMessage } func HandleErrors(gen ErrorBodies) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(&bodyOverrider{ ResponseWriter: w, statusNotFoundBody: gen.NotFound, statusMethodNotAllowedBody: gen.MethodNotAllowed, }, r) }) } } type bodyOverrider struct { http.ResponseWriter code int override bool written bool hijacked bool statusNotFoundBody json.RawMessage statusMethodNotAllowedBody json.RawMessage } var ( _ http.ResponseWriter = (*bodyOverrider)(nil) _ http.Flusher = (*bodyOverrider)(nil) _ http.Hijacker = (*bodyOverrider)(nil) ) func (b *bodyOverrider) WriteHeader(code int) { if !b.hijacked && b.Header().Get("Content-Type") == "text/plain; charset=utf-8" && (code == http.StatusNotFound || code == http.StatusMethodNotAllowed) { b.Header().Set("Content-Type", "application/json") b.override = true } b.code = code b.ResponseWriter.WriteHeader(code) } func (b *bodyOverrider) Write(body []byte) (n int, err error) { if b.override { n = len(body) if !b.written { switch b.code { case http.StatusNotFound: _, err = b.ResponseWriter.Write(b.statusNotFoundBody) case http.StatusMethodNotAllowed: _, err = b.ResponseWriter.Write(b.statusMethodNotAllowedBody) } } b.written = true return } return b.ResponseWriter.Write(body) } func (b *bodyOverrider) Hijack() (net.Conn, *bufio.ReadWriter, error) { hijacker, ok := b.ResponseWriter.(http.Hijacker) if !ok { return nil, nil, fmt.Errorf("HandleErrors: %T does not implement http.Hijacker", b.ResponseWriter) } b.hijacked = true return hijacker.Hijack() } func (b *bodyOverrider) Flush() { flusher, ok := b.ResponseWriter.(http.Flusher) if ok { flusher.Flush() } } go-util-0.9.5/exhttp/json.go000066400000000000000000000016371513243016000157230ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exhttp import ( "encoding/json" "net/http" ) var AutoAllowCORS = true func WriteJSONResponse(w http.ResponseWriter, httpStatusCode int, jsonData any) { if AutoAllowCORS { AddCORSHeaders(w) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpStatusCode) _ = json.NewEncoder(w).Encode(jsonData) } func WriteJSONData(w http.ResponseWriter, httpStatusCode int, data []byte) { if AutoAllowCORS { AddCORSHeaders(w) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpStatusCode) _, _ = w.Write(data) } func WriteEmptyJSONResponse(w http.ResponseWriter, httpStatusCode int) { WriteJSONData(w, httpStatusCode, []byte("{}")) } go-util-0.9.5/exhttp/middleware.go000066400000000000000000000020741513243016000170630ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exhttp import "net/http" // Middleware represents a middleware that can be applied to an [http.Handler]. type Middleware func(http.Handler) http.Handler // ApplyMiddleware applies the provided [Middleware] functions to the given // router. The middlewares will be applied in the order they are provided. func ApplyMiddleware(router http.Handler, middlewares ...Middleware) http.Handler { // Apply middlewares in reverse order because the first middleware provided // needs to be the outermost one. for i := len(middlewares) - 1; i >= 0; i-- { router = middlewares[i](router) } return router } // StripPrefix is a wrapper for [http.StripPrefix] is compatible with the middleware pattern. func StripPrefix(prefix string) Middleware { return func(next http.Handler) http.Handler { return http.StripPrefix(prefix, next) } } go-util-0.9.5/exhttp/networkerror.go000066400000000000000000000017521513243016000175130ustar00rootroot00000000000000// Copyright (c) 2025 Toni Spets // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exhttp import ( "errors" "net" "syscall" "golang.org/x/net/http2" ) func IsNetworkError(err error) bool { if errno := syscall.Errno(0); errors.As(err, &errno) { // common errnos for network related operations return errno == syscall.ENETDOWN || errno == syscall.ENETUNREACH || errno == syscall.ENETRESET || errno == syscall.ECONNABORTED || errno == syscall.ECONNRESET || errno == syscall.ENOBUFS || errno == syscall.ETIMEDOUT || errno == syscall.ECONNREFUSED || errno == syscall.EHOSTDOWN || errno == syscall.EHOSTUNREACH || errno == syscall.EPIPE } else if netError := net.Error(nil); errors.As(err, &netError) { return true } else if errors.As(err, &http2.StreamError{}) { return true } return false } go-util-0.9.5/exmaps/000077500000000000000000000000001513243016000143755ustar00rootroot00000000000000go-util-0.9.5/exmaps/clone.go000066400000000000000000000007761513243016000160360ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exmaps import ( "maps" ) // NonNilClone returns a copy of the given map, or an empty map if the input is nil. func NonNilClone[Key comparable, Value any](m map[Key]Value) map[Key]Value { if m == nil { return make(map[Key]Value) } return maps.Clone(m) } go-util-0.9.5/exmaps/set.go000066400000000000000000000030631513243016000155210ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exmaps import ( "iter" ) // AbstractSet is an interface implemented by [Set] and [exsync.Set] type AbstractSet[T comparable] interface { Add(item T) bool Has(item T) bool Pop(item T) bool Remove(item T) Size() int AsList() []T Iter() iter.Seq[T] } type empty = struct{} var emptyVal = empty{} // Set is a generic set type based on a map. It is not thread-safe, use [exsync.Set] for a thread-safe variant. type Set[T comparable] map[T]empty var _ AbstractSet[int] = (Set[int])(nil) func NewSetWithItems[T comparable](items []T) Set[T] { s := make(Set[T], len(items)) for _, item := range items { s[item] = emptyVal } return s } func (s Set[T]) Size() int { return len(s) } func (s Set[T]) Add(item T) bool { _, exists := s[item] if !exists { s[item] = emptyVal return true } return false } func (s Set[T]) Remove(item T) { delete(s, item) } func (s Set[T]) Pop(item T) bool { _, exists := s[item] if exists { delete(s, item) } return exists } func (s Set[T]) Has(item T) bool { _, exists := s[item] return exists } func (s Set[T]) AsList() []T { result := make([]T, len(s)) i := 0 for item := range s { result[i] = item i++ } return result } func (s Set[T]) Iter() iter.Seq[T] { return func(yield func(T) bool) { for item := range s { if !yield(item) { return } } } } go-util-0.9.5/exmime/000077500000000000000000000000001513243016000143645ustar00rootroot00000000000000go-util-0.9.5/exmime/mimetypes.go000066400000000000000000000024431513243016000167320ustar00rootroot00000000000000// Copyright (c) 2022 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exmime import ( "mime" "strings" ) // MimeExtensionSanityOverrides includes extensions for various common mimetypes. // // This is necessary because sometimes the OS mimetype database and Go interact in weird ways, // which causes very obscure extensions to be first in the array for common mimetypes // (e.g. image/jpeg -> .jpe, text/plain -> ,v). var MimeExtensionSanityOverrides = map[string]string{ "image/png": ".png", "image/webp": ".webp", "image/jpeg": ".jpg", "image/tiff": ".tiff", "image/heif": ".heic", "image/heic": ".heic", "audio/mpeg": ".mp3", "audio/ogg": ".ogg", "audio/webm": ".webm", "audio/x-caf": ".caf", "audio/mp4": ".m4a", "video/mp4": ".mp4", "video/mpeg": ".mpeg", "video/webm": ".webm", "text/plain": ".txt", "text/html": ".html", "application/xml": ".xml", } func ExtensionFromMimetype(mimetype string) string { ext, ok := MimeExtensionSanityOverrides[strings.Split(mimetype, ";")[0]] if !ok { exts, _ := mime.ExtensionsByType(mimetype) if len(exts) > 0 { ext = exts[0] } } return ext } go-util-0.9.5/exslices/000077500000000000000000000000001513243016000147175ustar00rootroot00000000000000go-util-0.9.5/exslices/cast.go000066400000000000000000000017271513243016000162070ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exslices func CastFunc[To, From any](source []From, conv func(From) To) []To { result := make([]To, len(source)) for i, v := range source { result[i] = conv(v) } return result } func CastFuncFilter[To, From any](source []From, conv func(From) (To, bool)) []To { result := make([]To, 0, len(source)) for _, v := range source { res, ok := conv(v) if ok { result = append(result, res) } } return result } func CastToString[To, From ~string](source []From) []To { result := make([]To, len(source)) for i, v := range source { result[i] = To(v) } return result } func CastToAny[From any](source []From) []any { result := make([]any, len(source)) for i, v := range source { result[i] = v } return result } go-util-0.9.5/exslices/chunk.go000066400000000000000000000015241513243016000163600ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exslices // Chunk splits a slice into chunks of the given size. // // From https://github.com/golang/go/issues/53987#issuecomment-1224367139 // // TODO remove this after slices.Chunk can be used (it'll probably be added in Go 1.23, so it can be used after 1.22 is EOL) func Chunk[T any](slice []T, size int) (chunks [][]T) { if size < 1 { panic("chunk size cannot be less than 1") } for i := 0; ; i++ { next := i * size if len(slice[next:]) > size { end := next + size chunks = append(chunks, slice[next:end:end]) } else { chunks = append(chunks, slice[i*size:]) return } } } go-util-0.9.5/exslices/deduplicate.go000066400000000000000000000053631513243016000175400ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exslices // DeduplicateUnsorted removes duplicates from the given slice without requiring that the input slice is sorted. // The order of the output will be the same as the input. The input slice will not be modified. // // If you don't care about the order of the output, it's recommended to sort the list and then use [slices.Compact]. func DeduplicateUnsorted[T comparable](s []T) []T { return deduplicateUnsortedInto(s, make([]T, 0, len(s))) } // DeduplicateUnsortedOverwrite removes duplicates from the given slice without requiring that the input slice is sorted. // The input slice will be modified and used as the output slice to avoid extra allocations. // // If you don't care about the order of the output, it's recommended to sort the list and then use [slices.Compact]. func DeduplicateUnsortedOverwrite[T comparable](s []T) []T { out := deduplicateUnsortedInto(s, s[:0]) clear(s[len(out):]) return out } func deduplicateUnsortedInto[T comparable](s, result []T) []T { seen := make(map[T]struct{}, len(s)) for _, item := range s { if _, ok := seen[item]; !ok { seen[item] = struct{}{} result = append(result, item) } } return result } // DeduplicateUnsortedFunc removes duplicates from the given slice using the given key function without requiring // that the input slice is sorted. The order of the output will be the same as the input. // // If you don't care about the order of the output, it's recommended to sort the list and then use [slices.CompactFunc]. func DeduplicateUnsortedFunc[T any, K comparable](s []T, getKey func(T) K) []T { return deduplicateUnsortedFuncInto(s, make([]T, 0, len(s)), getKey) } // DeduplicateUnsortedOverwriteFunc removes duplicates from the given slice using the given key function // without requiring that the input slice is sorted. The order of the output will be the same as the input. // The input slice will be modified and used as the output slice to avoid extra allocations. // // If you don't care about the order of the output, it's recommended to sort the list and then use [slices.CompactFunc]. func DeduplicateUnsortedOverwriteFunc[T any, K comparable](s []T, getKey func(T) K) []T { out := deduplicateUnsortedFuncInto(s, s[:0], getKey) clear(s[len(out):]) return out } func deduplicateUnsortedFuncInto[T any, K comparable](s, result []T, getKey func(T) K) []T { seen := make(map[K]struct{}, len(s)) for _, item := range s { key := getKey(item) if _, ok := seen[key]; !ok { seen[key] = struct{}{} result = append(result, item) } } return result } go-util-0.9.5/exslices/delete.go000066400000000000000000000022721513243016000165130ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exslices import ( "slices" ) // FastDeleteIndex deletes the item at the given index without preserving slice order. // This is faster than normal deletion, as it doesn't need to copy all elements after the deleted index. func FastDeleteIndex[T any](s []T, index int) []T { s[index] = s[len(s)-1] clear(s[len(s)-1:]) return s[:len(s)-1] } // FastDeleteItem finds the first index of the given item in the slice and deletes it without preserving slice order. // This is faster than normal deletion, as it doesn't need to copy all elements after the deleted index. func FastDeleteItem[T comparable](s []T, item T) []T { index := slices.Index(s, item) if index < 0 { return s } return FastDeleteIndex(s, index) } // DeleteItem finds the first index of the given item in the slice and deletes it. func DeleteItem[T comparable](s []T, item T) []T { index := slices.Index(s, item) if index < 0 { return s } return slices.Delete(s, index, index+1) } go-util-0.9.5/exslices/diff.go000066400000000000000000000032521513243016000161600ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exslices // SortedDiff returns the difference between two already-sorted slices, with the help of the given comparison function. // The output will be in the same order as the input, which means it'll be sorted. func SortedDiff[T any](a, b []T, compare func(a, b T) int) (uniqueToA, uniqueToB []T) { uniqueToA = make([]T, 0, len(a)) uniqueToB = make([]T, 0, len(b)) var i, j int for { if j >= len(b) { uniqueToA = append(uniqueToA, a[i:]...) break } else if i >= len(a) { uniqueToB = append(uniqueToB, b[j:]...) break } c := compare(a[i], b[j]) if c < 0 { uniqueToA = append(uniqueToA, a[i]) i++ } else if c > 0 { uniqueToB = append(uniqueToB, b[j]) j++ } else { i++ j++ } } return } // Diff returns the difference between two slices. The slices may contain duplicates and don't need to be sorted. // The output will not be sorted, but is guaranteed to not contain any duplicates. func Diff[T comparable](a, b []T) (uniqueToA, uniqueToB []T) { maxLen := len(a) if len(b) > maxLen { maxLen = len(b) } collector := make(map[T]uint8, maxLen) for _, item := range a { collector[item] |= 0b01 } for _, item := range b { collector[item] |= 0b10 } uniqueToA = make([]T, 0, maxLen) uniqueToB = make([]T, 0, maxLen) for item, mask := range collector { if mask == 0b01 { uniqueToA = append(uniqueToA, item) } else if mask == 0b10 { uniqueToB = append(uniqueToB, item) } } return } go-util-0.9.5/exslices/diff_test.go000066400000000000000000000112421513243016000172150ustar00rootroot00000000000000//go:build go1.21 package exslices_test import ( "cmp" "math/rand" "slices" "sort" "strings" "testing" "github.com/stretchr/testify/assert" "go.mau.fi/util/exslices" ) func generateInts(seed int64, size, maxVal int, sorted bool) ([]int, []int) { l1 := make([]int, size) l2 := make([]int, size) r := rand.New(rand.NewSource(seed)) for i := 0; i < size; i++ { l1[i] = r.Intn(maxVal) l2[i] = r.Intn(maxVal) } if sorted { sort.Ints(l1) sort.Ints(l2) l1 = slices.Compact(l1) l2 = slices.Compact(l2) } return l1, l2 } const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" func randomString(r *rand.Rand, length int) string { var sb strings.Builder for i := 0; i < length; i++ { sb.WriteByte(alphabet[r.Intn(len(alphabet))]) } return sb.String() } func generateStrings(seed int64, size, length int, sorted bool) ([]string, []string) { l1 := make([]string, size) l2 := make([]string, size) r := rand.New(rand.NewSource(seed)) for i := 0; i < size; i++ { l1[i] = randomString(r, length) l2[i] = randomString(r, length) } if sorted { sort.Strings(l1) sort.Strings(l2) l1 = slices.Compact(l1) l2 = slices.Compact(l2) } return l1, l2 } func TestDiff_Ints_Basic(t *testing.T) { l1 := []int{1, 2, 3, 4, 5} l2 := []int{3, 4, 5, 6, 7} uniqueToA, uniqueToB := exslices.Diff(l1, l2) sort.Ints(uniqueToA) sort.Ints(uniqueToB) assert.Equal(t, []int{1, 2}, uniqueToA) assert.Equal(t, []int{6, 7}, uniqueToB) } func TestSortedDiff_Ints_Edge(t *testing.T) { testCases := []struct { name string l1, l2, a, b []int }{ { "shorter left", []int{1, 2}, []int{1, 3, 4, 5}, []int{2}, []int{3, 4, 5}, }, { "shorter right", []int{1, 3, 4, 5}, []int{1, 2}, []int{3, 4, 5}, []int{2}, }, { "empty side", []int{}, []int{1, 2, 3}, []int{}, []int{1, 2, 3}, }, { "empty both", []int{}, []int{}, []int{}, []int{}, }, { "equal", []int{1, 2, 3}, []int{1, 2, 3}, []int{}, []int{}, }, { "jump", []int{1, 999, 1001}, []int{1, 2, 3, 4, 1000, 1001}, []int{999}, []int{2, 3, 4, 1000}, }, } for _, c := range testCases { t.Run(c.name, func(t *testing.T) { uniqueToA, uniqueToB := exslices.Diff(c.l1, c.l2) sort.Ints(uniqueToA) sort.Ints(uniqueToB) assert.Equal(t, c.a, uniqueToA) assert.Equal(t, c.b, uniqueToB) }) } } func TestDiff_Strings_Basic(t *testing.T) { l1 := []string{"a", "b", "c", "d", "e"} l2 := []string{"c", "d", "e", "f", "g"} uniqueToA, uniqueToB := exslices.Diff(l1, l2) sort.Strings(uniqueToA) sort.Strings(uniqueToB) assert.Equal(t, []string{"a", "b"}, uniqueToA) assert.Equal(t, []string{"f", "g"}, uniqueToB) } func TestSortedDiff_Strings_Basic(t *testing.T) { l1 := []string{"a", "b", "c", "d", "e"} l2 := []string{"c", "d", "e", "f", "g"} uniqueToA, uniqueToB := exslices.SortedDiff(l1, l2, cmp.Compare[string]) assert.Equal(t, []string{"a", "b"}, uniqueToA) assert.Equal(t, []string{"f", "g"}, uniqueToB) } func tSortedDiff_Random[T cmp.Ordered](t *testing.T, generateFunc func(int64, int, int, bool) ([]T, []T)) { l1, l2 := generateFunc(1, 1000, 1000, true) uniqueToA1, uniqueToB1 := exslices.SortedDiff(l1, l2, cmp.Compare[T]) uniqueToA2, uniqueToB2 := exslices.Diff(l1, l2) // Diff doesn't guarantee order, so sort the results slices.Sort(uniqueToA2) slices.Sort(uniqueToB2) assert.Equal(t, uniqueToA2, uniqueToA1) assert.Equal(t, uniqueToB2, uniqueToB1) } func TestSortedDiff_Random(t *testing.T) { t.Run("Ints", func(t *testing.T) { tSortedDiff_Random[int](t, generateInts) }) t.Run("Strings", func(t *testing.T) { tSortedDiff_Random[string](t, generateStrings) }) } func bDiff[T cmp.Ordered](b *testing.B, generateFunc func(int64, int, int, bool) ([]T, []T), sorted bool) { l1, l2 := generateFunc(1, 1000, 1000, sorted) b.ResetTimer() for i := 0; i < b.N; i++ { exslices.Diff(l1, l2) } } func BenchmarkDiff(b *testing.B) { b.Run("Ints", func(b *testing.B) { bDiff(b, generateInts, false) }) b.Run("Ints/Sorted", func(b *testing.B) { bDiff(b, generateInts, true) }) b.Run("Strings", func(b *testing.B) { bDiff(b, generateStrings, false) }) b.Run("Strings/Sorted", func(b *testing.B) { bDiff(b, generateStrings, true) }) } func bSortedDiff[T comparable](b *testing.B, generateFunc func(int64, int, int, bool) ([]T, []T), compareFunc func(a, b T) int) { l1, l2 := generateFunc(1, 1000, 1000, true) b.ResetTimer() for i := 0; i < b.N; i++ { exslices.SortedDiff(l1, l2, compareFunc) } } func BenchmarkSortedDiff(b *testing.B) { b.Run("Ints", func(b *testing.B) { bSortedDiff(b, generateInts, cmp.Compare[int]) }) b.Run("Strings", func(b *testing.B) { bSortedDiff(b, generateStrings, cmp.Compare[string]) }) } go-util-0.9.5/exslices/stack.go000066400000000000000000000027331513243016000163600ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exslices import ( "iter" ) type Stack[T comparable] []T func (s *Stack[T]) Push(v ...T) { *s = append(*s, v...) } // Peek returns the top item from the stack without removing it. func (s *Stack[T]) Peek() (v T, ok bool) { return s.PeekN(1) } // PeekN returns the nth item from the top of the stack (1-based). func (s *Stack[T]) PeekN(n int) (v T, ok bool) { if len(*s) < n { return } v = (*s)[len(*s)-n] ok = true return } // Pop removes and returns the top item from the stack. func (s *Stack[T]) Pop() (v T, ok bool) { v, ok = s.Peek() if ok { *s = (*s)[:len(*s)-1] } return } func (s *Stack[T]) PeekValue() T { v, _ := s.Peek() return v } func (s *Stack[T]) PopValue() T { v, _ := s.Pop() return v } // Index returns the highest index of the given value in the stack, or -1 if not found. func (s *Stack[T]) Index(val T) int { for i := len(*s) - 1; i >= 0; i-- { if (*s)[i] == val { return i } } return -1 } // Has returns whether the given value is in the stack. func (s *Stack[T]) Has(val T) bool { return s.Index(val) != -1 } func (s *Stack[T]) PopIter() iter.Seq[T] { return func(yield func(T) bool) { for { v, ok := s.Pop() if !ok { return } if !yield(v) { return } } } } go-util-0.9.5/exstrings/000077500000000000000000000000001513243016000151265ustar00rootroot00000000000000go-util-0.9.5/exstrings/stringutil.go000066400000000000000000000061701513243016000176650ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exstrings import ( "crypto/sha256" "crypto/subtle" "slices" "strings" "unsafe" ) // UnsafeBytes returns a byte slice that points to the same memory as the input string. // // The returned byte slice must not be modified. // // See [go.mau.fi/util/exbytes.UnsafeString] for the reverse operation. func UnsafeBytes(str string) []byte { return unsafe.Slice(unsafe.StringData(str), len(str)) } // SHA256 returns the SHA-256 hash of the input string without copying the string. func SHA256(str string) [32]byte { return sha256.Sum256(UnsafeBytes(str)) } // ConstantTimeEqual compares two strings using [subtle.ConstantTimeCompare] without copying the strings. // // Note that ConstantTimeCompare is not constant time if the strings are of different length. func ConstantTimeEqual(a, b string) bool { return subtle.ConstantTimeCompare(UnsafeBytes(a), UnsafeBytes(b)) == 1 } // LongestSequenceOf returns the length of the longest contiguous sequence of a single rune in a string. func LongestSequenceOf(a string, b rune) int { // IndexRune has some optimizations, so use it to find the starting point firstIndex := strings.IndexRune(a, b) if firstIndex == -1 { return 0 } count := 0 maxCount := 0 for _, r := range a[firstIndex:] { if r == b { count++ if count > maxCount { maxCount = count } } else { count = 0 } } return maxCount } // PrefixByteRunLength returns the number of the given byte at the start of a string. func PrefixByteRunLength(s string, b byte) int { count := 0 for ; count < len(s) && s[count] == b; count++ { } return count } // CollapseSpaces replaces all runs of multiple spaces (\x20) in a string with a single space. func CollapseSpaces(s string) string { doubleSpaceIdx := strings.Index(s, " ") if doubleSpaceIdx < 0 { return s } var buf strings.Builder buf.Grow(len(s)) for doubleSpaceIdx >= 0 { buf.WriteString(s[:doubleSpaceIdx+1]) spaceCount := PrefixByteRunLength(s[doubleSpaceIdx+2:], ' ') + 2 s = s[doubleSpaceIdx+spaceCount:] doubleSpaceIdx = strings.Index(s, " ") } buf.WriteString(s) return buf.String() } // LongestSequenceOfFunc returns the length of the longest contiguous sequence of runes in a string. // // If the provided function returns zero or higher, the return value is added to the current count. // If the return value is negative, the count is reset to zero. func LongestSequenceOfFunc(a string, fn func(b rune) int) int { count := 0 maxCount := 0 for _, r := range a { val := fn(r) if val < 0 { count = 0 } else { count += val if count > maxCount { maxCount = count } } } return maxCount } func LongestCommonPrefix(in []string) string { if len(in) == 0 { return "" } else if len(in) == 1 { return in[0] } minStr := slices.Min(in) maxStr := slices.Max(in) for i := 0; i < len(minStr) && i < len(maxStr); i++ { if minStr[i] != maxStr[i] { return minStr[:i] } } return minStr } go-util-0.9.5/exstrings/stringutil_test.go000066400000000000000000000047341513243016000207300ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exstrings import ( "testing" "github.com/stretchr/testify/assert" ) func TestLongestSequenceOf(t *testing.T) { testCases := []struct { input string runeVal rune expected int }{ {"", 'a', 0}, {"aaaaa", 'a', 5}, {"aaaaab", 'a', 5}, {"bbbaaa", 'a', 3}, {"bbbaaa", 'b', 3}, {"a", 'a', 1}, {"b", 'a', 0}, {"aabbaa", 'a', 2}, {"aabbaaa", 'a', 3}, {"aabbaaa", 'b', 2}, } for _, tc := range testCases { result := LongestSequenceOf(tc.input, tc.runeVal) assert.Equal(t, tc.expected, result) } } func TestPrefixByteRunLength(t *testing.T) { testCases := []struct { input string byteVal byte expected int }{ {"", 'a', 0}, {"aaaaa", 'a', 5}, {"aaaaab", 'a', 5}, {"bbbaaa", 'a', 0}, {"bbbaaa", 'b', 3}, {"a", 'a', 1}, {"b", 'a', 0}, {"aabbaa", 'a', 2}, {"aabbaaa", 'a', 2}, {"aabbaaa", 'b', 0}, {" ", ' ', 4}, {" a", ' ', 4}, } for _, tc := range testCases { result := PrefixByteRunLength(tc.input, tc.byteVal) assert.Equal(t, tc.expected, result) } } func TestCollapseSpaces(t *testing.T) { testCases := []struct { input string expected string }{ {"", ""}, {" ", " "}, {" ", " "}, {" ", " "}, {"a", "a"}, {" a ", " a "}, {" a ", " a "}, {"a b", "a b"}, {"a b", "a b"}, {" a b", " a b"}, {" a b ", " a b "}, {" a b ", " a b "}, } for _, tc := range testCases { result := CollapseSpaces(tc.input) assert.Equal(t, tc.expected, result) } } func TestLongestCommonPrefix(t *testing.T) { testCases := []struct { input []string expected string }{ {[]string{}, ""}, {[]string{"flower"}, "flower"}, {[]string{"flower", "flow", "flight"}, "fl"}, {[]string{"dog", "racecar", "car"}, ""}, {[]string{"interspecies", "interstellar", "interstate"}, "inters"}, {[]string{"throne", "throne"}, "throne"}, {[]string{"throne", "dungeon"}, ""}, {[]string{"prefix", "prefixes", "prefixed"}, "prefix"}, {[]string{"a"}, "a"}, {[]string{"", "b"}, ""}, {[]string{"ab", "a"}, "a"}, {[]string{"a", "b"}, ""}, {[]string{"aaab", "aaac", "aaad", "aaae", "aaf", "aaag", "aaah"}, "aa"}, } for _, tc := range testCases { result := LongestCommonPrefix(tc.input) assert.Equal(t, tc.expected, result) } } go-util-0.9.5/exsync/000077500000000000000000000000001513243016000144115ustar00rootroot00000000000000go-util-0.9.5/exsync/event.go000066400000000000000000000055661513243016000160750ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exsync import ( "context" "fmt" "sync" "time" ) // Event is a wrapper around a channel that can be used to notify multiple waiters that some event has happened. // // It's modelled after Python's asyncio.Event: https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event type Event struct { ch chan empty set bool waiting bool l sync.RWMutex } // NewEvent creates a new event. It will initially be unset. func NewEvent() *Event { return &Event{ ch: make(chan empty), } } type EventChan = <-chan empty // GetChan returns the channel that will be closed when the event is set. func (e *Event) GetChan() EventChan { e.l.RLock() defer e.l.RUnlock() e.waiting = true return e.ch } // Wait waits for either the event to happen or the given context to be done. // If the context is done first, the error is returned, otherwise the return value is nil. func (e *Event) Wait(ctx context.Context) error { select { case <-e.GetChan(): return nil case <-ctx.Done(): return ctx.Err() } } // WaitTimeout waits for the event to happen within the given timeout. // If the timeout expires first, the return value is false, otherwise it's true. func (e *Event) WaitTimeout(timeout time.Duration) bool { select { case <-e.GetChan(): return true case <-time.After(timeout): return false } } // WaitTimeoutCtx waits for the event to happen, the timeout to expire, or the given context to be done. // If the context or timeout is done first, an error is returned, otherwise the return value is nil. func (e *Event) WaitTimeoutCtx(ctx context.Context, timeout time.Duration) error { select { case <-e.GetChan(): return nil case <-ctx.Done(): return ctx.Err() case <-time.After(timeout): return fmt.Errorf("exsync.Event: wait timeout") } } // IsSet returns true if the event has been set. func (e *Event) IsSet() bool { e.l.RLock() defer e.l.RUnlock() return e.set } // Set sets the event, notifying all waiters. func (e *Event) Set() { e.l.Lock() defer e.l.Unlock() if !e.set { close(e.ch) e.set = true } } // Notify notifies all waiters, but doesn't set the event. // // Calling Notify when the event is already set is a no-op. func (e *Event) Notify() { e.l.Lock() defer e.l.Unlock() if !e.set && e.waiting { close(e.ch) e.ch = make(chan empty) e.waiting = false } } // Clear clears the event, making it unset. Future calls to Wait will now block until Set is called again. // If the event is not already set, this is a no-op and existing calls to Wait will keep working. func (e *Event) Clear() { e.l.Lock() defer e.l.Unlock() if e.set { e.ch = make(chan empty) e.set = false e.waiting = false } } go-util-0.9.5/exsync/returnonce.go000066400000000000000000000012201513243016000171170ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exsync import "sync" // ReturnableOnce is a wrapper for sync.Once that can return a value // // Deprecated: Use [sync.OnceValues] instead. type ReturnableOnce[Value any] struct { once sync.Once output Value err error } func (ronce *ReturnableOnce[Value]) Do(fn func() (Value, error)) (Value, error) { ronce.once.Do(func() { ronce.output, ronce.err = fn() }) return ronce.output, ronce.err } go-util-0.9.5/exsync/ringbuffer.go000066400000000000000000000063001513243016000170700ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exsync import ( "errors" "sync" ) type pair[Key comparable, Value any] struct { Set bool Key Key Value Value } type RingBuffer[Key comparable, Value any] struct { ptr int data []pair[Key, Value] lock sync.RWMutex size int } func NewRingBuffer[Key comparable, Value any](size int) *RingBuffer[Key, Value] { return &RingBuffer[Key, Value]{ data: make([]pair[Key, Value], size), } } var ( // StopIteration can be returned by the RingBuffer.Iter or MapRingBuffer callbacks to stop iteration immediately. StopIteration = errors.New("stop iteration") //lint:ignore ST1012 not an error // SkipItem can be returned by the MapRingBuffer callback to skip adding a specific item. SkipItem = errors.New("skip item") //lint:ignore ST1012 not an error ) func (rb *RingBuffer[Key, Value]) unlockedIter(callback func(key Key, val Value) error) error { end := rb.ptr for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) { entry := rb.data[i] if !entry.Set { break } err := callback(entry.Key, entry.Value) if err != nil { if errors.Is(err, StopIteration) { return nil } return err } } return nil } func (rb *RingBuffer[Key, Value]) Iter(callback func(key Key, val Value) error) error { rb.lock.RLock() defer rb.lock.RUnlock() return rb.unlockedIter(callback) } func MapRingBuffer[Key comparable, Value, Output any](rb *RingBuffer[Key, Value], callback func(key Key, val Value) (Output, error)) ([]Output, error) { rb.lock.RLock() defer rb.lock.RUnlock() output := make([]Output, 0, rb.size) err := rb.unlockedIter(func(key Key, val Value) error { item, err := callback(key, val) if err != nil { if errors.Is(err, SkipItem) { return nil } return err } output = append(output, item) return nil }) return output, err } func (rb *RingBuffer[Key, Value]) Size() int { rb.lock.RLock() defer rb.lock.RUnlock() return rb.size } func (rb *RingBuffer[Key, Value]) Contains(val Key) bool { _, ok := rb.Get(val) return ok } func (rb *RingBuffer[Key, Value]) Get(key Key) (val Value, found bool) { rb.lock.RLock() end := rb.ptr for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) { if rb.data[i].Key == key { val = rb.data[i].Value found = true break } } rb.lock.RUnlock() return } func (rb *RingBuffer[Key, Value]) Replace(key Key, val Value) bool { rb.lock.Lock() defer rb.lock.Unlock() end := rb.ptr for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) { if rb.data[i].Key == key { rb.data[i].Value = val return true } } return false } func (rb *RingBuffer[Key, Value]) Push(key Key, val Value) { rb.lock.Lock() rb.data[rb.ptr] = pair[Key, Value]{Key: key, Value: val, Set: true} rb.ptr = (rb.ptr + 1) % len(rb.data) if rb.size < len(rb.data) { rb.size++ } rb.lock.Unlock() } func clamp(index, len int) int { if index < 0 { return len + index } else if index >= len { return len - index } else { return index } } go-util-0.9.5/exsync/syncmap.go000066400000000000000000000111111513243016000164050ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exsync import ( "iter" "maps" "sync" ) // Map is a simple map with a built-in mutex. type Map[Key comparable, Value any] struct { data map[Key]Value lock sync.RWMutex } func NewMap[Key comparable, Value any]() *Map[Key, Value] { return NewMapWithData(make(map[Key]Value)) } // NewMapWithData constructs a Map with the given map as data. Accessing the map directly after passing it here is not safe. func NewMapWithData[Key comparable, Value any](data map[Key]Value) *Map[Key, Value] { return &Map[Key, Value]{ data: data, } } // Set stores a value in the map. func (sm *Map[Key, Value]) Set(key Key, value Value) { sm.Swap(key, value) } // Swap sets a value in the map and returns the old value. // // The boolean return parameter is true if the value already existed, false if not. func (sm *Map[Key, Value]) Swap(key Key, value Value) (oldValue Value, wasReplaced bool) { sm.lock.Lock() oldValue, wasReplaced = sm.data[key] sm.data[key] = value sm.lock.Unlock() return } // Delete removes a key from the map. func (sm *Map[Key, Value]) Delete(key Key) { sm.Pop(key) } // Pop removes a key from the map and returns the old value. // // The boolean return parameter is the same as with normal Go map access (true if the key exists, false if not). func (sm *Map[Key, Value]) Pop(key Key) (value Value, ok bool) { sm.lock.Lock() value, ok = sm.data[key] delete(sm.data, key) sm.lock.Unlock() return } // Get gets a value in the map. // // The boolean return parameter is the same as with normal Go map access (true if the key exists, false if not). func (sm *Map[Key, Value]) Get(key Key) (value Value, ok bool) { sm.lock.RLock() value, ok = sm.data[key] sm.lock.RUnlock() return } // GetDefault gets a value in the map, or the given default value if there's no such key. func (sm *Map[Key, Value]) GetDefault(key Key, def Value) Value { sm.lock.RLock() value, ok := sm.data[key] sm.lock.RUnlock() if ok { return value } return def } // GetOrSetFactory gets a value in the map if the key already exists, // otherwise inserts a new value from the given function and returns it. func (sm *Map[Key, Value]) GetOrSetFactory(key Key, def func() Value) Value { val, ok := sm.Get(key) if ok { return val } sm.lock.Lock() defer sm.lock.Unlock() value, ok := sm.data[key] if !ok { value = def() sm.data[key] = value } return value } // GetOrSet gets a value in the map if the key already exists, otherwise inserts the given value and returns it. // // The boolean return parameter is true if the key already exists, and false if the given value was inserted. func (sm *Map[Key, Value]) GetOrSet(key Key, value Value) (actual Value, wasGet bool) { sm.lock.Lock() defer sm.lock.Unlock() actual, wasGet = sm.data[key] if wasGet { return } sm.data[key] = value actual = value return } // Clear removes all items from the map. func (sm *Map[Key, Value]) Clear() { sm.lock.Lock() clear(sm.data) sm.lock.Unlock() } // Len returns the number of items in the map. func (sm *Map[Key, Value]) Len() int { sm.lock.RLock() l := len(sm.data) sm.lock.RUnlock() return l } // CopyFrom copies all key/value pairs from the given map into this map, overriding any existing keys. // Keys present in this map but not in the given map are not removed. func (sm *Map[Key, Value]) CopyFrom(other map[Key]Value) { sm.lock.Lock() maps.Copy(sm.data, other) sm.lock.Unlock() } // SwapData replaces the internal map with the given map, returning the previous map. // If the given map is nil, a new empty map is created. // The given map must not be modified after passing it to this function. func (sm *Map[Key, Value]) SwapData(other map[Key]Value) map[Key]Value { sm.lock.Lock() prev := sm.data if other == nil { sm.data = make(map[Key]Value) } else { sm.data = other } sm.lock.Unlock() return prev } // Clone returns a copy of the map. func (sm *Map[Key, Value]) Clone() *Map[Key, Value] { return NewMapWithData(sm.CopyData()) } // CopyData returns a copy of the data in the map as a normal (non-atomic) map. func (sm *Map[Key, Value]) CopyData() map[Key]Value { sm.lock.RLock() copied := maps.Clone(sm.data) sm.lock.RUnlock() return copied } func (sm *Map[Key, Value]) Iter() iter.Seq2[Key, Value] { return func(yield func(Key, Value) bool) { sm.lock.RLock() defer sm.lock.RUnlock() for k, v := range sm.data { if !yield(k, v) { return } } } } go-util-0.9.5/exsync/syncset.go000066400000000000000000000060711513243016000164340ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exsync import ( "iter" "sync" "go.mau.fi/util/exmaps" ) type empty struct{} var emptyVal = empty{} // Set is a wrapper around a map[T]struct{} with a built-in mutex. type Set[T comparable] struct { m map[T]empty l sync.RWMutex } var _ exmaps.AbstractSet[int] = (*Set[int])(nil) // NewSet constructs a Set with an empty map. func NewSet[T comparable]() *Set[T] { return NewSetWithMap[T](make(map[T]empty)) } // NewSetWithSize constructs a Set with a map that has been allocated the given amount of space. func NewSetWithSize[T comparable](size int) *Set[T] { return NewSetWithMap[T](make(map[T]empty, size)) } // NewSetWithMap constructs a Set with the given map. Accessing the map directly after passing it here is not safe. func NewSetWithMap[T comparable](m map[T]empty) *Set[T] { return &Set[T]{m: m} } // NewSetWithItems constructs a Set with items from the given slice pre-filled. // The slice is not modified or used after the function returns, so using it after this is safe. func NewSetWithItems[T comparable](items []T) *Set[T] { s := NewSetWithSize[T](len(items)) for _, item := range items { s.m[item] = emptyVal } return s } // Add adds an item to the set. The return value is true if the item was added to the set, or false otherwise. func (s *Set[T]) Add(item T) bool { if s == nil { return false } s.l.Lock() _, exists := s.m[item] if exists { s.l.Unlock() return false } s.m[item] = emptyVal s.l.Unlock() return true } // Has checks if the given item is in the set. func (s *Set[T]) Has(item T) bool { if s == nil { return false } s.l.RLock() _, exists := s.m[item] s.l.RUnlock() return exists } // Pop removes the given item from the set. The return value is true if the item was in the set, or false otherwise. func (s *Set[T]) Pop(item T) bool { if s == nil { return false } s.l.Lock() _, exists := s.m[item] if exists { delete(s.m, item) } s.l.Unlock() return exists } // Remove removes the given item from the set. func (s *Set[T]) Remove(item T) { if s == nil { return } s.l.Lock() delete(s.m, item) s.l.Unlock() } // ReplaceAll replaces this set with the given set. If the given set is nil, the set is cleared. func (s *Set[T]) ReplaceAll(newSet *Set[T]) { if s == nil { return } s.l.Lock() if newSet == nil { s.m = make(map[T]empty) } else { s.m = newSet.m } s.l.Unlock() } func (s *Set[T]) Size() int { if s == nil { return 0 } s.l.RLock() size := len(s.m) s.l.RUnlock() return size } func (s *Set[T]) AsList() []T { if s == nil { return nil } s.l.RLock() list := make([]T, len(s.m)) i := 0 for item := range s.m { list[i] = item i++ } s.l.RUnlock() return list } func (s *Set[T]) Iter() iter.Seq[T] { return func(yield func(T) bool) { s.l.RLock() defer s.l.RUnlock() for item := range s.m { if !yield(item) { return } } } } go-util-0.9.5/exzerolog/000077500000000000000000000000001513243016000151165ustar00rootroot00000000000000go-util-0.9.5/exzerolog/callermarshal.go000066400000000000000000000014711513243016000202620ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exzerolog import ( "fmt" "runtime" "strings" ) // CallerWithFunctionName is an implementation for zerolog.CallerMarshalFunc that includes the caller function name // in addition to the file and line number. // // Use as // // zerolog.CallerMarshalFunc = exzerolog.CallerWithFunctionName func CallerWithFunctionName(pc uintptr, file string, line int) string { files := strings.Split(file, "/") file = files[len(files)-1] name := runtime.FuncForPC(pc).Name() fns := strings.Split(name, ".") name = fns[len(fns)-1] return fmt.Sprintf("%s:%d:%s()", file, line, name) } go-util-0.9.5/exzerolog/defaults.go000066400000000000000000000025261513243016000172610ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exzerolog import ( "time" "github.com/rs/zerolog" deflog "github.com/rs/zerolog/log" ) // SetupDefaults updates zerolog globals with sensible defaults. // // * [zerolog.TimeFieldFormat] is set to time.RFC3339Nano instead of time.RFC3339 // * [zerolog.CallerMarshalFunc] is set to [CallerWithFunctionName] // * [zerolog.DefaultContextLogger] is set to the given logger with default_context_log=true and caller info enabled // * The global default [log.Logger] is set to the given logger with global_log=true and caller info enabled // * [zerolog.LevelColors] are updated to swap trace and debug level colors func SetupDefaults(log *zerolog.Logger) { zerolog.TimeFieldFormat = time.RFC3339Nano zerolog.CallerMarshalFunc = CallerWithFunctionName defaultCtxLog := log.With().Bool("default_context_log", true).Caller().Logger() zerolog.DefaultContextLogger = &defaultCtxLog deflog.Logger = log.With().Bool("global_log", true).Caller().Logger() // Swap trace and debug level colors so trace pops out the least zerolog.LevelColors[zerolog.TraceLevel] = 0 zerolog.LevelColors[zerolog.DebugLevel] = 34 // blue } go-util-0.9.5/exzerolog/generics.go000066400000000000000000000020321513243016000172410ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exzerolog import ( "fmt" "github.com/rs/zerolog" ) func ArrayOf[T any](slice []T, fn func(arr *zerolog.Array, item T)) *zerolog.Array { arr := zerolog.Arr() for _, item := range slice { fn(arr, item) } return arr } func AddObject[T zerolog.LogObjectMarshaler](arr *zerolog.Array, obj T) { arr.Object(obj) } func AddStringer[T fmt.Stringer](arr *zerolog.Array, str T) { arr.Str(str.String()) } func AddStr[T ~string](arr *zerolog.Array, str T) { arr.Str(string(str)) } func ArrayOfObjs[T zerolog.LogObjectMarshaler](slice []T) *zerolog.Array { return ArrayOf(slice, AddObject[T]) } func ArrayOfStringers[T fmt.Stringer](slice []T) *zerolog.Array { return ArrayOf(slice, AddStringer[T]) } func ArrayOfStrs[T ~string](slice []T) *zerolog.Array { return ArrayOf(slice, AddStr[T]) } go-util-0.9.5/exzerolog/writer.go000066400000000000000000000032661513243016000167700ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package exzerolog import ( "bytes" "sync" "github.com/rs/zerolog" ) // LogWriter wraps a zerolog.Logger and provides an io.Writer with buffering so each written line is logged separately. type LogWriter struct { log zerolog.Logger level zerolog.Level field string mu sync.Mutex buf bytes.Buffer } func NewLogWriter(log zerolog.Logger) *LogWriter { zerolog.Nop() return &LogWriter{ log: log, level: zerolog.DebugLevel, field: zerolog.MessageFieldName, } } func (lw *LogWriter) WithLevel(level zerolog.Level) *LogWriter { return &LogWriter{ log: lw.log, level: level, field: lw.field, } } func (lw *LogWriter) WithField(field string) *LogWriter { return &LogWriter{ log: lw.log, level: lw.level, field: field, } } func (lw *LogWriter) writeLine(data []byte) { if len(data) == 0 { return } if data[len(data)-1] == '\n' { data = data[:len(data)-1] } if lw.buf.Len() > 0 { lw.buf.Write(data) data = lw.buf.Bytes() lw.buf.Reset() } lw.log.WithLevel(lw.level).Bytes(lw.field, data).Send() } func (lw *LogWriter) Write(data []byte) (n int, err error) { lw.mu.Lock() defer lw.mu.Unlock() newline := bytes.IndexByte(data, '\n') if newline == len(data)-1 { lw.writeLine(data) } else if newline < 0 { lw.buf.Write(data) } else { lines := bytes.Split(data, []byte{'\n'}) for _, line := range lines[:len(lines)-1] { lw.writeLine(line) } lw.buf.Write(lines[len(lines)-1]) } return len(data), nil } go-util-0.9.5/fallocate/000077500000000000000000000000001513243016000150325ustar00rootroot00000000000000go-util-0.9.5/fallocate/doc.go000066400000000000000000000005341513243016000161300ustar00rootroot00000000000000// Copyright (C) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package fallocate provides a unified interface for preallocating space for a // file. package fallocate go-util-0.9.5/fallocate/fallocate_darwin.go000066400000000000000000000011651513243016000206620ustar00rootroot00000000000000// Copyright (C) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build darwin package fallocate import ( "os" "golang.org/x/sys/unix" ) var ErrOutOfSpace error = unix.ENOSPC func Fallocate(file *os.File, size int) error { if size <= 0 { return nil } return unix.FcntlFstore(uintptr(file.Fd()), unix.F_PREALLOCATE, &unix.Fstore_t{ Flags: unix.F_ALLOCATEALL, Posmode: unix.F_PEOFPOSMODE, Offset: 0, Length: int64(size), }) } go-util-0.9.5/fallocate/fallocate_linux.go000066400000000000000000000007651513243016000205420ustar00rootroot00000000000000// Copyright (C) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build linux package fallocate import ( "os" "golang.org/x/sys/unix" ) var ErrOutOfSpace error = unix.ENOSPC func Fallocate(file *os.File, size int) error { if size <= 0 { return nil } return unix.Fallocate(int(file.Fd()), 0, 0, int64(size)) } go-util-0.9.5/fallocate/fallocate_unknown.go000066400000000000000000000006301513243016000210710ustar00rootroot00000000000000// Copyright (C) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build !linux && !android && !darwin package fallocate import "os" var ErrOutOfSpace error = nil func Fallocate(file *os.File, size int) error { return nil } go-util-0.9.5/ffmpeg/000077500000000000000000000000001513243016000143445ustar00rootroot00000000000000go-util-0.9.5/ffmpeg/convert.go000066400000000000000000000112751513243016000163610ustar00rootroot00000000000000// Copyright (c) 2022 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package ffmpeg import ( "context" "fmt" "os" "os/exec" "path/filepath" "strings" "github.com/rs/zerolog" "go.mau.fi/util/exmime" "go.mau.fi/util/exzerolog" ) var ffmpegDefaultParams = []string{"-hide_banner", "-loglevel", "warning"} var ffmpegPath, ffprobePath string func init() { ffmpegPath, _ = exec.LookPath("ffmpeg") ffprobePath, _ = exec.LookPath("ffprobe") } // Supported returns whether ffmpeg is available on the system. // // ffmpeg is considered to be available if a binary called ffmpeg is found in $PATH, // or if [SetPath] has been called explicitly with a non-empty path. func Supported() bool { return ffmpegPath != "" } // SetPath overrides the path to the ffmpeg binary. func SetPath(path string) { ffmpegPath = path } func ProbeSupported() bool { return ffprobePath != "" } // SetPath overrides the path to the ffprobe binary. func SetProbePath(path string) { ffprobePath = path } // ConvertPath converts a media file on the disk using ffmpeg and auto-generates the output file name. // // Args: // * inputFile: The full path to the file. // * outputExtension: The extension that the output file should be. // * inputArgs: Arguments to tell ffmpeg how to parse the input file. // * outputArgs: Arguments to tell ffmpeg how to convert the file to reach the wanted output. // * removeInput: Whether the input file should be removed after converting. // // Returns: the path to the converted file. func ConvertPath(ctx context.Context, inputFile string, outputExtension string, inputArgs []string, outputArgs []string, removeInput bool) (string, error) { outputFilename := strings.TrimSuffix(strings.TrimSuffix(inputFile, filepath.Ext(inputFile)), "*") + outputExtension return outputFilename, ConvertPathWithDestination(ctx, inputFile, outputFilename, inputArgs, outputArgs, removeInput) } // ConvertPathWithDestination converts a media file on the disk using ffmpeg and saves the result to the provided file name. // // Args: // * inputFile: The full path to the file. // * outputFile: The full path to the output file. Must include the appropriate extension so ffmpeg knows what to convert to. // * inputArgs: Arguments to tell ffmpeg how to parse the input file. // * outputArgs: Arguments to tell ffmpeg how to convert the file to reach the wanted output. // * removeInput: Whether the input file should be removed after converting. func ConvertPathWithDestination(ctx context.Context, inputFile string, outputFile string, inputArgs []string, outputArgs []string, removeInput bool) error { if removeInput { defer func() { _ = os.Remove(inputFile) }() } args := make([]string, 0, len(ffmpegDefaultParams)+len(inputArgs)+2+len(outputArgs)+1) args = append(args, ffmpegDefaultParams...) args = append(args, inputArgs...) args = append(args, "-i", inputFile) args = append(args, outputArgs...) args = append(args, outputFile) cmd := exec.CommandContext(ctx, ffmpegPath, args...) ctxLog := zerolog.Ctx(ctx).With().Str("command", "ffmpeg").Logger() logWriter := exzerolog.NewLogWriter(ctxLog).WithLevel(zerolog.WarnLevel) cmd.Stdout = logWriter cmd.Stderr = logWriter err := cmd.Run() if err != nil { _ = os.Remove(outputFile) return fmt.Errorf("ffmpeg error: %w", err) } return nil } // ConvertBytes converts media data using ffmpeg. // // Args: // * data: The media data to convert // * outputExtension: The extension that the output file should be. // * inputArgs: Arguments to tell ffmpeg how to parse the input file. // * outputArgs: Arguments to tell ffmpeg how to convert the file to reach the wanted output. // * inputMime: The mimetype of the input data. // // Returns: the converted data func ConvertBytes(ctx context.Context, data []byte, outputExtension string, inputArgs []string, outputArgs []string, inputMime string) ([]byte, error) { tempdir, err := os.MkdirTemp("", "mautrix_ffmpeg_*") if err != nil { return nil, err } defer os.RemoveAll(tempdir) inputFileName := fmt.Sprintf("%s/input%s", tempdir, exmime.ExtensionFromMimetype(inputMime)) inputFile, err := os.OpenFile(inputFileName, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) if err != nil { return nil, fmt.Errorf("failed to open input file: %w", err) } _, err = inputFile.Write(data) if err != nil { _ = inputFile.Close() return nil, fmt.Errorf("failed to write data to input file: %w", err) } _ = inputFile.Close() outputPath, err := ConvertPath(ctx, inputFileName, outputExtension, inputArgs, outputArgs, false) if err != nil { return nil, err } return os.ReadFile(outputPath) } go-util-0.9.5/ffmpeg/probe.go000066400000000000000000000122251513243016000160040ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package ffmpeg import ( "bytes" "context" "encoding/json" "fmt" "os/exec" "github.com/rs/zerolog" "go.mau.fi/util/exzerolog" ) type Format struct { Filename string `json:"filename"` NBStreams int `json:"nb_streams"` NBPrograms int `json:"nb_programs"` FormatName string `json:"format_name"` FormatLongName string `json:"format_long_name"` StartTime float64 `json:"start_time,string"` Duration float64 `json:"duration,string"` Size int `json:"size,string"` BitRate int `json:"bit_rate,string"` ProbeScore int `json:"probe_score"` Tags map[string]string `json:"tags"` } type Disposition struct { Default int `json:"default"` Dub int `json:"dub"` Original int `json:"original"` Comment int `json:"comment"` Lyrics int `json:"lyrics"` Karaoke int `json:"karaoke"` Forced int `json:"forced"` HearingImpaired int `json:"hearing_impaired"` VisualImpaired int `json:"visual_impaired"` CleanEffects int `json:"clean_effects"` AttachedPic int `json:"attached_pic"` TimedThumbnails int `json:"timed_thumbnails"` NonDiegetic int `json:"non_diegetic"` Captions int `json:"captions"` Descriptions int `json:"descriptions"` Metadata int `json:"metadata"` Dependent int `json:"dependent"` StillImage int `json:"still_image"` } type Stream struct { Index int `json:"index"` CodecName string `json:"codec_name"` CodecLongName string `json:"codec_long_name"` Profile string `json:"profile"` CodecType string `json:"codec_type"` CodecTagString string `json:"codec_tag_string"` CodecTag string `json:"codec_tag"` Width int `json:"width"` Height int `json:"height"` CodedWidth int `json:"coded_width"` CodedHeight int `json:"coded_height"` ClosedCaptions int `json:"closed_captions"` FilmGrain int `json:"film_grain"` HasBFrames int `json:"has_b_frames"` PixFmt string `json:"pix_fmt"` Level int `json:"level"` ColorRange string `json:"color_range"` ColorSpace string `json:"color_space"` ColorTransfer string `json:"color_transfer"` ColorPrimaries string `json:"color_primaries"` ChromaLocation string `json:"chroma_location"` FieldOrder string `json:"field_order"` Refs int `json:"refs"` IsAvc string `json:"is_avc"` NalLengthSize string `json:"nal_length_size"` ID string `json:"id"` RFrameRate string `json:"r_frame_rate"` AvgFrameRate string `json:"avg_frame_rate"` TimeBase string `json:"time_base"` StartPts int `json:"start_pts"` StartTime float64 `json:"start_time,string"` DurationTS int `json:"duration_ts"` Duration float64 `json:"duration,string"` BitRate int `json:"bit_rate,string"` BitsPerRawSample int `json:"bits_per_raw_sample,string"` NumberOfFrames int `json:"nb_frames,string"` ExtradataSize int `json:"extradata_size"` Disposition Disposition `json:"disposition"` Tags map[string]string `json:"tags"` SampleFormat string `json:"sample_fmt"` SampleRate int `json:"sample_rate,string"` Channels int `json:"channels"` ChannelLayout string `json:"channel_layout"` BitsPerSample int `json:"bits_per_sample"` InitialPadding int `json:"initial_padding"` } type ProbeResult struct { Streams []*Stream `json:"streams"` Format *Format `json:"format"` } var ffprobeDefaultParams = []string{"-hide_banner", "-loglevel", "warning", "-print_format", "json", "-show_format", "-show_streams"} func Probe(ctx context.Context, path string) (*ProbeResult, error) { ctxLog := zerolog.Ctx(ctx).With().Str("command", "ffmpeg").Logger() logWriter := exzerolog.NewLogWriter(ctxLog).WithLevel(zerolog.WarnLevel) cmd := exec.CommandContext(ctx, ffprobePath, append(ffprobeDefaultParams, path)...) var stdout bytes.Buffer cmd.Stderr = logWriter cmd.Stdout = &stdout err := cmd.Run() if err != nil { return nil, fmt.Errorf("failed to run ffprobe: %w", err) } var result ProbeResult err = json.NewDecoder(&stdout).Decode(&result) if err != nil { return nil, fmt.Errorf("failed to parse ffmpeg output: %w", err) } return &result, nil } go-util-0.9.5/ffmpeg/waveform/000077500000000000000000000000001513243016000161725ustar00rootroot00000000000000go-util-0.9.5/ffmpeg/waveform/waveform.go000066400000000000000000000057731513243016000203630ustar00rootroot00000000000000// Copyright (c) 2025 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package waveform import ( "bytes" "context" "fmt" "image" "image/color" "image/png" "os" "slices" "go.mau.fi/util/ffmpeg" ) func makeWaveformArgs(samples, maxValue int) []string { return []string{ "-filter_complex", fmt.Sprintf("aformat=channel_layouts=mono,showwavespic=s=%dx%d:colors=white", samples, maxValue*2), "-frames:v", "1", "-update", "1", } } func Generate(ctx context.Context, inputFile string, samples, maxValue int) ([]int, error) { tempFile, err := os.CreateTemp("", "waveform-*.png") if err != nil { return nil, fmt.Errorf("failed to create temp file: %w", err) } _ = tempFile.Close() _ = os.Remove(tempFile.Name()) err = ffmpeg.ConvertPathWithDestination(ctx, inputFile, tempFile.Name(), nil, makeWaveformArgs(samples, maxValue), false) if err != nil { return nil, fmt.Errorf("failed to generate waveform png with ffmpeg: %w", err) } defer func() { _ = os.Remove(tempFile.Name()) }() tempFile, err = os.Open(tempFile.Name()) if err != nil { return nil, fmt.Errorf("failed to reopen temp file: %w", err) } defer func() { _ = tempFile.Close() }() decoded, err := png.Decode(tempFile) if err != nil { return nil, fmt.Errorf("failed to decode waveform png: %w", err) } return parseWaveformImage(decoded, maxValue), nil } func GenerateBytes(ctx context.Context, inputData []byte, inputMime string, samples, maxValue int) ([]int, error) { waveformBytes, err := ffmpeg.ConvertBytes(ctx, inputData, ".png", nil, makeWaveformArgs(samples, maxValue), inputMime) if err != nil { return nil, fmt.Errorf("failed to generate waveform png with ffmpeg: %w", err) } decoded, err := png.Decode(bytes.NewReader(waveformBytes)) if err != nil { return nil, fmt.Errorf("failed to decode waveform png: %w", err) } return parseWaveformImage(decoded, maxValue), nil } func isWhite(c color.Color) bool { r, g, b, a := c.RGBA() return a > 0 && r > 0x7FFF && g > 0x7FFF && b > 0x7FFF } func findAvgMinMax(img image.Image, bounds image.Rectangle, x, targetMaxVal int) int { var topVal, bottomVal int for topVal = targetMaxVal; topVal > 0; topVal-- { if isWhite(img.At(bounds.Min.X+x, bounds.Min.Y+targetMaxVal-topVal)) { break } } for bottomVal = targetMaxVal; bottomVal > 0; bottomVal-- { if isWhite(img.At(bounds.Min.X+x, bounds.Max.Y-targetMaxVal+bottomVal)) { break } } return (topVal + bottomVal) / 2 } func clamp(data []int, to int) { maxVal := slices.Max(data) if maxVal < to { for i := range data { data[i] = int(float64(data[i]) * float64(to) / float64(maxVal)) } } } func parseWaveformImage(img image.Image, targetMaxVal int) []int { bounds := img.Bounds() out := make([]int, bounds.Dx()) for x := 0; x < len(out); x++ { out[x] = findAvgMinMax(img, bounds, x, targetMaxVal) } clamp(out, targetMaxVal) return out } go-util-0.9.5/glob/000077500000000000000000000000001513243016000140235ustar00rootroot00000000000000go-util-0.9.5/glob/glob.go000066400000000000000000000051121513243016000152740ustar00rootroot00000000000000// Package glob implements very simple glob pattern matching used in various parts of the Matrix spec, // such as push rules and moderation policy lists. // // See https://spec.matrix.org/v1.11/appendices/#glob-style-matching for more info. package glob import ( "strings" ) type Glob interface { Match(string) bool } var ( _ Glob = ExactGlob("") _ Glob = PrefixGlob("") _ Glob = SuffixGlob("") _ Glob = ContainsGlob("") _ Glob = (*PrefixAndSuffixGlob)(nil) _ Glob = (*PrefixSuffixAndContainsGlob)(nil) _ Glob = (*RegexGlob)(nil) ) // Compile compiles a glob pattern into an object that can be used to efficiently match strings against the pattern. // // Simple globs will be converted into prefix/suffix/contains checks, while complicated ones will be compiled as regex. func Compile(pattern string) Glob { pattern = Simplify(pattern) g := compileSimple(pattern) if g != nil { return g } g, _ = CompileRegex(pattern) return g } // CompileWithImplicitContains is a wrapper for Compile which will replace exact matches with contains matches. // i.e. if the pattern has no wildcards, it will be treated as if it was surrounded in asterisks (`foo` -> `*foo*`). func CompileWithImplicitContains(pattern string) Glob { g := Compile(pattern) if _, isExact := g.(ExactGlob); isExact { return ContainsGlob(pattern) } return g } // CompileSimple compiles a glob pattern into one of the non-regex forms. // // If the pattern can't be compiled into a simple form, it returns nil. func CompileSimple(pattern string) Glob { return compileSimple(Simplify(pattern)) } func compileSimple(pattern string) Glob { if strings.ContainsRune(pattern, '?') { return nil } switch strings.Count(pattern, "*") { case 0: return ExactGlob(pattern) case 1: if strings.HasPrefix(pattern, "*") { return SuffixGlob(pattern[1:]) } else if strings.HasSuffix(pattern, "*") { return PrefixGlob(pattern[:len(pattern)-1]) } else { parts := strings.Split(pattern, "*") return PrefixAndSuffixGlob{ Prefix: parts[0], Suffix: parts[1], } } case 2: if strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*") { return ContainsGlob(pattern[1 : len(pattern)-1]) } parts := strings.Split(pattern, "*") return PrefixSuffixAndContainsGlob{ Prefix: parts[0], Contains: parts[1], Suffix: parts[2], } default: return nil } } var sqlCompiler = strings.NewReplacer( `\`, `\\`, `%`, `\%`, `_`, `\_`, `*`, `%`, `?`, `_`, ) // ToSQL converts a Matrix glob pattern to a SQL LIKE pattern. func ToSQL(pattern string) string { return sqlCompiler.Replace(Simplify(pattern)) } go-util-0.9.5/glob/glob_test.go000066400000000000000000000044431513243016000163410ustar00rootroot00000000000000package glob_test import ( "strings" "testing" "unicode/utf8" "go.mau.fi/util/glob" ) type mparams struct { input string pattern string } type MatchTest struct { mparams result bool } var matchTests = []MatchTest{ {mparams{"", ""}, true}, {mparams{"", "a"}, false}, {mparams{"a", ""}, false}, {mparams{"a", "a"}, true}, {mparams{"a", "b"}, false}, {mparams{"a", "a*"}, true}, {mparams{"a", "b*"}, false}, {mparams{"a", "*a"}, true}, {mparams{"a", "*b"}, false}, {mparams{"a", "*"}, true}, {mparams{"a", "a*?"}, false}, {mparams{"ab", "a*?"}, true}, {mparams{"a", "b*?"}, false}, {mparams{"a", "*a?"}, false}, {mparams{"ab", "*a?"}, true}, {mparams{"a", "*b?"}, false}, {mparams{"a", "*?"}, true}, {mparams{"a", "a*?b"}, false}, {mparams{"a", "a*?*"}, false}, {mparams{"ab", "a*?*"}, true}, {mparams{"a", "a*?*b"}, false}, {mparams{"a", "a*?*a"}, false}, {mparams{"aba", "a*?*a*"}, true}, {mparams{"hellomeowworld", "*meow*"}, true}, {mparams{"hellomeowworld", "hello*world"}, true}, {mparams{"hellomeowworld", "hellomeow*"}, true}, {mparams{"hellomeowworld", "*meowworld"}, true}, {mparams{"meowwoofhmmdsa!asddfdfhg", "meow**?*?***?*?***hmm**?*?***?*?***asd**?*?***?*?***"}, true}, } func TestCompile(t *testing.T) { for _, test := range matchTests { g := glob.Compile(test.pattern) if g.Match(test.input) != test.result { t.Errorf("Compile(%q).Match(%q) = %v; want %v", test.pattern, test.input, !test.result, test.result) } } } func BenchmarkCompile(b *testing.B) { for _, test := range matchTests { b.Run(test.pattern, func(b *testing.B) { g := glob.Compile(test.pattern) b.ResetTimer() for i := 0; i < b.N; i++ { g.Match(test.input) } }) } } func FuzzCompile(f *testing.F) { for _, test := range matchTests { f.Add(test.input, test.pattern) } f.Fuzz(func(t *testing.T, input, pattern string) { if !utf8.ValidString(pattern) || strings.ContainsRune(pattern, '\n') || strings.ContainsRune(input, '\n') { return } g := glob.CompileSimple(pattern) if g == nil { return } r, err := glob.CompileRegex(pattern) if err != nil { t.Fatalf("CompileRegex(%q) failed: %v", pattern, err) } if g.Match(input) != r.Match(input) { t.Errorf("Compile(%q).Match(%q) = %v; want %v", pattern, input, !r.Match(input), r.Match(input)) } }) } go-util-0.9.5/glob/regex.go000066400000000000000000000033061513243016000154660ustar00rootroot00000000000000package glob import ( "fmt" "io" "regexp" "strings" ) type RegexGlob struct { regex *regexp.Regexp } func (rg *RegexGlob) Match(s string) bool { return rg.regex.MatchString(s) } type swWriter interface { io.StringWriter io.Writer } // ToRegexPattern converts a glob pattern to a regex pattern and writes it to the given buffer. // // Only errors returned by the Write calls are returned by this function // (so if a non-erroring writer is used, this function will always return nil). func ToRegexPattern(pattern string, buf swWriter) error { for _, part := range SplitPattern(pattern) { if strings.ContainsRune(part, '*') || strings.ContainsRune(part, '?') { questions := strings.Count(part, "?") star := strings.ContainsRune(part, '*') if star { if questions > 0 { _, err := fmt.Fprintf(buf, ".{%d,}", questions) if err != nil { return err } } else { _, err := buf.WriteString(".*") if err != nil { return err } } } else if questions > 0 { _, err := fmt.Fprintf(buf, ".{%d}", questions) if err != nil { return err } } } else { _, err := buf.WriteString(regexp.QuoteMeta(part)) if err != nil { return err } } } return nil } // CompileRegex compiles the given glob pattern into a regex pattern. // // If you want the raw regex string, use [ToRegexPattern] instead func CompileRegex(pattern string) (*RegexGlob, error) { var buf strings.Builder buf.WriteRune('^') err := ToRegexPattern(pattern, &buf) if err != nil { // This will never actually happen return nil, err } buf.WriteRune('$') regex, err := regexp.Compile(buf.String()) if err != nil { return nil, err } return &RegexGlob{regex}, nil } go-util-0.9.5/glob/simple.go000066400000000000000000000036661513243016000156560ustar00rootroot00000000000000package glob import ( "strings" ) // ExactGlob is the result of [Compile] when the pattern contains no glob characters. // It uses a simple string comparison to match. type ExactGlob string func (eg ExactGlob) Match(s string) bool { return string(eg) == s } // SuffixGlob is the result of [Compile] when the pattern only has one `*` at the beginning. // It uses [strings.HasSuffix] to match. type SuffixGlob string func (sg SuffixGlob) Match(s string) bool { return strings.HasSuffix(s, string(sg)) } // PrefixGlob is the result of [Compile] when the pattern only has one `*` at the end. // It uses [strings.HasPrefix] to match. type PrefixGlob string func (pg PrefixGlob) Match(s string) bool { return strings.HasPrefix(s, string(pg)) } // ContainsGlob is the result of [Compile] when the pattern has two `*`s, one at the beginning and one at the end. // It uses [strings.Contains] to match. // // When there are exactly two `*`s, but they're not surrounding the string, the pattern is compiled as a [PrefixSuffixAndContainsGlob] instead. type ContainsGlob string func (cg ContainsGlob) Match(s string) bool { return strings.Contains(s, string(cg)) } // PrefixAndSuffixGlob is the result of [Compile] when the pattern only has one `*` in the middle. type PrefixAndSuffixGlob struct { Prefix string Suffix string } func (psg PrefixAndSuffixGlob) Match(s string) bool { return strings.HasPrefix(s, psg.Prefix) && strings.HasSuffix(s[len(psg.Prefix):], psg.Suffix) } // PrefixSuffixAndContainsGlob is the result of [Compile] when the pattern has two `*`s which are not surrounding the rest of the pattern. type PrefixSuffixAndContainsGlob struct { Prefix string Suffix string Contains string } func (psacg PrefixSuffixAndContainsGlob) Match(s string) bool { return strings.HasPrefix(s, psacg.Prefix) && strings.HasSuffix(s[len(psacg.Prefix):], psacg.Suffix) && strings.Contains(s[len(psacg.Prefix):len(s)-len(psacg.Suffix)], psacg.Contains) } go-util-0.9.5/glob/util.go000066400000000000000000000020061513243016000153250ustar00rootroot00000000000000package glob import ( "regexp" "strings" ) var redundantStarRegex = regexp.MustCompile(`\*{2,}`) var maybeRedundantQuestionRegex = regexp.MustCompile(`[*?]{2,}`) var wildcardRegex = regexp.MustCompile(`[*?]+`) func SplitPattern(pattern string) []string { indexes := wildcardRegex.FindAllStringIndex(pattern, -1) if len(indexes) == 0 { return []string{pattern} } parts := make([]string, 0, len(indexes)+1) start := 0 for _, part := range indexes { end := part[0] if end > start { parts = append(parts, pattern[start:end]) } parts = append(parts, pattern[part[0]:part[1]]) start = part[1] } if start < len(pattern) { parts = append(parts, pattern[start:]) } return parts } func Simplify(pattern string) string { pattern = redundantStarRegex.ReplaceAllString(pattern, "*") pattern = maybeRedundantQuestionRegex.ReplaceAllStringFunc(pattern, func(s string) string { if !strings.ContainsRune(s, '*') { return s } return strings.Repeat("?", strings.Count(s, "?")) + "*" }) return pattern } go-util-0.9.5/glob/util_test.go000066400000000000000000000024141513243016000163670ustar00rootroot00000000000000package glob_test import ( "strings" "testing" "go.mau.fi/util/glob" ) var simplifyTests = []struct { input string output string }{ {"", ""}, {"a", "a"}, {"*", "*"}, {"**", "*"}, {strings.Repeat("*", 9999), "*"}, {"a*", "a*"}, {"a**", "a*"}, {"a**b", "a*b"}, {"a**b**", "a*b*"}, {"a**b**c", "a*b*c"}, {"*?*", "?*"}, {"**????***", "????*"}, {"**?*?***?*?***", "????*"}, {"meow**?*?***?*?***hmm**?*?***?*?***asd**?*?***?*?***", "meow????*hmm????*asd????*"}, } func TestSimplify(t *testing.T) { for _, test := range simplifyTests { if got := glob.Simplify(test.input); got != test.output { t.Errorf("Simplify(%q) = %q; want %q", test.input, got, test.output) } } } func simplifyBySplitting(input string) string { parts := glob.SplitPattern(input) for i, part := range parts { if strings.ContainsRune(part, '*') { parts[i] = strings.Repeat("?", strings.Count(part, "?")) + "*" } } return strings.Join(parts, "") } func FuzzSimplify(f *testing.F) { f.Add(simplifyTests[0].input) f.Fuzz(func(t *testing.T, input string) { simplified := glob.Simplify(input) simplifiedBySplitting := simplifyBySplitting(input) if simplified != simplifiedBySplitting { t.Errorf("Simplify(%q) = %q; want %q", input, simplified, simplifiedBySplitting) } }) } go-util-0.9.5/gnuzip/000077500000000000000000000000001513243016000144145ustar00rootroot00000000000000go-util-0.9.5/gnuzip/gunzip.go000066400000000000000000000011001513243016000162470ustar00rootroot00000000000000// Copyright (C) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package gnuzip import ( "bytes" "compress/gzip" "errors" "io" ) func MaybeGUnzip(body []byte) ([]byte, error) { reader, err := gzip.NewReader(bytes.NewReader(body)) if err != nil { if errors.Is(err, gzip.ErrHeader) { return body, nil } else { return nil, err } } defer reader.Close() return io.ReadAll(reader) } go-util-0.9.5/gnuzip/gzip.go000066400000000000000000000010271513243016000157140ustar00rootroot00000000000000// Copyright (C) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package gnuzip import ( "bytes" "compress/gzip" ) func GZip(body []byte) ([]byte, error) { var compressedBuffer bytes.Buffer writer := gzip.NewWriter(&compressedBuffer) if _, err := writer.Write(body); err != nil { return nil, err } return compressedBuffer.Bytes(), writer.Close() } go-util-0.9.5/go.mod000066400000000000000000000013461513243016000142120ustar00rootroot00000000000000module go.mau.fi/util go 1.24.0 toolchain go1.25.6 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/mattn/go-sqlite3 v1.14.33 github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 github.com/rs/zerolog v1.34.0 github.com/stretchr/testify v1.11.1 golang.org/x/exp v0.0.0-20260112195511-716be5621a96 golang.org/x/mod v0.32.0 golang.org/x/net v0.49.0 golang.org/x/sys v0.40.0 golang.org/x/text v0.33.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.6.0 // indirect ) go-util-0.9.5/go.sum000066400000000000000000000102011513243016000142250ustar00rootroot00000000000000github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= go-util-0.9.5/jsonbytes/000077500000000000000000000000001513243016000151205ustar00rootroot00000000000000go-util-0.9.5/jsonbytes/unpadded.go000066400000000000000000000024411513243016000172340ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package jsonbytes import ( "encoding/base64" "encoding/json" ) // UnpaddedBytes is a byte slice that is encoded and decoded using // [base64.RawStdEncoding] instead of the default padded base64. type UnpaddedBytes []byte func (b UnpaddedBytes) MarshalJSON() ([]byte, error) { return json.Marshal(base64.RawStdEncoding.EncodeToString(b)) } func (b *UnpaddedBytes) UnmarshalJSON(data []byte) error { var b64str string err := json.Unmarshal(data, &b64str) if err != nil { return err } *b, err = base64.RawStdEncoding.DecodeString(b64str) return err } // UnpaddedURLBytes is a byte slice that is encoded and decoded using // [base64.RawURLEncoding] instead of the default padded base64. type UnpaddedURLBytes []byte func (b UnpaddedURLBytes) MarshalJSON() ([]byte, error) { return json.Marshal(base64.RawURLEncoding.EncodeToString(b)) } func (b *UnpaddedURLBytes) UnmarshalJSON(data []byte) error { var b64str string err := json.Unmarshal(data, &b64str) if err != nil { return err } *b, err = base64.RawURLEncoding.DecodeString(b64str) return err } go-util-0.9.5/jsontime/000077500000000000000000000000001513243016000147305ustar00rootroot00000000000000go-util-0.9.5/jsontime/duration.go000066400000000000000000000065231513243016000171120ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package jsontime import ( "database/sql/driver" "strconv" "time" ) func unmarshalDuration(into *time.Duration, jsonData []byte, unit time.Duration) error { val, err := strconv.ParseInt(string(jsonData), 10, 64) if err != nil { return err } *into = time.Duration(val) * unit return nil } func anyIntegerToDuration(src any, unit time.Duration, into *time.Duration) error { i64, err := anyIntegerTo64(src) if err != nil { return err } *into = time.Duration(i64) * unit return nil } type Seconds struct { time.Duration } func S(dur time.Duration) Seconds { return Seconds{Duration: dur} } func SInt(dur int) Seconds { return Seconds{Duration: time.Duration(dur) * time.Second} } func (s Seconds) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(int64(s.Seconds()), 10)), nil } func (s Seconds) Value() (driver.Value, error) { return int64(s.Seconds()), nil } func (s *Seconds) UnmarshalJSON(data []byte) error { return unmarshalDuration(&s.Duration, data, time.Second) } func (s *Seconds) Scan(src interface{}) error { return anyIntegerToDuration(src, time.Second, &s.Duration) } func (s *Seconds) Get() time.Duration { if s == nil { return 0 } return s.Duration } type Milliseconds struct { time.Duration } func MS(dur time.Duration) Milliseconds { return Milliseconds{Duration: dur} } func MSInt(dur int64) Milliseconds { return Milliseconds{Duration: time.Duration(dur) * time.Millisecond} } func (s Milliseconds) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(s.Milliseconds(), 10)), nil } func (s Milliseconds) Value() (driver.Value, error) { return s.Milliseconds(), nil } func (s *Milliseconds) UnmarshalJSON(data []byte) error { return unmarshalDuration(&s.Duration, data, time.Millisecond) } func (s *Milliseconds) Scan(src interface{}) error { return anyIntegerToDuration(src, time.Millisecond, &s.Duration) } func (s *Milliseconds) Get() time.Duration { if s == nil { return 0 } return s.Duration } type Microseconds struct { time.Duration } func (s Microseconds) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(s.Microseconds(), 10)), nil } func (s Microseconds) Value() (driver.Value, error) { return s.Microseconds(), nil } func (s *Microseconds) UnmarshalJSON(data []byte) error { return unmarshalDuration(&s.Duration, data, time.Microsecond) } func (s *Microseconds) Scan(src interface{}) error { return anyIntegerToDuration(src, time.Microsecond, &s.Duration) } func (s *Microseconds) Get() time.Duration { if s == nil { return 0 } return s.Duration } type Nanoseconds struct { time.Duration } func (s Nanoseconds) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(s.Nanoseconds(), 10)), nil } func (s Nanoseconds) Value() (driver.Value, error) { return s.Nanoseconds(), nil } func (s *Nanoseconds) UnmarshalJSON(data []byte) error { return unmarshalDuration(&s.Duration, data, time.Nanosecond) } func (s *Nanoseconds) Scan(src interface{}) error { return anyIntegerToDuration(src, time.Nanosecond, &s.Duration) } func (s *Nanoseconds) Get() time.Duration { if s == nil { return 0 } return s.Duration } go-util-0.9.5/jsontime/helpers.go000066400000000000000000000024551513243016000167270ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package jsontime import ( "time" ) func zeroSafeUnixToTime(val int64, fn func(int64) time.Time) time.Time { if val == 0 { return time.Time{} } return fn(val) } func UM(time time.Time) UnixMilli { return UnixMilli{Time: time} } func UMInt(ts int64) UnixMilli { return UM(zeroSafeUnixToTime(ts, time.UnixMilli)) } func UnixMilliNow() UnixMilli { return UM(time.Now()) } func UMicro(time time.Time) UnixMicro { return UnixMicro{Time: time} } func UMicroInt(ts int64) UnixMicro { return UMicro(zeroSafeUnixToTime(ts, time.UnixMicro)) } func UnixMicroNow() UnixMicro { return UMicro(time.Now()) } func UN(time time.Time) UnixNano { return UnixNano{Time: time} } func UNInt(ts int64) UnixNano { return UN(zeroSafeUnixToTime(ts, func(i int64) time.Time { return time.Unix(0, i) })) } func UnixNanoNow() UnixNano { return UN(time.Now()) } func U(time time.Time) Unix { return Unix{Time: time} } func UInt(ts int64) Unix { return U(zeroSafeUnixToTime(ts, func(i int64) time.Time { return time.Unix(i, 0) })) } func UnixNow() Unix { return U(time.Now()) } go-util-0.9.5/jsontime/integer.go000066400000000000000000000112451513243016000167170ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package jsontime import ( "database/sql" "database/sql/driver" "encoding/json" "errors" "fmt" "time" ) var ErrNotInteger = errors.New("value is not an integer") func parseTime(data []byte, unixConv func(int64) time.Time, into *time.Time) error { var val int64 err := json.Unmarshal(data, &val) if err != nil { return err } if val == 0 { *into = time.Time{} } else { *into = unixConv(val) } return nil } func anyIntegerTo64(src any) (int64, error) { switch v := src.(type) { case int: return int64(v), nil case int8: return int64(v), nil case int16: return int64(v), nil case int32: return int64(v), nil case int64: return v, nil default: return 0, fmt.Errorf("%w: %T", ErrNotInteger, src) } } func anyIntegerToTime(src any, unixConv func(int64) time.Time, into *time.Time) error { i64, err := anyIntegerTo64(src) if err != nil { return err } *into = unixConv(i64) return nil } func zeroSafeUnix(t time.Time, method func(time.Time) int64) int64 { if t.IsZero() { return 0 } return method(t) } var _ sql.Scanner = &UnixMilli{} var _ driver.Valuer = UnixMilli{} type UnixMilli struct { time.Time } func (um UnixMilli) MarshalJSON() ([]byte, error) { return json.Marshal(um.UnixMilli()) } func (um *UnixMilli) UnmarshalJSON(data []byte) error { return parseTime(data, time.UnixMilli, &um.Time) } func (um UnixMilli) Value() (driver.Value, error) { return um.UnixMilli(), nil } func (um *UnixMilli) Scan(src any) error { return anyIntegerToTime(src, time.UnixMilli, &um.Time) } func (um UnixMilli) Unix() int64 { return zeroSafeUnix(um.Time, time.Time.Unix) } func (um UnixMilli) UnixMilli() int64 { return zeroSafeUnix(um.Time, time.Time.UnixMilli) } func (um UnixMilli) UnixMicro() int64 { return zeroSafeUnix(um.Time, time.Time.UnixMicro) } func (um UnixMilli) UnixNano() int64 { return zeroSafeUnix(um.Time, time.Time.UnixNano) } var _ sql.Scanner = &UnixMicro{} var _ driver.Valuer = UnixMicro{} type UnixMicro struct { time.Time } func (um UnixMicro) MarshalJSON() ([]byte, error) { return json.Marshal(um.UnixMicro()) } func (um *UnixMicro) UnmarshalJSON(data []byte) error { return parseTime(data, time.UnixMicro, &um.Time) } func (um UnixMicro) Value() (driver.Value, error) { return um.UnixMicro(), nil } func (um *UnixMicro) Scan(src any) error { return anyIntegerToTime(src, time.UnixMicro, &um.Time) } func (um UnixMicro) Unix() int64 { return zeroSafeUnix(um.Time, time.Time.Unix) } func (um UnixMicro) UnixMilli() int64 { return zeroSafeUnix(um.Time, time.Time.UnixMilli) } func (um UnixMicro) UnixMicro() int64 { return zeroSafeUnix(um.Time, time.Time.UnixMicro) } func (um UnixMicro) UnixNano() int64 { return zeroSafeUnix(um.Time, time.Time.UnixNano) } var _ sql.Scanner = &UnixNano{} var _ driver.Valuer = UnixNano{} type UnixNano struct { time.Time } func (un UnixNano) MarshalJSON() ([]byte, error) { return json.Marshal(un.UnixNano()) } func (un *UnixNano) UnmarshalJSON(data []byte) error { return parseTime(data, func(i int64) time.Time { return time.Unix(0, i) }, &un.Time) } func (un UnixNano) Value() (driver.Value, error) { return un.UnixNano(), nil } func (un *UnixNano) Scan(src any) error { return anyIntegerToTime(src, func(i int64) time.Time { return time.Unix(0, i) }, &un.Time) } func (un UnixNano) Unix() int64 { return zeroSafeUnix(un.Time, time.Time.Unix) } func (un UnixNano) UnixMilli() int64 { return zeroSafeUnix(un.Time, time.Time.UnixMilli) } func (un UnixNano) UnixMicro() int64 { return zeroSafeUnix(un.Time, time.Time.UnixMicro) } func (un UnixNano) UnixNano() int64 { return zeroSafeUnix(un.Time, time.Time.UnixNano) } type Unix struct { time.Time } func (u Unix) MarshalJSON() ([]byte, error) { return json.Marshal(u.Unix()) } var _ sql.Scanner = &Unix{} var _ driver.Valuer = Unix{} func (u *Unix) UnmarshalJSON(data []byte) error { return parseTime(data, func(i int64) time.Time { return time.Unix(i, 0) }, &u.Time) } func (u Unix) Value() (driver.Value, error) { return u.Unix(), nil } func (u *Unix) Scan(src any) error { return anyIntegerToTime(src, func(i int64) time.Time { return time.Unix(i, 0) }, &u.Time) } func (u Unix) Unix() int64 { return zeroSafeUnix(u.Time, time.Time.Unix) } func (u Unix) UnixMilli() int64 { return zeroSafeUnix(u.Time, time.Time.UnixMilli) } func (u Unix) UnixMicro() int64 { return zeroSafeUnix(u.Time, time.Time.UnixMicro) } func (u Unix) UnixNano() int64 { return zeroSafeUnix(u.Time, time.Time.UnixNano) } go-util-0.9.5/jsontime/string.go000066400000000000000000000040341513243016000165660ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package jsontime import ( "encoding/json" "strconv" "time" ) func parseTimeString(data []byte, unixConv func(int64) time.Time, into *time.Time) error { var strVal string err := json.Unmarshal(data, &strVal) if err != nil { return err } val, err := strconv.ParseInt(strVal, 10, 64) if err != nil { return err } if val == 0 { *into = time.Time{} } else { *into = unixConv(val) } return nil } type UnixMilliString struct { time.Time } func (um UnixMilliString) MarshalJSON() ([]byte, error) { if um.IsZero() { return []byte{'"', '0', '"'}, nil } return json.Marshal(strconv.FormatInt(um.UnixMilli(), 10)) } func (um *UnixMilliString) UnmarshalJSON(data []byte) error { return parseTimeString(data, time.UnixMilli, &um.Time) } type UnixMicroString struct { time.Time } func (um UnixMicroString) MarshalJSON() ([]byte, error) { if um.IsZero() { return []byte{'"', '0', '"'}, nil } return json.Marshal(strconv.FormatInt(um.UnixMicro(), 10)) } func (um *UnixMicroString) UnmarshalJSON(data []byte) error { return parseTimeString(data, time.UnixMicro, &um.Time) } type UnixNanoString struct { time.Time } func (um UnixNanoString) MarshalJSON() ([]byte, error) { if um.IsZero() { return []byte{'"', '0', '"'}, nil } return json.Marshal(strconv.FormatInt(um.UnixNano(), 10)) } func (um *UnixNanoString) UnmarshalJSON(data []byte) error { return parseTimeString(data, func(i int64) time.Time { return time.Unix(0, i) }, &um.Time) } type UnixString struct { time.Time } func (u UnixString) MarshalJSON() ([]byte, error) { if u.IsZero() { return []byte{'"', '0', '"'}, nil } return json.Marshal(strconv.FormatInt(u.Unix(), 10)) } func (u *UnixString) UnmarshalJSON(data []byte) error { return parseTimeString(data, func(i int64) time.Time { return time.Unix(i, 0) }, &u.Time) } go-util-0.9.5/lottie/000077500000000000000000000000001513243016000144005ustar00rootroot00000000000000go-util-0.9.5/lottie/convert.go000066400000000000000000000110711513243016000164070ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package lottie import ( "context" "fmt" "io" "os" "os/exec" "path/filepath" "strconv" "github.com/rs/zerolog" "go.mau.fi/util/exzerolog" "go.mau.fi/util/ffmpeg" ) var lottieconverterPath string func init() { lottieconverterPath, _ = exec.LookPath("lottieconverter") } // Supported returns whether lottieconverter is available on the system. // // lottieconverter is considered to be available if a binary called // lottieconverter is found in $PATH, or if [SetPath] has been called // explicitly with a non-empty path. func Supported() bool { return lottieconverterPath != "" } // SetPath overrides the path to the lottieconverter binary. func SetPath(path string) { lottieconverterPath = path } // Convert converts lottie data an image or image(s) using lottieconverter. // // Args: // - input: an io.Reader containing the lottie data to convert. // - outputFilename: the filename to write the output to. // - outputWriter: an io.Writer to write the output to. // - format: the output format. Can be one of: png, gif, or pngs. // - width: the width of the output image(s). // - height: the height of the output image(s). // - extraArgs: additional arguments to pass to lottieconverter. // // The outputFilename and outputWriter parameters are mutually exclusive. func Convert(ctx context.Context, input io.Reader, outputFilename string, outputWriter io.Writer, format string, width, height int, extraArgs ...string) error { // Verify the input parameters and calculate the actual outputFilename that // will be used when shelling out to lottieconverter. // // We are panicking here because it's a programming error to call this // function with invalid parameters. if outputFilename == "" && outputWriter == nil { panic("lottie.Convert: either outputFile or outputWriter must be provided") } else if outputWriter != nil { if outputFilename != "" { panic("lottie.Convert: only one of outputFile or outputWriter can be provided") } outputFilename = "-" } args := []string{"-", outputFilename, format, fmt.Sprintf("%dx%d", width, height)} args = append(args, extraArgs...) cmd := exec.CommandContext(ctx, lottieconverterPath, args...) cmd.Stdin = input cmd.Stdout = outputWriter log := zerolog.Ctx(ctx).With().Str("command", "lottieconverter").Logger() cmd.Stderr = exzerolog.NewLogWriter(log).WithLevel(zerolog.WarnLevel) if err := cmd.Run(); err != nil { return fmt.Errorf("lottieconverter error: %w", err) } return nil } // FFmpegConvert converts lottie data to a video or image using ffmpeg. // // This function should only be called if [ffmpeg.Supported] returns true. // // Args: // - input: an io.Reader containing the lottie data to convert. // - outputFile: the filename to write the output to. Must have .webp or .webm extension. // - width: the width of the output video or image. // - height: the height of the output video or image. // - fps: the framerate of the output video. // // Returns: the converted data as a *bytes.Buffer, the mimetype of the output, // and the thumbnail data as a PNG. func FFmpegConvert(ctx context.Context, input io.Reader, outputFile string, width, height, fps int) (thumbnailData []byte, err error) { if !ffmpeg.Supported() { return nil, fmt.Errorf("ffmpeg is not available") } tmpDir, err := os.MkdirTemp("", "lottieconvert") if err != nil { return } defer os.RemoveAll(tmpDir) err = Convert(ctx, input, tmpDir+"/out_", nil, "pngs", width, height, strconv.Itoa(fps)) if err != nil { return } files, err := os.ReadDir(tmpDir) if err != nil { return } var firstFrameName string for _, file := range files { if firstFrameName == "" || file.Name() < firstFrameName { firstFrameName = file.Name() } } thumbnailData, err = os.ReadFile(fmt.Sprintf("%s/%s", tmpDir, firstFrameName)) if err != nil { return } var outputArgs []string switch filepath.Ext(outputFile) { case ".webm": outputArgs = []string{"-c:v", "libvpx-vp9", "-pix_fmt", "yuva420p", "-f", "webm"} case ".webp": outputArgs = []string{"-c:v", "libwebp_anim", "-pix_fmt", "yuva420p", "-f", "webp", "-loop", "0"} default: err = fmt.Errorf("unsupported extension %s", filepath.Ext(outputFile)) return } err = ffmpeg.ConvertPathWithDestination( ctx, tmpDir+"/out_*.png", outputFile, []string{"-framerate", strconv.Itoa(fps), "-pattern_type", "glob"}, outputArgs, false, ) return } go-util-0.9.5/pblite/000077500000000000000000000000001513243016000143575ustar00rootroot00000000000000go-util-0.9.5/pblite/deserialize.go000066400000000000000000000156001513243016000172100ustar00rootroot00000000000000package pblite import ( "encoding/base64" "encoding/json" "fmt" "strconv" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/descriptorpb" ) func Unmarshal(data []byte, m proto.Message) error { var anyData any if err := json.Unmarshal(data, &anyData); err != nil { return err } anyDataArr, ok := anyData.([]any) if !ok { return fmt.Errorf("expected array in JSON, got %T", anyData) } return deserializeFromSlice(anyDataArr, m.ProtoReflect()) } func isPbliteBinary(descriptor protoreflect.FieldDescriptor) bool { opts := descriptor.Options().(*descriptorpb.FieldOptions) pbliteBinary, ok := proto.GetExtension(opts, E_PbliteBinary).(bool) return ok && pbliteBinary } func deserializeOne(val any, index int, ref protoreflect.Message, insideList protoreflect.List, fieldDescriptor protoreflect.FieldDescriptor) (protoreflect.Value, error) { var num float64 var expectedKind, str string var boolean, ok bool var outputVal protoreflect.Value if fieldDescriptor.IsList() && insideList == nil { nestedData, ok := val.([]any) if !ok { return outputVal, fmt.Errorf("expected untyped array at index %d for repeated field %s, got %T", index, fieldDescriptor.FullName(), val) } list := ref.NewField(fieldDescriptor).List() list.NewElement() for i, nestedVal := range nestedData { nestedParsed, err := deserializeOne(nestedVal, i, ref, list, fieldDescriptor) if err != nil { return outputVal, err } list.Append(nestedParsed) } return protoreflect.ValueOfList(list), nil } switch fieldDescriptor.Kind() { case protoreflect.MessageKind: ok = true var nestedMessage protoreflect.Message if insideList != nil { nestedMessage = insideList.NewElement().Message() } else { nestedMessage = ref.NewField(fieldDescriptor).Message() } if isPbliteBinary(fieldDescriptor) { bytesBase64, ok := val.(string) if !ok { return outputVal, fmt.Errorf("expected string at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val) } bytes, err := base64.StdEncoding.DecodeString(bytesBase64) if err != nil { return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } err = proto.Unmarshal(bytes, nestedMessage.Interface()) if err != nil { return outputVal, fmt.Errorf("failed to unmarshal binary protobuf at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } } else { nestedData, ok := val.([]any) if !ok { return outputVal, fmt.Errorf("expected untyped array at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val) } if err := deserializeFromSlice(nestedData, nestedMessage); err != nil { return outputVal, err } } outputVal = protoreflect.ValueOfMessage(nestedMessage) case protoreflect.BytesKind: ok = true bytesBase64, ok := val.(string) if !ok { return outputVal, fmt.Errorf("expected string at index %d for field %s, got %T", index, fieldDescriptor.FullName(), val) } bytes, err := base64.StdEncoding.DecodeString(bytesBase64) if err != nil { return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } outputVal = protoreflect.ValueOfBytes(bytes) case protoreflect.EnumKind: num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfEnum(protoreflect.EnumNumber(int32(num))) case protoreflect.Int32Kind: if str, ok = val.(string); ok { parsedVal, err := strconv.ParseInt(str, 10, 32) if err != nil { return outputVal, fmt.Errorf("failed to parse int32 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } outputVal = protoreflect.ValueOfInt32(int32(parsedVal)) } else { num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfInt32(int32(num)) } case protoreflect.Int64Kind: if str, ok = val.(string); ok { parsedVal, err := strconv.ParseInt(str, 10, 64) if err != nil { return outputVal, fmt.Errorf("failed to parse int64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } outputVal = protoreflect.ValueOfInt64(parsedVal) } else { num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfInt64(int64(num)) } case protoreflect.Uint32Kind: if str, ok = val.(string); ok { parsedVal, err := strconv.ParseUint(str, 10, 32) if err != nil { return outputVal, fmt.Errorf("failed to parse uint32 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } outputVal = protoreflect.ValueOfUint32(uint32(parsedVal)) } else { num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfUint32(uint32(num)) } case protoreflect.Uint64Kind: if str, ok = val.(string); ok { parsedVal, err := strconv.ParseUint(str, 10, 64) if err != nil { return outputVal, fmt.Errorf("failed to parse uint64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } outputVal = protoreflect.ValueOfUint64(parsedVal) } else { num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfUint64(uint64(num)) } case protoreflect.FloatKind: num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfFloat32(float32(num)) case protoreflect.DoubleKind: num, ok = val.(float64) expectedKind = "float64" outputVal = protoreflect.ValueOfFloat64(num) case protoreflect.StringKind: str, ok = val.(string) if ok && isPbliteBinary(fieldDescriptor) { bytes, err := base64.StdEncoding.DecodeString(str) if err != nil { return outputVal, fmt.Errorf("failed to decode base64 at index %d for field %s: %w", index, fieldDescriptor.FullName(), err) } str = string(bytes) } expectedKind = "string" outputVal = protoreflect.ValueOfString(str) case protoreflect.BoolKind: expectedKind = "bool or float" var float float64 if boolean, ok = val.(bool); ok { outputVal = protoreflect.ValueOfBool(boolean) } else if float, ok = val.(float64); ok { outputVal = protoreflect.ValueOfBool(float != 0) } default: return outputVal, fmt.Errorf("unsupported field type %s in %s", fieldDescriptor.Kind(), fieldDescriptor.FullName()) } if !ok { return outputVal, fmt.Errorf("expected %s at index %d for field %s, got %T", expectedKind, index, fieldDescriptor.FullName(), val) } return outputVal, nil } func deserializeFromSlice(data []any, ref protoreflect.Message) error { for i := 0; i < ref.Descriptor().Fields().Len(); i++ { fieldDescriptor := ref.Descriptor().Fields().Get(i) index := int(fieldDescriptor.Number()) - 1 if index < 0 || index >= len(data) || data[index] == nil { continue } val := data[index] outputVal, err := deserializeOne(val, index, ref, nil, fieldDescriptor) if err != nil { return err } ref.Set(fieldDescriptor, outputVal) } return nil } go-util-0.9.5/pblite/pblite.pb.go000066400000000000000000000044631513243016000165740ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 // protoc v3.21.12 // source: pblite.proto package pblite import ( reflect "reflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" descriptorpb "google.golang.org/protobuf/types/descriptorpb" _ "embed" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) var file_pblite_proto_extTypes = []protoimpl.ExtensionInfo{ { ExtendedType: (*descriptorpb.FieldOptions)(nil), ExtensionType: (*bool)(nil), Field: 50000, Name: "pblite.pblite_binary", Tag: "varint,50000,opt,name=pblite_binary", Filename: "pblite.proto", }, } // Extension fields to descriptorpb.FieldOptions. var ( // optional bool pblite_binary = 50000; E_PbliteBinary = &file_pblite_proto_extTypes[0] ) var File_pblite_proto protoreflect.FileDescriptor //go:embed pblite.pb.raw var file_pblite_proto_rawDesc []byte var file_pblite_proto_goTypes = []any{ (*descriptorpb.FieldOptions)(nil), // 0: google.protobuf.FieldOptions } var file_pblite_proto_depIdxs = []int32{ 0, // 0: pblite.pblite_binary:extendee -> google.protobuf.FieldOptions 1, // [1:1] is the sub-list for method output_type 1, // [1:1] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 0, // [0:1] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_pblite_proto_init() } func file_pblite_proto_init() { if File_pblite_proto != nil { return } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pblite_proto_rawDesc, NumEnums: 0, NumMessages: 0, NumExtensions: 1, NumServices: 0, }, GoTypes: file_pblite_proto_goTypes, DependencyIndexes: file_pblite_proto_depIdxs, ExtensionInfos: file_pblite_proto_extTypes, }.Build() File_pblite_proto = out.File file_pblite_proto_rawDesc = nil file_pblite_proto_goTypes = nil file_pblite_proto_depIdxs = nil } go-util-0.9.5/pblite/pblite.pb.raw000066400000000000000000000002261513243016000167510ustar00rootroot00000000000000 pblite.protopblite google/protobuf/descriptor.proto:G pblite_binary.google.protobuf.FieldOptionsะ† (R pbliteBinaryˆB Z ../pblitebproto3go-util-0.9.5/pblite/pblite.proto000066400000000000000000000003031513243016000167170ustar00rootroot00000000000000syntax = "proto3"; package pblite; option go_package = "../pblite"; import "google/protobuf/descriptor.proto"; extend google.protobuf.FieldOptions { optional bool pblite_binary = 50000; } go-util-0.9.5/pblite/serialize.go000066400000000000000000000075701513243016000167060ustar00rootroot00000000000000package pblite /* in protobuf, a message looks like this: message SomeMessage { string stringField1 = 1; int64 intField = 6; bytes byteField = 9; } but when this function is done serializing this protobuf message into a slice, it should look something like this: [ "someString", nil, nil, nil, nil, 6, nil, nil, "\x9\x91\x942" ] Any integer should be translated into int64, it doesn't matter if it's defined as int32 in the proto schema. In the finished serialized slice it should be int64. Let's also take in count where there is a message nested inside a message: message SomeMessage { string stringField1 = 1; NestedMessage1 nestedMessage1 = 2; int64 intField = 6; bytes byteField = 9; } message NestedMessage1 { string msg1 = 1; } Then the serialized output would be: [ "someString", ["msg1FieldValue"], nil, nil, nil, 6, nil, nil, "\x9\x91\x942" ] This means that any slice inside of the current slice, indicates another message nested inside of it. */ import ( "encoding/base64" "encoding/json" "fmt" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" ) func serializeOneOrList(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) { switch { case fieldDescriptor.IsList(): var serializedList []any list := fieldValue.List() for i := 0; i < list.Len(); i++ { serialized, err := serializeOne(fieldDescriptor, list.Get(i)) if err != nil { return nil, err } serializedList = append(serializedList, serialized) } return serializedList, nil default: return serializeOne(fieldDescriptor, fieldValue) } } func serializeOne(fieldDescriptor protoreflect.FieldDescriptor, fieldValue protoreflect.Value) (any, error) { switch fieldDescriptor.Kind() { case protoreflect.MessageKind: if isPbliteBinary(fieldDescriptor) { serializedMsg, err := proto.Marshal(fieldValue.Message().Interface()) if err != nil { return nil, err } return base64.StdEncoding.EncodeToString(serializedMsg), nil } else { serializedMsg, err := SerializeToSlice(fieldValue.Message().Interface()) if err != nil { return nil, err } return serializedMsg, nil } case protoreflect.BytesKind: return base64.StdEncoding.EncodeToString(fieldValue.Bytes()), nil case protoreflect.Int32Kind, protoreflect.Int64Kind: return fieldValue.Int(), nil case protoreflect.Uint32Kind, protoreflect.Uint64Kind: return fieldValue.Uint(), nil case protoreflect.FloatKind, protoreflect.DoubleKind: return fieldValue.Float(), nil case protoreflect.EnumKind: return int(fieldValue.Enum()), nil case protoreflect.BoolKind: return fieldValue.Bool(), nil case protoreflect.StringKind: if isPbliteBinary(fieldDescriptor) { return base64.StdEncoding.EncodeToString([]byte(fieldValue.String())), nil } else { return fieldValue.String(), nil } default: return nil, fmt.Errorf("unsupported field type %s in %s", fieldDescriptor.Kind(), fieldDescriptor.FullName()) } } func SerializeToSlice(msg proto.Message) ([]any, error) { ref := msg.ProtoReflect() maxFieldNumber := 0 for i := 0; i < ref.Descriptor().Fields().Len(); i++ { fieldNumber := int(ref.Descriptor().Fields().Get(i).Number()) if fieldNumber > maxFieldNumber { maxFieldNumber = fieldNumber } } serialized := make([]any, maxFieldNumber) for i := 0; i < ref.Descriptor().Fields().Len(); i++ { fieldDescriptor := ref.Descriptor().Fields().Get(i) fieldValue := ref.Get(fieldDescriptor) fieldNumber := int(fieldDescriptor.Number()) if !ref.Has(fieldDescriptor) { continue } serializedVal, err := serializeOneOrList(fieldDescriptor, fieldValue) if err != nil { return nil, err } serialized[fieldNumber-1] = serializedVal } return serialized, nil } func Marshal(m proto.Message) ([]byte, error) { serialized, err := SerializeToSlice(m) if err != nil { return nil, err } return json.Marshal(serialized) } go-util-0.9.5/progress/000077500000000000000000000000001513243016000147445ustar00rootroot00000000000000go-util-0.9.5/progress/doc.go000066400000000000000000000006751513243016000160500ustar00rootroot00000000000000// Copyright (c) 2024 Sumner Evans // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package progress provides wrappers for [io.Writer] and [io.Reader] that // report the progress of the read or write operation via a callback. package progress const defaultUpdateInterval = 256 * 1024 go-util-0.9.5/progress/reader.go000066400000000000000000000035121513243016000165360ustar00rootroot00000000000000package progress import ( "fmt" "io" ) // Reader is an [io.ReadSeekCloser] that reports the number of bytes read from it via a callback. // // The callback is called at most every "updateInterval" bytes. // The updateInterval can be set using the [Reader.WithUpdateInterval] method. // The callback will also be called whenever [Reader.Seek] is called. // // The following is an example of how to use [Reader] to report the progress of // reading from a file: // // file, _ := os.Open("file.txt") // progressReader := NewReader(f, func(readBytes int) { // fmt.Printf("Read %d bytes\n", readBytes) // }) // io.ReadAll(progressReader) type Reader struct { inner io.Reader readBytes int progressFn func(readBytes int) lastUpdate int updateInterval int } func NewReader(r io.Reader, progressFn func(readBytes int)) *Reader { return &Reader{inner: r, progressFn: progressFn, updateInterval: defaultUpdateInterval} } func (r *Reader) WithUpdateInterval(bytes int) *Reader { r.updateInterval = bytes return r } func (r *Reader) Read(p []byte) (n int, err error) { n, err = r.inner.Read(p) if err != nil { return n, err } r.readBytes += n if r.lastUpdate == 0 || r.readBytes-r.lastUpdate > r.updateInterval { r.progressFn(r.readBytes) r.lastUpdate = r.readBytes } return n, nil } func (r *Reader) Close() error { if closer, ok := r.inner.(io.Closer); ok { return closer.Close() } return nil } func (r *Reader) Seek(offset int64, whence int) (int64, error) { seeker, ok := r.inner.(io.ReadSeeker) if !ok { return 0, fmt.Errorf("progress.Reader: source reader (%T) is not an io.ReadSeeker", r.inner) } n, err := seeker.Seek(offset, whence) if err != nil { return 0, err } r.readBytes = int(n) r.progressFn(r.readBytes) r.lastUpdate = r.readBytes return n, nil } var _ io.ReadSeekCloser = (*Reader)(nil) go-util-0.9.5/progress/reader_test.go000066400000000000000000000025621513243016000176010ustar00rootroot00000000000000package progress_test import ( "bytes" "errors" "io" "testing" "github.com/stretchr/testify/assert" "go.mau.fi/util/progress" ) func TestReader(t *testing.T) { reader := bytes.NewReader(bytes.Repeat([]byte{42}, 1024*1024)) var progressUpdates []int progressReader := progress.NewReader(reader, func(readBytes int) { progressUpdates = append(progressUpdates, readBytes) }) data, err := io.ReadAll(progressReader) assert.NoError(t, err) assert.Equal(t, data, bytes.Repeat([]byte{42}, 1024*1024)) assert.Greater(t, len(progressUpdates), 1) assert.IsIncreasing(t, progressUpdates) } type testReader struct { *bytes.Reader closed bool } func (r *testReader) Close() error { if r.closed { return errors.New("already closed") } r.closed = true return nil } func TestReadCloser(t *testing.T) { readCloser := &testReader{Reader: bytes.NewReader(bytes.Repeat([]byte{42}, 1024*1024))} var progressUpdates []int progressReader := progress.NewReader(readCloser, func(readBytes int) { progressUpdates = append(progressUpdates, readBytes) }) data, err := io.ReadAll(progressReader) assert.NoError(t, err) assert.Equal(t, data, bytes.Repeat([]byte{42}, 1024*1024)) assert.Greater(t, len(progressUpdates), 1) assert.IsIncreasing(t, progressUpdates) assert.NoError(t, progressReader.Close()) assert.ErrorContains(t, progressReader.Close(), "already closed") } go-util-0.9.5/progress/writer.go000066400000000000000000000024701513243016000166120ustar00rootroot00000000000000package progress import "io" // Writer is an [io.Writer] that reports the number of bytes written to it via // a callback. The callback is called at most every "updateInterval" bytes. The // updateInterval can be set using the [Writer.WithUpdateInterval] method. // // The following is an example of how to use [Writer] to report the progress of // writing to a file: // // file, _ := os.Create("file.txt") // progressWriter := progress.NewWriter(func(processedBytes int) { // fmt.Printf("Processed %d bytes\n", processedBytes) // }) // writerWithProgress := io.MultiWriter(file, progressWriter) // io.Copy(writerWithProgress, bytes.NewReader(bytes.Repeat([]byte{42}, 1024*1024))) type Writer struct { processedBytes int progressFn func(processedBytes int) lastUpdate int updateInterval int } func NewWriter(progressFn func(processedBytes int)) *Writer { return &Writer{progressFn: progressFn, updateInterval: defaultUpdateInterval} } func (w *Writer) WithUpdateInterval(bytes int) *Writer { w.updateInterval = bytes return w } func (w *Writer) Write(p []byte) (n int, err error) { w.processedBytes += len(p) if w.lastUpdate == 0 || w.processedBytes-w.lastUpdate > w.updateInterval { w.progressFn(w.processedBytes) w.lastUpdate = w.processedBytes } return len(p), nil } var _ io.Writer = (*Writer)(nil) go-util-0.9.5/progress/writer_test.go000066400000000000000000000011151513243016000176440ustar00rootroot00000000000000package progress_test import ( "bytes" "io" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mau.fi/util/progress" ) func TestWriter(t *testing.T) { var progressUpdates []int progressWriter := progress.NewWriter(func(processedBytes int) { progressUpdates = append(progressUpdates, processedBytes) }) for i := 0; i < 10; i++ { _, err := io.Copy(progressWriter, bytes.NewReader(bytes.Repeat([]byte{42}, 256*1024))) require.NoError(t, err) } assert.Greater(t, len(progressUpdates), 1) assert.IsIncreasing(t, progressUpdates) } go-util-0.9.5/progver/000077500000000000000000000000001513243016000145645ustar00rootroot00000000000000go-util-0.9.5/progver/progver.go000066400000000000000000000061651513243016000166070ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package progver import ( "fmt" "runtime" "runtime/debug" "strings" "time" ) type ProgramVersion struct { // These should be hardcoded Name string URL string BaseVersion string SemCalVer bool // These are set by the values passed to InitVersion Commit string Tag string BuildTime time.Time // These are computed by InitVersion IsRelease bool FormattedVersion string LinkifiedVersion string VersionDescription string } func (pv ProgramVersion) MarkdownDescription() string { return fmt.Sprintf("[%s](%s) %s (%s)", pv.Name, pv.URL, pv.LinkifiedVersion, pv.BuildTime.Format(time.RFC1123)) } func findCommitFromBuildInfo() string { info, _ := debug.ReadBuildInfo() if info == nil { return "" } for _, setting := range info.Settings { if setting.Key == "vcs.revision" && len(setting.Value) >= 40 { return setting.Value } } return "" } func (pv ProgramVersion) Init(tag, commit, rawBuildTime string) ProgramVersion { if commit == "" || commit == "unknown" { commit = findCommitFromBuildInfo() } if tag == "unknown" { tag = "" } pv.Tag = tag baseVersion := strings.TrimPrefix(pv.BaseVersion, "v") tag = strings.TrimPrefix(tag, "v") if pv.SemCalVer && len(tag) > 0 { tag = semverToCalver(tag) } if tag == baseVersion || tag == baseVersion+".0" { pv.IsRelease = true pv.FormattedVersion = "v" + baseVersion pv.LinkifiedVersion = fmt.Sprintf("[%s](%s/releases/%s)", pv.FormattedVersion, pv.URL, pv.Tag) } else { suffix := "" if !strings.HasSuffix(baseVersion, "+dev") { suffix = "+dev" } if len(commit) > 8 { pv.FormattedVersion = fmt.Sprintf("v%s%s.%s", baseVersion, suffix, commit[:8]) } else { pv.FormattedVersion = fmt.Sprintf("v%s%s.unknown", baseVersion, suffix) } if len(commit) > 8 { pv.LinkifiedVersion = strings.Replace(pv.FormattedVersion, commit[:8], fmt.Sprintf("[%s](%s/commit/%s)", commit[:8], pv.URL, commit), 1) } else { pv.LinkifiedVersion = pv.FormattedVersion } } var buildTime time.Time if rawBuildTime != "unknown" { buildTime, _ = time.Parse(time.RFC3339, rawBuildTime) } var builtWith string if buildTime.IsZero() { rawBuildTime = "unknown" builtWith = fmt.Sprintf("built with %s", runtime.Version()) } else { rawBuildTime = buildTime.Format(time.RFC1123) builtWith = fmt.Sprintf("built at %s with %s", rawBuildTime, runtime.Version()) } pv.VersionDescription = fmt.Sprintf("%s %s (%s)", pv.Name, pv.FormattedVersion, builtWith) pv.Commit = commit if pv.Commit == "" { pv.Commit = "unknown" } pv.BuildTime = buildTime return pv } func semverToCalver(semver string) string { parts := strings.SplitN(semver, ".", 3) if len(parts) != 3 { panic(fmt.Errorf("invalid semver: %s", semver)) } if len(parts[1]) != 4 { panic(fmt.Errorf("invalid minor semver component for calendar versioning: %s", parts[1])) } return parts[1][:2] + "." + parts[1][2:] + "." + parts[2] } go-util-0.9.5/ptr/000077500000000000000000000000001513243016000137055ustar00rootroot00000000000000go-util-0.9.5/ptr/ptr.go000066400000000000000000000020421513243016000150370ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package ptr // Clone creates a shallow copy of the given pointer. func Clone[T any](val *T) *T { if val == nil { return nil } valCopy := *val return &valCopy } // Ptr returns a pointer to the given value. func Ptr[T any](val T) *T { return &val } // NonZero returns a pointer to the given comparable value, unless the value is the type's zero value. func NonZero[T comparable](val T) *T { var zero T return NonDefault(val, zero) } // NonDefault returns a pointer to the first parameter, unless it is equal to the second parameter. func NonDefault[T comparable](val, def T) *T { if val == def { return nil } return &val } // Val returns the value of the given pointer, or the zero value of the type if the pointer is nil. func Val[T any](ptr *T) (val T) { if ptr != nil { val = *ptr } return } go-util-0.9.5/random/000077500000000000000000000000001513243016000143605ustar00rootroot00000000000000go-util-0.9.5/random/bytes.go000066400000000000000000000007721513243016000160430ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package random import ( "crypto/rand" ) // Bytes generates the given amount of random bytes using crypto/rand, and panics if it fails. func Bytes(n int) []byte { data := make([]byte, n) _, err := rand.Read(data) if err != nil { panic(err) } return data } go-util-0.9.5/random/string.go000066400000000000000000000076561513243016000162330ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package random import ( "encoding/binary" "hash/crc32" "strings" "go.mau.fi/util/exbytes" ) const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // StringBytes generates a random string of the given length and returns it as a byte array. func StringBytes(n int) []byte { return StringBytesCharset(n, letters) } // AppendSequence generates a random sequence of the given length using the given character set // and appends it to the given output slice. func AppendSequence[T any](n int, charset, output []T) []T { if n <= 0 { return output } if output == nil { output = make([]T, 0, n) } // If risk of modulo bias is too high, use 32-bit integers as source instead of 16-bit. if 65536%len(charset) < 200 { input := Bytes(n * 2) for i := 0; i < n; i++ { output = append(output, charset[binary.BigEndian.Uint16(input[i*2:])%uint16(len(charset))]) } } else { input := Bytes(n * 4) for i := 0; i < n; i++ { output = append(output, charset[binary.BigEndian.Uint32(input[i*4:])%uint32(len(charset))]) } } return output } // StringBytesCharset generates a random string of the given length using the given character set and returns it as a byte array. // Note that the character set must be ASCII. For arbitrary Unicode, use [AppendSequence] with a `[]rune`. func StringBytesCharset(n int, charset string) []byte { if n <= 0 { return []byte{} } input := Bytes(n * 2) for i := 0; i < n; i++ { // The risk of modulo bias is (65536 % len(charset)) / 65536. // For the default charset, that's 2 in 65536 or 0.003 %. input[i] = charset[binary.BigEndian.Uint16(input[i*2:])%uint16(len(charset))] } input = input[:n] return input } // String generates a random string of the given length. func String(n int) string { if n <= 0 { return "" } return exbytes.UnsafeString(StringBytes(n)) } // StringCharset generates a random string of the given length using the given character set. // Note that the character set must be ASCII. For arbitrary Unicode, use [AppendSequence] with a `[]rune`. func StringCharset(n int, charset string) string { if n <= 0 { return "" } return exbytes.UnsafeString(StringBytesCharset(n, charset)) } func base62Encode(val uint32, minWidth int) []byte { out := make([]byte, 0, minWidth) for val > 0 { out = append(out, letters[val%uint32(len(letters))]) val /= 62 } if len(out) < minWidth { paddedOut := make([]byte, minWidth) copy(paddedOut[minWidth-len(out):], out) for i := 0; i < minWidth-len(out); i++ { paddedOut[i] = '0' } out = paddedOut } return out } // Token generates a GitHub-style token with the given prefix, a random part, and a checksum at the end. // The format is `prefix_random_checksum`. The checksum is always 6 characters. func Token(namespace string, randomLength int) string { token := make([]byte, len(namespace)+1+randomLength+1+6) copy(token, namespace) token[len(namespace)] = '_' copy(token[len(namespace)+1:], StringBytes(randomLength)) token[len(namespace)+randomLength+1] = '_' checksum := base62Encode(crc32.ChecksumIEEE(token[:len(token)-7]), 6) copy(token[len(token)-6:], checksum) return exbytes.UnsafeString(token) } // GetTokenPrefix parses the given token generated with Token, validates the checksum and returns the prefix namespace. func GetTokenPrefix(token string) string { parts := strings.Split(token, "_") if len(parts) != 3 { return "" } checksum := base62Encode(crc32.ChecksumIEEE([]byte(parts[0]+"_"+parts[1])), 6) if string(checksum) != parts[2] { return "" } return parts[0] } // IsToken checks if the given token is a valid token generated with Token with the given namespace.. func IsToken(namespace, token string) bool { return GetTokenPrefix(token) == namespace } go-util-0.9.5/random/string_test.go000066400000000000000000000050521513243016000172560ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package random_test import ( "fmt" "regexp" "testing" "github.com/stretchr/testify/require" "go.mau.fi/util/random" ) func TestString_Length(t *testing.T) { for i := 0; i < 256; i++ { require.Len(t, random.String(i), i) } } var stringRegex = regexp.MustCompile(`^[0-9A-Za-z]*$`) var tokenRegex = regexp.MustCompile(`^.+?_[0-9A-Za-z]*_[0-9A-Za-z]{6}$`) func TestString_Content(t *testing.T) { for i := 0; i < 256; i++ { require.Regexp(t, stringRegex, random.String(i)) } } var prefixes = []string{"ght", "hut", "meow", "FOOBAR", "๐Ÿˆ๏ธ"} func TestToken(t *testing.T) { for _, prefix := range prefixes { for i := 0; i < 256; i++ { t.Run(fmt.Sprintf("%s-%d", prefix, i), func(t *testing.T) { // Format: prefix_random_checksum // Length: prefix (4) + 1 + random (i) + 1 + checksum (6) token := random.Token(prefix, i) require.Len(t, token, len(prefix)+1+i+1+6) require.Regexp(t, tokenRegex, token) }) } } } func TestGetTokenPrefix(t *testing.T) { for _, prefix := range prefixes { for i := 0; i < 256; i++ { t.Run(fmt.Sprintf("%s-%d", prefix, i), func(t *testing.T) { token := random.Token(prefix, i) require.Equal(t, prefix, random.GetTokenPrefix(token)) }) } } } func TestGetTokenPrefix_Static(t *testing.T) { var tokens = []string{ "meow_FXfcJomwUu9hVqmxiEqq_wZsw02", "meow_54aDbVIVDO4fQkB80uAkoXpnISggmVDVrV_0yIw64", } for _, token := range tokens { require.Equal(t, "meow", random.GetTokenPrefix(token)) } } func TestGetTokenPrefix_Invalid(t *testing.T) { var tokens = []string{ "meow_FXfcJomwUu9hVqmxiEqq_wZsw12", "meow_54aDbVIVDO4fQkB80uAkoXpnISggmVDVV_0yIw64", } for _, token := range tokens { require.Empty(t, random.GetTokenPrefix(token)) } } func BenchmarkString8(b *testing.B) { for i := 0; i < b.N; i++ { random.String(8) } } func BenchmarkString32(b *testing.B) { for i := 0; i < b.N; i++ { random.String(32) } } func BenchmarkString50(b *testing.B) { for i := 0; i < b.N; i++ { random.String(50) } } func BenchmarkString256(b *testing.B) { for i := 0; i < b.N; i++ { random.String(256) } } func BenchmarkToken32(b *testing.B) { for i := 0; i < b.N; i++ { random.Token("meow", 32) } } func BenchmarkGetTokenPrefix32(b *testing.B) { tok := random.Token("meow", 32) for i := 0; i < b.N; i++ { random.GetTokenPrefix(tok) } } go-util-0.9.5/requestlog/000077500000000000000000000000001513243016000152725ustar00rootroot00000000000000go-util-0.9.5/requestlog/accesslogger.go000066400000000000000000000077741513243016000203010ustar00rootroot00000000000000package requestlog import ( "bytes" "encoding/json" "net/http" "runtime/debug" "strings" "time" "github.com/rs/zerolog" "github.com/rs/zerolog/hlog" ) const MaxRequestSizeLog = 4 * 1024 const MaxStringRequestSizeLog = MaxRequestSizeLog / 2 type Options struct { // Should OPTIONS requests be logged? LogOptions bool // Should remote_addr logging prefer X-Forwarded-For if present? TrustXForwardedFor bool // Should we recover from panics? Recover bool } func AccessLogger(opts Options) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log := hlog.FromRequest(r) crw := &CountingResponseWriter{ ResponseWriter: w, ResponseLength: -1, StatusCode: -1, } start := time.Now() fillRequestLog := func(requestLog *zerolog.Event) { requestDuration := time.Since(start) if userAgent := r.UserAgent(); userAgent != "" { requestLog.Str("user_agent", userAgent) } if referer := r.Referer(); referer != "" { requestLog.Str("referer", referer) } remoteAddr := r.RemoteAddr if opts.TrustXForwardedFor { forwarded := strings.Split(r.Header.Get("X-Forwarded-For"), ", ") if len(forwarded) > 0 && len(forwarded[0]) > 0 { requestLog.Str("x_forwarded_for", forwarded[0]) } } requestLog.Str("remote_addr", remoteAddr) requestLog.Str("method", r.Method) requestLog.Str("proto", r.Proto) requestLog.Int64("request_length", r.ContentLength) requestLog.Str("host", r.Host) requestLog.Str("request_uri", r.RequestURI) if r.Method != http.MethodGet && r.Method != http.MethodHead { requestLog.Str("request_content_type", r.Header.Get("Content-Type")) if crw.RequestBody != nil { logRequestMaybeJSON(requestLog, "request_body", crw.RequestBody.Bytes()) } } // response requestLog.Int64("request_time_ms", requestDuration.Milliseconds()) requestLog.Int("status_code", crw.StatusCode) requestLog.Int("response_length", crw.ResponseLength) requestLog.Str("response_content_type", crw.Header().Get("Content-Type")) if crw.ResponseBody != nil { logRequestMaybeJSON(requestLog, "response_body", crw.ResponseBody.Bytes()) } } if opts.Recover { defer func() { if rvr := recover(); rvr != nil { if rvr == http.ErrAbortHandler { panic(rvr) } if crw.StatusCode == -1 && r.Header.Get("Connection") != "Upgrade" { crw.StatusCode = http.StatusInternalServerError w.WriteHeader(crw.StatusCode) } requestLog := log.Error() fillRequestLog(requestLog) requestLog.Bytes(zerolog.ErrorStackFieldName, debug.Stack()) if err, ok := rvr.(error); ok { requestLog.Err(err) } else { requestLog.Any(zerolog.ErrorFieldName, rvr) } requestLog.Msg("Access") } }() } next.ServeHTTP(crw, r) if r.Method == http.MethodOptions && !opts.LogOptions { return } // don't log successful health requests if r.URL.Path == "/health" && crw.StatusCode == http.StatusNoContent { return } var requestLog *zerolog.Event if crw.StatusCode >= 500 { requestLog = log.Error() } else if crw.StatusCode >= 400 { requestLog = log.Warn() } else { requestLog = log.Info() } fillRequestLog(requestLog) requestLog.Msg("Access") }) } } func logRequestMaybeJSON(evt *zerolog.Event, key string, data []byte) { data = removeNewlines(data) if json.Valid(data) { evt.RawJSON(key, data) } else { // Logging as a string will create lots of escaping and it's not valid json anyway, so cut off a bit more if len(data) > MaxStringRequestSizeLog { data = data[:MaxStringRequestSizeLog] } evt.Bytes(key+"_invalid", data) } } func removeNewlines(data []byte) []byte { data = bytes.TrimSpace(data) if bytes.ContainsRune(data, '\n') { data = bytes.ReplaceAll(data, []byte{'\n'}, []byte{}) data = bytes.ReplaceAll(data, []byte{'\r'}, []byte{}) } return data } go-util-0.9.5/requestlog/countingresponsewriter.go000066400000000000000000000034431513243016000224670ustar00rootroot00000000000000package requestlog import ( "bufio" "bytes" "fmt" "net" "net/http" "strings" ) type CountingResponseWriter struct { StatusCode int ResponseLength int Hijacked bool ResponseWriter http.ResponseWriter ResponseBody *bytes.Buffer RequestBody *bytes.Buffer } var ( _ http.ResponseWriter = (*CountingResponseWriter)(nil) _ http.Flusher = (*CountingResponseWriter)(nil) _ http.Hijacker = (*CountingResponseWriter)(nil) ) func (crw *CountingResponseWriter) Header() http.Header { return crw.ResponseWriter.Header() } func (crw *CountingResponseWriter) Write(data []byte) (int, error) { if crw.ResponseLength == -1 { crw.ResponseLength = 0 } if crw.StatusCode == -1 { crw.StatusCode = http.StatusOK } crw.ResponseLength += len(data) if crw.ResponseBody != nil && crw.ResponseBody.Len() < MaxRequestSizeLog { crw.ResponseBody.Write(CutRequestData(data, crw.ResponseBody.Len())) } return crw.ResponseWriter.Write(data) } func (crw *CountingResponseWriter) WriteHeader(statusCode int) { crw.StatusCode = statusCode crw.ResponseWriter.WriteHeader(statusCode) if !strings.HasPrefix(crw.Header().Get("Content-Type"), "application/json") { crw.ResponseBody = nil } } func (crw *CountingResponseWriter) Flush() { flusher, ok := crw.ResponseWriter.(http.Flusher) if !ok { return } flusher.Flush() } func (crw *CountingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { hijacker, ok := crw.ResponseWriter.(http.Hijacker) if !ok { return nil, nil, fmt.Errorf("CountingResponseWriter: %T does not implement http.Hijacker", crw.ResponseWriter) } crw.Hijacked = true return hijacker.Hijack() } func CutRequestData(data []byte, length int) []byte { if len(data)+length > MaxRequestSizeLog { return data[:MaxRequestSizeLog-length] } return data } go-util-0.9.5/requestlog/route.go000066400000000000000000000021351513243016000167600ustar00rootroot00000000000000package requestlog import ( "bytes" "io" "net/http" "strings" ) type Route struct { Path string Method string Handler http.HandlerFunc TrackHTTPMetrics func(*Route) func(*CountingResponseWriter) LogContent bool } var _ http.Handler = (*Route)(nil) func (rt *Route) ServeHTTP(w http.ResponseWriter, r *http.Request) { crw := w.(*CountingResponseWriter) if rt.TrackHTTPMetrics != nil { defer rt.TrackHTTPMetrics(rt)(crw) } if rt.LogContent { if r.Method != http.MethodGet && r.Method != http.MethodHead { crw.ResponseBody = &bytes.Buffer{} } if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { pcr := &partialCachingReader{Reader: r.Body} crw.RequestBody = &pcr.Buffer r.Body = pcr } } rt.Handler(w, r) } type partialCachingReader struct { Reader io.ReadCloser Buffer bytes.Buffer } func (pcr *partialCachingReader) Read(p []byte) (int, error) { n, err := pcr.Reader.Read(p) if n > 0 { pcr.Buffer.Write(CutRequestData(p[:n], pcr.Buffer.Len())) } return n, err } func (pcr *partialCachingReader) Close() error { return pcr.Reader.Close() } go-util-0.9.5/retryafter/000077500000000000000000000000001513243016000152675ustar00rootroot00000000000000go-util-0.9.5/retryafter/retryafter.go000066400000000000000000000031631513243016000200100ustar00rootroot00000000000000// Copyright (c) 2021 Dillon Dixon // Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package retryafter contains a utility function for parsing the Retry-After HTTP header. package retryafter import ( "net/http" "strconv" "time" ) var now = time.Now // Parse parses the backoff time specified in the Retry-After header if present. // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After. // // The second parameter is the fallback duration to use if the header is not present or invalid. // // Example: // // time.Sleep(retryafter.Parse(resp.Header.Get("Retry-After"), 5*time.Second)) func Parse(retryAfter string, fallback time.Duration) time.Duration { if retryAfter == "" { return fallback } else if t, err := time.Parse(http.TimeFormat, retryAfter); err == nil { return t.Sub(now()) } else if seconds, err := strconv.Atoi(retryAfter); err == nil { return time.Duration(seconds) * time.Second } return fallback } // Should returns true if the given status code indicates that the request should be retried. // // if retryafter.Should(resp.StatusCode, true) { // time.Sleep(retryafter.Parse(resp.Header.Get("Retry-After"), 5*time.Second)) // } func Should(statusCode int, retryOnRateLimit bool) bool { switch statusCode { case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: return true case http.StatusTooManyRequests: return retryOnRateLimit default: return false } } go-util-0.9.5/retryafter/retryafter_test.go000066400000000000000000000022531513243016000210460ustar00rootroot00000000000000// Copyright (c) 2021 Dillon Dixon // Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package retryafter import ( "net/http" "testing" "time" "github.com/stretchr/testify/assert" ) func TestBackoffFromResponse(t *testing.T) { currentTime := time.Now().Truncate(time.Second) now = func() time.Time { return currentTime } defaultBackoff := time.Duration(123) for name, tt := range map[string]struct { headerValue string expected time.Duration }{ "AsDate": { headerValue: currentTime.In(time.UTC).Add(5 * time.Hour).Format(http.TimeFormat), expected: time.Duration(5) * time.Hour, }, "AsSeconds": { headerValue: "12345", expected: time.Duration(12345) * time.Second, }, "Missing": { headerValue: "", expected: defaultBackoff, }, "Bad": { headerValue: "invalid", expected: defaultBackoff, }, } { t.Run(name, func(t *testing.T) { parsed := Parse(tt.headerValue, defaultBackoff) assert.Equal(t, tt.expected, parsed) }) } } go-util-0.9.5/shlex/000077500000000000000000000000001513243016000142235ustar00rootroot00000000000000go-util-0.9.5/shlex/shlex.go000066400000000000000000000243661513243016000157100ustar00rootroot00000000000000/* Copyright 2012 Google Inc. All Rights Reserved. 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 shlex implements a simple lexer which splits input in to tokens using shell-style rules for quoting and commenting. The basic use case uses the default ASCII lexer to split a string into sub-strings: shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"} To process a stream of strings: l := NewLexer(os.Stdin) for ; token, err := l.Next(); err != nil { // process token } To access the raw token stream (which includes tokens for comments): t := NewTokenizer(os.Stdin) for ; token, err := t.Next(); err != nil { // process token } */ package shlex import ( "bufio" "fmt" "io" "strings" ) // TokenType is a top-level token classification: A word, space, comment, unknown. type TokenType int // runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape. type runeTokenClass int // the internal state used by the lexer state machine type lexerState int // Token is a (type, value) pair representing a lexographical token. type Token struct { tokenType TokenType value string } // Equal reports whether tokens a, and b, are equal. // Two tokens are equal if both their types and values are equal. A nil token can // never be equal to another token. func (a *Token) Equal(b *Token) bool { if a == nil || b == nil { return false } if a.tokenType != b.tokenType { return false } return a.value == b.value } // Named classes of UTF-8 runes const ( spaceRunes = " \t\r\n" escapingQuoteRunes = `"` nonEscapingQuoteRunes = "'" escapeRunes = `\` commentRunes = "#" ) // Classes of rune token const ( unknownRuneClass runeTokenClass = iota spaceRuneClass escapingQuoteRuneClass nonEscapingQuoteRuneClass escapeRuneClass commentRuneClass eofRuneClass ) // Classes of lexographic token const ( UnknownToken TokenType = iota WordToken SpaceToken CommentToken ) // Lexer state machine states const ( startState lexerState = iota // no runes have been seen inWordState // processing regular runes in a word escapingState // we have just consumed an escape rune; the next rune is literal escapingQuotedState // we have just consumed an escape rune within a quoted string quotingEscapingState // we are within a quoted string that supports escaping ("...") quotingState // we are within a string that does not support escaping ('...') commentState // we are within a comment (everything following an unquoted or unescaped # ) // tokenClassifier is used for classifying rune characters. type tokenClassifier map[rune]runeTokenClass func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) { for _, runeChar := range runes { typeMap[runeChar] = tokenType } } // newDefaultClassifier creates a new classifier for ASCII characters. func newDefaultClassifier() tokenClassifier { t := tokenClassifier{} t.addRuneClass(spaceRunes, spaceRuneClass) t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass) t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass) t.addRuneClass(escapeRunes, escapeRuneClass) t.addRuneClass(commentRunes, commentRuneClass) return t } // ClassifyRune classifiees a rune func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass { return t[runeVal] } // Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped. type Lexer Tokenizer // NewLexer creates a new lexer from an input stream. func NewLexer(r io.Reader) *Lexer { return (*Lexer)(NewTokenizer(r)) } // Next returns the next word, or an error. If there are no more words, // the error will be io.EOF. func (l *Lexer) Next() (string, error) { for { token, err := (*Tokenizer)(l).Next() if err != nil { return "", err } switch token.tokenType { case WordToken: return token.value, nil case CommentToken: // skip comments default: return "", fmt.Errorf("unknown token type: %v", token.tokenType) } } } // Tokenizer turns an input stream into a sequence of typed tokens type Tokenizer struct { input bufio.Reader classifier tokenClassifier } // NewTokenizer creates a new tokenizer from an input stream. func NewTokenizer(r io.Reader) *Tokenizer { input := bufio.NewReader(r) classifier := newDefaultClassifier() return &Tokenizer{ input: *input, classifier: classifier} } // scanStream scans the stream for the next token using the internal state machine. // It will panic if it encounters a rune which it does not know how to handle. func (t *Tokenizer) scanStream() (*Token, error) { state := startState var tokenType TokenType var value []rune var nextRune rune var nextRuneType runeTokenClass var err error for { nextRune, _, err = t.input.ReadRune() nextRuneType = t.classifier.ClassifyRune(nextRune) if err == io.EOF { nextRuneType = eofRuneClass err = nil } else if err != nil { return nil, err } switch state { case startState: // no runes read yet { switch nextRuneType { case eofRuneClass: { return nil, io.EOF } case spaceRuneClass: { } case escapingQuoteRuneClass: { tokenType = WordToken state = quotingEscapingState } case nonEscapingQuoteRuneClass: { tokenType = WordToken state = quotingState } case escapeRuneClass: { state = escapingState } case commentRuneClass: { tokenType = CommentToken state = commentState } default: { tokenType = WordToken value = append(value, nextRune) state = inWordState } } } case inWordState: // in a regular word { switch nextRuneType { case eofRuneClass: { token := &Token{ tokenType: tokenType, value: string(value)} return token, err } case spaceRuneClass: { token := &Token{ tokenType: tokenType, value: string(value)} return token, err } case escapingQuoteRuneClass: { state = quotingEscapingState } case nonEscapingQuoteRuneClass: { state = quotingState } case escapeRuneClass: { state = escapingState } default: { value = append(value, nextRune) } } } case escapingState: // the rune after an escape character { switch nextRuneType { case eofRuneClass: { err = fmt.Errorf("EOF found after escape character") token := &Token{ tokenType: tokenType, value: string(value)} return token, err } default: { // Backslash at end of line does not mean literal // newline, rather it means line continuation. If we // see this then we need to go back to the previous // state. If we had already been parsing a word // token then we can continue that, otherwise we // haven't seen any runes yet so we should go back // to the start. if nextRune == '\n' { if tokenType == WordToken { state = inWordState } else { state = startState } } else { tokenType = WordToken state = inWordState value = append(value, nextRune) } } } } case escapingQuotedState: // the next rune after an escape character, in double quotes { switch nextRuneType { case eofRuneClass: { err = fmt.Errorf("EOF found after escape character") token := &Token{ tokenType: tokenType, value: string(value)} return token, err } default: { state = quotingEscapingState value = append(value, nextRune) } } } case quotingEscapingState: // in escaping double quotes { switch nextRuneType { case eofRuneClass: { err = fmt.Errorf("EOF found when expecting closing quote") token := &Token{ tokenType: tokenType, value: string(value)} return token, err } case escapingQuoteRuneClass: { state = inWordState } case escapeRuneClass: { state = escapingQuotedState } default: { value = append(value, nextRune) } } } case quotingState: // in non-escaping single quotes { switch nextRuneType { case eofRuneClass: { err = fmt.Errorf("EOF found when expecting closing quote") token := &Token{ tokenType: tokenType, value: string(value)} return token, err } case nonEscapingQuoteRuneClass: { state = inWordState } default: { value = append(value, nextRune) } } } case commentState: // in a comment { switch nextRuneType { case eofRuneClass: { token := &Token{ tokenType: tokenType, value: string(value)} return token, err } case spaceRuneClass: { if nextRune == '\n' { state = startState token := &Token{ tokenType: tokenType, value: string(value)} return token, err } else { value = append(value, nextRune) } } default: { value = append(value, nextRune) } } } default: { return nil, fmt.Errorf("unexpected state: %v", state) } } } } // Next returns the next token in the stream. func (t *Tokenizer) Next() (*Token, error) { return t.scanStream() } // Split partitions a string into a slice of strings. func Split(s string) ([]string, error) { l := NewLexer(strings.NewReader(s)) subStrings := make([]string, 0) for { word, err := l.Next() if err != nil { if err == io.EOF { return subStrings, nil } return subStrings, err } subStrings = append(subStrings, word) } } go-util-0.9.5/shlex/shlex_test.go000066400000000000000000000056071513243016000167440ustar00rootroot00000000000000/* Copyright 2012 Google Inc. All Rights Reserved. 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 shlex import ( "strings" "testing" ) var ( // one two "three four" "five \"six\"" seven#eight # nine # ten // eleven 'twelve\' thirteen=13 fourteen/14 fif\ // teen \ // sixteen seven\ teen testString = "one two \"three four\" \"five \\\"six\\\"\" seven#eight # nine # ten\n eleven 'twelve\\' thirteen=13 fourteen/14 fif\\\nteen \\\n sixteen seven\\ teen" ) func TestClassifier(t *testing.T) { classifier := newDefaultClassifier() tests := map[rune]runeTokenClass{ ' ': spaceRuneClass, '"': escapingQuoteRuneClass, '\'': nonEscapingQuoteRuneClass, '#': commentRuneClass} for runeChar, want := range tests { got := classifier.ClassifyRune(runeChar) if got != want { t.Errorf("ClassifyRune(%v) -> %v. Want: %v", runeChar, got, want) } } } func TestTokenizer(t *testing.T) { testInput := strings.NewReader(testString) expectedTokens := []*Token{ {WordToken, "one"}, {WordToken, "two"}, {WordToken, "three four"}, {WordToken, "five \"six\""}, {WordToken, "seven#eight"}, {CommentToken, " nine # ten"}, {WordToken, "eleven"}, {WordToken, "twelve\\"}, {WordToken, "thirteen=13"}, {WordToken, "fourteen/14"}, {WordToken, "fifteen"}, {WordToken, "sixteen"}, {WordToken, "seven teen"}, } tokenizer := NewTokenizer(testInput) for i, want := range expectedTokens { got, err := tokenizer.Next() if err != nil { t.Error(err) } if !got.Equal(want) { t.Errorf("Tokenizer.Next()[%v] of %q -> %v. Want: %v", i, testString, got, want) } } } var expectedSplit = []string{"one", "two", "three four", "five \"six\"", "seven#eight", "eleven", "twelve\\", "thirteen=13", "fourteen/14", "fifteen", "sixteen", "seven teen"} func TestLexer(t *testing.T) { testInput := strings.NewReader(testString) lexer := NewLexer(testInput) for i, want := range expectedSplit { got, err := lexer.Next() if err != nil { t.Error(err) } if got != want { t.Errorf("Lexer.Next()[%v] of %q -> %v. Want: %v", i, testString, got, want) } } } func TestSplit(t *testing.T) { got, err := Split(testString) if err != nil { t.Error(err) } if len(expectedSplit) != len(got) { t.Errorf("Split(%q) -> %v. Want: %v", testString, got, expectedSplit) } for i := range got { if got[i] != expectedSplit[i] { t.Errorf("Split(%q)[%v] -> %v. Want: %v", testString, i, got[i], expectedSplit[i]) } } } go-util-0.9.5/unicodeurls/000077500000000000000000000000001513243016000154345ustar00rootroot00000000000000go-util-0.9.5/unicodeurls/urls.go000066400000000000000000000057301513243016000167550ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package unicodeurls contains URLs for Unicode data files. // It is meant to be used in code generators that parse the files. package unicodeurls import ( "bufio" "errors" "fmt" "io" "net/http" "os" "path/filepath" "strconv" "strings" "go.mau.fi/util/exerrors" ) const UnicodeVersion = "17.0.0" const BaseURL = "https://www.unicode.org/Public/" + UnicodeVersion const EmojiVariationSequences = BaseURL + "/ucd/emoji/emoji-variation-sequences.txt" const EmojiTest = BaseURL + "/emoji/emoji-test.txt" const Confusables = BaseURL + "/security/confusables.txt" type ReadParams struct { ProcessComments bool } var dataDir = os.Getenv("MAUTRIX_GO_UTIL_UNICODE_DATA_DIR") // ReadDataFile fetches a data file from a URL and processes it line by line with the given processor function. func ReadDataFile(url string, processor func(string), params ...ReadParams) { var param ReadParams if len(params) > 0 { param = params[0] } cachePath := filepath.Join(dataDir, filepath.Base(url)) f, err := os.Open(cachePath) var buf *bufio.Reader if err != nil { req := exerrors.Must(http.NewRequest(http.MethodGet, url, nil)) req.Header.Set("User-Agent", "Unicode data parser +https://github.com/mautrix/go-util") resp := exerrors.Must(http.DefaultClient.Do(req)) if resp.StatusCode != http.StatusOK { panic(fmt.Errorf("unexpected status code %d from %s", resp.StatusCode, url)) } buf = bufio.NewReader(resp.Body) } else { defer f.Close() buf = bufio.NewReader(f) } for { line, err := buf.ReadString('\n') if errors.Is(err, io.EOF) { break } else if err != nil { panic(err) } else if line == "" || (strings.HasPrefix(line, "#") && !param.ProcessComments) { continue } processor(line) } } // ReadDataFileList fetches a data file from a URL and converts lines into array items with the given function. func ReadDataFileList[T any](url string, processor func(string) (T, bool), params ...ReadParams) (output []T) { ReadDataFile(url, func(s string) { if item, ok := processor(s); ok { output = append(output, item) } }, params...) return } // ReadDataFileMap fetches a data file from a URL and converts lines into a map with the given function. func ReadDataFileMap[Key comparable, Value any](url string, processor func(string) (Key, Value, bool), params ...ReadParams) (output map[Key]Value) { output = make(map[Key]Value) ReadDataFile(url, func(s string) { if key, value, ok := processor(s); ok { output[key] = value } }, params...) return } // ParseHex parses a list of Unicode codepoints encoded as hex into a string func ParseHex(parts []string) string { output := make([]rune, len(parts)) for i, part := range parts { output[i] = rune(exerrors.Must(strconv.ParseInt(part, 16, 32))) } return string(output) } go-util-0.9.5/util.go000066400000000000000000000005221513243016000144030ustar00rootroot00000000000000// Copyright (c) 2025 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package util const Version = "v0.9.5" func VersionArray() [3]uint { return [3]uint{0, 9, 5} } go-util-0.9.5/variationselector/000077500000000000000000000000001513243016000166355ustar00rootroot00000000000000go-util-0.9.5/variationselector/emojis.go000066400000000000000000000744661513243016000204730ustar00rootroot00000000000000// Code generated by go generate; DO NOT EDIT. package variationselector import ( "regexp" "strings" ) func doInit() { variationRegex = regexp.MustCompile( `(^|[^\x{200D}])(` + `\x{231A}|` + `\x{231B}|` + `\x{23E9}|` + `\x{23EA}|` + `\x{23EB}|` + `\x{23EC}|` + `\x{23F0}|` + `\x{23F3}|` + `\x{25FD}|` + `\x{25FE}|` + `\x{2614}|` + `\x{2615}|` + `\x{2648}|` + `\x{2649}|` + `\x{264A}|` + `\x{264B}|` + `\x{264C}|` + `\x{264D}|` + `\x{264E}|` + `\x{264F}|` + `\x{2650}|` + `\x{2651}|` + `\x{2652}|` + `\x{2653}|` + `\x{267F}|` + `\x{2693}|` + `\x{26A1}|` + `\x{26AA}|` + `\x{26AB}|` + `\x{26BD}|` + `\x{26BE}|` + `\x{26C4}|` + `\x{26C5}|` + `\x{26CE}|` + `\x{26D4}|` + `\x{26EA}|` + `\x{26F2}|` + `\x{26F3}|` + `\x{26F5}|` + `\x{26FA}|` + `\x{26FD}|` + `\x{2705}|` + `\x{270A}|` + `\x{270B}|` + `\x{2728}|` + `\x{274C}|` + `\x{274E}|` + `\x{2753}|` + `\x{2754}|` + `\x{2755}|` + `\x{2757}|` + `\x{2795}|` + `\x{2796}|` + `\x{2797}|` + `\x{27B0}|` + `\x{27BF}|` + `\x{2B1B}|` + `\x{2B1C}|` + `\x{2B50}|` + `\x{2B55}|` + `\x{1F004}|` + `\x{1F21A}|` + `\x{1F22F}|` + `\x{1F30D}|` + `\x{1F30E}|` + `\x{1F30F}|` + `\x{1F315}|` + `\x{1F31C}|` + `\x{1F378}|` + `\x{1F393}|` + `\x{1F3A7}|` + `\x{1F3AC}|` + `\x{1F3AD}|` + `\x{1F3AE}|` + `\x{1F3C2}|` + `\x{1F3C4}|` + `\x{1F3C6}|` + `\x{1F3CA}|` + `\x{1F3E0}|` + `\x{1F3ED}|` + `\x{1F408}|` + `\x{1F415}|` + `\x{1F41F}|` + `\x{1F426}|` + `\x{1F442}|` + `\x{1F446}|` + `\x{1F447}|` + `\x{1F448}|` + `\x{1F449}|` + `\x{1F44D}|` + `\x{1F44E}|` + `\x{1F453}|` + `\x{1F46A}|` + `\x{1F47D}|` + `\x{1F4A3}|` + `\x{1F4B0}|` + `\x{1F4B3}|` + `\x{1F4BB}|` + `\x{1F4BF}|` + `\x{1F4CB}|` + `\x{1F4DA}|` + `\x{1F4DF}|` + `\x{1F4E4}|` + `\x{1F4E5}|` + `\x{1F4E6}|` + `\x{1F4EA}|` + `\x{1F4EB}|` + `\x{1F4EC}|` + `\x{1F4ED}|` + `\x{1F4F7}|` + `\x{1F4F9}|` + `\x{1F4FA}|` + `\x{1F4FB}|` + `\x{1F508}|` + `\x{1F50D}|` + `\x{1F512}|` + `\x{1F513}|` + `\x{1F550}|` + `\x{1F551}|` + `\x{1F552}|` + `\x{1F553}|` + `\x{1F554}|` + `\x{1F555}|` + `\x{1F556}|` + `\x{1F557}|` + `\x{1F558}|` + `\x{1F559}|` + `\x{1F55A}|` + `\x{1F55B}|` + `\x{1F55C}|` + `\x{1F55D}|` + `\x{1F55E}|` + `\x{1F55F}|` + `\x{1F560}|` + `\x{1F561}|` + `\x{1F562}|` + `\x{1F563}|` + `\x{1F564}|` + `\x{1F565}|` + `\x{1F566}|` + `\x{1F567}|` + `\x{1F610}|` + `\x{1F687}|` + `\x{1F68D}|` + `\x{1F691}|` + `\x{1F694}|` + `\x{1F698}|` + `\x{1F6AD}|` + `\x{1F6B2}|` + `\x{1F6B9}|` + `\x{1F6BA}|` + `\x{1F6BC}` + `)([^\x{FE0F}\x{FE0E}\x{200D}\x{1F3FB}\x{1F3FC}\x{1F3FD}\x{1F3FE}\x{1F3FF}]|$)`, ) var qualifiedEmojis = []string{ "โ˜บ๏ธ", "๐Ÿ˜ถโ€๐ŸŒซ๏ธ", "๐Ÿ™‚โ€โ†”๏ธ", "๐Ÿ™‚โ€โ†•๏ธ", "โ˜น๏ธ", "โ˜ ๏ธ", "โฃ๏ธ", "โค๏ธโ€๐Ÿ”ฅ", "โค๏ธโ€๐Ÿฉน", "โค๏ธ", "๐Ÿ•ณ๏ธ", "๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", "๐Ÿ—จ๏ธ", "๐Ÿ—ฏ๏ธ", "๐Ÿ–๏ธ", "โœŒ๏ธ", "โ˜๏ธ", "โœ๏ธ", "๐Ÿ‘๏ธ", "๐Ÿง”โ€โ™‚๏ธ", "๐Ÿง”๐Ÿปโ€โ™‚๏ธ", "๐Ÿง”๐Ÿผโ€โ™‚๏ธ", "๐Ÿง”๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง”๐Ÿพโ€โ™‚๏ธ", "๐Ÿง”๐Ÿฟโ€โ™‚๏ธ", "๐Ÿง”โ€โ™€๏ธ", "๐Ÿง”๐Ÿปโ€โ™€๏ธ", "๐Ÿง”๐Ÿผโ€โ™€๏ธ", "๐Ÿง”๐Ÿฝโ€โ™€๏ธ", "๐Ÿง”๐Ÿพโ€โ™€๏ธ", "๐Ÿง”๐Ÿฟโ€โ™€๏ธ", "๐Ÿ‘ฑโ€โ™€๏ธ", "๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ", "๐Ÿ‘ฑ๐Ÿผโ€โ™€๏ธ", "๐Ÿ‘ฑ๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‘ฑ๐Ÿพโ€โ™€๏ธ", "๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ", "๐Ÿ‘ฑโ€โ™‚๏ธ", "๐Ÿ‘ฑ๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‘ฑ๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‘ฑ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‘ฑ๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‘ฑ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™โ€โ™‚๏ธ", "๐Ÿ™๐Ÿปโ€โ™‚๏ธ", "๐Ÿ™๐Ÿผโ€โ™‚๏ธ", "๐Ÿ™๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ™๐Ÿพโ€โ™‚๏ธ", "๐Ÿ™๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™โ€โ™€๏ธ", "๐Ÿ™๐Ÿปโ€โ™€๏ธ", "๐Ÿ™๐Ÿผโ€โ™€๏ธ", "๐Ÿ™๐Ÿฝโ€โ™€๏ธ", "๐Ÿ™๐Ÿพโ€โ™€๏ธ", "๐Ÿ™๐Ÿฟโ€โ™€๏ธ", "๐Ÿ™Žโ€โ™‚๏ธ", "๐Ÿ™Ž๐Ÿปโ€โ™‚๏ธ", "๐Ÿ™Ž๐Ÿผโ€โ™‚๏ธ", "๐Ÿ™Ž๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ™Ž๐Ÿพโ€โ™‚๏ธ", "๐Ÿ™Ž๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™Žโ€โ™€๏ธ", "๐Ÿ™Ž๐Ÿปโ€โ™€๏ธ", "๐Ÿ™Ž๐Ÿผโ€โ™€๏ธ", "๐Ÿ™Ž๐Ÿฝโ€โ™€๏ธ", "๐Ÿ™Ž๐Ÿพโ€โ™€๏ธ", "๐Ÿ™Ž๐Ÿฟโ€โ™€๏ธ", "๐Ÿ™…โ€โ™‚๏ธ", "๐Ÿ™…๐Ÿปโ€โ™‚๏ธ", "๐Ÿ™…๐Ÿผโ€โ™‚๏ธ", "๐Ÿ™…๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ™…๐Ÿพโ€โ™‚๏ธ", "๐Ÿ™…๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™…โ€โ™€๏ธ", "๐Ÿ™…๐Ÿปโ€โ™€๏ธ", "๐Ÿ™…๐Ÿผโ€โ™€๏ธ", "๐Ÿ™…๐Ÿฝโ€โ™€๏ธ", "๐Ÿ™…๐Ÿพโ€โ™€๏ธ", "๐Ÿ™…๐Ÿฟโ€โ™€๏ธ", "๐Ÿ™†โ€โ™‚๏ธ", "๐Ÿ™†๐Ÿปโ€โ™‚๏ธ", "๐Ÿ™†๐Ÿผโ€โ™‚๏ธ", "๐Ÿ™†๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ™†๐Ÿพโ€โ™‚๏ธ", "๐Ÿ™†๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™†โ€โ™€๏ธ", "๐Ÿ™†๐Ÿปโ€โ™€๏ธ", "๐Ÿ™†๐Ÿผโ€โ™€๏ธ", "๐Ÿ™†๐Ÿฝโ€โ™€๏ธ", "๐Ÿ™†๐Ÿพโ€โ™€๏ธ", "๐Ÿ™†๐Ÿฟโ€โ™€๏ธ", "๐Ÿ’โ€โ™‚๏ธ", "๐Ÿ’๐Ÿปโ€โ™‚๏ธ", "๐Ÿ’๐Ÿผโ€โ™‚๏ธ", "๐Ÿ’๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ’๐Ÿพโ€โ™‚๏ธ", "๐Ÿ’๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ’โ€โ™€๏ธ", "๐Ÿ’๐Ÿปโ€โ™€๏ธ", "๐Ÿ’๐Ÿผโ€โ™€๏ธ", "๐Ÿ’๐Ÿฝโ€โ™€๏ธ", "๐Ÿ’๐Ÿพโ€โ™€๏ธ", "๐Ÿ’๐Ÿฟโ€โ™€๏ธ", "๐Ÿ™‹โ€โ™‚๏ธ", "๐Ÿ™‹๐Ÿปโ€โ™‚๏ธ", "๐Ÿ™‹๐Ÿผโ€โ™‚๏ธ", "๐Ÿ™‹๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ™‹๐Ÿพโ€โ™‚๏ธ", "๐Ÿ™‹๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™‹โ€โ™€๏ธ", "๐Ÿ™‹๐Ÿปโ€โ™€๏ธ", "๐Ÿ™‹๐Ÿผโ€โ™€๏ธ", "๐Ÿ™‹๐Ÿฝโ€โ™€๏ธ", "๐Ÿ™‹๐Ÿพโ€โ™€๏ธ", "๐Ÿ™‹๐Ÿฟโ€โ™€๏ธ", "๐Ÿงโ€โ™‚๏ธ", "๐Ÿง๐Ÿปโ€โ™‚๏ธ", "๐Ÿง๐Ÿผโ€โ™‚๏ธ", "๐Ÿง๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง๐Ÿพโ€โ™‚๏ธ", "๐Ÿง๐Ÿฟโ€โ™‚๏ธ", "๐Ÿงโ€โ™€๏ธ", "๐Ÿง๐Ÿปโ€โ™€๏ธ", "๐Ÿง๐Ÿผโ€โ™€๏ธ", "๐Ÿง๐Ÿฝโ€โ™€๏ธ", "๐Ÿง๐Ÿพโ€โ™€๏ธ", "๐Ÿง๐Ÿฟโ€โ™€๏ธ", "๐Ÿ™‡โ€โ™‚๏ธ", "๐Ÿ™‡๐Ÿปโ€โ™‚๏ธ", "๐Ÿ™‡๐Ÿผโ€โ™‚๏ธ", "๐Ÿ™‡๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ™‡๐Ÿพโ€โ™‚๏ธ", "๐Ÿ™‡๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ™‡โ€โ™€๏ธ", "๐Ÿ™‡๐Ÿปโ€โ™€๏ธ", "๐Ÿ™‡๐Ÿผโ€โ™€๏ธ", "๐Ÿ™‡๐Ÿฝโ€โ™€๏ธ", "๐Ÿ™‡๐Ÿพโ€โ™€๏ธ", "๐Ÿ™‡๐Ÿฟโ€โ™€๏ธ", "๐Ÿคฆโ€โ™‚๏ธ", "๐Ÿคฆ๐Ÿปโ€โ™‚๏ธ", "๐Ÿคฆ๐Ÿผโ€โ™‚๏ธ", "๐Ÿคฆ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคฆ๐Ÿพโ€โ™‚๏ธ", "๐Ÿคฆ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคฆโ€โ™€๏ธ", "๐Ÿคฆ๐Ÿปโ€โ™€๏ธ", "๐Ÿคฆ๐Ÿผโ€โ™€๏ธ", "๐Ÿคฆ๐Ÿฝโ€โ™€๏ธ", "๐Ÿคฆ๐Ÿพโ€โ™€๏ธ", "๐Ÿคฆ๐Ÿฟโ€โ™€๏ธ", "๐Ÿคทโ€โ™‚๏ธ", "๐Ÿคท๐Ÿปโ€โ™‚๏ธ", "๐Ÿคท๐Ÿผโ€โ™‚๏ธ", "๐Ÿคท๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคท๐Ÿพโ€โ™‚๏ธ", "๐Ÿคท๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคทโ€โ™€๏ธ", "๐Ÿคท๐Ÿปโ€โ™€๏ธ", "๐Ÿคท๐Ÿผโ€โ™€๏ธ", "๐Ÿคท๐Ÿฝโ€โ™€๏ธ", "๐Ÿคท๐Ÿพโ€โ™€๏ธ", "๐Ÿคท๐Ÿฟโ€โ™€๏ธ", "๐Ÿง‘โ€โš•๏ธ", "๐Ÿง‘๐Ÿปโ€โš•๏ธ", "๐Ÿง‘๐Ÿผโ€โš•๏ธ", "๐Ÿง‘๐Ÿฝโ€โš•๏ธ", "๐Ÿง‘๐Ÿพโ€โš•๏ธ", "๐Ÿง‘๐Ÿฟโ€โš•๏ธ", "๐Ÿ‘จโ€โš•๏ธ", "๐Ÿ‘จ๐Ÿปโ€โš•๏ธ", "๐Ÿ‘จ๐Ÿผโ€โš•๏ธ", "๐Ÿ‘จ๐Ÿฝโ€โš•๏ธ", "๐Ÿ‘จ๐Ÿพโ€โš•๏ธ", "๐Ÿ‘จ๐Ÿฟโ€โš•๏ธ", "๐Ÿ‘ฉโ€โš•๏ธ", "๐Ÿ‘ฉ๐Ÿปโ€โš•๏ธ", "๐Ÿ‘ฉ๐Ÿผโ€โš•๏ธ", "๐Ÿ‘ฉ๐Ÿฝโ€โš•๏ธ", "๐Ÿ‘ฉ๐Ÿพโ€โš•๏ธ", "๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ", "๐Ÿง‘โ€โš–๏ธ", "๐Ÿง‘๐Ÿปโ€โš–๏ธ", "๐Ÿง‘๐Ÿผโ€โš–๏ธ", "๐Ÿง‘๐Ÿฝโ€โš–๏ธ", "๐Ÿง‘๐Ÿพโ€โš–๏ธ", "๐Ÿง‘๐Ÿฟโ€โš–๏ธ", "๐Ÿ‘จโ€โš–๏ธ", "๐Ÿ‘จ๐Ÿปโ€โš–๏ธ", "๐Ÿ‘จ๐Ÿผโ€โš–๏ธ", "๐Ÿ‘จ๐Ÿฝโ€โš–๏ธ", "๐Ÿ‘จ๐Ÿพโ€โš–๏ธ", "๐Ÿ‘จ๐Ÿฟโ€โš–๏ธ", "๐Ÿ‘ฉโ€โš–๏ธ", "๐Ÿ‘ฉ๐Ÿปโ€โš–๏ธ", "๐Ÿ‘ฉ๐Ÿผโ€โš–๏ธ", "๐Ÿ‘ฉ๐Ÿฝโ€โš–๏ธ", "๐Ÿ‘ฉ๐Ÿพโ€โš–๏ธ", "๐Ÿ‘ฉ๐Ÿฟโ€โš–๏ธ", "๐Ÿง‘โ€โœˆ๏ธ", "๐Ÿง‘๐Ÿปโ€โœˆ๏ธ", "๐Ÿง‘๐Ÿผโ€โœˆ๏ธ", "๐Ÿง‘๐Ÿฝโ€โœˆ๏ธ", "๐Ÿง‘๐Ÿพโ€โœˆ๏ธ", "๐Ÿง‘๐Ÿฟโ€โœˆ๏ธ", "๐Ÿ‘จโ€โœˆ๏ธ", "๐Ÿ‘จ๐Ÿปโ€โœˆ๏ธ", "๐Ÿ‘จ๐Ÿผโ€โœˆ๏ธ", "๐Ÿ‘จ๐Ÿฝโ€โœˆ๏ธ", "๐Ÿ‘จ๐Ÿพโ€โœˆ๏ธ", "๐Ÿ‘จ๐Ÿฟโ€โœˆ๏ธ", "๐Ÿ‘ฉโ€โœˆ๏ธ", "๐Ÿ‘ฉ๐Ÿปโ€โœˆ๏ธ", "๐Ÿ‘ฉ๐Ÿผโ€โœˆ๏ธ", "๐Ÿ‘ฉ๐Ÿฝโ€โœˆ๏ธ", "๐Ÿ‘ฉ๐Ÿพโ€โœˆ๏ธ", "๐Ÿ‘ฉ๐Ÿฟโ€โœˆ๏ธ", "๐Ÿ‘ฎโ€โ™‚๏ธ", "๐Ÿ‘ฎ๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‘ฎ๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‘ฎ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‘ฎ๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‘ฎ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ‘ฎโ€โ™€๏ธ", "๐Ÿ‘ฎ๐Ÿปโ€โ™€๏ธ", "๐Ÿ‘ฎ๐Ÿผโ€โ™€๏ธ", "๐Ÿ‘ฎ๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‘ฎ๐Ÿพโ€โ™€๏ธ", "๐Ÿ‘ฎ๐Ÿฟโ€โ™€๏ธ", "๐Ÿ•ต๏ธ", "๐Ÿ•ต๏ธโ€โ™‚๏ธ", "๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ", "๐Ÿ•ต๐Ÿผโ€โ™‚๏ธ", "๐Ÿ•ต๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ•ต๐Ÿพโ€โ™‚๏ธ", "๐Ÿ•ต๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ•ต๏ธโ€โ™€๏ธ", "๐Ÿ•ต๐Ÿปโ€โ™€๏ธ", "๐Ÿ•ต๐Ÿผโ€โ™€๏ธ", "๐Ÿ•ต๐Ÿฝโ€โ™€๏ธ", "๐Ÿ•ต๐Ÿพโ€โ™€๏ธ", "๐Ÿ•ต๐Ÿฟโ€โ™€๏ธ", "๐Ÿ’‚โ€โ™‚๏ธ", "๐Ÿ’‚๐Ÿปโ€โ™‚๏ธ", "๐Ÿ’‚๐Ÿผโ€โ™‚๏ธ", "๐Ÿ’‚๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ’‚๐Ÿพโ€โ™‚๏ธ", "๐Ÿ’‚๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ’‚โ€โ™€๏ธ", "๐Ÿ’‚๐Ÿปโ€โ™€๏ธ", "๐Ÿ’‚๐Ÿผโ€โ™€๏ธ", "๐Ÿ’‚๐Ÿฝโ€โ™€๏ธ", "๐Ÿ’‚๐Ÿพโ€โ™€๏ธ", "๐Ÿ’‚๐Ÿฟโ€โ™€๏ธ", "๐Ÿ‘ทโ€โ™‚๏ธ", "๐Ÿ‘ท๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‘ท๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‘ท๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‘ท๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‘ท๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ‘ทโ€โ™€๏ธ", "๐Ÿ‘ท๐Ÿปโ€โ™€๏ธ", "๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ", "๐Ÿ‘ท๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‘ท๐Ÿพโ€โ™€๏ธ", "๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ", "๐Ÿ‘ณโ€โ™‚๏ธ", "๐Ÿ‘ณ๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‘ณ๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‘ณ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‘ณ๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‘ณ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ‘ณโ€โ™€๏ธ", "๐Ÿ‘ณ๐Ÿปโ€โ™€๏ธ", "๐Ÿ‘ณ๐Ÿผโ€โ™€๏ธ", "๐Ÿ‘ณ๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‘ณ๐Ÿพโ€โ™€๏ธ", "๐Ÿ‘ณ๐Ÿฟโ€โ™€๏ธ", "๐Ÿคตโ€โ™‚๏ธ", "๐Ÿคต๐Ÿปโ€โ™‚๏ธ", "๐Ÿคต๐Ÿผโ€โ™‚๏ธ", "๐Ÿคต๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคต๐Ÿพโ€โ™‚๏ธ", "๐Ÿคต๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคตโ€โ™€๏ธ", "๐Ÿคต๐Ÿปโ€โ™€๏ธ", "๐Ÿคต๐Ÿผโ€โ™€๏ธ", "๐Ÿคต๐Ÿฝโ€โ™€๏ธ", "๐Ÿคต๐Ÿพโ€โ™€๏ธ", "๐Ÿคต๐Ÿฟโ€โ™€๏ธ", "๐Ÿ‘ฐโ€โ™‚๏ธ", "๐Ÿ‘ฐ๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‘ฐ๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‘ฐ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‘ฐ๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‘ฐ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ‘ฐโ€โ™€๏ธ", "๐Ÿ‘ฐ๐Ÿปโ€โ™€๏ธ", "๐Ÿ‘ฐ๐Ÿผโ€โ™€๏ธ", "๐Ÿ‘ฐ๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‘ฐ๐Ÿพโ€โ™€๏ธ", "๐Ÿ‘ฐ๐Ÿฟโ€โ™€๏ธ", "๐Ÿฆธโ€โ™‚๏ธ", "๐Ÿฆธ๐Ÿปโ€โ™‚๏ธ", "๐Ÿฆธ๐Ÿผโ€โ™‚๏ธ", "๐Ÿฆธ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿฆธ๐Ÿพโ€โ™‚๏ธ", "๐Ÿฆธ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿฆธโ€โ™€๏ธ", "๐Ÿฆธ๐Ÿปโ€โ™€๏ธ", "๐Ÿฆธ๐Ÿผโ€โ™€๏ธ", "๐Ÿฆธ๐Ÿฝโ€โ™€๏ธ", "๐Ÿฆธ๐Ÿพโ€โ™€๏ธ", "๐Ÿฆธ๐Ÿฟโ€โ™€๏ธ", "๐Ÿฆนโ€โ™‚๏ธ", "๐Ÿฆน๐Ÿปโ€โ™‚๏ธ", "๐Ÿฆน๐Ÿผโ€โ™‚๏ธ", "๐Ÿฆน๐Ÿฝโ€โ™‚๏ธ", "๐Ÿฆน๐Ÿพโ€โ™‚๏ธ", "๐Ÿฆน๐Ÿฟโ€โ™‚๏ธ", "๐Ÿฆนโ€โ™€๏ธ", "๐Ÿฆน๐Ÿปโ€โ™€๏ธ", "๐Ÿฆน๐Ÿผโ€โ™€๏ธ", "๐Ÿฆน๐Ÿฝโ€โ™€๏ธ", "๐Ÿฆน๐Ÿพโ€โ™€๏ธ", "๐Ÿฆน๐Ÿฟโ€โ™€๏ธ", "๐Ÿง™โ€โ™‚๏ธ", "๐Ÿง™๐Ÿปโ€โ™‚๏ธ", "๐Ÿง™๐Ÿผโ€โ™‚๏ธ", "๐Ÿง™๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง™๐Ÿพโ€โ™‚๏ธ", "๐Ÿง™๐Ÿฟโ€โ™‚๏ธ", "๐Ÿง™โ€โ™€๏ธ", "๐Ÿง™๐Ÿปโ€โ™€๏ธ", "๐Ÿง™๐Ÿผโ€โ™€๏ธ", "๐Ÿง™๐Ÿฝโ€โ™€๏ธ", "๐Ÿง™๐Ÿพโ€โ™€๏ธ", "๐Ÿง™๐Ÿฟโ€โ™€๏ธ", "๐Ÿงšโ€โ™‚๏ธ", "๐Ÿงš๐Ÿปโ€โ™‚๏ธ", "๐Ÿงš๐Ÿผโ€โ™‚๏ธ", "๐Ÿงš๐Ÿฝโ€โ™‚๏ธ", "๐Ÿงš๐Ÿพโ€โ™‚๏ธ", "๐Ÿงš๐Ÿฟโ€โ™‚๏ธ", "๐Ÿงšโ€โ™€๏ธ", "๐Ÿงš๐Ÿปโ€โ™€๏ธ", "๐Ÿงš๐Ÿผโ€โ™€๏ธ", "๐Ÿงš๐Ÿฝโ€โ™€๏ธ", "๐Ÿงš๐Ÿพโ€โ™€๏ธ", "๐Ÿงš๐Ÿฟโ€โ™€๏ธ", "๐Ÿง›โ€โ™‚๏ธ", "๐Ÿง›๐Ÿปโ€โ™‚๏ธ", "๐Ÿง›๐Ÿผโ€โ™‚๏ธ", "๐Ÿง›๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง›๐Ÿพโ€โ™‚๏ธ", "๐Ÿง›๐Ÿฟโ€โ™‚๏ธ", "๐Ÿง›โ€โ™€๏ธ", "๐Ÿง›๐Ÿปโ€โ™€๏ธ", "๐Ÿง›๐Ÿผโ€โ™€๏ธ", "๐Ÿง›๐Ÿฝโ€โ™€๏ธ", "๐Ÿง›๐Ÿพโ€โ™€๏ธ", "๐Ÿง›๐Ÿฟโ€โ™€๏ธ", "๐Ÿงœโ€โ™‚๏ธ", "๐Ÿงœ๐Ÿปโ€โ™‚๏ธ", "๐Ÿงœ๐Ÿผโ€โ™‚๏ธ", "๐Ÿงœ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿงœ๐Ÿพโ€โ™‚๏ธ", "๐Ÿงœ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿงœโ€โ™€๏ธ", "๐Ÿงœ๐Ÿปโ€โ™€๏ธ", "๐Ÿงœ๐Ÿผโ€โ™€๏ธ", "๐Ÿงœ๐Ÿฝโ€โ™€๏ธ", "๐Ÿงœ๐Ÿพโ€โ™€๏ธ", "๐Ÿงœ๐Ÿฟโ€โ™€๏ธ", "๐Ÿงโ€โ™‚๏ธ", "๐Ÿง๐Ÿปโ€โ™‚๏ธ", "๐Ÿง๐Ÿผโ€โ™‚๏ธ", "๐Ÿง๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง๐Ÿพโ€โ™‚๏ธ", "๐Ÿง๐Ÿฟโ€โ™‚๏ธ", "๐Ÿงโ€โ™€๏ธ", "๐Ÿง๐Ÿปโ€โ™€๏ธ", "๐Ÿง๐Ÿผโ€โ™€๏ธ", "๐Ÿง๐Ÿฝโ€โ™€๏ธ", "๐Ÿง๐Ÿพโ€โ™€๏ธ", "๐Ÿง๐Ÿฟโ€โ™€๏ธ", "๐Ÿงžโ€โ™‚๏ธ", "๐Ÿงžโ€โ™€๏ธ", "๐ŸงŸโ€โ™‚๏ธ", "๐ŸงŸโ€โ™€๏ธ", "๐Ÿ’†โ€โ™‚๏ธ", "๐Ÿ’†๐Ÿปโ€โ™‚๏ธ", "๐Ÿ’†๐Ÿผโ€โ™‚๏ธ", "๐Ÿ’†๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ’†๐Ÿพโ€โ™‚๏ธ", "๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ’†โ€โ™€๏ธ", "๐Ÿ’†๐Ÿปโ€โ™€๏ธ", "๐Ÿ’†๐Ÿผโ€โ™€๏ธ", "๐Ÿ’†๐Ÿฝโ€โ™€๏ธ", "๐Ÿ’†๐Ÿพโ€โ™€๏ธ", "๐Ÿ’†๐Ÿฟโ€โ™€๏ธ", "๐Ÿ’‡โ€โ™‚๏ธ", "๐Ÿ’‡๐Ÿปโ€โ™‚๏ธ", "๐Ÿ’‡๐Ÿผโ€โ™‚๏ธ", "๐Ÿ’‡๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ’‡๐Ÿพโ€โ™‚๏ธ", "๐Ÿ’‡๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ’‡โ€โ™€๏ธ", "๐Ÿ’‡๐Ÿปโ€โ™€๏ธ", "๐Ÿ’‡๐Ÿผโ€โ™€๏ธ", "๐Ÿ’‡๐Ÿฝโ€โ™€๏ธ", "๐Ÿ’‡๐Ÿพโ€โ™€๏ธ", "๐Ÿ’‡๐Ÿฟโ€โ™€๏ธ", "๐Ÿšถโ€โ™‚๏ธ", "๐Ÿšถ๐Ÿปโ€โ™‚๏ธ", "๐Ÿšถ๐Ÿผโ€โ™‚๏ธ", "๐Ÿšถ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿšถ๐Ÿพโ€โ™‚๏ธ", "๐Ÿšถ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿšถโ€โ™€๏ธ", "๐Ÿšถ๐Ÿปโ€โ™€๏ธ", "๐Ÿšถ๐Ÿผโ€โ™€๏ธ", "๐Ÿšถ๐Ÿฝโ€โ™€๏ธ", "๐Ÿšถ๐Ÿพโ€โ™€๏ธ", "๐Ÿšถ๐Ÿฟโ€โ™€๏ธ", "๐Ÿšถโ€โžก๏ธ", "๐Ÿšถ๐Ÿปโ€โžก๏ธ", "๐Ÿšถ๐Ÿผโ€โžก๏ธ", "๐Ÿšถ๐Ÿฝโ€โžก๏ธ", "๐Ÿšถ๐Ÿพโ€โžก๏ธ", "๐Ÿšถ๐Ÿฟโ€โžก๏ธ", "๐Ÿšถโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿšถโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿšถ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿงโ€โ™‚๏ธ", "๐Ÿง๐Ÿปโ€โ™‚๏ธ", "๐Ÿง๐Ÿผโ€โ™‚๏ธ", "๐Ÿง๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง๐Ÿพโ€โ™‚๏ธ", "๐Ÿง๐Ÿฟโ€โ™‚๏ธ", "๐Ÿงโ€โ™€๏ธ", "๐Ÿง๐Ÿปโ€โ™€๏ธ", "๐Ÿง๐Ÿผโ€โ™€๏ธ", "๐Ÿง๐Ÿฝโ€โ™€๏ธ", "๐Ÿง๐Ÿพโ€โ™€๏ธ", "๐Ÿง๐Ÿฟโ€โ™€๏ธ", "๐ŸงŽโ€โ™‚๏ธ", "๐ŸงŽ๐Ÿปโ€โ™‚๏ธ", "๐ŸงŽ๐Ÿผโ€โ™‚๏ธ", "๐ŸงŽ๐Ÿฝโ€โ™‚๏ธ", "๐ŸงŽ๐Ÿพโ€โ™‚๏ธ", "๐ŸงŽ๐Ÿฟโ€โ™‚๏ธ", "๐ŸงŽโ€โ™€๏ธ", "๐ŸงŽ๐Ÿปโ€โ™€๏ธ", "๐ŸงŽ๐Ÿผโ€โ™€๏ธ", "๐ŸงŽ๐Ÿฝโ€โ™€๏ธ", "๐ŸงŽ๐Ÿพโ€โ™€๏ธ", "๐ŸงŽ๐Ÿฟโ€โ™€๏ธ", "๐ŸงŽโ€โžก๏ธ", "๐ŸงŽ๐Ÿปโ€โžก๏ธ", "๐ŸงŽ๐Ÿผโ€โžก๏ธ", "๐ŸงŽ๐Ÿฝโ€โžก๏ธ", "๐ŸงŽ๐Ÿพโ€โžก๏ธ", "๐ŸงŽ๐Ÿฟโ€โžก๏ธ", "๐ŸงŽโ€โ™€๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ", "๐ŸงŽโ€โ™‚๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ", "๐ŸงŽ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿง‘โ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿง‘๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿง‘๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿง‘๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘จโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘ฉโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฏโ€โžก๏ธ", "๐Ÿง‘โ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿง‘๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿง‘๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿง‘๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿง‘๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿง‘๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘จโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘ฉโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆผโ€โžก๏ธ", "๐Ÿง‘โ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿง‘๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿง‘๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿง‘๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿง‘๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿง‘๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘จโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘จ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘ฉโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿปโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿผโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿพโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿฆฝโ€โžก๏ธ", "๐Ÿƒโ€โ™‚๏ธ", "๐Ÿƒ๐Ÿปโ€โ™‚๏ธ", "๐Ÿƒ๐Ÿผโ€โ™‚๏ธ", "๐Ÿƒ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿƒ๐Ÿพโ€โ™‚๏ธ", "๐Ÿƒ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿƒโ€โ™€๏ธ", "๐Ÿƒ๐Ÿปโ€โ™€๏ธ", "๐Ÿƒ๐Ÿผโ€โ™€๏ธ", "๐Ÿƒ๐Ÿฝโ€โ™€๏ธ", "๐Ÿƒ๐Ÿพโ€โ™€๏ธ", "๐Ÿƒ๐Ÿฟโ€โ™€๏ธ", "๐Ÿƒโ€โžก๏ธ", "๐Ÿƒ๐Ÿปโ€โžก๏ธ", "๐Ÿƒ๐Ÿผโ€โžก๏ธ", "๐Ÿƒ๐Ÿฝโ€โžก๏ธ", "๐Ÿƒ๐Ÿพโ€โžก๏ธ", "๐Ÿƒ๐Ÿฟโ€โžก๏ธ", "๐Ÿƒโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿปโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿผโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿฝโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿพโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿฟโ€โ™€๏ธโ€โžก๏ธ", "๐Ÿƒโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿปโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿผโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿฝโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿพโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿƒ๐Ÿฟโ€โ™‚๏ธโ€โžก๏ธ", "๐Ÿ•ด๏ธ", "๐Ÿ‘ฏโ€โ™‚๏ธ", "๐Ÿ‘ฏ๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‘ฏ๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‘ฏ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‘ฏ๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‘ฏ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ‘ฏโ€โ™€๏ธ", "๐Ÿ‘ฏ๐Ÿปโ€โ™€๏ธ", "๐Ÿ‘ฏ๐Ÿผโ€โ™€๏ธ", "๐Ÿ‘ฏ๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‘ฏ๐Ÿพโ€โ™€๏ธ", "๐Ÿ‘ฏ๐Ÿฟโ€โ™€๏ธ", "๐Ÿง–โ€โ™‚๏ธ", "๐Ÿง–๐Ÿปโ€โ™‚๏ธ", "๐Ÿง–๐Ÿผโ€โ™‚๏ธ", "๐Ÿง–๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง–๐Ÿพโ€โ™‚๏ธ", "๐Ÿง–๐Ÿฟโ€โ™‚๏ธ", "๐Ÿง–โ€โ™€๏ธ", "๐Ÿง–๐Ÿปโ€โ™€๏ธ", "๐Ÿง–๐Ÿผโ€โ™€๏ธ", "๐Ÿง–๐Ÿฝโ€โ™€๏ธ", "๐Ÿง–๐Ÿพโ€โ™€๏ธ", "๐Ÿง–๐Ÿฟโ€โ™€๏ธ", "๐Ÿง—โ€โ™‚๏ธ", "๐Ÿง—๐Ÿปโ€โ™‚๏ธ", "๐Ÿง—๐Ÿผโ€โ™‚๏ธ", "๐Ÿง—๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง—๐Ÿพโ€โ™‚๏ธ", "๐Ÿง—๐Ÿฟโ€โ™‚๏ธ", "๐Ÿง—โ€โ™€๏ธ", "๐Ÿง—๐Ÿปโ€โ™€๏ธ", "๐Ÿง—๐Ÿผโ€โ™€๏ธ", "๐Ÿง—๐Ÿฝโ€โ™€๏ธ", "๐Ÿง—๐Ÿพโ€โ™€๏ธ", "๐Ÿง—๐Ÿฟโ€โ™€๏ธ", "โ›ท๏ธ", "๐ŸŒ๏ธ", "๐ŸŒ๏ธโ€โ™‚๏ธ", "๐ŸŒ๐Ÿปโ€โ™‚๏ธ", "๐ŸŒ๐Ÿผโ€โ™‚๏ธ", "๐ŸŒ๐Ÿฝโ€โ™‚๏ธ", "๐ŸŒ๐Ÿพโ€โ™‚๏ธ", "๐ŸŒ๐Ÿฟโ€โ™‚๏ธ", "๐ŸŒ๏ธโ€โ™€๏ธ", "๐ŸŒ๐Ÿปโ€โ™€๏ธ", "๐ŸŒ๐Ÿผโ€โ™€๏ธ", "๐ŸŒ๐Ÿฝโ€โ™€๏ธ", "๐ŸŒ๐Ÿพโ€โ™€๏ธ", "๐ŸŒ๐Ÿฟโ€โ™€๏ธ", "๐Ÿ„โ€โ™‚๏ธ", "๐Ÿ„๐Ÿปโ€โ™‚๏ธ", "๐Ÿ„๐Ÿผโ€โ™‚๏ธ", "๐Ÿ„๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ„๐Ÿพโ€โ™‚๏ธ", "๐Ÿ„๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ„โ€โ™€๏ธ", "๐Ÿ„๐Ÿปโ€โ™€๏ธ", "๐Ÿ„๐Ÿผโ€โ™€๏ธ", "๐Ÿ„๐Ÿฝโ€โ™€๏ธ", "๐Ÿ„๐Ÿพโ€โ™€๏ธ", "๐Ÿ„๐Ÿฟโ€โ™€๏ธ", "๐Ÿšฃโ€โ™‚๏ธ", "๐Ÿšฃ๐Ÿปโ€โ™‚๏ธ", "๐Ÿšฃ๐Ÿผโ€โ™‚๏ธ", "๐Ÿšฃ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿšฃ๐Ÿพโ€โ™‚๏ธ", "๐Ÿšฃ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿšฃโ€โ™€๏ธ", "๐Ÿšฃ๐Ÿปโ€โ™€๏ธ", "๐Ÿšฃ๐Ÿผโ€โ™€๏ธ", "๐Ÿšฃ๐Ÿฝโ€โ™€๏ธ", "๐Ÿšฃ๐Ÿพโ€โ™€๏ธ", "๐Ÿšฃ๐Ÿฟโ€โ™€๏ธ", "๐ŸŠโ€โ™‚๏ธ", "๐ŸŠ๐Ÿปโ€โ™‚๏ธ", "๐ŸŠ๐Ÿผโ€โ™‚๏ธ", "๐ŸŠ๐Ÿฝโ€โ™‚๏ธ", "๐ŸŠ๐Ÿพโ€โ™‚๏ธ", "๐ŸŠ๐Ÿฟโ€โ™‚๏ธ", "๐ŸŠโ€โ™€๏ธ", "๐ŸŠ๐Ÿปโ€โ™€๏ธ", "๐ŸŠ๐Ÿผโ€โ™€๏ธ", "๐ŸŠ๐Ÿฝโ€โ™€๏ธ", "๐ŸŠ๐Ÿพโ€โ™€๏ธ", "๐ŸŠ๐Ÿฟโ€โ™€๏ธ", "โ›น๏ธ", "โ›น๏ธโ€โ™‚๏ธ", "โ›น๐Ÿปโ€โ™‚๏ธ", "โ›น๐Ÿผโ€โ™‚๏ธ", "โ›น๐Ÿฝโ€โ™‚๏ธ", "โ›น๐Ÿพโ€โ™‚๏ธ", "โ›น๐Ÿฟโ€โ™‚๏ธ", "โ›น๏ธโ€โ™€๏ธ", "โ›น๐Ÿปโ€โ™€๏ธ", "โ›น๐Ÿผโ€โ™€๏ธ", "โ›น๐Ÿฝโ€โ™€๏ธ", "โ›น๐Ÿพโ€โ™€๏ธ", "โ›น๐Ÿฟโ€โ™€๏ธ", "๐Ÿ‹๏ธ", "๐Ÿ‹๏ธโ€โ™‚๏ธ", "๐Ÿ‹๐Ÿปโ€โ™‚๏ธ", "๐Ÿ‹๐Ÿผโ€โ™‚๏ธ", "๐Ÿ‹๐Ÿฝโ€โ™‚๏ธ", "๐Ÿ‹๐Ÿพโ€โ™‚๏ธ", "๐Ÿ‹๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ‹๏ธโ€โ™€๏ธ", "๐Ÿ‹๐Ÿปโ€โ™€๏ธ", "๐Ÿ‹๐Ÿผโ€โ™€๏ธ", "๐Ÿ‹๐Ÿฝโ€โ™€๏ธ", "๐Ÿ‹๐Ÿพโ€โ™€๏ธ", "๐Ÿ‹๐Ÿฟโ€โ™€๏ธ", "๐Ÿšดโ€โ™‚๏ธ", "๐Ÿšด๐Ÿปโ€โ™‚๏ธ", "๐Ÿšด๐Ÿผโ€โ™‚๏ธ", "๐Ÿšด๐Ÿฝโ€โ™‚๏ธ", "๐Ÿšด๐Ÿพโ€โ™‚๏ธ", "๐Ÿšด๐Ÿฟโ€โ™‚๏ธ", "๐Ÿšดโ€โ™€๏ธ", "๐Ÿšด๐Ÿปโ€โ™€๏ธ", "๐Ÿšด๐Ÿผโ€โ™€๏ธ", "๐Ÿšด๐Ÿฝโ€โ™€๏ธ", "๐Ÿšด๐Ÿพโ€โ™€๏ธ", "๐Ÿšด๐Ÿฟโ€โ™€๏ธ", "๐Ÿšตโ€โ™‚๏ธ", "๐Ÿšต๐Ÿปโ€โ™‚๏ธ", "๐Ÿšต๐Ÿผโ€โ™‚๏ธ", "๐Ÿšต๐Ÿฝโ€โ™‚๏ธ", "๐Ÿšต๐Ÿพโ€โ™‚๏ธ", "๐Ÿšต๐Ÿฟโ€โ™‚๏ธ", "๐Ÿšตโ€โ™€๏ธ", "๐Ÿšต๐Ÿปโ€โ™€๏ธ", "๐Ÿšต๐Ÿผโ€โ™€๏ธ", "๐Ÿšต๐Ÿฝโ€โ™€๏ธ", "๐Ÿšต๐Ÿพโ€โ™€๏ธ", "๐Ÿšต๐Ÿฟโ€โ™€๏ธ", "๐Ÿคธโ€โ™‚๏ธ", "๐Ÿคธ๐Ÿปโ€โ™‚๏ธ", "๐Ÿคธ๐Ÿผโ€โ™‚๏ธ", "๐Ÿคธ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคธ๐Ÿพโ€โ™‚๏ธ", "๐Ÿคธ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคธโ€โ™€๏ธ", "๐Ÿคธ๐Ÿปโ€โ™€๏ธ", "๐Ÿคธ๐Ÿผโ€โ™€๏ธ", "๐Ÿคธ๐Ÿฝโ€โ™€๏ธ", "๐Ÿคธ๐Ÿพโ€โ™€๏ธ", "๐Ÿคธ๐Ÿฟโ€โ™€๏ธ", "๐Ÿคผโ€โ™‚๏ธ", "๐Ÿคผ๐Ÿปโ€โ™‚๏ธ", "๐Ÿคผ๐Ÿผโ€โ™‚๏ธ", "๐Ÿคผ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคผ๐Ÿพโ€โ™‚๏ธ", "๐Ÿคผ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคผโ€โ™€๏ธ", "๐Ÿคผ๐Ÿปโ€โ™€๏ธ", "๐Ÿคผ๐Ÿผโ€โ™€๏ธ", "๐Ÿคผ๐Ÿฝโ€โ™€๏ธ", "๐Ÿคผ๐Ÿพโ€โ™€๏ธ", "๐Ÿคผ๐Ÿฟโ€โ™€๏ธ", "๐Ÿคฝโ€โ™‚๏ธ", "๐Ÿคฝ๐Ÿปโ€โ™‚๏ธ", "๐Ÿคฝ๐Ÿผโ€โ™‚๏ธ", "๐Ÿคฝ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคฝ๐Ÿพโ€โ™‚๏ธ", "๐Ÿคฝ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคฝโ€โ™€๏ธ", "๐Ÿคฝ๐Ÿปโ€โ™€๏ธ", "๐Ÿคฝ๐Ÿผโ€โ™€๏ธ", "๐Ÿคฝ๐Ÿฝโ€โ™€๏ธ", "๐Ÿคฝ๐Ÿพโ€โ™€๏ธ", "๐Ÿคฝ๐Ÿฟโ€โ™€๏ธ", "๐Ÿคพโ€โ™‚๏ธ", "๐Ÿคพ๐Ÿปโ€โ™‚๏ธ", "๐Ÿคพ๐Ÿผโ€โ™‚๏ธ", "๐Ÿคพ๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคพ๐Ÿพโ€โ™‚๏ธ", "๐Ÿคพ๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคพโ€โ™€๏ธ", "๐Ÿคพ๐Ÿปโ€โ™€๏ธ", "๐Ÿคพ๐Ÿผโ€โ™€๏ธ", "๐Ÿคพ๐Ÿฝโ€โ™€๏ธ", "๐Ÿคพ๐Ÿพโ€โ™€๏ธ", "๐Ÿคพ๐Ÿฟโ€โ™€๏ธ", "๐Ÿคนโ€โ™‚๏ธ", "๐Ÿคน๐Ÿปโ€โ™‚๏ธ", "๐Ÿคน๐Ÿผโ€โ™‚๏ธ", "๐Ÿคน๐Ÿฝโ€โ™‚๏ธ", "๐Ÿคน๐Ÿพโ€โ™‚๏ธ", "๐Ÿคน๐Ÿฟโ€โ™‚๏ธ", "๐Ÿคนโ€โ™€๏ธ", "๐Ÿคน๐Ÿปโ€โ™€๏ธ", "๐Ÿคน๐Ÿผโ€โ™€๏ธ", "๐Ÿคน๐Ÿฝโ€โ™€๏ธ", "๐Ÿคน๐Ÿพโ€โ™€๏ธ", "๐Ÿคน๐Ÿฟโ€โ™€๏ธ", "๐Ÿง˜โ€โ™‚๏ธ", "๐Ÿง˜๐Ÿปโ€โ™‚๏ธ", "๐Ÿง˜๐Ÿผโ€โ™‚๏ธ", "๐Ÿง˜๐Ÿฝโ€โ™‚๏ธ", "๐Ÿง˜๐Ÿพโ€โ™‚๏ธ", "๐Ÿง˜๐Ÿฟโ€โ™‚๏ธ", "๐Ÿง˜โ€โ™€๏ธ", "๐Ÿง˜๐Ÿปโ€โ™€๏ธ", "๐Ÿง˜๐Ÿผโ€โ™€๏ธ", "๐Ÿง˜๐Ÿฝโ€โ™€๏ธ", "๐Ÿง˜๐Ÿพโ€โ™€๏ธ", "๐Ÿง˜๐Ÿฟโ€โ™€๏ธ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿง‘๐Ÿพ", "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", "๐Ÿง‘๐Ÿปโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", "๐Ÿง‘๐Ÿผโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", "๐Ÿง‘๐Ÿฝโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿพโ€โค๏ธโ€๐Ÿง‘๐Ÿฟ", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿป", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿผ", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿฝ", "๐Ÿง‘๐Ÿฟโ€โค๏ธโ€๐Ÿง‘๐Ÿพ", "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿปโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿผโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿพโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿป", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿผ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฝ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿพ", "๐Ÿ‘จ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘จ๐Ÿฟ", "๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿปโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿผโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฝโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿพโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿป", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿผ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฝ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿพ", "๐Ÿ‘ฉ๐Ÿฟโ€โค๏ธโ€๐Ÿ‘ฉ๐Ÿฟ", "๐Ÿ—ฃ๏ธ", "๐Ÿฟ๏ธ", "๐Ÿปโ€โ„๏ธ", "๐Ÿ•Š๏ธ", "๐Ÿ•ท๏ธ", "๐Ÿ•ธ๏ธ", "๐Ÿต๏ธ", "โ˜˜๏ธ", "๐ŸŒถ๏ธ", "๐Ÿฝ๏ธ", "๐Ÿ—บ๏ธ", "๐Ÿ”๏ธ", "โ›ฐ๏ธ", "๐Ÿ•๏ธ", "๐Ÿ–๏ธ", "๐Ÿœ๏ธ", "๐Ÿ๏ธ", "๐Ÿž๏ธ", "๐ŸŸ๏ธ", "๐Ÿ›๏ธ", "๐Ÿ—๏ธ", "๐Ÿ˜๏ธ", "๐Ÿš๏ธ", "โ›ฉ๏ธ", "๐Ÿ™๏ธ", "โ™จ๏ธ", "๐ŸŽ๏ธ", "๐Ÿ๏ธ", "๐Ÿ›ฃ๏ธ", "๐Ÿ›ค๏ธ", "๐Ÿ›ข๏ธ", "๐Ÿ›ณ๏ธ", "โ›ด๏ธ", "๐Ÿ›ฅ๏ธ", "โœˆ๏ธ", "๐Ÿ›ฉ๏ธ", "๐Ÿ›ฐ๏ธ", "๐Ÿ›Ž๏ธ", "โฑ๏ธ", "โฒ๏ธ", "๐Ÿ•ฐ๏ธ", "๐ŸŒก๏ธ", "โ˜€๏ธ", "โ˜๏ธ", "โ›ˆ๏ธ", "๐ŸŒค๏ธ", "๐ŸŒฅ๏ธ", "๐ŸŒฆ๏ธ", "๐ŸŒง๏ธ", "๐ŸŒจ๏ธ", "๐ŸŒฉ๏ธ", "๐ŸŒช๏ธ", "๐ŸŒซ๏ธ", "๐ŸŒฌ๏ธ", "โ˜‚๏ธ", "โ›ฑ๏ธ", "โ„๏ธ", "โ˜ƒ๏ธ", "โ˜„๏ธ", "๐ŸŽ—๏ธ", "๐ŸŽŸ๏ธ", "๐ŸŽ–๏ธ", "โ›ธ๏ธ", "๐Ÿ•น๏ธ", "โ™ ๏ธ", "โ™ฅ๏ธ", "โ™ฆ๏ธ", "โ™ฃ๏ธ", "โ™Ÿ๏ธ", "๐Ÿ–ผ๏ธ", "๐Ÿ•ถ๏ธ", "๐Ÿ›๏ธ", "โ›‘๏ธ", "๐ŸŽ™๏ธ", "๐ŸŽš๏ธ", "๐ŸŽ›๏ธ", "โ˜Ž๏ธ", "๐Ÿ–ฅ๏ธ", "๐Ÿ–จ๏ธ", "โŒจ๏ธ", "๐Ÿ–ฑ๏ธ", "๐Ÿ–ฒ๏ธ", "๐ŸŽž๏ธ", "๐Ÿ“ฝ๏ธ", "๐Ÿ•ฏ๏ธ", "๐Ÿ—ž๏ธ", "๐Ÿท๏ธ", "โœ‰๏ธ", "๐Ÿ—ณ๏ธ", "โœ๏ธ", "โœ’๏ธ", "๐Ÿ–‹๏ธ", "๐Ÿ–Š๏ธ", "๐Ÿ–Œ๏ธ", "๐Ÿ–๏ธ", "๐Ÿ—‚๏ธ", "๐Ÿ—’๏ธ", "๐Ÿ—“๏ธ", "๐Ÿ–‡๏ธ", "โœ‚๏ธ", "๐Ÿ—ƒ๏ธ", "๐Ÿ—„๏ธ", "๐Ÿ—‘๏ธ", "๐Ÿ—๏ธ", "โ›๏ธ", "โš’๏ธ", "๐Ÿ› ๏ธ", "๐Ÿ—ก๏ธ", "โš”๏ธ", "๐Ÿ›ก๏ธ", "โš™๏ธ", "๐Ÿ—œ๏ธ", "โš–๏ธ", "โ›“๏ธโ€๐Ÿ’ฅ", "โ›“๏ธ", "โš—๏ธ", "๐Ÿ›๏ธ", "๐Ÿ›‹๏ธ", "โšฐ๏ธ", "โšฑ๏ธ", "โš ๏ธ", "โ˜ข๏ธ", "โ˜ฃ๏ธ", "โฌ†๏ธ", "โ†—๏ธ", "โžก๏ธ", "โ†˜๏ธ", "โฌ‡๏ธ", "โ†™๏ธ", "โฌ…๏ธ", "โ†–๏ธ", "โ†•๏ธ", "โ†”๏ธ", "โ†ฉ๏ธ", "โ†ช๏ธ", "โคด๏ธ", "โคต๏ธ", "โš›๏ธ", "๐Ÿ•‰๏ธ", "โœก๏ธ", "โ˜ธ๏ธ", "โ˜ฏ๏ธ", "โœ๏ธ", "โ˜ฆ๏ธ", "โ˜ช๏ธ", "โ˜ฎ๏ธ", "โ–ถ๏ธ", "โญ๏ธ", "โฏ๏ธ", "โ—€๏ธ", "โฎ๏ธ", "โธ๏ธ", "โน๏ธ", "โบ๏ธ", "โ๏ธ", "โ™€๏ธ", "โ™‚๏ธ", "โšง๏ธ", "โœ–๏ธ", "โ™พ๏ธ", "โ€ผ๏ธ", "โ‰๏ธ", "ใ€ฐ๏ธ", "โš•๏ธ", "โ™ป๏ธ", "โšœ๏ธ", "โ˜‘๏ธ", "โœ”๏ธ", "ใ€ฝ๏ธ", "โœณ๏ธ", "โœด๏ธ", "โ‡๏ธ", "ยฉ๏ธ", "ยฎ๏ธ", "โ„ข๏ธ", "#๏ธโƒฃ", "*๏ธโƒฃ", "0๏ธโƒฃ", "1๏ธโƒฃ", "2๏ธโƒฃ", "3๏ธโƒฃ", "4๏ธโƒฃ", "5๏ธโƒฃ", "6๏ธโƒฃ", "7๏ธโƒฃ", "8๏ธโƒฃ", "9๏ธโƒฃ", "๐Ÿ…ฐ๏ธ", "๐Ÿ…ฑ๏ธ", "โ„น๏ธ", "โ“‚๏ธ", "๐Ÿ…พ๏ธ", "๐Ÿ…ฟ๏ธ", "๐Ÿˆ‚๏ธ", "๐Ÿˆท๏ธ", "ใŠ—๏ธ", "ใŠ™๏ธ", "โ—ผ๏ธ", "โ—ป๏ธ", "โ–ช๏ธ", "โ–ซ๏ธ", "๐Ÿณ๏ธ", "๐Ÿณ๏ธโ€๐ŸŒˆ", "๐Ÿณ๏ธโ€โšง๏ธ", "๐Ÿดโ€โ˜ ๏ธ", } replacerArgs := make([]string, len(qualifiedEmojis)*2) for i, emoji := range qualifiedEmojis { replacerArgs[i*2] = strings.ReplaceAll(emoji, "\ufe0f", "") replacerArgs[(i*2)+1] = emoji } fullyQualifier = strings.NewReplacer(replacerArgs...) } go-util-0.9.5/variationselector/generate.go000066400000000000000000000050101513243016000207520ustar00rootroot00000000000000// Copyright (c) 2024 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build ignore package main import ( "fmt" "os" "strings" "golang.org/x/exp/slices" "go.mau.fi/util/exerrors" "go.mau.fi/util/unicodeurls" ) func readEmojiLines(url, filter string) []string { return unicodeurls.ReadDataFileList(url, func(line string) (string, bool) { parts := strings.Split(line, "; ") if len(parts) < 2 || !strings.HasPrefix(parts[1], filter) { return "", false } return strings.TrimSpace(parts[0]), true }) } func main() { variationSequences := readEmojiLines(unicodeurls.EmojiVariationSequences, "emoji style") fullyQualifiedSequences := readEmojiLines(unicodeurls.EmojiTest, "fully-qualified") var extraVariations []string for _, seq := range variationSequences { if !slices.Contains(fullyQualifiedSequences, seq) && !strings.HasPrefix(seq, "00") { unifiedWithoutVS := strings.Split(seq, " ")[0] extraVariations = append(extraVariations, fmt.Sprintf(`\x{%s}`, unifiedWithoutVS)) } } file := exerrors.Must(os.OpenFile("emojis.go", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)) exerrors.Must(file.WriteString(`// Code generated by go generate; DO NOT EDIT. package variationselector import ( "regexp" "strings" ) func doInit() { `)) exerrors.Must(file.WriteString("\tvariationRegex = regexp.MustCompile(\n")) exerrors.Must(file.WriteString("\t\t`(^|[^\\x{200D}])(` +\n\t\t\t`")) exerrors.Must(file.WriteString(strings.Join(extraVariations, "|` +\n\t\t\t`"))) exerrors.Must(file.WriteString("` +\n\t\t\t`)([^\\x{FE0F}\\x{FE0E}\\x{200D}\\x{1F3FB}\\x{1F3FC}\\x{1F3FD}\\x{1F3FE}\\x{1F3FF}]|$)`,\n\t)\n")) exerrors.Must(file.WriteString("\tvar qualifiedEmojis = []string{\n")) for _, unified := range fullyQualifiedSequences { if !strings.Contains(unified, "FE0F") { continue } unicode := unicodeurls.ParseHex(strings.Split(unified, " ")) exerrors.Must(fmt.Fprintf(file, "\t\t\"%s\",\n", unicode)) } exerrors.Must(file.WriteString("\t}\n")) exerrors.Must(file.WriteString(` replacerArgs := make([]string, len(qualifiedEmojis)*2) for i, emoji := range qualifiedEmojis { replacerArgs[i*2] = strings.ReplaceAll(emoji, "\ufe0f", "") replacerArgs[(i*2)+1] = emoji } `)) exerrors.Must(file.WriteString("\tfullyQualifier = strings.NewReplacer(replacerArgs...)\n")) exerrors.Must(file.WriteString("}\n")) exerrors.PanicIfNotNil(file.Close()) } go-util-0.9.5/variationselector/variationselector.go000066400000000000000000000050271513243016000227250ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Package variationselector provides utility functions for adding and removing emoji variation selectors (16) // that matches the suggestions in the Matrix spec. package variationselector import ( _ "embed" "regexp" "strings" "sync" ) //go:generate go run ./generate.go var initOnce sync.Once var fullyQualifier *strings.Replacer var variationRegex *regexp.Regexp // The fully qualifying replacer will add incorrect variation selectors before skin tones, this removes those. var skinToneReplacer = strings.NewReplacer( "\ufe0f\U0001F3FB", "\U0001F3FB", "\ufe0f\U0001F3FC", "\U0001F3FC", "\ufe0f\U0001F3FD", "\U0001F3FD", "\ufe0f\U0001F3FE", "\U0001F3FE", "\ufe0f\U0001F3FF", "\U0001F3FF", "\ufe0f\ufe0e", "\ufe0e", ) const VS16 = "\ufe0f" // Add adds emoji variation selectors to all emojis that have multiple forms in the given string. // // Variation selectors will be added to everything that is allowed to have both a text presentation and // an emoji presentation according to Unicode Technical Standard #51. // If you only want to add variation selectors necessary for fully-qualified forms, use FullyQualify instead. // // This method uses data from emoji-variation-sequences.txt in the official Unicode emoji data set. // // This will remove all variation selectors first to make sure it doesn't add duplicates. func Add(val string) string { initOnce.Do(doInit) return variationRegex.ReplaceAllString(FullyQualify(val), "$1$2\ufe0f$3") } // Remove removes all emoji variation selectors in the given string. func Remove(val string) string { return strings.ReplaceAll(val, VS16, "") } // FullyQualify converts all emojis to their fully-qualified form by adding variation selectors where necessary. // // This will not add variation selectors to all possible emojis, only the ones that require a variation selector // to be "fully qualified" according to Unicode Technical Standard #51. // If you want to add variation selectors in all allowed cases, use Add instead. // // This method uses data from emoji-test.txt in the official Unicode emoji data set. // // N.B. This method is not currently used by the Matrix spec, but it is included as bridging to other networks may need it. func FullyQualify(val string) string { initOnce.Do(doInit) return skinToneReplacer.Replace(fullyQualifier.Replace(Remove(val))) } go-util-0.9.5/variationselector/variationselector_test.go000066400000000000000000000213771513243016000237720ustar00rootroot00000000000000// Copyright (c) 2023 Tulir Asokan // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package variationselector_test import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mau.fi/util/exerrors" "go.mau.fi/util/variationselector" ) func TestAdd_Full(t *testing.T) { resp := get(t, "https://raw.githubusercontent.com/milesj/emojibase/master/packages/data/en/data.raw.json") var emojis []emojibaseEmoji exerrors.PanicIfNotNil(json.NewDecoder(resp.Body).Decode(&emojis)) for _, e := range emojis { compareEmoji(t, e.Emoji, variationselector.Add) for _, s := range e.Skins { compareEmoji(t, s.Emoji, variationselector.Add) } } } func TestFullyQualify_Full(t *testing.T) { resp := get(t, "https://raw.githubusercontent.com/iamcal/emoji-data/master/emoji.json") var emojis []iamcalEmoji exerrors.PanicIfNotNil(json.NewDecoder(resp.Body).Decode(&emojis)) for _, e := range emojis { compareEmoji(t, unifiedToUnicode(e.Unified), variationselector.FullyQualify) for _, s := range e.SkinVariations { compareEmoji(t, unifiedToUnicode(s.Unified), variationselector.FullyQualify) } } } func get(t *testing.T, url string) *http.Response { req, err := http.NewRequest(http.MethodGet, url, nil) require.NoError(t, err) req.Header.Set("User-Agent", "GitHub actions @ https://github.com/mautrix/go-util/blob/main/variationselector/variationselector_test.go") resp, err := http.DefaultClient.Do(req) require.NoError(t, err) return resp } type emojibaseEmoji struct { Emoji string `json:"emoji"` Hexcode string `json:"hexcode"` Skins []emojibaseEmoji `json:"skins"` } type iamcalEmoji struct { Unified string `json:"unified"` SkinVariations map[string]iamcalEmoji `json:"skin_variations"` } func unifiedToUnicode(input string) string { parts := strings.Split(input, "-") output := make([]rune, len(parts)) for i, part := range parts { output[i] = rune(exerrors.Must(strconv.ParseInt(part, 16, 32))) } return string(output) } func unicodeToUnified(input string) string { runes := []rune(input) output := make([]string, len(runes)) for i, r := range runes { output[i] = fmt.Sprintf("%X", r) } return strings.Join(output, "-") } func compareEmoji(t *testing.T, orig string, fn func(string) string) { proc := fn(orig) if proc != orig { t.Errorf("emoji: %s\nexpected: %s\ngot: %s", orig, unicodeToUnified(orig), unicodeToUnified(proc)) } } func TestAdd(t *testing.T) { assert.Equal(t, "\U0001f44d\U0001f3fd", variationselector.Add("\U0001f44d\U0001f3fd")) assert.Equal(t, "\U0001f44d\ufe0f", variationselector.Add("\U0001f44d")) assert.Equal(t, "\U0001f44d\ufe0f", variationselector.Add("\U0001f44d\ufe0f")) assert.Equal(t, "4\ufe0f\u20e3", variationselector.Add("4\u20e3")) assert.Equal(t, "4\ufe0f\u20e3", variationselector.Add("4\ufe0f\u20e3")) assert.Equal(t, "4", variationselector.Add("4")) assert.Equal(t, "\U0001f914", variationselector.Add("\U0001f914")) assert.Equal(t, "\U0001f408\u200d\u2b1b", variationselector.Add("\U0001f408\u200d\u2b1b")) assert.Equal(t, "\u2122\ufe0f", variationselector.Add("\u2122")) assert.Equal(t, "\u2122\ufe0e", variationselector.Add("\u2122\ufe0e")) } func TestFullyQualify(t *testing.T) { assert.Equal(t, "\U0001f44d", variationselector.FullyQualify("\U0001f44d")) assert.Equal(t, "\U0001f44d", variationselector.FullyQualify("\U0001f44d\ufe0f")) assert.Equal(t, "4\ufe0f\u20e3", variationselector.FullyQualify("4\u20e3")) assert.Equal(t, "4\ufe0f\u20e3", variationselector.FullyQualify("4\ufe0f\u20e3")) assert.Equal(t, "4", variationselector.FullyQualify("4")) assert.Equal(t, "\U0001f914", variationselector.FullyQualify("\U0001f914")) assert.Equal(t, "\u263a\ufe0f", variationselector.FullyQualify("\u263a")) assert.Equal(t, "\u263a\ufe0f", variationselector.FullyQualify("\u263a")) assert.Equal(t, "\U0001f3f3\ufe0f\u200D\U0001f308", variationselector.FullyQualify("\U0001f3f3\u200D\U0001f308")) assert.Equal(t, "\U0001f3f3\ufe0f\u200D\U0001f308", variationselector.FullyQualify("\U0001f3f3\ufe0f\u200D\U0001f308")) assert.Equal(t, "\U0001f408\u200d\u2b1b", variationselector.FullyQualify("\U0001f408\u200d\u2b1b")) assert.Equal(t, "\u2122\ufe0f", variationselector.FullyQualify("\u2122")) assert.Equal(t, "\u2122\ufe0e", variationselector.FullyQualify("\u2122\ufe0e")) } func TestRemove(t *testing.T) { assert.Equal(t, "\U0001f44d", variationselector.Remove("\U0001f44d")) assert.Equal(t, "\U0001f44d", variationselector.Remove("\U0001f44d\ufe0f")) assert.Equal(t, "4\u20e3", variationselector.Remove("4\u20e3")) assert.Equal(t, "4\u20e3", variationselector.Remove("4\ufe0f\u20e3")) assert.Equal(t, "4", variationselector.Remove("4")) assert.Equal(t, "\U0001f914", variationselector.Remove("\U0001f914")) } func ExampleAdd() { fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f44d"))) // thumbs up (needs selector) fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f44d\ufe0f"))) // thumbs up with variation selector (stays as-is) fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f44d\U0001f3fd"))) // thumbs up with skin tone (shouldn't get selector) fmt.Println(strconv.QuoteToASCII(variationselector.Add("\U0001f914"))) // thinking face (shouldn't get selector) // Output: // "\U0001f44d\ufe0f" // "\U0001f44d\ufe0f" // "\U0001f44d\U0001f3fd" // "\U0001f914" } func ExampleFullyQualify() { fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f44d"))) // thumbs up (already fully qualified) fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f44d\ufe0f"))) // thumbs up with variation selector (variation selector removed) fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f44d\U0001f3fd"))) // thumbs up with skin tone (already fully qualified) fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\u263a"))) // smiling face (unqualified, should get selector) fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f3f3\u200d\U0001f308"))) // rainbow flag (unqualified, should get selector) fmt.Println(strconv.QuoteToASCII(variationselector.FullyQualify("\U0001f3f3\ufe0f\u200d\U0001f308"))) // rainbow flag with variation selector (already fully qualified) // Output: // "\U0001f44d" // "\U0001f44d" // "\U0001f44d\U0001f3fd" // "\u263a\ufe0f" // "\U0001f3f3\ufe0f\u200d\U0001f308" // "\U0001f3f3\ufe0f\u200d\U0001f308" } func ExampleRemove() { fmt.Println(strconv.QuoteToASCII(variationselector.Remove("\U0001f44d"))) fmt.Println(strconv.QuoteToASCII(variationselector.Remove("\U0001f44d\ufe0f"))) // Output: // "\U0001f44d" // "\U0001f44d" } func doBenchmarkAdd(b *testing.B, input string) { for i := 0; i < b.N; i++ { variationselector.Add(input) } } func BenchmarkAddNumber4(b *testing.B) { doBenchmarkAdd(b, "4๏ธ\u20e3") } func BenchmarkAddBlackCat(b *testing.B) { doBenchmarkAdd(b, "\U0001f408\u200d\u2b1b") } func BenchmarkAddString(b *testing.B) { doBenchmarkAdd(b, "This is a slightly longer ๐Ÿค” string ๐Ÿˆ๏ธ that contains a few emojis ๐Ÿ‘๏ธ") } func BenchmarkAddLongString(b *testing.B) { doBenchmarkAdd(b, strings.Repeat("This is a slightly longer ๐Ÿค” string ๐Ÿˆ๏ธ that contains a few emojis ๐Ÿ‘๏ธ", 1000)) } func BenchmarkAddNoEmojis(b *testing.B) { doBenchmarkAdd(b, "This is a slightly longer string that does not contain any emojis") } func BenchmarkAddNoEmojisLong(b *testing.B) { doBenchmarkAdd(b, strings.Repeat("This is a slightly longer string that does not contain any emojis", 1000)) } func doBenchmarkFullyQualify(b *testing.B, input string) { for i := 0; i < b.N; i++ { variationselector.FullyQualify(input) } } func BenchmarkFullyQualifyNumber4(b *testing.B) { doBenchmarkFullyQualify(b, "4\ufe0f\u20e3") } func BenchmarkFullyQualifyBlackCat(b *testing.B) { doBenchmarkFullyQualify(b, "\U0001f408\u200d\u2b1b") } func BenchmarkFullyQualifyString(b *testing.B) { doBenchmarkFullyQualify(b, "This is a slightly longer ๐Ÿค” string ๐Ÿˆ๏ธ that contains a few emojis ๐Ÿ‘๏ธ") } func BenchmarkFullyQualifyLongString(b *testing.B) { doBenchmarkFullyQualify(b, strings.Repeat("This is a slightly longer ๐Ÿค” string ๐Ÿˆ๏ธ that contains a few emojis ๐Ÿ‘๏ธ", 1000)) } func BenchmarkFullyQualifyNoEmojis(b *testing.B) { doBenchmarkFullyQualify(b, "This is a slightly longer string that does not contain any emojis") } func BenchmarkFullyQualifyNoEmojisLong(b *testing.B) { doBenchmarkFullyQualify(b, strings.Repeat("This is a slightly longer string that does not contain any emojis", 1000)) }