mirror of https://github.com/k3d-io/k3d
[Feature] add: config options to configure extra hosts (#938)
- add: `k3d cluster create --host-alias <ip>:<hostname>[,alias,...]` - add: `.hostAliases` config option (to be present in v1alpha4/Simple - change: regexp hostname validation - add: `HostAliases` in `SimpleConfig`, `ClusterCreateOpts` and `ClusterStartOpts` - add: `NewHostAliasesInjectEtcHostsAction` - add: `RewriteFileActionOpts` with option `NoCopy` to work around "Resource Busy" issuepull/942/head
parent
c16bf6f3d2
commit
aec4507db8
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
Copyright © 2020-2021 The k3d Author(s) |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in |
||||||
|
all copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
THE SOFTWARE. |
||||||
|
*/ |
||||||
|
package client |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/goodhosts/hostsfile" |
||||||
|
"github.com/rancher/k3d/v5/pkg/actions" |
||||||
|
"github.com/rancher/k3d/v5/pkg/runtimes" |
||||||
|
k3d "github.com/rancher/k3d/v5/pkg/types" |
||||||
|
) |
||||||
|
|
||||||
|
func NewHostAliasesInjectEtcHostsAction(runtime runtimes.Runtime, hostAliases []k3d.HostAlias) actions.RewriteFileAction { |
||||||
|
return actions.RewriteFileAction{ |
||||||
|
Runtime: runtime, |
||||||
|
Path: "/etc/hosts", |
||||||
|
Mode: 0644, |
||||||
|
Description: "Adding HostAliases to /etc/hosts in nodes", |
||||||
|
Opts: actions.RewriteFileActionOpts{ |
||||||
|
NoCopy: true, |
||||||
|
}, |
||||||
|
RewriteFunc: func(input []byte) ([]byte, error) { |
||||||
|
|
||||||
|
tmpHosts, err := os.CreateTemp("", "k3d-hostsfile-*") |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("error creating temp hosts file: %w", err) |
||||||
|
} |
||||||
|
os.WriteFile(tmpHosts.Name(), input, 0777) |
||||||
|
|
||||||
|
hostsfile, err := hostsfile.NewCustomHosts(tmpHosts.Name()) |
||||||
|
if err != nil { |
||||||
|
return nil, fmt.Errorf("error reading temp hosts file: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
for _, hostAlias := range hostAliases { |
||||||
|
if err := hostsfile.Add(hostAlias.IP, hostAlias.Hostnames...); err != nil { |
||||||
|
return nil, fmt.Errorf("error adding hosts file entry for %s:%s: %w", hostAlias.IP, hostAlias.Hostnames, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
hostsfile.Clean() |
||||||
|
|
||||||
|
hostsfile.Flush() |
||||||
|
|
||||||
|
time.Sleep(time.Second) |
||||||
|
|
||||||
|
return os.ReadFile(tmpHosts.Name()) |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
bin/ |
||||||
|
.idea/ |
||||||
|
# Binaries for programs and plugins |
||||||
|
*.exe |
||||||
|
*.exe~ |
||||||
|
*.dll |
||||||
|
*.so |
||||||
|
*.dylib |
||||||
|
|
||||||
|
# Test binary, built with `go test -c` |
||||||
|
*.test |
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE |
||||||
|
*.out |
||||||
|
|
@ -0,0 +1,12 @@ |
|||||||
|
language: go |
||||||
|
dist: xenial |
||||||
|
go: |
||||||
|
- '1.10' |
||||||
|
- '1.11' |
||||||
|
- '1.12' |
||||||
|
- '1.13' |
||||||
|
- 'tip' |
||||||
|
|
||||||
|
script: |
||||||
|
- go test -coverpkg=./... -coverprofile=coverage.info -timeout=5s |
||||||
|
- bash <(curl -s https://codecov.io/bash) |
@ -0,0 +1,43 @@ |
|||||||
|
# Contributor Code of Conduct |
||||||
|
|
||||||
|
This project adheres to [The Code Manifesto](http://codemanifesto.com) |
||||||
|
as its guidelines for contributor interactions. |
||||||
|
|
||||||
|
## The Code Manifesto |
||||||
|
|
||||||
|
We want to work in an ecosystem that empowers developers to reach their |
||||||
|
potential — one that encourages growth and effective collaboration. A space |
||||||
|
that is safe for all. |
||||||
|
|
||||||
|
A space such as this benefits everyone that participates in it. It encourages |
||||||
|
new developers to enter our field. It is through discussion and collaboration |
||||||
|
that we grow, and through growth that we improve. |
||||||
|
|
||||||
|
In the effort to create such a place, we hold to these values: |
||||||
|
|
||||||
|
1. **Discrimination limits us.** This includes discrimination on the basis of |
||||||
|
race, gender, sexual orientation, gender identity, age, nationality, |
||||||
|
technology and any other arbitrary exclusion of a group of people. |
||||||
|
2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort |
||||||
|
levels. Remember that, and if brought to your attention, heed it. |
||||||
|
3. **We are our biggest assets.** None of us were born masters of our trade. |
||||||
|
Each of us has been helped along the way. Return that favor, when and where |
||||||
|
you can. |
||||||
|
4. **We are resources for the future.** As an extension of #3, share what you |
||||||
|
know. Make yourself a resource to help those that come after you. |
||||||
|
5. **Respect defines us.** Treat others as you wish to be treated. Make your |
||||||
|
discussions, criticisms and debates from a position of respectfulness. Ask |
||||||
|
yourself, is it true? Is it necessary? Is it constructive? Anything less is |
||||||
|
unacceptable. |
||||||
|
6. **Reactions require grace.** Angry responses are valid, but abusive language |
||||||
|
and vindictive actions are toxic. When something happens that offends you, |
||||||
|
handle it assertively, but be respectful. Escalate reasonably, and try to |
||||||
|
allow the offender an opportunity to explain themselves, and possibly |
||||||
|
correct the issue. |
||||||
|
7. **Opinions are just that: opinions.** Each and every one of us, due to our |
||||||
|
background and upbringing, have varying opinions. That is perfectly |
||||||
|
acceptable. Remember this: if you respect your own opinions, you should |
||||||
|
respect the opinions of others. |
||||||
|
8. **To err is human.** You might not intend it, but mistakes do happen and |
||||||
|
contribute to build experience. Tolerate honest mistakes, and don't |
||||||
|
hesitate to apologize if you make one yourself. |
@ -0,0 +1,63 @@ |
|||||||
|
#### Support |
||||||
|
If you do have a contribution to the package, feel free to create a Pull Request or an Issue. |
||||||
|
|
||||||
|
#### What to contribute |
||||||
|
If you don't know what to do, there are some features and functions that need to be done |
||||||
|
|
||||||
|
- [ ] Refactor code |
||||||
|
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check |
||||||
|
- [ ] Create actual list of contributors and projects that currently using this package |
||||||
|
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues) |
||||||
|
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions) |
||||||
|
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new |
||||||
|
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc |
||||||
|
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) |
||||||
|
- [ ] Implement fuzzing testing |
||||||
|
- [ ] Implement some struct/map/array utilities |
||||||
|
- [ ] Implement map/array validation |
||||||
|
- [ ] Implement benchmarking |
||||||
|
- [ ] Implement batch of examples |
||||||
|
- [ ] Look at forks for new features and fixes |
||||||
|
|
||||||
|
#### Advice |
||||||
|
Feel free to create what you want, but keep in mind when you implement new features: |
||||||
|
- Code must be clear and readable, names of variables/constants clearly describes what they are doing |
||||||
|
- Public functions must be documented and described in source file and added to README.md to the list of available functions |
||||||
|
- There are must be unit-tests for any new functions and improvements |
||||||
|
|
||||||
|
## Financial contributions |
||||||
|
|
||||||
|
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/govalidator). |
||||||
|
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed. |
||||||
|
|
||||||
|
|
||||||
|
## Credits |
||||||
|
|
||||||
|
|
||||||
|
### Contributors |
||||||
|
|
||||||
|
Thank you to all the people who have already contributed to govalidator! |
||||||
|
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a> |
||||||
|
|
||||||
|
|
||||||
|
### Backers |
||||||
|
|
||||||
|
Thank you to all our backers! [[Become a backer](https://opencollective.com/govalidator#backer)] |
||||||
|
|
||||||
|
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a> |
||||||
|
|
||||||
|
|
||||||
|
### Sponsors |
||||||
|
|
||||||
|
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/govalidator#sponsor)) |
||||||
|
|
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a> |
@ -0,0 +1,21 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2014-2020 Alex Saskevich |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,622 @@ |
|||||||
|
govalidator |
||||||
|
=========== |
||||||
|
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) |
||||||
|
[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator) |
||||||
|
[![Coverage](https://codecov.io/gh/asaskevich/govalidator/branch/master/graph/badge.svg)](https://codecov.io/gh/asaskevich/govalidator) [![Go Report Card](https://goreportcard.com/badge/github.com/asaskevich/govalidator)](https://goreportcard.com/report/github.com/asaskevich/govalidator) [![GoSearch](http://go-search.org/badge?id=github.com%2Fasaskevich%2Fgovalidator)](http://go-search.org/view?id=github.com%2Fasaskevich%2Fgovalidator) [![Backers on Open Collective](https://opencollective.com/govalidator/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/govalidator/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_shield) |
||||||
|
|
||||||
|
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js). |
||||||
|
|
||||||
|
#### Installation |
||||||
|
Make sure that Go is installed on your computer. |
||||||
|
Type the following command in your terminal: |
||||||
|
|
||||||
|
go get github.com/asaskevich/govalidator |
||||||
|
|
||||||
|
or you can get specified release of the package with `gopkg.in`: |
||||||
|
|
||||||
|
go get gopkg.in/asaskevich/govalidator.v10 |
||||||
|
|
||||||
|
After it the package is ready to use. |
||||||
|
|
||||||
|
|
||||||
|
#### Import package in your project |
||||||
|
Add following line in your `*.go` file: |
||||||
|
```go |
||||||
|
import "github.com/asaskevich/govalidator" |
||||||
|
``` |
||||||
|
If you are unhappy to use long `govalidator`, you can do something like this: |
||||||
|
```go |
||||||
|
import ( |
||||||
|
valid "github.com/asaskevich/govalidator" |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
#### Activate behavior to require all fields have a validation tag by default |
||||||
|
`SetFieldsRequiredByDefault` causes validation to fail when struct fields do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). A good place to activate this is a package init function or the main() function. |
||||||
|
|
||||||
|
`SetNilPtrAllowedByRequired` causes validation to pass when struct fields marked by `required` are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between `nil` and `zero value` state can use this. If disabled, both `nil` and `zero` values cause validation errors. |
||||||
|
|
||||||
|
```go |
||||||
|
import "github.com/asaskevich/govalidator" |
||||||
|
|
||||||
|
func init() { |
||||||
|
govalidator.SetFieldsRequiredByDefault(true) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Here's some code to explain it: |
||||||
|
```go |
||||||
|
// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): |
||||||
|
type exampleStruct struct { |
||||||
|
Name string `` |
||||||
|
Email string `valid:"email"` |
||||||
|
} |
||||||
|
|
||||||
|
// this, however, will only fail when Email is empty or an invalid email address: |
||||||
|
type exampleStruct2 struct { |
||||||
|
Name string `valid:"-"` |
||||||
|
Email string `valid:"email"` |
||||||
|
} |
||||||
|
|
||||||
|
// lastly, this will only fail when Email is an invalid email address but not when it's empty: |
||||||
|
type exampleStruct2 struct { |
||||||
|
Name string `valid:"-"` |
||||||
|
Email string `valid:"email,optional"` |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123)) |
||||||
|
##### Custom validator function signature |
||||||
|
A context was added as the second parameter, for structs this is the object being validated – this makes dependent validation possible. |
||||||
|
```go |
||||||
|
import "github.com/asaskevich/govalidator" |
||||||
|
|
||||||
|
// old signature |
||||||
|
func(i interface{}) bool |
||||||
|
|
||||||
|
// new signature |
||||||
|
func(i interface{}, o interface{}) bool |
||||||
|
``` |
||||||
|
|
||||||
|
##### Adding a custom validator |
||||||
|
This was changed to prevent data races when accessing custom validators. |
||||||
|
```go |
||||||
|
import "github.com/asaskevich/govalidator" |
||||||
|
|
||||||
|
// before |
||||||
|
govalidator.CustomTypeTagMap["customByteArrayValidator"] = func(i interface{}, o interface{}) bool { |
||||||
|
// ... |
||||||
|
} |
||||||
|
|
||||||
|
// after |
||||||
|
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, o interface{}) bool { |
||||||
|
// ... |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
#### List of functions: |
||||||
|
```go |
||||||
|
func Abs(value float64) float64 |
||||||
|
func BlackList(str, chars string) string |
||||||
|
func ByteLength(str string, params ...string) bool |
||||||
|
func CamelCaseToUnderscore(str string) string |
||||||
|
func Contains(str, substring string) bool |
||||||
|
func Count(array []interface{}, iterator ConditionIterator) int |
||||||
|
func Each(array []interface{}, iterator Iterator) |
||||||
|
func ErrorByField(e error, field string) string |
||||||
|
func ErrorsByField(e error) map[string]string |
||||||
|
func Filter(array []interface{}, iterator ConditionIterator) []interface{} |
||||||
|
func Find(array []interface{}, iterator ConditionIterator) interface{} |
||||||
|
func GetLine(s string, index int) (string, error) |
||||||
|
func GetLines(s string) []string |
||||||
|
func HasLowerCase(str string) bool |
||||||
|
func HasUpperCase(str string) bool |
||||||
|
func HasWhitespace(str string) bool |
||||||
|
func HasWhitespaceOnly(str string) bool |
||||||
|
func InRange(value interface{}, left interface{}, right interface{}) bool |
||||||
|
func InRangeFloat32(value, left, right float32) bool |
||||||
|
func InRangeFloat64(value, left, right float64) bool |
||||||
|
func InRangeInt(value, left, right interface{}) bool |
||||||
|
func IsASCII(str string) bool |
||||||
|
func IsAlpha(str string) bool |
||||||
|
func IsAlphanumeric(str string) bool |
||||||
|
func IsBase64(str string) bool |
||||||
|
func IsByteLength(str string, min, max int) bool |
||||||
|
func IsCIDR(str string) bool |
||||||
|
func IsCRC32(str string) bool |
||||||
|
func IsCRC32b(str string) bool |
||||||
|
func IsCreditCard(str string) bool |
||||||
|
func IsDNSName(str string) bool |
||||||
|
func IsDataURI(str string) bool |
||||||
|
func IsDialString(str string) bool |
||||||
|
func IsDivisibleBy(str, num string) bool |
||||||
|
func IsEmail(str string) bool |
||||||
|
func IsExistingEmail(email string) bool |
||||||
|
func IsFilePath(str string) (bool, int) |
||||||
|
func IsFloat(str string) bool |
||||||
|
func IsFullWidth(str string) bool |
||||||
|
func IsHalfWidth(str string) bool |
||||||
|
func IsHash(str string, algorithm string) bool |
||||||
|
func IsHexadecimal(str string) bool |
||||||
|
func IsHexcolor(str string) bool |
||||||
|
func IsHost(str string) bool |
||||||
|
func IsIP(str string) bool |
||||||
|
func IsIPv4(str string) bool |
||||||
|
func IsIPv6(str string) bool |
||||||
|
func IsISBN(str string, version int) bool |
||||||
|
func IsISBN10(str string) bool |
||||||
|
func IsISBN13(str string) bool |
||||||
|
func IsISO3166Alpha2(str string) bool |
||||||
|
func IsISO3166Alpha3(str string) bool |
||||||
|
func IsISO4217(str string) bool |
||||||
|
func IsISO693Alpha2(str string) bool |
||||||
|
func IsISO693Alpha3b(str string) bool |
||||||
|
func IsIn(str string, params ...string) bool |
||||||
|
func IsInRaw(str string, params ...string) bool |
||||||
|
func IsInt(str string) bool |
||||||
|
func IsJSON(str string) bool |
||||||
|
func IsLatitude(str string) bool |
||||||
|
func IsLongitude(str string) bool |
||||||
|
func IsLowerCase(str string) bool |
||||||
|
func IsMAC(str string) bool |
||||||
|
func IsMD4(str string) bool |
||||||
|
func IsMD5(str string) bool |
||||||
|
func IsMagnetURI(str string) bool |
||||||
|
func IsMongoID(str string) bool |
||||||
|
func IsMultibyte(str string) bool |
||||||
|
func IsNatural(value float64) bool |
||||||
|
func IsNegative(value float64) bool |
||||||
|
func IsNonNegative(value float64) bool |
||||||
|
func IsNonPositive(value float64) bool |
||||||
|
func IsNotNull(str string) bool |
||||||
|
func IsNull(str string) bool |
||||||
|
func IsNumeric(str string) bool |
||||||
|
func IsPort(str string) bool |
||||||
|
func IsPositive(value float64) bool |
||||||
|
func IsPrintableASCII(str string) bool |
||||||
|
func IsRFC3339(str string) bool |
||||||
|
func IsRFC3339WithoutZone(str string) bool |
||||||
|
func IsRGBcolor(str string) bool |
||||||
|
func IsRegex(str string) bool |
||||||
|
func IsRequestURI(rawurl string) bool |
||||||
|
func IsRequestURL(rawurl string) bool |
||||||
|
func IsRipeMD128(str string) bool |
||||||
|
func IsRipeMD160(str string) bool |
||||||
|
func IsRsaPub(str string, params ...string) bool |
||||||
|
func IsRsaPublicKey(str string, keylen int) bool |
||||||
|
func IsSHA1(str string) bool |
||||||
|
func IsSHA256(str string) bool |
||||||
|
func IsSHA384(str string) bool |
||||||
|
func IsSHA512(str string) bool |
||||||
|
func IsSSN(str string) bool |
||||||
|
func IsSemver(str string) bool |
||||||
|
func IsTiger128(str string) bool |
||||||
|
func IsTiger160(str string) bool |
||||||
|
func IsTiger192(str string) bool |
||||||
|
func IsTime(str string, format string) bool |
||||||
|
func IsType(v interface{}, params ...string) bool |
||||||
|
func IsURL(str string) bool |
||||||
|
func IsUTFDigit(str string) bool |
||||||
|
func IsUTFLetter(str string) bool |
||||||
|
func IsUTFLetterNumeric(str string) bool |
||||||
|
func IsUTFNumeric(str string) bool |
||||||
|
func IsUUID(str string) bool |
||||||
|
func IsUUIDv3(str string) bool |
||||||
|
func IsUUIDv4(str string) bool |
||||||
|
func IsUUIDv5(str string) bool |
||||||
|
func IsULID(str string) bool |
||||||
|
func IsUnixTime(str string) bool |
||||||
|
func IsUpperCase(str string) bool |
||||||
|
func IsVariableWidth(str string) bool |
||||||
|
func IsWhole(value float64) bool |
||||||
|
func LeftTrim(str, chars string) string |
||||||
|
func Map(array []interface{}, iterator ResultIterator) []interface{} |
||||||
|
func Matches(str, pattern string) bool |
||||||
|
func MaxStringLength(str string, params ...string) bool |
||||||
|
func MinStringLength(str string, params ...string) bool |
||||||
|
func NormalizeEmail(str string) (string, error) |
||||||
|
func PadBoth(str string, padStr string, padLen int) string |
||||||
|
func PadLeft(str string, padStr string, padLen int) string |
||||||
|
func PadRight(str string, padStr string, padLen int) string |
||||||
|
func PrependPathToErrors(err error, path string) error |
||||||
|
func Range(str string, params ...string) bool |
||||||
|
func RemoveTags(s string) string |
||||||
|
func ReplacePattern(str, pattern, replace string) string |
||||||
|
func Reverse(s string) string |
||||||
|
func RightTrim(str, chars string) string |
||||||
|
func RuneLength(str string, params ...string) bool |
||||||
|
func SafeFileName(str string) string |
||||||
|
func SetFieldsRequiredByDefault(value bool) |
||||||
|
func SetNilPtrAllowedByRequired(value bool) |
||||||
|
func Sign(value float64) float64 |
||||||
|
func StringLength(str string, params ...string) bool |
||||||
|
func StringMatches(s string, params ...string) bool |
||||||
|
func StripLow(str string, keepNewLines bool) string |
||||||
|
func ToBoolean(str string) (bool, error) |
||||||
|
func ToFloat(str string) (float64, error) |
||||||
|
func ToInt(value interface{}) (res int64, err error) |
||||||
|
func ToJSON(obj interface{}) (string, error) |
||||||
|
func ToString(obj interface{}) string |
||||||
|
func Trim(str, chars string) string |
||||||
|
func Truncate(str string, length int, ending string) string |
||||||
|
func TruncatingErrorf(str string, args ...interface{}) error |
||||||
|
func UnderscoreToCamelCase(s string) string |
||||||
|
func ValidateMap(inputMap map[string]interface{}, validationMap map[string]interface{}) (bool, error) |
||||||
|
func ValidateStruct(s interface{}) (bool, error) |
||||||
|
func WhiteList(str, chars string) string |
||||||
|
type ConditionIterator |
||||||
|
type CustomTypeValidator |
||||||
|
type Error |
||||||
|
func (e Error) Error() string |
||||||
|
type Errors |
||||||
|
func (es Errors) Error() string |
||||||
|
func (es Errors) Errors() []error |
||||||
|
type ISO3166Entry |
||||||
|
type ISO693Entry |
||||||
|
type InterfaceParamValidator |
||||||
|
type Iterator |
||||||
|
type ParamValidator |
||||||
|
type ResultIterator |
||||||
|
type UnsupportedTypeError |
||||||
|
func (e *UnsupportedTypeError) Error() string |
||||||
|
type Validator |
||||||
|
``` |
||||||
|
|
||||||
|
#### Examples |
||||||
|
###### IsURL |
||||||
|
```go |
||||||
|
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`)) |
||||||
|
``` |
||||||
|
###### IsType |
||||||
|
```go |
||||||
|
println(govalidator.IsType("Bob", "string")) |
||||||
|
println(govalidator.IsType(1, "int")) |
||||||
|
i := 1 |
||||||
|
println(govalidator.IsType(&i, "*int")) |
||||||
|
``` |
||||||
|
|
||||||
|
IsType can be used through the tag `type` which is essential for map validation: |
||||||
|
```go |
||||||
|
type User struct { |
||||||
|
Name string `valid:"type(string)"` |
||||||
|
Age int `valid:"type(int)"` |
||||||
|
Meta interface{} `valid:"type(string)"` |
||||||
|
} |
||||||
|
result, err := govalidator.ValidateStruct(User{"Bob", 20, "meta"}) |
||||||
|
if err != nil { |
||||||
|
println("error: " + err.Error()) |
||||||
|
} |
||||||
|
println(result) |
||||||
|
``` |
||||||
|
###### ToString |
||||||
|
```go |
||||||
|
type User struct { |
||||||
|
FirstName string |
||||||
|
LastName string |
||||||
|
} |
||||||
|
|
||||||
|
str := govalidator.ToString(&User{"John", "Juan"}) |
||||||
|
println(str) |
||||||
|
``` |
||||||
|
###### Each, Map, Filter, Count for slices |
||||||
|
Each iterates over the slice/array and calls Iterator for every item |
||||||
|
```go |
||||||
|
data := []interface{}{1, 2, 3, 4, 5} |
||||||
|
var fn govalidator.Iterator = func(value interface{}, index int) { |
||||||
|
println(value.(int)) |
||||||
|
} |
||||||
|
govalidator.Each(data, fn) |
||||||
|
``` |
||||||
|
```go |
||||||
|
data := []interface{}{1, 2, 3, 4, 5} |
||||||
|
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} { |
||||||
|
return value.(int) * 3 |
||||||
|
} |
||||||
|
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15} |
||||||
|
``` |
||||||
|
```go |
||||||
|
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} |
||||||
|
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool { |
||||||
|
return value.(int)%2 == 0 |
||||||
|
} |
||||||
|
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10} |
||||||
|
_ = govalidator.Count(data, fn) // result = 5 |
||||||
|
``` |
||||||
|
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2) |
||||||
|
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this: |
||||||
|
```go |
||||||
|
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool { |
||||||
|
return str == "duck" |
||||||
|
}) |
||||||
|
``` |
||||||
|
For completely custom validators (interface-based), see below. |
||||||
|
|
||||||
|
Here is a list of available validators for struct fields (validator - used function): |
||||||
|
```go |
||||||
|
"email": IsEmail, |
||||||
|
"url": IsURL, |
||||||
|
"dialstring": IsDialString, |
||||||
|
"requrl": IsRequestURL, |
||||||
|
"requri": IsRequestURI, |
||||||
|
"alpha": IsAlpha, |
||||||
|
"utfletter": IsUTFLetter, |
||||||
|
"alphanum": IsAlphanumeric, |
||||||
|
"utfletternum": IsUTFLetterNumeric, |
||||||
|
"numeric": IsNumeric, |
||||||
|
"utfnumeric": IsUTFNumeric, |
||||||
|
"utfdigit": IsUTFDigit, |
||||||
|
"hexadecimal": IsHexadecimal, |
||||||
|
"hexcolor": IsHexcolor, |
||||||
|
"rgbcolor": IsRGBcolor, |
||||||
|
"lowercase": IsLowerCase, |
||||||
|
"uppercase": IsUpperCase, |
||||||
|
"int": IsInt, |
||||||
|
"float": IsFloat, |
||||||
|
"null": IsNull, |
||||||
|
"uuid": IsUUID, |
||||||
|
"uuidv3": IsUUIDv3, |
||||||
|
"uuidv4": IsUUIDv4, |
||||||
|
"uuidv5": IsUUIDv5, |
||||||
|
"creditcard": IsCreditCard, |
||||||
|
"isbn10": IsISBN10, |
||||||
|
"isbn13": IsISBN13, |
||||||
|
"json": IsJSON, |
||||||
|
"multibyte": IsMultibyte, |
||||||
|
"ascii": IsASCII, |
||||||
|
"printableascii": IsPrintableASCII, |
||||||
|
"fullwidth": IsFullWidth, |
||||||
|
"halfwidth": IsHalfWidth, |
||||||
|
"variablewidth": IsVariableWidth, |
||||||
|
"base64": IsBase64, |
||||||
|
"datauri": IsDataURI, |
||||||
|
"ip": IsIP, |
||||||
|
"port": IsPort, |
||||||
|
"ipv4": IsIPv4, |
||||||
|
"ipv6": IsIPv6, |
||||||
|
"dns": IsDNSName, |
||||||
|
"host": IsHost, |
||||||
|
"mac": IsMAC, |
||||||
|
"latitude": IsLatitude, |
||||||
|
"longitude": IsLongitude, |
||||||
|
"ssn": IsSSN, |
||||||
|
"semver": IsSemver, |
||||||
|
"rfc3339": IsRFC3339, |
||||||
|
"rfc3339WithoutZone": IsRFC3339WithoutZone, |
||||||
|
"ISO3166Alpha2": IsISO3166Alpha2, |
||||||
|
"ISO3166Alpha3": IsISO3166Alpha3, |
||||||
|
"ulid": IsULID, |
||||||
|
``` |
||||||
|
Validators with parameters |
||||||
|
|
||||||
|
```go |
||||||
|
"range(min|max)": Range, |
||||||
|
"length(min|max)": ByteLength, |
||||||
|
"runelength(min|max)": RuneLength, |
||||||
|
"stringlength(min|max)": StringLength, |
||||||
|
"matches(pattern)": StringMatches, |
||||||
|
"in(string1|string2|...|stringN)": IsIn, |
||||||
|
"rsapub(keylength)" : IsRsaPub, |
||||||
|
"minstringlength(int): MinStringLength, |
||||||
|
"maxstringlength(int): MaxStringLength, |
||||||
|
``` |
||||||
|
Validators with parameters for any type |
||||||
|
|
||||||
|
```go |
||||||
|
"type(type)": IsType, |
||||||
|
``` |
||||||
|
|
||||||
|
And here is small example of usage: |
||||||
|
```go |
||||||
|
type Post struct { |
||||||
|
Title string `valid:"alphanum,required"` |
||||||
|
Message string `valid:"duck,ascii"` |
||||||
|
Message2 string `valid:"animal(dog)"` |
||||||
|
AuthorIP string `valid:"ipv4"` |
||||||
|
Date string `valid:"-"` |
||||||
|
} |
||||||
|
post := &Post{ |
||||||
|
Title: "My Example Post", |
||||||
|
Message: "duck", |
||||||
|
Message2: "dog", |
||||||
|
AuthorIP: "123.234.54.3", |
||||||
|
} |
||||||
|
|
||||||
|
// Add your own struct validation tags |
||||||
|
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool { |
||||||
|
return str == "duck" |
||||||
|
}) |
||||||
|
|
||||||
|
// Add your own struct validation tags with parameter |
||||||
|
govalidator.ParamTagMap["animal"] = govalidator.ParamValidator(func(str string, params ...string) bool { |
||||||
|
species := params[0] |
||||||
|
return str == species |
||||||
|
}) |
||||||
|
govalidator.ParamTagRegexMap["animal"] = regexp.MustCompile("^animal\\((\\w+)\\)$") |
||||||
|
|
||||||
|
result, err := govalidator.ValidateStruct(post) |
||||||
|
if err != nil { |
||||||
|
println("error: " + err.Error()) |
||||||
|
} |
||||||
|
println(result) |
||||||
|
``` |
||||||
|
###### ValidateMap [#2](https://github.com/asaskevich/govalidator/pull/338) |
||||||
|
If you want to validate maps, you can use the map to be validated and a validation map that contain the same tags used in ValidateStruct, both maps have to be in the form `map[string]interface{}` |
||||||
|
|
||||||
|
So here is small example of usage: |
||||||
|
```go |
||||||
|
var mapTemplate = map[string]interface{}{ |
||||||
|
"name":"required,alpha", |
||||||
|
"family":"required,alpha", |
||||||
|
"email":"required,email", |
||||||
|
"cell-phone":"numeric", |
||||||
|
"address":map[string]interface{}{ |
||||||
|
"line1":"required,alphanum", |
||||||
|
"line2":"alphanum", |
||||||
|
"postal-code":"numeric", |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var inputMap = map[string]interface{}{ |
||||||
|
"name":"Bob", |
||||||
|
"family":"Smith", |
||||||
|
"email":"foo@bar.baz", |
||||||
|
"address":map[string]interface{}{ |
||||||
|
"line1":"", |
||||||
|
"line2":"", |
||||||
|
"postal-code":"", |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
result, err := govalidator.ValidateMap(inputMap, mapTemplate) |
||||||
|
if err != nil { |
||||||
|
println("error: " + err.Error()) |
||||||
|
} |
||||||
|
println(result) |
||||||
|
``` |
||||||
|
|
||||||
|
###### WhiteList |
||||||
|
```go |
||||||
|
// Remove all characters from string ignoring characters between "a" and "z" |
||||||
|
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa") |
||||||
|
``` |
||||||
|
|
||||||
|
###### Custom validation functions |
||||||
|
Custom validation using your own domain specific validators is also available - here's an example of how to use it: |
||||||
|
```go |
||||||
|
import "github.com/asaskevich/govalidator" |
||||||
|
|
||||||
|
type CustomByteArray [6]byte // custom types are supported and can be validated |
||||||
|
|
||||||
|
type StructWithCustomByteArray struct { |
||||||
|
ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence |
||||||
|
Email string `valid:"email"` |
||||||
|
CustomMinLength int `valid:"-"` |
||||||
|
} |
||||||
|
|
||||||
|
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, context interface{}) bool { |
||||||
|
switch v := context.(type) { // you can type switch on the context interface being validated |
||||||
|
case StructWithCustomByteArray: |
||||||
|
// you can check and validate against some other field in the context, |
||||||
|
// return early or not validate against the context at all – your choice |
||||||
|
case SomeOtherType: |
||||||
|
// ... |
||||||
|
default: |
||||||
|
// expecting some other type? Throw/panic here or continue |
||||||
|
} |
||||||
|
|
||||||
|
switch v := i.(type) { // type switch on the struct field being validated |
||||||
|
case CustomByteArray: |
||||||
|
for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes |
||||||
|
if e != 0 { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
}) |
||||||
|
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", func(i interface{}, context interface{}) bool { |
||||||
|
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation |
||||||
|
case StructWithCustomByteArray: |
||||||
|
return len(v.ID) >= v.CustomMinLength |
||||||
|
} |
||||||
|
return false |
||||||
|
}) |
||||||
|
``` |
||||||
|
|
||||||
|
###### Loop over Error() |
||||||
|
By default .Error() returns all errors in a single String. To access each error you can do this: |
||||||
|
```go |
||||||
|
if err != nil { |
||||||
|
errs := err.(govalidator.Errors).Errors() |
||||||
|
for _, e := range errs { |
||||||
|
fmt.Println(e.Error()) |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
###### Custom error messages |
||||||
|
Custom error messages are supported via annotations by adding the `~` separator - here's an example of how to use it: |
||||||
|
```go |
||||||
|
type Ticket struct { |
||||||
|
Id int64 `json:"id"` |
||||||
|
FirstName string `json:"firstname" valid:"required~First name is blank"` |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Notes |
||||||
|
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator). |
||||||
|
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator). |
||||||
|
|
||||||
|
#### Support |
||||||
|
If you do have a contribution to the package, feel free to create a Pull Request or an Issue. |
||||||
|
|
||||||
|
#### What to contribute |
||||||
|
If you don't know what to do, there are some features and functions that need to be done |
||||||
|
|
||||||
|
- [ ] Refactor code |
||||||
|
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check |
||||||
|
- [ ] Create actual list of contributors and projects that currently using this package |
||||||
|
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues) |
||||||
|
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions) |
||||||
|
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new |
||||||
|
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc |
||||||
|
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) |
||||||
|
- [ ] Implement fuzzing testing |
||||||
|
- [ ] Implement some struct/map/array utilities |
||||||
|
- [ ] Implement map/array validation |
||||||
|
- [ ] Implement benchmarking |
||||||
|
- [ ] Implement batch of examples |
||||||
|
- [ ] Look at forks for new features and fixes |
||||||
|
|
||||||
|
#### Advice |
||||||
|
Feel free to create what you want, but keep in mind when you implement new features: |
||||||
|
- Code must be clear and readable, names of variables/constants clearly describes what they are doing |
||||||
|
- Public functions must be documented and described in source file and added to README.md to the list of available functions |
||||||
|
- There are must be unit-tests for any new functions and improvements |
||||||
|
|
||||||
|
## Credits |
||||||
|
### Contributors |
||||||
|
|
||||||
|
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. |
||||||
|
|
||||||
|
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors) |
||||||
|
* [Daniel Lohse](https://github.com/annismckenzie) |
||||||
|
* [Attila Oláh](https://github.com/attilaolah) |
||||||
|
* [Daniel Korner](https://github.com/Dadie) |
||||||
|
* [Steven Wilkin](https://github.com/stevenwilkin) |
||||||
|
* [Deiwin Sarjas](https://github.com/deiwin) |
||||||
|
* [Noah Shibley](https://github.com/slugmobile) |
||||||
|
* [Nathan Davies](https://github.com/nathj07) |
||||||
|
* [Matt Sanford](https://github.com/mzsanford) |
||||||
|
* [Simon ccl1115](https://github.com/ccl1115) |
||||||
|
|
||||||
|
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a> |
||||||
|
|
||||||
|
|
||||||
|
### Backers |
||||||
|
|
||||||
|
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/govalidator#backer)] |
||||||
|
|
||||||
|
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a> |
||||||
|
|
||||||
|
|
||||||
|
### Sponsors |
||||||
|
|
||||||
|
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/govalidator#sponsor)] |
||||||
|
|
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a> |
||||||
|
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a> |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## License |
||||||
|
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large) |
@ -0,0 +1,87 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
// Iterator is the function that accepts element of slice/array and its index
|
||||||
|
type Iterator func(interface{}, int) |
||||||
|
|
||||||
|
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
|
||||||
|
type ResultIterator func(interface{}, int) interface{} |
||||||
|
|
||||||
|
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
|
||||||
|
type ConditionIterator func(interface{}, int) bool |
||||||
|
|
||||||
|
// ReduceIterator is the function that accepts two element of slice/array and returns result of merging those values
|
||||||
|
type ReduceIterator func(interface{}, interface{}) interface{} |
||||||
|
|
||||||
|
// Some validates that any item of array corresponds to ConditionIterator. Returns boolean.
|
||||||
|
func Some(array []interface{}, iterator ConditionIterator) bool { |
||||||
|
res := false |
||||||
|
for index, data := range array { |
||||||
|
res = res || iterator(data, index) |
||||||
|
} |
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
// Every validates that every item of array corresponds to ConditionIterator. Returns boolean.
|
||||||
|
func Every(array []interface{}, iterator ConditionIterator) bool { |
||||||
|
res := true |
||||||
|
for index, data := range array { |
||||||
|
res = res && iterator(data, index) |
||||||
|
} |
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
// Reduce boils down a list of values into a single value by ReduceIterator
|
||||||
|
func Reduce(array []interface{}, iterator ReduceIterator, initialValue interface{}) interface{} { |
||||||
|
for _, data := range array { |
||||||
|
initialValue = iterator(initialValue, data) |
||||||
|
} |
||||||
|
return initialValue |
||||||
|
} |
||||||
|
|
||||||
|
// Each iterates over the slice and apply Iterator to every item
|
||||||
|
func Each(array []interface{}, iterator Iterator) { |
||||||
|
for index, data := range array { |
||||||
|
iterator(data, index) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
|
||||||
|
func Map(array []interface{}, iterator ResultIterator) []interface{} { |
||||||
|
var result = make([]interface{}, len(array)) |
||||||
|
for index, data := range array { |
||||||
|
result[index] = iterator(data, index) |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
|
||||||
|
func Find(array []interface{}, iterator ConditionIterator) interface{} { |
||||||
|
for index, data := range array { |
||||||
|
if iterator(data, index) { |
||||||
|
return data |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
|
||||||
|
func Filter(array []interface{}, iterator ConditionIterator) []interface{} { |
||||||
|
var result = make([]interface{}, 0) |
||||||
|
for index, data := range array { |
||||||
|
if iterator(data, index) { |
||||||
|
result = append(result, data) |
||||||
|
} |
||||||
|
} |
||||||
|
return result |
||||||
|
} |
||||||
|
|
||||||
|
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
|
||||||
|
func Count(array []interface{}, iterator ConditionIterator) int { |
||||||
|
count := 0 |
||||||
|
for index, data := range array { |
||||||
|
if iterator(data, index) { |
||||||
|
count = count + 1 |
||||||
|
} |
||||||
|
} |
||||||
|
return count |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"reflect" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
// ToString convert the input to a string.
|
||||||
|
func ToString(obj interface{}) string { |
||||||
|
res := fmt.Sprintf("%v", obj) |
||||||
|
return res |
||||||
|
} |
||||||
|
|
||||||
|
// ToJSON convert the input to a valid JSON string
|
||||||
|
func ToJSON(obj interface{}) (string, error) { |
||||||
|
res, err := json.Marshal(obj) |
||||||
|
if err != nil { |
||||||
|
res = []byte("") |
||||||
|
} |
||||||
|
return string(res), err |
||||||
|
} |
||||||
|
|
||||||
|
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
|
||||||
|
func ToFloat(value interface{}) (res float64, err error) { |
||||||
|
val := reflect.ValueOf(value) |
||||||
|
|
||||||
|
switch value.(type) { |
||||||
|
case int, int8, int16, int32, int64: |
||||||
|
res = float64(val.Int()) |
||||||
|
case uint, uint8, uint16, uint32, uint64: |
||||||
|
res = float64(val.Uint()) |
||||||
|
case float32, float64: |
||||||
|
res = val.Float() |
||||||
|
case string: |
||||||
|
res, err = strconv.ParseFloat(val.String(), 64) |
||||||
|
if err != nil { |
||||||
|
res = 0 |
||||||
|
} |
||||||
|
default: |
||||||
|
err = fmt.Errorf("ToInt: unknown interface type %T", value) |
||||||
|
res = 0 |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer.
|
||||||
|
func ToInt(value interface{}) (res int64, err error) { |
||||||
|
val := reflect.ValueOf(value) |
||||||
|
|
||||||
|
switch value.(type) { |
||||||
|
case int, int8, int16, int32, int64: |
||||||
|
res = val.Int() |
||||||
|
case uint, uint8, uint16, uint32, uint64: |
||||||
|
res = int64(val.Uint()) |
||||||
|
case float32, float64: |
||||||
|
res = int64(val.Float()) |
||||||
|
case string: |
||||||
|
if IsInt(val.String()) { |
||||||
|
res, err = strconv.ParseInt(val.String(), 0, 64) |
||||||
|
if err != nil { |
||||||
|
res = 0 |
||||||
|
} |
||||||
|
} else { |
||||||
|
err = fmt.Errorf("ToInt: invalid numeric format %g", value) |
||||||
|
res = 0 |
||||||
|
} |
||||||
|
default: |
||||||
|
err = fmt.Errorf("ToInt: unknown interface type %T", value) |
||||||
|
res = 0 |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// ToBoolean convert the input string to a boolean.
|
||||||
|
func ToBoolean(str string) (bool, error) { |
||||||
|
return strconv.ParseBool(str) |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
// A package of validators and sanitizers for strings, structures and collections.
|
@ -0,0 +1,47 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
import ( |
||||||
|
"sort" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// Errors is an array of multiple errors and conforms to the error interface.
|
||||||
|
type Errors []error |
||||||
|
|
||||||
|
// Errors returns itself.
|
||||||
|
func (es Errors) Errors() []error { |
||||||
|
return es |
||||||
|
} |
||||||
|
|
||||||
|
func (es Errors) Error() string { |
||||||
|
var errs []string |
||||||
|
for _, e := range es { |
||||||
|
errs = append(errs, e.Error()) |
||||||
|
} |
||||||
|
sort.Strings(errs) |
||||||
|
return strings.Join(errs, ";") |
||||||
|
} |
||||||
|
|
||||||
|
// Error encapsulates a name, an error and whether there's a custom error message or not.
|
||||||
|
type Error struct { |
||||||
|
Name string |
||||||
|
Err error |
||||||
|
CustomErrorMessageExists bool |
||||||
|
|
||||||
|
// Validator indicates the name of the validator that failed
|
||||||
|
Validator string |
||||||
|
Path []string |
||||||
|
} |
||||||
|
|
||||||
|
func (e Error) Error() string { |
||||||
|
if e.CustomErrorMessageExists { |
||||||
|
return e.Err.Error() |
||||||
|
} |
||||||
|
|
||||||
|
errName := e.Name |
||||||
|
if len(e.Path) > 0 { |
||||||
|
errName = strings.Join(append(e.Path, e.Name), ".") |
||||||
|
} |
||||||
|
|
||||||
|
return errName + ": " + e.Err.Error() |
||||||
|
} |
@ -0,0 +1,100 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
import ( |
||||||
|
"math" |
||||||
|
) |
||||||
|
|
||||||
|
// Abs returns absolute value of number
|
||||||
|
func Abs(value float64) float64 { |
||||||
|
return math.Abs(value) |
||||||
|
} |
||||||
|
|
||||||
|
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
|
||||||
|
func Sign(value float64) float64 { |
||||||
|
if value > 0 { |
||||||
|
return 1 |
||||||
|
} else if value < 0 { |
||||||
|
return -1 |
||||||
|
} else { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// IsNegative returns true if value < 0
|
||||||
|
func IsNegative(value float64) bool { |
||||||
|
return value < 0 |
||||||
|
} |
||||||
|
|
||||||
|
// IsPositive returns true if value > 0
|
||||||
|
func IsPositive(value float64) bool { |
||||||
|
return value > 0 |
||||||
|
} |
||||||
|
|
||||||
|
// IsNonNegative returns true if value >= 0
|
||||||
|
func IsNonNegative(value float64) bool { |
||||||
|
return value >= 0 |
||||||
|
} |
||||||
|
|
||||||
|
// IsNonPositive returns true if value <= 0
|
||||||
|
func IsNonPositive(value float64) bool { |
||||||
|
return value <= 0 |
||||||
|
} |
||||||
|
|
||||||
|
// InRangeInt returns true if value lies between left and right border
|
||||||
|
func InRangeInt(value, left, right interface{}) bool { |
||||||
|
value64, _ := ToInt(value) |
||||||
|
left64, _ := ToInt(left) |
||||||
|
right64, _ := ToInt(right) |
||||||
|
if left64 > right64 { |
||||||
|
left64, right64 = right64, left64 |
||||||
|
} |
||||||
|
return value64 >= left64 && value64 <= right64 |
||||||
|
} |
||||||
|
|
||||||
|
// InRangeFloat32 returns true if value lies between left and right border
|
||||||
|
func InRangeFloat32(value, left, right float32) bool { |
||||||
|
if left > right { |
||||||
|
left, right = right, left |
||||||
|
} |
||||||
|
return value >= left && value <= right |
||||||
|
} |
||||||
|
|
||||||
|
// InRangeFloat64 returns true if value lies between left and right border
|
||||||
|
func InRangeFloat64(value, left, right float64) bool { |
||||||
|
if left > right { |
||||||
|
left, right = right, left |
||||||
|
} |
||||||
|
return value >= left && value <= right |
||||||
|
} |
||||||
|
|
||||||
|
// InRange returns true if value lies between left and right border, generic type to handle int, float32, float64 and string.
|
||||||
|
// All types must the same type.
|
||||||
|
// False if value doesn't lie in range or if it incompatible or not comparable
|
||||||
|
func InRange(value interface{}, left interface{}, right interface{}) bool { |
||||||
|
switch value.(type) { |
||||||
|
case int: |
||||||
|
intValue, _ := ToInt(value) |
||||||
|
intLeft, _ := ToInt(left) |
||||||
|
intRight, _ := ToInt(right) |
||||||
|
return InRangeInt(intValue, intLeft, intRight) |
||||||
|
case float32, float64: |
||||||
|
intValue, _ := ToFloat(value) |
||||||
|
intLeft, _ := ToFloat(left) |
||||||
|
intRight, _ := ToFloat(right) |
||||||
|
return InRangeFloat64(intValue, intLeft, intRight) |
||||||
|
case string: |
||||||
|
return value.(string) >= left.(string) && value.(string) <= right.(string) |
||||||
|
default: |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// IsWhole returns true if value is whole number
|
||||||
|
func IsWhole(value float64) bool { |
||||||
|
return math.Remainder(value, 1) == 0 |
||||||
|
} |
||||||
|
|
||||||
|
// IsNatural returns true if value is natural number (positive and whole)
|
||||||
|
func IsNatural(value float64) bool { |
||||||
|
return IsWhole(value) && IsPositive(value) |
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
import "regexp" |
||||||
|
|
||||||
|
// Basic regular expressions for validating strings
|
||||||
|
const ( |
||||||
|
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" |
||||||
|
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$" |
||||||
|
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$" |
||||||
|
ISBN13 string = "^(?:[0-9]{13})$" |
||||||
|
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" |
||||||
|
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" |
||||||
|
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" |
||||||
|
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" |
||||||
|
Alpha string = "^[a-zA-Z]+$" |
||||||
|
Alphanumeric string = "^[a-zA-Z0-9]+$" |
||||||
|
Numeric string = "^[0-9]+$" |
||||||
|
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$" |
||||||
|
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$" |
||||||
|
Hexadecimal string = "^[0-9a-fA-F]+$" |
||||||
|
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" |
||||||
|
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" |
||||||
|
ASCII string = "^[\x00-\x7F]+$" |
||||||
|
Multibyte string = "[^\x00-\x7F]" |
||||||
|
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" |
||||||
|
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" |
||||||
|
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" |
||||||
|
PrintableASCII string = "^[\x20-\x7E]+$" |
||||||
|
DataURI string = "^data:.+\\/(.+);base64$" |
||||||
|
MagnetURI string = "^magnet:\\?xt=urn:[a-zA-Z0-9]+:[a-zA-Z0-9]{32,40}&dn=.+&tr=.+$" |
||||||
|
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" |
||||||
|
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" |
||||||
|
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$` |
||||||
|
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))` |
||||||
|
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)` |
||||||
|
URLUsername string = `(\S+(:\S*)?@)` |
||||||
|
URLPath string = `((\/|\?|#)[^\s]*)` |
||||||
|
URLPort string = `(:(\d{1,5}))` |
||||||
|
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))` |
||||||
|
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))` |
||||||
|
URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$` |
||||||
|
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` |
||||||
|
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` |
||||||
|
UnixPath string = `^(/[^/\x00]*)+/?$` |
||||||
|
WinARPath string = `^(?:(?:[a-zA-Z]:|\\\\[a-z0-9_.$â—Ź-]+\\[a-z0-9_.$â—Ź-]+)\\|\\?[^\\/:*?"<>|\r\n]+\\?)(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` |
||||||
|
UnixARPath string = `^((\.{0,2}/)?([^/\x00]*))+/?$` |
||||||
|
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$" |
||||||
|
tagName string = "valid" |
||||||
|
hasLowerCase string = ".*[[:lower:]]" |
||||||
|
hasUpperCase string = ".*[[:upper:]]" |
||||||
|
hasWhitespace string = ".*[[:space:]]" |
||||||
|
hasWhitespaceOnly string = "^[[:space:]]+$" |
||||||
|
IMEI string = "^[0-9a-f]{14}$|^\\d{15}$|^\\d{18}$" |
||||||
|
IMSI string = "^\\d{14,15}$" |
||||||
|
E164 string = `^\+?[1-9]\d{1,14}$` |
||||||
|
) |
||||||
|
|
||||||
|
// Used by IsFilePath func
|
||||||
|
const ( |
||||||
|
// Unknown is unresolved OS type
|
||||||
|
Unknown = iota |
||||||
|
// Win is Windows type
|
||||||
|
Win |
||||||
|
// Unix is *nix OS types
|
||||||
|
Unix |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$") |
||||||
|
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$") |
||||||
|
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})") |
||||||
|
rxEmail = regexp.MustCompile(Email) |
||||||
|
rxCreditCard = regexp.MustCompile(CreditCard) |
||||||
|
rxISBN10 = regexp.MustCompile(ISBN10) |
||||||
|
rxISBN13 = regexp.MustCompile(ISBN13) |
||||||
|
rxUUID3 = regexp.MustCompile(UUID3) |
||||||
|
rxUUID4 = regexp.MustCompile(UUID4) |
||||||
|
rxUUID5 = regexp.MustCompile(UUID5) |
||||||
|
rxUUID = regexp.MustCompile(UUID) |
||||||
|
rxAlpha = regexp.MustCompile(Alpha) |
||||||
|
rxAlphanumeric = regexp.MustCompile(Alphanumeric) |
||||||
|
rxNumeric = regexp.MustCompile(Numeric) |
||||||
|
rxInt = regexp.MustCompile(Int) |
||||||
|
rxFloat = regexp.MustCompile(Float) |
||||||
|
rxHexadecimal = regexp.MustCompile(Hexadecimal) |
||||||
|
rxHexcolor = regexp.MustCompile(Hexcolor) |
||||||
|
rxRGBcolor = regexp.MustCompile(RGBcolor) |
||||||
|
rxASCII = regexp.MustCompile(ASCII) |
||||||
|
rxPrintableASCII = regexp.MustCompile(PrintableASCII) |
||||||
|
rxMultibyte = regexp.MustCompile(Multibyte) |
||||||
|
rxFullWidth = regexp.MustCompile(FullWidth) |
||||||
|
rxHalfWidth = regexp.MustCompile(HalfWidth) |
||||||
|
rxBase64 = regexp.MustCompile(Base64) |
||||||
|
rxDataURI = regexp.MustCompile(DataURI) |
||||||
|
rxMagnetURI = regexp.MustCompile(MagnetURI) |
||||||
|
rxLatitude = regexp.MustCompile(Latitude) |
||||||
|
rxLongitude = regexp.MustCompile(Longitude) |
||||||
|
rxDNSName = regexp.MustCompile(DNSName) |
||||||
|
rxURL = regexp.MustCompile(URL) |
||||||
|
rxSSN = regexp.MustCompile(SSN) |
||||||
|
rxWinPath = regexp.MustCompile(WinPath) |
||||||
|
rxUnixPath = regexp.MustCompile(UnixPath) |
||||||
|
rxARWinPath = regexp.MustCompile(WinARPath) |
||||||
|
rxARUnixPath = regexp.MustCompile(UnixARPath) |
||||||
|
rxSemver = regexp.MustCompile(Semver) |
||||||
|
rxHasLowerCase = regexp.MustCompile(hasLowerCase) |
||||||
|
rxHasUpperCase = regexp.MustCompile(hasUpperCase) |
||||||
|
rxHasWhitespace = regexp.MustCompile(hasWhitespace) |
||||||
|
rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly) |
||||||
|
rxIMEI = regexp.MustCompile(IMEI) |
||||||
|
rxIMSI = regexp.MustCompile(IMSI) |
||||||
|
rxE164 = regexp.MustCompile(E164) |
||||||
|
) |
@ -0,0 +1,656 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
import ( |
||||||
|
"reflect" |
||||||
|
"regexp" |
||||||
|
"sort" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
// Validator is a wrapper for a validator function that returns bool and accepts string.
|
||||||
|
type Validator func(str string) bool |
||||||
|
|
||||||
|
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
|
||||||
|
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
|
||||||
|
type CustomTypeValidator func(i interface{}, o interface{}) bool |
||||||
|
|
||||||
|
// ParamValidator is a wrapper for validator functions that accept additional parameters.
|
||||||
|
type ParamValidator func(str string, params ...string) bool |
||||||
|
|
||||||
|
// InterfaceParamValidator is a wrapper for functions that accept variants parameters for an interface value
|
||||||
|
type InterfaceParamValidator func(in interface{}, params ...string) bool |
||||||
|
type tagOptionsMap map[string]tagOption |
||||||
|
|
||||||
|
func (t tagOptionsMap) orderedKeys() []string { |
||||||
|
var keys []string |
||||||
|
for k := range t { |
||||||
|
keys = append(keys, k) |
||||||
|
} |
||||||
|
|
||||||
|
sort.Slice(keys, func(a, b int) bool { |
||||||
|
return t[keys[a]].order < t[keys[b]].order |
||||||
|
}) |
||||||
|
|
||||||
|
return keys |
||||||
|
} |
||||||
|
|
||||||
|
type tagOption struct { |
||||||
|
name string |
||||||
|
customErrorMessage string |
||||||
|
order int |
||||||
|
} |
||||||
|
|
||||||
|
// UnsupportedTypeError is a wrapper for reflect.Type
|
||||||
|
type UnsupportedTypeError struct { |
||||||
|
Type reflect.Type |
||||||
|
} |
||||||
|
|
||||||
|
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
|
||||||
|
// It implements the methods to sort by string.
|
||||||
|
type stringValues []reflect.Value |
||||||
|
|
||||||
|
// InterfaceParamTagMap is a map of functions accept variants parameters for an interface value
|
||||||
|
var InterfaceParamTagMap = map[string]InterfaceParamValidator{ |
||||||
|
"type": IsType, |
||||||
|
} |
||||||
|
|
||||||
|
// InterfaceParamTagRegexMap maps interface param tags to their respective regexes.
|
||||||
|
var InterfaceParamTagRegexMap = map[string]*regexp.Regexp{ |
||||||
|
"type": regexp.MustCompile(`^type\((.*)\)$`), |
||||||
|
} |
||||||
|
|
||||||
|
// ParamTagMap is a map of functions accept variants parameters
|
||||||
|
var ParamTagMap = map[string]ParamValidator{ |
||||||
|
"length": ByteLength, |
||||||
|
"range": Range, |
||||||
|
"runelength": RuneLength, |
||||||
|
"stringlength": StringLength, |
||||||
|
"matches": StringMatches, |
||||||
|
"in": IsInRaw, |
||||||
|
"rsapub": IsRsaPub, |
||||||
|
"minstringlength": MinStringLength, |
||||||
|
"maxstringlength": MaxStringLength, |
||||||
|
} |
||||||
|
|
||||||
|
// ParamTagRegexMap maps param tags to their respective regexes.
|
||||||
|
var ParamTagRegexMap = map[string]*regexp.Regexp{ |
||||||
|
"range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"), |
||||||
|
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"), |
||||||
|
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"), |
||||||
|
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"), |
||||||
|
"in": regexp.MustCompile(`^in\((.*)\)`), |
||||||
|
"matches": regexp.MustCompile(`^matches\((.+)\)$`), |
||||||
|
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"), |
||||||
|
"minstringlength": regexp.MustCompile("^minstringlength\\((\\d+)\\)$"), |
||||||
|
"maxstringlength": regexp.MustCompile("^maxstringlength\\((\\d+)\\)$"), |
||||||
|
} |
||||||
|
|
||||||
|
type customTypeTagMap struct { |
||||||
|
validators map[string]CustomTypeValidator |
||||||
|
|
||||||
|
sync.RWMutex |
||||||
|
} |
||||||
|
|
||||||
|
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) { |
||||||
|
tm.RLock() |
||||||
|
defer tm.RUnlock() |
||||||
|
v, ok := tm.validators[name] |
||||||
|
return v, ok |
||||||
|
} |
||||||
|
|
||||||
|
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) { |
||||||
|
tm.Lock() |
||||||
|
defer tm.Unlock() |
||||||
|
tm.validators[name] = ctv |
||||||
|
} |
||||||
|
|
||||||
|
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
|
||||||
|
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
|
||||||
|
// `type UUID [16]byte` (this would be handled as an array of bytes).
|
||||||
|
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)} |
||||||
|
|
||||||
|
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
|
||||||
|
var TagMap = map[string]Validator{ |
||||||
|
"email": IsEmail, |
||||||
|
"url": IsURL, |
||||||
|
"dialstring": IsDialString, |
||||||
|
"requrl": IsRequestURL, |
||||||
|
"requri": IsRequestURI, |
||||||
|
"alpha": IsAlpha, |
||||||
|
"utfletter": IsUTFLetter, |
||||||
|
"alphanum": IsAlphanumeric, |
||||||
|
"utfletternum": IsUTFLetterNumeric, |
||||||
|
"numeric": IsNumeric, |
||||||
|
"utfnumeric": IsUTFNumeric, |
||||||
|
"utfdigit": IsUTFDigit, |
||||||
|
"hexadecimal": IsHexadecimal, |
||||||
|
"hexcolor": IsHexcolor, |
||||||
|
"rgbcolor": IsRGBcolor, |
||||||
|
"lowercase": IsLowerCase, |
||||||
|
"uppercase": IsUpperCase, |
||||||
|
"int": IsInt, |
||||||
|
"float": IsFloat, |
||||||
|
"null": IsNull, |
||||||
|
"notnull": IsNotNull, |
||||||
|
"uuid": IsUUID, |
||||||
|
"uuidv3": IsUUIDv3, |
||||||
|
"uuidv4": IsUUIDv4, |
||||||
|
"uuidv5": IsUUIDv5, |
||||||
|
"creditcard": IsCreditCard, |
||||||
|
"isbn10": IsISBN10, |
||||||
|
"isbn13": IsISBN13, |
||||||
|
"json": IsJSON, |
||||||
|
"multibyte": IsMultibyte, |
||||||
|
"ascii": IsASCII, |
||||||
|
"printableascii": IsPrintableASCII, |
||||||
|
"fullwidth": IsFullWidth, |
||||||
|
"halfwidth": IsHalfWidth, |
||||||
|
"variablewidth": IsVariableWidth, |
||||||
|
"base64": IsBase64, |
||||||
|
"datauri": IsDataURI, |
||||||
|
"ip": IsIP, |
||||||
|
"port": IsPort, |
||||||
|
"ipv4": IsIPv4, |
||||||
|
"ipv6": IsIPv6, |
||||||
|
"dns": IsDNSName, |
||||||
|
"host": IsHost, |
||||||
|
"mac": IsMAC, |
||||||
|
"latitude": IsLatitude, |
||||||
|
"longitude": IsLongitude, |
||||||
|
"ssn": IsSSN, |
||||||
|
"semver": IsSemver, |
||||||
|
"rfc3339": IsRFC3339, |
||||||
|
"rfc3339WithoutZone": IsRFC3339WithoutZone, |
||||||
|
"ISO3166Alpha2": IsISO3166Alpha2, |
||||||
|
"ISO3166Alpha3": IsISO3166Alpha3, |
||||||
|
"ISO4217": IsISO4217, |
||||||
|
"IMEI": IsIMEI, |
||||||
|
"ulid": IsULID, |
||||||
|
} |
||||||
|
|
||||||
|
// ISO3166Entry stores country codes
|
||||||
|
type ISO3166Entry struct { |
||||||
|
EnglishShortName string |
||||||
|
FrenchShortName string |
||||||
|
Alpha2Code string |
||||||
|
Alpha3Code string |
||||||
|
Numeric string |
||||||
|
} |
||||||
|
|
||||||
|
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
|
||||||
|
var ISO3166List = []ISO3166Entry{ |
||||||
|
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"}, |
||||||
|
{"Albania", "Albanie (l')", "AL", "ALB", "008"}, |
||||||
|
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"}, |
||||||
|
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"}, |
||||||
|
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"}, |
||||||
|
{"Andorra", "Andorre (l')", "AD", "AND", "020"}, |
||||||
|
{"Angola", "Angola (l')", "AO", "AGO", "024"}, |
||||||
|
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"}, |
||||||
|
{"Azerbaijan", "AzerbaĂŻdjan (l')", "AZ", "AZE", "031"}, |
||||||
|
{"Argentina", "Argentine (l')", "AR", "ARG", "032"}, |
||||||
|
{"Australia", "Australie (l')", "AU", "AUS", "036"}, |
||||||
|
{"Austria", "Autriche (l')", "AT", "AUT", "040"}, |
||||||
|
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"}, |
||||||
|
{"Bahrain", "BahreĂŻn", "BH", "BHR", "048"}, |
||||||
|
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"}, |
||||||
|
{"Armenia", "Arménie (l')", "AM", "ARM", "051"}, |
||||||
|
{"Barbados", "Barbade (la)", "BB", "BRB", "052"}, |
||||||
|
{"Belgium", "Belgique (la)", "BE", "BEL", "056"}, |
||||||
|
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"}, |
||||||
|
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"}, |
||||||
|
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"}, |
||||||
|
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"}, |
||||||
|
{"Botswana", "Botswana (le)", "BW", "BWA", "072"}, |
||||||
|
{"Bouvet Island", "Bouvet (l'ĂŽle)", "BV", "BVT", "074"}, |
||||||
|
{"Brazil", "Brésil (le)", "BR", "BRA", "076"}, |
||||||
|
{"Belize", "Belize (le)", "BZ", "BLZ", "084"}, |
||||||
|
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"}, |
||||||
|
{"Solomon Islands", "Salomon (ĂŽles)", "SB", "SLB", "090"}, |
||||||
|
{"Virgin Islands (British)", "Vierges britanniques (les ĂŽles)", "VG", "VGB", "092"}, |
||||||
|
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"}, |
||||||
|
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"}, |
||||||
|
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"}, |
||||||
|
{"Burundi", "Burundi (le)", "BI", "BDI", "108"}, |
||||||
|
{"Belarus", "BĂ©larus (le)", "BY", "BLR", "112"}, |
||||||
|
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"}, |
||||||
|
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"}, |
||||||
|
{"Canada", "Canada (le)", "CA", "CAN", "124"}, |
||||||
|
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"}, |
||||||
|
{"Cayman Islands (the)", "CaĂŻmans (les ĂŽles)", "KY", "CYM", "136"}, |
||||||
|
{"Central African Republic (the)", "RĂ©publique centrafricaine (la)", "CF", "CAF", "140"}, |
||||||
|
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"}, |
||||||
|
{"Chad", "Tchad (le)", "TD", "TCD", "148"}, |
||||||
|
{"Chile", "Chili (le)", "CL", "CHL", "152"}, |
||||||
|
{"China", "Chine (la)", "CN", "CHN", "156"}, |
||||||
|
{"Taiwan (Province of China)", "TaĂŻwan (Province de Chine)", "TW", "TWN", "158"}, |
||||||
|
{"Christmas Island", "Christmas (l'ĂŽle)", "CX", "CXR", "162"}, |
||||||
|
{"Cocos (Keeling) Islands (the)", "Cocos (les ĂŽles)/ Keeling (les ĂŽles)", "CC", "CCK", "166"}, |
||||||
|
{"Colombia", "Colombie (la)", "CO", "COL", "170"}, |
||||||
|
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"}, |
||||||
|
{"Mayotte", "Mayotte", "YT", "MYT", "175"}, |
||||||
|
{"Congo (the)", "Congo (le)", "CG", "COG", "178"}, |
||||||
|
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"}, |
||||||
|
{"Cook Islands (the)", "Cook (les ĂŽles)", "CK", "COK", "184"}, |
||||||
|
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"}, |
||||||
|
{"Croatia", "Croatie (la)", "HR", "HRV", "191"}, |
||||||
|
{"Cuba", "Cuba", "CU", "CUB", "192"}, |
||||||
|
{"Cyprus", "Chypre", "CY", "CYP", "196"}, |
||||||
|
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"}, |
||||||
|
{"Benin", "BĂ©nin (le)", "BJ", "BEN", "204"}, |
||||||
|
{"Denmark", "Danemark (le)", "DK", "DNK", "208"}, |
||||||
|
{"Dominica", "Dominique (la)", "DM", "DMA", "212"}, |
||||||
|
{"Dominican Republic (the)", "dominicaine (la RĂ©publique)", "DO", "DOM", "214"}, |
||||||
|
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"}, |
||||||
|
{"El Salvador", "El Salvador", "SV", "SLV", "222"}, |
||||||
|
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"}, |
||||||
|
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"}, |
||||||
|
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"}, |
||||||
|
{"Estonia", "Estonie (l')", "EE", "EST", "233"}, |
||||||
|
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"}, |
||||||
|
{"Falkland Islands (the) [Malvinas]", "Falkland (les ĂŽles)/Malouines (les ĂŽles)", "FK", "FLK", "238"}, |
||||||
|
{"South Georgia and the South Sandwich Islands", "GĂ©orgie du Sud-et-les ĂŽles Sandwich du Sud (la)", "GS", "SGS", "239"}, |
||||||
|
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"}, |
||||||
|
{"Finland", "Finlande (la)", "FI", "FIN", "246"}, |
||||||
|
{"Ă…land Islands", "Ă…land(les ĂŽles)", "AX", "ALA", "248"}, |
||||||
|
{"France", "France (la)", "FR", "FRA", "250"}, |
||||||
|
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"}, |
||||||
|
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"}, |
||||||
|
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"}, |
||||||
|
{"Djibouti", "Djibouti", "DJ", "DJI", "262"}, |
||||||
|
{"Gabon", "Gabon (le)", "GA", "GAB", "266"}, |
||||||
|
{"Georgia", "GĂ©orgie (la)", "GE", "GEO", "268"}, |
||||||
|
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"}, |
||||||
|
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"}, |
||||||
|
{"Germany", "Allemagne (l')", "DE", "DEU", "276"}, |
||||||
|
{"Ghana", "Ghana (le)", "GH", "GHA", "288"}, |
||||||
|
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"}, |
||||||
|
{"Kiribati", "Kiribati", "KI", "KIR", "296"}, |
||||||
|
{"Greece", "Grèce (la)", "GR", "GRC", "300"}, |
||||||
|
{"Greenland", "Groenland (le)", "GL", "GRL", "304"}, |
||||||
|
{"Grenada", "Grenade (la)", "GD", "GRD", "308"}, |
||||||
|
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"}, |
||||||
|
{"Guam", "Guam", "GU", "GUM", "316"}, |
||||||
|
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"}, |
||||||
|
{"Guinea", "Guinée (la)", "GN", "GIN", "324"}, |
||||||
|
{"Guyana", "Guyana (le)", "GY", "GUY", "328"}, |
||||||
|
{"Haiti", "HaĂŻti", "HT", "HTI", "332"}, |
||||||
|
{"Heard Island and McDonald Islands", "Heard-et-ĂŽles MacDonald (l'ĂŽle)", "HM", "HMD", "334"}, |
||||||
|
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"}, |
||||||
|
{"Honduras", "Honduras (le)", "HN", "HND", "340"}, |
||||||
|
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"}, |
||||||
|
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"}, |
||||||
|
{"Iceland", "Islande (l')", "IS", "ISL", "352"}, |
||||||
|
{"India", "Inde (l')", "IN", "IND", "356"}, |
||||||
|
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"}, |
||||||
|
{"Iran (Islamic Republic of)", "Iran (RĂ©publique Islamique d')", "IR", "IRN", "364"}, |
||||||
|
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"}, |
||||||
|
{"Ireland", "Irlande (l')", "IE", "IRL", "372"}, |
||||||
|
{"Israel", "Israël", "IL", "ISR", "376"}, |
||||||
|
{"Italy", "Italie (l')", "IT", "ITA", "380"}, |
||||||
|
{"CĂ´te d'Ivoire", "CĂ´te d'Ivoire (la)", "CI", "CIV", "384"}, |
||||||
|
{"Jamaica", "JamaĂŻque (la)", "JM", "JAM", "388"}, |
||||||
|
{"Japan", "Japon (le)", "JP", "JPN", "392"}, |
||||||
|
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"}, |
||||||
|
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"}, |
||||||
|
{"Kenya", "Kenya (le)", "KE", "KEN", "404"}, |
||||||
|
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"}, |
||||||
|
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"}, |
||||||
|
{"Kuwait", "KoweĂŻt (le)", "KW", "KWT", "414"}, |
||||||
|
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"}, |
||||||
|
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"}, |
||||||
|
{"Lebanon", "Liban (le)", "LB", "LBN", "422"}, |
||||||
|
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"}, |
||||||
|
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"}, |
||||||
|
{"Liberia", "Libéria (le)", "LR", "LBR", "430"}, |
||||||
|
{"Libya", "Libye (la)", "LY", "LBY", "434"}, |
||||||
|
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"}, |
||||||
|
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"}, |
||||||
|
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"}, |
||||||
|
{"Macao", "Macao", "MO", "MAC", "446"}, |
||||||
|
{"Madagascar", "Madagascar", "MG", "MDG", "450"}, |
||||||
|
{"Malawi", "Malawi (le)", "MW", "MWI", "454"}, |
||||||
|
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"}, |
||||||
|
{"Maldives", "Maldives (les)", "MV", "MDV", "462"}, |
||||||
|
{"Mali", "Mali (le)", "ML", "MLI", "466"}, |
||||||
|
{"Malta", "Malte", "MT", "MLT", "470"}, |
||||||
|
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"}, |
||||||
|
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"}, |
||||||
|
{"Mauritius", "Maurice", "MU", "MUS", "480"}, |
||||||
|
{"Mexico", "Mexique (le)", "MX", "MEX", "484"}, |
||||||
|
{"Monaco", "Monaco", "MC", "MCO", "492"}, |
||||||
|
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"}, |
||||||
|
{"Moldova (the Republic of)", "Moldova , RĂ©publique de", "MD", "MDA", "498"}, |
||||||
|
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"}, |
||||||
|
{"Montserrat", "Montserrat", "MS", "MSR", "500"}, |
||||||
|
{"Morocco", "Maroc (le)", "MA", "MAR", "504"}, |
||||||
|
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"}, |
||||||
|
{"Oman", "Oman", "OM", "OMN", "512"}, |
||||||
|
{"Namibia", "Namibie (la)", "NA", "NAM", "516"}, |
||||||
|
{"Nauru", "Nauru", "NR", "NRU", "520"}, |
||||||
|
{"Nepal", "NĂ©pal (le)", "NP", "NPL", "524"}, |
||||||
|
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"}, |
||||||
|
{"Curaçao", "Curaçao", "CW", "CUW", "531"}, |
||||||
|
{"Aruba", "Aruba", "AW", "ABW", "533"}, |
||||||
|
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"}, |
||||||
|
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"}, |
||||||
|
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"}, |
||||||
|
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"}, |
||||||
|
{"New Zealand", "Nouvelle-ZĂ©lande (la)", "NZ", "NZL", "554"}, |
||||||
|
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"}, |
||||||
|
{"Niger (the)", "Niger (le)", "NE", "NER", "562"}, |
||||||
|
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"}, |
||||||
|
{"Niue", "Niue", "NU", "NIU", "570"}, |
||||||
|
{"Norfolk Island", "Norfolk (l'ĂŽle)", "NF", "NFK", "574"}, |
||||||
|
{"Norway", "Norvège (la)", "NO", "NOR", "578"}, |
||||||
|
{"Northern Mariana Islands (the)", "Mariannes du Nord (les ĂŽles)", "MP", "MNP", "580"}, |
||||||
|
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"}, |
||||||
|
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"}, |
||||||
|
{"Marshall Islands (the)", "Marshall (ĂŽles)", "MH", "MHL", "584"}, |
||||||
|
{"Palau", "Palaos (les)", "PW", "PLW", "585"}, |
||||||
|
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"}, |
||||||
|
{"Panama", "Panama (le)", "PA", "PAN", "591"}, |
||||||
|
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"}, |
||||||
|
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"}, |
||||||
|
{"Peru", "PĂ©rou (le)", "PE", "PER", "604"}, |
||||||
|
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"}, |
||||||
|
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"}, |
||||||
|
{"Poland", "Pologne (la)", "PL", "POL", "616"}, |
||||||
|
{"Portugal", "Portugal (le)", "PT", "PRT", "620"}, |
||||||
|
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"}, |
||||||
|
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"}, |
||||||
|
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"}, |
||||||
|
{"Qatar", "Qatar (le)", "QA", "QAT", "634"}, |
||||||
|
{"RĂ©union", "RĂ©union (La)", "RE", "REU", "638"}, |
||||||
|
{"Romania", "Roumanie (la)", "RO", "ROU", "642"}, |
||||||
|
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"}, |
||||||
|
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"}, |
||||||
|
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"}, |
||||||
|
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"}, |
||||||
|
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"}, |
||||||
|
{"Anguilla", "Anguilla", "AI", "AIA", "660"}, |
||||||
|
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"}, |
||||||
|
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"}, |
||||||
|
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"}, |
||||||
|
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"}, |
||||||
|
{"San Marino", "Saint-Marin", "SM", "SMR", "674"}, |
||||||
|
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"}, |
||||||
|
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"}, |
||||||
|
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"}, |
||||||
|
{"Serbia", "Serbie (la)", "RS", "SRB", "688"}, |
||||||
|
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"}, |
||||||
|
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"}, |
||||||
|
{"Singapore", "Singapour", "SG", "SGP", "702"}, |
||||||
|
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"}, |
||||||
|
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"}, |
||||||
|
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"}, |
||||||
|
{"Somalia", "Somalie (la)", "SO", "SOM", "706"}, |
||||||
|
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"}, |
||||||
|
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"}, |
||||||
|
{"Spain", "Espagne (l')", "ES", "ESP", "724"}, |
||||||
|
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"}, |
||||||
|
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"}, |
||||||
|
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"}, |
||||||
|
{"Suriname", "Suriname (le)", "SR", "SUR", "740"}, |
||||||
|
{"Svalbard and Jan Mayen", "Svalbard et l'ĂŽle Jan Mayen (le)", "SJ", "SJM", "744"}, |
||||||
|
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"}, |
||||||
|
{"Sweden", "Suède (la)", "SE", "SWE", "752"}, |
||||||
|
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"}, |
||||||
|
{"Syrian Arab Republic", "RĂ©publique arabe syrienne (la)", "SY", "SYR", "760"}, |
||||||
|
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"}, |
||||||
|
{"Thailand", "ThaĂŻlande (la)", "TH", "THA", "764"}, |
||||||
|
{"Togo", "Togo (le)", "TG", "TGO", "768"}, |
||||||
|
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"}, |
||||||
|
{"Tonga", "Tonga (les)", "TO", "TON", "776"}, |
||||||
|
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"}, |
||||||
|
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"}, |
||||||
|
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"}, |
||||||
|
{"Turkey", "Turquie (la)", "TR", "TUR", "792"}, |
||||||
|
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"}, |
||||||
|
{"Turks and Caicos Islands (the)", "Turks-et-CaĂŻcos (les ĂŽles)", "TC", "TCA", "796"}, |
||||||
|
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"}, |
||||||
|
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"}, |
||||||
|
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"}, |
||||||
|
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'ex‑République yougoslave de)", "MK", "MKD", "807"}, |
||||||
|
{"Egypt", "Égypte (l')", "EG", "EGY", "818"}, |
||||||
|
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"}, |
||||||
|
{"Guernsey", "Guernesey", "GG", "GGY", "831"}, |
||||||
|
{"Jersey", "Jersey", "JE", "JEY", "832"}, |
||||||
|
{"Isle of Man", "ĂŽle de Man", "IM", "IMN", "833"}, |
||||||
|
{"Tanzania, United Republic of", "Tanzanie, RĂ©publique-Unie de", "TZ", "TZA", "834"}, |
||||||
|
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"}, |
||||||
|
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"}, |
||||||
|
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"}, |
||||||
|
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"}, |
||||||
|
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"}, |
||||||
|
{"Venezuela (Bolivarian Republic of)", "Venezuela (RĂ©publique bolivarienne du)", "VE", "VEN", "862"}, |
||||||
|
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"}, |
||||||
|
{"Samoa", "Samoa (le)", "WS", "WSM", "882"}, |
||||||
|
{"Yemen", "YĂ©men (le)", "YE", "YEM", "887"}, |
||||||
|
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"}, |
||||||
|
} |
||||||
|
|
||||||
|
// ISO4217List is the list of ISO currency codes
|
||||||
|
var ISO4217List = []string{ |
||||||
|
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", |
||||||
|
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", |
||||||
|
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK", |
||||||
|
"DJF", "DKK", "DOP", "DZD", |
||||||
|
"EGP", "ERN", "ETB", "EUR", |
||||||
|
"FJD", "FKP", |
||||||
|
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", |
||||||
|
"HKD", "HNL", "HRK", "HTG", "HUF", |
||||||
|
"IDR", "ILS", "INR", "IQD", "IRR", "ISK", |
||||||
|
"JMD", "JOD", "JPY", |
||||||
|
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", |
||||||
|
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD", |
||||||
|
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", |
||||||
|
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD", |
||||||
|
"OMR", |
||||||
|
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", |
||||||
|
"QAR", |
||||||
|
"RON", "RSD", "RUB", "RWF", |
||||||
|
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL", |
||||||
|
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", |
||||||
|
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS", |
||||||
|
"VEF", "VES", "VND", "VUV", |
||||||
|
"WST", |
||||||
|
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX", |
||||||
|
"YER", |
||||||
|
"ZAR", "ZMW", "ZWL", |
||||||
|
} |
||||||
|
|
||||||
|
// ISO693Entry stores ISO language codes
|
||||||
|
type ISO693Entry struct { |
||||||
|
Alpha3bCode string |
||||||
|
Alpha2Code string |
||||||
|
English string |
||||||
|
} |
||||||
|
|
||||||
|
//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json
|
||||||
|
var ISO693List = []ISO693Entry{ |
||||||
|
{Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"}, |
||||||
|
{Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"}, |
||||||
|
{Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"}, |
||||||
|
{Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"}, |
||||||
|
{Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"}, |
||||||
|
{Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"}, |
||||||
|
{Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"}, |
||||||
|
{Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"}, |
||||||
|
{Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"}, |
||||||
|
{Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"}, |
||||||
|
{Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"}, |
||||||
|
{Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"}, |
||||||
|
{Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"}, |
||||||
|
{Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"}, |
||||||
|
{Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"}, |
||||||
|
{Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"}, |
||||||
|
{Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"}, |
||||||
|
{Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"}, |
||||||
|
{Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"}, |
||||||
|
{Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"}, |
||||||
|
{Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"}, |
||||||
|
{Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"}, |
||||||
|
{Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"}, |
||||||
|
{Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"}, |
||||||
|
{Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"}, |
||||||
|
{Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"}, |
||||||
|
{Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"}, |
||||||
|
{Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"}, |
||||||
|
{Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"}, |
||||||
|
{Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"}, |
||||||
|
{Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"}, |
||||||
|
{Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"}, |
||||||
|
{Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"}, |
||||||
|
{Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"}, |
||||||
|
{Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"}, |
||||||
|
{Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"}, |
||||||
|
{Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"}, |
||||||
|
{Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"}, |
||||||
|
{Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"}, |
||||||
|
{Alpha3bCode: "eng", Alpha2Code: "en", English: "English"}, |
||||||
|
{Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"}, |
||||||
|
{Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"}, |
||||||
|
{Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"}, |
||||||
|
{Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"}, |
||||||
|
{Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"}, |
||||||
|
{Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"}, |
||||||
|
{Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"}, |
||||||
|
{Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"}, |
||||||
|
{Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"}, |
||||||
|
{Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"}, |
||||||
|
{Alpha3bCode: "ger", Alpha2Code: "de", English: "German"}, |
||||||
|
{Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"}, |
||||||
|
{Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"}, |
||||||
|
{Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"}, |
||||||
|
{Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"}, |
||||||
|
{Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"}, |
||||||
|
{Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"}, |
||||||
|
{Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"}, |
||||||
|
{Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"}, |
||||||
|
{Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"}, |
||||||
|
{Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"}, |
||||||
|
{Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"}, |
||||||
|
{Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"}, |
||||||
|
{Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"}, |
||||||
|
{Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"}, |
||||||
|
{Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"}, |
||||||
|
{Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"}, |
||||||
|
{Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"}, |
||||||
|
{Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"}, |
||||||
|
{Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"}, |
||||||
|
{Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"}, |
||||||
|
{Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"}, |
||||||
|
{Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"}, |
||||||
|
{Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"}, |
||||||
|
{Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"}, |
||||||
|
{Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"}, |
||||||
|
{Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"}, |
||||||
|
{Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"}, |
||||||
|
{Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"}, |
||||||
|
{Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"}, |
||||||
|
{Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"}, |
||||||
|
{Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"}, |
||||||
|
{Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"}, |
||||||
|
{Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"}, |
||||||
|
{Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"}, |
||||||
|
{Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"}, |
||||||
|
{Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"}, |
||||||
|
{Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"}, |
||||||
|
{Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"}, |
||||||
|
{Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"}, |
||||||
|
{Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"}, |
||||||
|
{Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"}, |
||||||
|
{Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"}, |
||||||
|
{Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"}, |
||||||
|
{Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"}, |
||||||
|
{Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"}, |
||||||
|
{Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"}, |
||||||
|
{Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"}, |
||||||
|
{Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"}, |
||||||
|
{Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"}, |
||||||
|
{Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"}, |
||||||
|
{Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"}, |
||||||
|
{Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"}, |
||||||
|
{Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"}, |
||||||
|
{Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"}, |
||||||
|
{Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"}, |
||||||
|
{Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"}, |
||||||
|
{Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"}, |
||||||
|
{Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"}, |
||||||
|
{Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"}, |
||||||
|
{Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"}, |
||||||
|
{Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"}, |
||||||
|
{Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"}, |
||||||
|
{Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"}, |
||||||
|
{Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"}, |
||||||
|
{Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"}, |
||||||
|
{Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"}, |
||||||
|
{Alpha3bCode: "nob", Alpha2Code: "nb", English: "BokmĂĄl, Norwegian; Norwegian BokmĂĄl"}, |
||||||
|
{Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"}, |
||||||
|
{Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"}, |
||||||
|
{Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"}, |
||||||
|
{Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"}, |
||||||
|
{Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"}, |
||||||
|
{Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"}, |
||||||
|
{Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"}, |
||||||
|
{Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"}, |
||||||
|
{Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"}, |
||||||
|
{Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"}, |
||||||
|
{Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"}, |
||||||
|
{Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"}, |
||||||
|
{Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"}, |
||||||
|
{Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"}, |
||||||
|
{Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"}, |
||||||
|
{Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"}, |
||||||
|
{Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"}, |
||||||
|
{Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"}, |
||||||
|
{Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"}, |
||||||
|
{Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"}, |
||||||
|
{Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"}, |
||||||
|
{Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"}, |
||||||
|
{Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"}, |
||||||
|
{Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"}, |
||||||
|
{Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"}, |
||||||
|
{Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"}, |
||||||
|
{Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"}, |
||||||
|
{Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"}, |
||||||
|
{Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"}, |
||||||
|
{Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"}, |
||||||
|
{Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"}, |
||||||
|
{Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"}, |
||||||
|
{Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"}, |
||||||
|
{Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"}, |
||||||
|
{Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"}, |
||||||
|
{Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"}, |
||||||
|
{Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"}, |
||||||
|
{Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"}, |
||||||
|
{Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"}, |
||||||
|
{Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"}, |
||||||
|
{Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"}, |
||||||
|
{Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"}, |
||||||
|
{Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"}, |
||||||
|
{Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"}, |
||||||
|
{Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"}, |
||||||
|
{Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"}, |
||||||
|
{Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"}, |
||||||
|
{Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"}, |
||||||
|
{Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"}, |
||||||
|
{Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"}, |
||||||
|
{Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"}, |
||||||
|
{Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"}, |
||||||
|
{Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"}, |
||||||
|
{Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"}, |
||||||
|
{Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"}, |
||||||
|
{Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"}, |
||||||
|
{Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"}, |
||||||
|
{Alpha3bCode: "vol", Alpha2Code: "vo", English: "VolapĂĽk"}, |
||||||
|
{Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"}, |
||||||
|
{Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"}, |
||||||
|
{Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"}, |
||||||
|
{Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"}, |
||||||
|
{Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"}, |
||||||
|
{Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"}, |
||||||
|
{Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"}, |
||||||
|
{Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"}, |
||||||
|
} |
@ -0,0 +1,270 @@ |
|||||||
|
package govalidator |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"html" |
||||||
|
"math" |
||||||
|
"path" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
"unicode" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
// Contains checks if the string contains the substring.
|
||||||
|
func Contains(str, substring string) bool { |
||||||
|
return strings.Contains(str, substring) |
||||||
|
} |
||||||
|
|
||||||
|
// Matches checks if string matches the pattern (pattern is regular expression)
|
||||||
|
// In case of error return false
|
||||||
|
func Matches(str, pattern string) bool { |
||||||
|
match, _ := regexp.MatchString(pattern, str) |
||||||
|
return match |
||||||
|
} |
||||||
|
|
||||||
|
// LeftTrim trims characters from the left side of the input.
|
||||||
|
// If second argument is empty, it will remove leading spaces.
|
||||||
|
func LeftTrim(str, chars string) string { |
||||||
|
if chars == "" { |
||||||
|
return strings.TrimLeftFunc(str, unicode.IsSpace) |
||||||
|
} |
||||||
|
r, _ := regexp.Compile("^[" + chars + "]+") |
||||||
|
return r.ReplaceAllString(str, "") |
||||||
|
} |
||||||
|
|
||||||
|
// RightTrim trims characters from the right side of the input.
|
||||||
|
// If second argument is empty, it will remove trailing spaces.
|
||||||
|
func RightTrim(str, chars string) string { |
||||||
|
if chars == "" { |
||||||
|
return strings.TrimRightFunc(str, unicode.IsSpace) |
||||||
|
} |
||||||
|
r, _ := regexp.Compile("[" + chars + "]+$") |
||||||
|
return r.ReplaceAllString(str, "") |
||||||
|
} |
||||||
|
|
||||||
|
// Trim trims characters from both sides of the input.
|
||||||
|
// If second argument is empty, it will remove spaces.
|
||||||
|
func Trim(str, chars string) string { |
||||||
|
return LeftTrim(RightTrim(str, chars), chars) |
||||||
|
} |
||||||
|
|
||||||
|
// WhiteList removes characters that do not appear in the whitelist.
|
||||||
|
func WhiteList(str, chars string) string { |
||||||
|
pattern := "[^" + chars + "]+" |
||||||
|
r, _ := regexp.Compile(pattern) |
||||||
|
return r.ReplaceAllString(str, "") |
||||||
|
} |
||||||
|
|
||||||
|
// BlackList removes characters that appear in the blacklist.
|
||||||
|
func BlackList(str, chars string) string { |
||||||
|
pattern := "[" + chars + "]+" |
||||||
|
r, _ := regexp.Compile(pattern) |
||||||
|
return r.ReplaceAllString(str, "") |
||||||
|
} |
||||||
|
|
||||||
|
// StripLow removes characters with a numerical value < 32 and 127, mostly control characters.
|
||||||
|
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
|
||||||
|
func StripLow(str string, keepNewLines bool) string { |
||||||
|
chars := "" |
||||||
|
if keepNewLines { |
||||||
|
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F" |
||||||
|
} else { |
||||||
|
chars = "\x00-\x1F\x7F" |
||||||
|
} |
||||||
|
return BlackList(str, chars) |
||||||
|
} |
||||||
|
|
||||||
|
// ReplacePattern replaces regular expression pattern in string
|
||||||
|
func ReplacePattern(str, pattern, replace string) string { |
||||||
|
r, _ := regexp.Compile(pattern) |
||||||
|
return r.ReplaceAllString(str, replace) |
||||||
|
} |
||||||
|
|
||||||
|
// Escape replaces <, >, & and " with HTML entities.
|
||||||
|
var Escape = html.EscapeString |
||||||
|
|
||||||
|
func addSegment(inrune, segment []rune) []rune { |
||||||
|
if len(segment) == 0 { |
||||||
|
return inrune |
||||||
|
} |
||||||
|
if len(inrune) != 0 { |
||||||
|
inrune = append(inrune, '_') |
||||||
|
} |
||||||
|
inrune = append(inrune, segment...) |
||||||
|
return inrune |
||||||
|
} |
||||||
|
|
||||||
|
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
|
||||||
|
// Ex.: my_func => MyFunc
|
||||||
|
func UnderscoreToCamelCase(s string) string { |
||||||
|
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1) |
||||||
|
} |
||||||
|
|
||||||
|
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
|
||||||
|
// Ex.: MyFunc => my_func
|
||||||
|
func CamelCaseToUnderscore(str string) string { |
||||||
|
var output []rune |
||||||
|
var segment []rune |
||||||
|
for _, r := range str { |
||||||
|
|
||||||
|
// not treat number as separate segment
|
||||||
|
if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) { |
||||||
|
output = addSegment(output, segment) |
||||||
|
segment = nil |
||||||
|
} |
||||||
|
segment = append(segment, unicode.ToLower(r)) |
||||||
|
} |
||||||
|
output = addSegment(output, segment) |
||||||
|
return string(output) |
||||||
|
} |
||||||
|
|
||||||
|
// Reverse returns reversed string
|
||||||
|
func Reverse(s string) string { |
||||||
|
r := []rune(s) |
||||||
|
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { |
||||||
|
r[i], r[j] = r[j], r[i] |
||||||
|
} |
||||||
|
return string(r) |
||||||
|
} |
||||||
|
|
||||||
|
// GetLines splits string by "\n" and return array of lines
|
||||||
|
func GetLines(s string) []string { |
||||||
|
return strings.Split(s, "\n") |
||||||
|
} |
||||||
|
|
||||||
|
// GetLine returns specified line of multiline string
|
||||||
|
func GetLine(s string, index int) (string, error) { |
||||||
|
lines := GetLines(s) |
||||||
|
if index < 0 || index >= len(lines) { |
||||||
|
return "", errors.New("line index out of bounds") |
||||||
|
} |
||||||
|
return lines[index], nil |
||||||
|
} |
||||||
|
|
||||||
|
// RemoveTags removes all tags from HTML string
|
||||||
|
func RemoveTags(s string) string { |
||||||
|
return ReplacePattern(s, "<[^>]*>", "") |
||||||
|
} |
||||||
|
|
||||||
|
// SafeFileName returns safe string that can be used in file names
|
||||||
|
func SafeFileName(str string) string { |
||||||
|
name := strings.ToLower(str) |
||||||
|
name = path.Clean(path.Base(name)) |
||||||
|
name = strings.Trim(name, " ") |
||||||
|
separators, err := regexp.Compile(`[ &_=+:]`) |
||||||
|
if err == nil { |
||||||
|
name = separators.ReplaceAllString(name, "-") |
||||||
|
} |
||||||
|
legal, err := regexp.Compile(`[^[:alnum:]-.]`) |
||||||
|
if err == nil { |
||||||
|
name = legal.ReplaceAllString(name, "") |
||||||
|
} |
||||||
|
for strings.Contains(name, "--") { |
||||||
|
name = strings.Replace(name, "--", "-", -1) |
||||||
|
} |
||||||
|
return name |
||||||
|
} |
||||||
|
|
||||||
|
// NormalizeEmail canonicalize an email address.
|
||||||
|
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
|
||||||
|
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
|
||||||
|
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
|
||||||
|
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
|
||||||
|
// normalized to @gmail.com.
|
||||||
|
func NormalizeEmail(str string) (string, error) { |
||||||
|
if !IsEmail(str) { |
||||||
|
return "", fmt.Errorf("%s is not an email", str) |
||||||
|
} |
||||||
|
parts := strings.Split(str, "@") |
||||||
|
parts[0] = strings.ToLower(parts[0]) |
||||||
|
parts[1] = strings.ToLower(parts[1]) |
||||||
|
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" { |
||||||
|
parts[1] = "gmail.com" |
||||||
|
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0] |
||||||
|
} |
||||||
|
return strings.Join(parts, "@"), nil |
||||||
|
} |
||||||
|
|
||||||
|
// Truncate a string to the closest length without breaking words.
|
||||||
|
func Truncate(str string, length int, ending string) string { |
||||||
|
var aftstr, befstr string |
||||||
|
if len(str) > length { |
||||||
|
words := strings.Fields(str) |
||||||
|
before, present := 0, 0 |
||||||
|
for i := range words { |
||||||
|
befstr = aftstr |
||||||
|
before = present |
||||||
|
aftstr = aftstr + words[i] + " " |
||||||
|
present = len(aftstr) |
||||||
|
if present > length && i != 0 { |
||||||
|
if (length - before) < (present - length) { |
||||||
|
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending |
||||||
|
} |
||||||
|
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return str |
||||||
|
} |
||||||
|
|
||||||
|
// PadLeft pads left side of a string if size of string is less then indicated pad length
|
||||||
|
func PadLeft(str string, padStr string, padLen int) string { |
||||||
|
return buildPadStr(str, padStr, padLen, true, false) |
||||||
|
} |
||||||
|
|
||||||
|
// PadRight pads right side of a string if size of string is less then indicated pad length
|
||||||
|
func PadRight(str string, padStr string, padLen int) string { |
||||||
|
return buildPadStr(str, padStr, padLen, false, true) |
||||||
|
} |
||||||
|
|
||||||
|
// PadBoth pads both sides of a string if size of string is less then indicated pad length
|
||||||
|
func PadBoth(str string, padStr string, padLen int) string { |
||||||
|
return buildPadStr(str, padStr, padLen, true, true) |
||||||
|
} |
||||||
|
|
||||||
|
// PadString either left, right or both sides.
|
||||||
|
// Note that padding string can be unicode and more then one character
|
||||||
|
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string { |
||||||
|
|
||||||
|
// When padded length is less then the current string size
|
||||||
|
if padLen < utf8.RuneCountInString(str) { |
||||||
|
return str |
||||||
|
} |
||||||
|
|
||||||
|
padLen -= utf8.RuneCountInString(str) |
||||||
|
|
||||||
|
targetLen := padLen |
||||||
|
|
||||||
|
targetLenLeft := targetLen |
||||||
|
targetLenRight := targetLen |
||||||
|
if padLeft && padRight { |
||||||
|
targetLenLeft = padLen / 2 |
||||||
|
targetLenRight = padLen - targetLenLeft |
||||||
|
} |
||||||
|
|
||||||
|
strToRepeatLen := utf8.RuneCountInString(padStr) |
||||||
|
|
||||||
|
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen))) |
||||||
|
repeatedString := strings.Repeat(padStr, repeatTimes) |
||||||
|
|
||||||
|
leftSide := "" |
||||||
|
if padLeft { |
||||||
|
leftSide = repeatedString[0:targetLenLeft] |
||||||
|
} |
||||||
|
|
||||||
|
rightSide := "" |
||||||
|
if padRight { |
||||||
|
rightSide = repeatedString[0:targetLenRight] |
||||||
|
} |
||||||
|
|
||||||
|
return leftSide + str + rightSide |
||||||
|
} |
||||||
|
|
||||||
|
// TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object
|
||||||
|
func TruncatingErrorf(str string, args ...interface{}) error { |
||||||
|
n := strings.Count(str, "%s") |
||||||
|
return fmt.Errorf(str, args[:n]...) |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@ |
|||||||
|
box: golang |
||||||
|
build: |
||||||
|
steps: |
||||||
|
- setup-go-workspace |
||||||
|
|
||||||
|
- script: |
||||||
|
name: go get |
||||||
|
code: | |
||||||
|
go version |
||||||
|
go get -t ./... |
||||||
|
|
||||||
|
- script: |
||||||
|
name: go test |
||||||
|
code: | |
||||||
|
go test -race -v ./... |
@ -0,0 +1,37 @@ |
|||||||
|
# Binaries for programs and plugins |
||||||
|
*.exe |
||||||
|
*.dll |
||||||
|
*.so |
||||||
|
*.dylib |
||||||
|
*.o |
||||||
|
*.a |
||||||
|
|
||||||
|
# Folders |
||||||
|
_obj |
||||||
|
_test |
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes |
||||||
|
*.[568vq] |
||||||
|
[568vq].out |
||||||
|
|
||||||
|
*.cgo1.go |
||||||
|
*.cgo2.c |
||||||
|
_cgo_defun.c |
||||||
|
_cgo_gotypes.go |
||||||
|
_cgo_export.* |
||||||
|
|
||||||
|
_testmain.go |
||||||
|
|
||||||
|
*.prof |
||||||
|
|
||||||
|
# Test binary, build with `go test -c` |
||||||
|
*.test |
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE |
||||||
|
*.out |
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 |
||||||
|
.glide/ |
||||||
|
|
||||||
|
# Gogland |
||||||
|
.idea/ |
@ -0,0 +1,29 @@ |
|||||||
|
language: go |
||||||
|
sudo: false |
||||||
|
|
||||||
|
go: |
||||||
|
- 1.10.x |
||||||
|
- 1.11.x |
||||||
|
- 1.12.x |
||||||
|
- 1.13.x |
||||||
|
- 1.14.x |
||||||
|
- 1.15.x |
||||||
|
|
||||||
|
cache: |
||||||
|
directories: |
||||||
|
- $HOME/.cache/go-build |
||||||
|
- $HOME/gopath/pkg/mod |
||||||
|
|
||||||
|
env: |
||||||
|
global: |
||||||
|
- GO111MODULE=on |
||||||
|
|
||||||
|
before_install: |
||||||
|
- go get github.com/mattn/goveralls |
||||||
|
- go get golang.org/x/tools/cmd/cover |
||||||
|
- go get golang.org/x/tools/cmd/goimports |
||||||
|
- go get golang.org/x/lint/golint |
||||||
|
script: |
||||||
|
- gofiles=$(find ./ -name '*.go') && [ -z "$gofiles" ] || unformatted=$(goimports -l $gofiles) && [ -z "$unformatted" ] || (echo >&2 "Go files must be formatted with gofmt. Following files has problem:\n $unformatted" && false) |
||||||
|
- golint ./... # This won't break the build, just show warnings |
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci |
@ -0,0 +1,201 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work. |
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following |
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}" |
||||||
|
replaced with your own identifying information. (Don't include |
||||||
|
the brackets!) The text should be enclosed in the appropriate |
||||||
|
comment syntax for the file format. We also recommend that a |
||||||
|
file or class name and description of purpose be included on the |
||||||
|
same "printed page" as the copyright notice for easier |
||||||
|
identification within third-party archives. |
||||||
|
|
||||||
|
Copyright (c) 2018-2020, Dmitrij Koniajev (dimchansky@gmail.com) |
||||||
|
|
||||||
|
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. |
@ -0,0 +1,66 @@ |
|||||||
|
# utfbom [![Godoc](https://godoc.org/github.com/dimchansky/utfbom?status.png)](https://godoc.org/github.com/dimchansky/utfbom) [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://travis-ci.org/dimchansky/utfbom.svg?branch=master)](https://travis-ci.org/dimchansky/utfbom) [![Go Report Card](https://goreportcard.com/badge/github.com/dimchansky/utfbom)](https://goreportcard.com/report/github.com/dimchansky/utfbom) [![Coverage Status](https://coveralls.io/repos/github/dimchansky/utfbom/badge.svg?branch=master)](https://coveralls.io/github/dimchansky/utfbom?branch=master) |
||||||
|
|
||||||
|
The package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary. It can also return the encoding detected by the BOM. |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
go get -u github.com/dimchansky/utfbom |
||||||
|
|
||||||
|
## Example |
||||||
|
|
||||||
|
```go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
|
||||||
|
"github.com/dimchansky/utfbom" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
trySkip([]byte("\xEF\xBB\xBFhello")) |
||||||
|
trySkip([]byte("hello")) |
||||||
|
} |
||||||
|
|
||||||
|
func trySkip(byteData []byte) { |
||||||
|
fmt.Println("Input:", byteData) |
||||||
|
|
||||||
|
// just skip BOM |
||||||
|
output, err := ioutil.ReadAll(utfbom.SkipOnly(bytes.NewReader(byteData))) |
||||||
|
if err != nil { |
||||||
|
fmt.Println(err) |
||||||
|
return |
||||||
|
} |
||||||
|
fmt.Println("ReadAll with BOM skipping", output) |
||||||
|
|
||||||
|
// skip BOM and detect encoding |
||||||
|
sr, enc := utfbom.Skip(bytes.NewReader(byteData)) |
||||||
|
fmt.Printf("Detected encoding: %s\n", enc) |
||||||
|
output, err = ioutil.ReadAll(sr) |
||||||
|
if err != nil { |
||||||
|
fmt.Println(err) |
||||||
|
return |
||||||
|
} |
||||||
|
fmt.Println("ReadAll with BOM detection and skipping", output) |
||||||
|
fmt.Println() |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Output: |
||||||
|
|
||||||
|
``` |
||||||
|
$ go run main.go |
||||||
|
Input: [239 187 191 104 101 108 108 111] |
||||||
|
ReadAll with BOM skipping [104 101 108 108 111] |
||||||
|
Detected encoding: UTF8 |
||||||
|
ReadAll with BOM detection and skipping [104 101 108 108 111] |
||||||
|
|
||||||
|
Input: [104 101 108 108 111] |
||||||
|
ReadAll with BOM skipping [104 101 108 108 111] |
||||||
|
Detected encoding: Unknown |
||||||
|
ReadAll with BOM detection and skipping [104 101 108 108 111] |
||||||
|
``` |
||||||
|
|
||||||
|
|
@ -0,0 +1,192 @@ |
|||||||
|
// Package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary.
|
||||||
|
// It wraps an io.Reader object, creating another object (Reader) that also implements the io.Reader
|
||||||
|
// interface but provides automatic BOM checking and removing as necessary.
|
||||||
|
package utfbom |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
) |
||||||
|
|
||||||
|
// Encoding is type alias for detected UTF encoding.
|
||||||
|
type Encoding int |
||||||
|
|
||||||
|
// Constants to identify detected UTF encodings.
|
||||||
|
const ( |
||||||
|
// Unknown encoding, returned when no BOM was detected
|
||||||
|
Unknown Encoding = iota |
||||||
|
|
||||||
|
// UTF8, BOM bytes: EF BB BF
|
||||||
|
UTF8 |
||||||
|
|
||||||
|
// UTF-16, big-endian, BOM bytes: FE FF
|
||||||
|
UTF16BigEndian |
||||||
|
|
||||||
|
// UTF-16, little-endian, BOM bytes: FF FE
|
||||||
|
UTF16LittleEndian |
||||||
|
|
||||||
|
// UTF-32, big-endian, BOM bytes: 00 00 FE FF
|
||||||
|
UTF32BigEndian |
||||||
|
|
||||||
|
// UTF-32, little-endian, BOM bytes: FF FE 00 00
|
||||||
|
UTF32LittleEndian |
||||||
|
) |
||||||
|
|
||||||
|
// String returns a user-friendly string representation of the encoding. Satisfies fmt.Stringer interface.
|
||||||
|
func (e Encoding) String() string { |
||||||
|
switch e { |
||||||
|
case UTF8: |
||||||
|
return "UTF8" |
||||||
|
case UTF16BigEndian: |
||||||
|
return "UTF16BigEndian" |
||||||
|
case UTF16LittleEndian: |
||||||
|
return "UTF16LittleEndian" |
||||||
|
case UTF32BigEndian: |
||||||
|
return "UTF32BigEndian" |
||||||
|
case UTF32LittleEndian: |
||||||
|
return "UTF32LittleEndian" |
||||||
|
default: |
||||||
|
return "Unknown" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const maxConsecutiveEmptyReads = 100 |
||||||
|
|
||||||
|
// Skip creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||||
|
// It also returns the encoding detected by the BOM.
|
||||||
|
// If the detected encoding is not needed, you can call the SkipOnly function.
|
||||||
|
func Skip(rd io.Reader) (*Reader, Encoding) { |
||||||
|
// Is it already a Reader?
|
||||||
|
b, ok := rd.(*Reader) |
||||||
|
if ok { |
||||||
|
return b, Unknown |
||||||
|
} |
||||||
|
|
||||||
|
enc, left, err := detectUtf(rd) |
||||||
|
return &Reader{ |
||||||
|
rd: rd, |
||||||
|
buf: left, |
||||||
|
err: err, |
||||||
|
}, enc |
||||||
|
} |
||||||
|
|
||||||
|
// SkipOnly creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||||
|
func SkipOnly(rd io.Reader) *Reader { |
||||||
|
r, _ := Skip(rd) |
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
// Reader implements automatic BOM (Unicode Byte Order Mark) checking and
|
||||||
|
// removing as necessary for an io.Reader object.
|
||||||
|
type Reader struct { |
||||||
|
rd io.Reader // reader provided by the client
|
||||||
|
buf []byte // buffered data
|
||||||
|
err error // last error
|
||||||
|
} |
||||||
|
|
||||||
|
// Read is an implementation of io.Reader interface.
|
||||||
|
// The bytes are taken from the underlying Reader, but it checks for BOMs, removing them as necessary.
|
||||||
|
func (r *Reader) Read(p []byte) (n int, err error) { |
||||||
|
if len(p) == 0 { |
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
if r.buf == nil { |
||||||
|
if r.err != nil { |
||||||
|
return 0, r.readErr() |
||||||
|
} |
||||||
|
|
||||||
|
return r.rd.Read(p) |
||||||
|
} |
||||||
|
|
||||||
|
// copy as much as we can
|
||||||
|
n = copy(p, r.buf) |
||||||
|
r.buf = nilIfEmpty(r.buf[n:]) |
||||||
|
return n, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (r *Reader) readErr() error { |
||||||
|
err := r.err |
||||||
|
r.err = nil |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
var errNegativeRead = errors.New("utfbom: reader returned negative count from Read") |
||||||
|
|
||||||
|
func detectUtf(rd io.Reader) (enc Encoding, buf []byte, err error) { |
||||||
|
buf, err = readBOM(rd) |
||||||
|
|
||||||
|
if len(buf) >= 4 { |
||||||
|
if isUTF32BigEndianBOM4(buf) { |
||||||
|
return UTF32BigEndian, nilIfEmpty(buf[4:]), err |
||||||
|
} |
||||||
|
if isUTF32LittleEndianBOM4(buf) { |
||||||
|
return UTF32LittleEndian, nilIfEmpty(buf[4:]), err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if len(buf) > 2 && isUTF8BOM3(buf) { |
||||||
|
return UTF8, nilIfEmpty(buf[3:]), err |
||||||
|
} |
||||||
|
|
||||||
|
if (err != nil && err != io.EOF) || (len(buf) < 2) { |
||||||
|
return Unknown, nilIfEmpty(buf), err |
||||||
|
} |
||||||
|
|
||||||
|
if isUTF16BigEndianBOM2(buf) { |
||||||
|
return UTF16BigEndian, nilIfEmpty(buf[2:]), err |
||||||
|
} |
||||||
|
if isUTF16LittleEndianBOM2(buf) { |
||||||
|
return UTF16LittleEndian, nilIfEmpty(buf[2:]), err |
||||||
|
} |
||||||
|
|
||||||
|
return Unknown, nilIfEmpty(buf), err |
||||||
|
} |
||||||
|
|
||||||
|
func readBOM(rd io.Reader) (buf []byte, err error) { |
||||||
|
const maxBOMSize = 4 |
||||||
|
var bom [maxBOMSize]byte // used to read BOM
|
||||||
|
|
||||||
|
// read as many bytes as possible
|
||||||
|
for nEmpty, n := 0, 0; err == nil && len(buf) < maxBOMSize; buf = bom[:len(buf)+n] { |
||||||
|
if n, err = rd.Read(bom[len(buf):]); n < 0 { |
||||||
|
panic(errNegativeRead) |
||||||
|
} |
||||||
|
if n > 0 { |
||||||
|
nEmpty = 0 |
||||||
|
} else { |
||||||
|
nEmpty++ |
||||||
|
if nEmpty >= maxConsecutiveEmptyReads { |
||||||
|
err = io.ErrNoProgress |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func isUTF32BigEndianBOM4(buf []byte) bool { |
||||||
|
return buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF |
||||||
|
} |
||||||
|
|
||||||
|
func isUTF32LittleEndianBOM4(buf []byte) bool { |
||||||
|
return buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00 |
||||||
|
} |
||||||
|
|
||||||
|
func isUTF8BOM3(buf []byte) bool { |
||||||
|
return buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF |
||||||
|
} |
||||||
|
|
||||||
|
func isUTF16BigEndianBOM2(buf []byte) bool { |
||||||
|
return buf[0] == 0xFE && buf[1] == 0xFF |
||||||
|
} |
||||||
|
|
||||||
|
func isUTF16LittleEndianBOM2(buf []byte) bool { |
||||||
|
return buf[0] == 0xFF && buf[1] == 0xFE |
||||||
|
} |
||||||
|
|
||||||
|
func nilIfEmpty(buf []byte) (res []byte) { |
||||||
|
if len(buf) > 0 { |
||||||
|
res = buf |
||||||
|
} |
||||||
|
return |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
.idea |
@ -0,0 +1,176 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
@ -0,0 +1,5 @@ |
|||||||
|
ci: |
||||||
|
@goimports -l . || (goimports -d . && exit 1)
|
||||||
|
@golangci-lint run
|
||||||
|
@go test -v .
|
||||||
|
.DEFAULT_GOAL := ci
|
@ -0,0 +1,451 @@ |
|||||||
|
package hostsfile |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"sort" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/asaskevich/govalidator" |
||||||
|
"github.com/dimchansky/utfbom" |
||||||
|
) |
||||||
|
|
||||||
|
type lookup struct { |
||||||
|
sync.RWMutex |
||||||
|
l map[string][]int |
||||||
|
} |
||||||
|
|
||||||
|
type Hosts struct { |
||||||
|
Path string |
||||||
|
Lines []HostsLine |
||||||
|
ips lookup |
||||||
|
hosts lookup |
||||||
|
} |
||||||
|
|
||||||
|
// NewHosts return a new instance of ``Hosts`` using the default hosts file path.
|
||||||
|
func NewHosts() (*Hosts, error) { |
||||||
|
osHostsFilePath := os.ExpandEnv(filepath.FromSlash(HostsFilePath)) |
||||||
|
|
||||||
|
if env, isset := os.LookupEnv("HOSTS_PATH"); isset && len(env) > 0 { |
||||||
|
osHostsFilePath = os.ExpandEnv(filepath.FromSlash(env)) |
||||||
|
} |
||||||
|
|
||||||
|
return NewCustomHosts(osHostsFilePath) |
||||||
|
} |
||||||
|
|
||||||
|
// NewCustomHosts return a new instance of ``Hosts`` using a custom hosts file path.
|
||||||
|
func NewCustomHosts(osHostsFilePath string) (*Hosts, error) { |
||||||
|
hosts := &Hosts{ |
||||||
|
Path: osHostsFilePath, |
||||||
|
ips: lookup{l: make(map[string][]int)}, |
||||||
|
hosts: lookup{l: make(map[string][]int)}, |
||||||
|
} |
||||||
|
|
||||||
|
if err := hosts.Load(); err != nil { |
||||||
|
return hosts, err |
||||||
|
} |
||||||
|
|
||||||
|
return hosts, nil |
||||||
|
} |
||||||
|
|
||||||
|
// IsWritable return ```true``` if hosts file is writable.
|
||||||
|
func (h *Hosts) IsWritable() bool { |
||||||
|
file, err := os.OpenFile(h.Path, os.O_WRONLY, 0660) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
defer file.Close() |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// Load the hosts file into ```l.Lines```.
|
||||||
|
// ```Load()``` is called by ```NewHosts()``` and ```Hosts.Flush()``` so you
|
||||||
|
// generally you won't need to call this yourself.
|
||||||
|
func (h *Hosts) Load() error { |
||||||
|
file, err := os.Open(h.Path) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer file.Close() |
||||||
|
|
||||||
|
// if you're reloading from disk confirm you refresh the hash maps and lines
|
||||||
|
if len(h.Lines) != 0 { |
||||||
|
h.ips = lookup{l: make(map[string][]int)} |
||||||
|
h.hosts = lookup{l: make(map[string][]int)} |
||||||
|
h.Lines = []HostsLine{} |
||||||
|
} |
||||||
|
|
||||||
|
scanner := bufio.NewScanner(utfbom.SkipOnly(file)) |
||||||
|
for scanner.Scan() { |
||||||
|
hl := NewHostsLine(scanner.Text()) |
||||||
|
h.Lines = append(h.Lines, hl) |
||||||
|
pos := len(h.Lines) - 1 |
||||||
|
h.addIpPosition(hl.IP, pos) |
||||||
|
for _, host := range hl.Hosts { |
||||||
|
h.addHostPositions(host, pos) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return scanner.Err() |
||||||
|
} |
||||||
|
|
||||||
|
// Flush any changes made to hosts file.
|
||||||
|
func (h *Hosts) Flush() error { |
||||||
|
h.preFlushClean() |
||||||
|
file, err := os.Create(h.Path) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
defer file.Close() |
||||||
|
|
||||||
|
w := bufio.NewWriter(file) |
||||||
|
for _, line := range h.Lines { |
||||||
|
if _, err := fmt.Fprintf(w, "%s%s", line.ToRaw(), eol); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
err = w.Flush() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return h.Load() |
||||||
|
} |
||||||
|
|
||||||
|
// AddRaw takes a line from a hosts file and parses/adds the HostsLine
|
||||||
|
func (h *Hosts) AddRaw(raw ...string) error { |
||||||
|
for _, r := range raw { |
||||||
|
nl := NewHostsLine(r) |
||||||
|
if nl.IP != "" && net.ParseIP(nl.IP) == nil { |
||||||
|
return fmt.Errorf("%q is an invalid IP address", nl.IP) |
||||||
|
} |
||||||
|
|
||||||
|
for _, host := range nl.Hosts { |
||||||
|
if !govalidator.IsDNSName(host) { |
||||||
|
return fmt.Errorf("hostname is not a valid dns name: %s", host) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
h.Lines = append(h.Lines, nl) |
||||||
|
pos := len(h.Lines) - 1 |
||||||
|
h.addIpPosition(nl.IP, pos) |
||||||
|
for _, host := range nl.Hosts { |
||||||
|
h.addHostPositions(host, pos) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Add an entry to the hosts file.
|
||||||
|
func (h *Hosts) Add(ip string, hosts ...string) error { |
||||||
|
if net.ParseIP(ip) == nil { |
||||||
|
return fmt.Errorf("%q is an invalid IP address", ip) |
||||||
|
} |
||||||
|
|
||||||
|
position := h.getIpPositions(ip) |
||||||
|
if len(position) == 0 { |
||||||
|
nl := HostsLine{ |
||||||
|
Raw: buildRawLine(ip, hosts), |
||||||
|
IP: ip, |
||||||
|
Hosts: hosts, |
||||||
|
} |
||||||
|
h.Lines = append(h.Lines, nl) |
||||||
|
pos := len(h.Lines) - 1 |
||||||
|
h.addIpPosition(ip, pos) |
||||||
|
for _, host := range nl.Hosts { |
||||||
|
h.addHostPositions(host, pos) |
||||||
|
} |
||||||
|
} else { |
||||||
|
// add new host to the first one we find
|
||||||
|
hostsCopy := h.Lines[position[0]].Hosts |
||||||
|
for _, addHost := range hosts { |
||||||
|
if h.Has(ip, addHost) { |
||||||
|
// this combo already exists
|
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if !govalidator.IsDNSName(addHost) { |
||||||
|
return fmt.Errorf("hostname is not a valid dns name: %s", addHost) |
||||||
|
} |
||||||
|
if itemInSliceString(addHost, hostsCopy) { |
||||||
|
continue // host exists for ip already
|
||||||
|
} |
||||||
|
|
||||||
|
hostsCopy = append(hostsCopy, addHost) |
||||||
|
h.addHostPositions(addHost, position[0]) |
||||||
|
} |
||||||
|
h.Lines[position[0]].Hosts = hostsCopy |
||||||
|
h.Lines[position[0]].Raw = h.Lines[position[0]].ToRaw() // reset raw
|
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) Clear() { |
||||||
|
h.Lines = []HostsLine{} |
||||||
|
} |
||||||
|
|
||||||
|
// Clean merge duplicate ips and hosts per ip
|
||||||
|
func (h *Hosts) Clean() { |
||||||
|
h.RemoveDuplicateIps() |
||||||
|
h.RemoveDuplicateHosts() |
||||||
|
h.SortHosts() |
||||||
|
h.SortByIp() |
||||||
|
h.HostsPerLine(HostsPerLine) |
||||||
|
} |
||||||
|
|
||||||
|
// Has return a bool if ip/host combo in hosts file.
|
||||||
|
func (h *Hosts) Has(ip string, host string) bool { |
||||||
|
ippos := h.getIpPositions(ip) |
||||||
|
hostpos := h.getHostPositions(host) |
||||||
|
for _, pos := range ippos { |
||||||
|
if itemInSliceInt(pos, hostpos) { |
||||||
|
// if ip and host have matching lookup positions we have a combo match
|
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// HasHostname return a bool if hostname in hosts file.
|
||||||
|
func (h *Hosts) HasHostname(host string) bool { |
||||||
|
return len(h.getHostPositions(host)) > 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) HasIp(ip string) bool { |
||||||
|
return len(h.getIpPositions(ip)) > 0 |
||||||
|
} |
||||||
|
|
||||||
|
// Remove an entry from the hosts file.
|
||||||
|
func (h *Hosts) Remove(ip string, hosts ...string) error { |
||||||
|
var outputLines []HostsLine |
||||||
|
if net.ParseIP(ip) == nil { |
||||||
|
return fmt.Errorf("%q is an invalid IP address", ip) |
||||||
|
} |
||||||
|
|
||||||
|
for _, line := range h.Lines { |
||||||
|
// Bad lines or comments just get readded.
|
||||||
|
if line.Err != nil || line.IsComment() || line.IP != ip { |
||||||
|
outputLines = append(outputLines, line) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
var newHosts []string |
||||||
|
for _, checkHost := range line.Hosts { |
||||||
|
if !itemInSliceString(checkHost, hosts) { |
||||||
|
newHosts = append(newHosts, checkHost) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// If hosts is empty, skip the line completely.
|
||||||
|
if len(newHosts) > 0 { |
||||||
|
newLineRaw := line.IP |
||||||
|
|
||||||
|
for _, host := range newHosts { |
||||||
|
newLineRaw = fmt.Sprintf("%s %s", newLineRaw, host) |
||||||
|
} |
||||||
|
newLine := NewHostsLine(newLineRaw) |
||||||
|
outputLines = append(outputLines, newLine) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
h.Lines = outputLines |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// RemoveByHostname remove entries by hostname from the hosts file.
|
||||||
|
func (h *Hosts) RemoveByHostname(host string) error { |
||||||
|
for _, p := range h.getHostPositions(host) { |
||||||
|
line := &h.Lines[p] |
||||||
|
if len(line.Hosts) > 0 { |
||||||
|
line.Hosts = removeFromSliceString(host, line.Hosts) |
||||||
|
line.RegenRaw() |
||||||
|
} |
||||||
|
h.removeHostPositions(host, p) |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) RemoveByIp(ip string) error { |
||||||
|
pos := h.getIpPositions(ip) |
||||||
|
for len(pos) > 0 { |
||||||
|
for _, p := range pos { |
||||||
|
h.removeByPosition(p) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) RemoveDuplicateIps() { |
||||||
|
ipCount := make(map[string]int) |
||||||
|
for _, line := range h.Lines { |
||||||
|
ipCount[line.IP]++ |
||||||
|
} |
||||||
|
for ip, count := range ipCount { |
||||||
|
if count > 1 { |
||||||
|
h.combineIp(ip) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) RemoveDuplicateHosts() { |
||||||
|
for pos, line := range h.Lines { |
||||||
|
line.RemoveDuplicateHosts() |
||||||
|
h.Lines[pos] = line |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) SortHosts() { |
||||||
|
for pos, line := range h.Lines { |
||||||
|
line.SortHosts() |
||||||
|
h.Lines[pos] = line |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// SortByIp convert to net.IP and byte.Compare
|
||||||
|
func (h *Hosts) SortByIp() { |
||||||
|
sortedIps := make([]net.IP, 0, len(h.Lines)) |
||||||
|
for _, l := range h.Lines { |
||||||
|
sortedIps = append(sortedIps, net.ParseIP(l.IP)) |
||||||
|
} |
||||||
|
sort.Slice(sortedIps, func(i, j int) bool { |
||||||
|
return bytes.Compare(sortedIps[i], sortedIps[j]) < 0 |
||||||
|
}) |
||||||
|
|
||||||
|
var sortedLines []HostsLine |
||||||
|
for _, ip := range sortedIps { |
||||||
|
for _, l := range h.Lines { |
||||||
|
if ip.String() == l.IP { |
||||||
|
sortedLines = append(sortedLines, l) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
h.Lines = sortedLines |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) HostsPerLine(count int) { |
||||||
|
// restacks everything into 1 ip again so we can do the split, do this even if count is -1 so it can reset the slice
|
||||||
|
h.RemoveDuplicateIps() |
||||||
|
if count <= 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
var newLines []HostsLine |
||||||
|
for _, line := range h.Lines { |
||||||
|
if len(line.Hosts) <= count { |
||||||
|
newLines = append(newLines, line) |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
for i := 0; i < len(line.Hosts); i += count { |
||||||
|
lineCopy := line |
||||||
|
end := len(line.Hosts) |
||||||
|
if end > i+count { |
||||||
|
end = i + count |
||||||
|
} |
||||||
|
|
||||||
|
lineCopy.Hosts = line.Hosts[i:end] |
||||||
|
lineCopy.Raw = lineCopy.ToRaw() |
||||||
|
newLines = append(newLines, lineCopy) |
||||||
|
} |
||||||
|
} |
||||||
|
h.Lines = newLines |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) combineIp(ip string) { |
||||||
|
newLine := HostsLine{ |
||||||
|
IP: ip, |
||||||
|
} |
||||||
|
|
||||||
|
linesCopy := make([]HostsLine, len(h.Lines)) |
||||||
|
copy(linesCopy, h.Lines) |
||||||
|
for _, line := range linesCopy { |
||||||
|
if line.IP == ip { |
||||||
|
newLine.Combine(line) |
||||||
|
} |
||||||
|
} |
||||||
|
newLine.SortHosts() |
||||||
|
h.removeIp(ip) |
||||||
|
h.Lines = append(h.Lines, newLine) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) removeByPosition(pos int) { |
||||||
|
if pos == 0 && len(h.Lines) == 1 { |
||||||
|
h.Clear() |
||||||
|
return |
||||||
|
} |
||||||
|
if pos == len(h.Lines) { |
||||||
|
h.Lines = h.Lines[:pos-1] |
||||||
|
return |
||||||
|
} |
||||||
|
h.Lines = append(h.Lines[:pos], h.Lines[pos+1:]...) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) removeIp(ip string) { |
||||||
|
var newLines []HostsLine |
||||||
|
for _, line := range h.Lines { |
||||||
|
if line.IP != ip { |
||||||
|
newLines = append(newLines, line) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
h.Lines = newLines |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) getHostPositions(host string) []int { |
||||||
|
h.hosts.RLock() |
||||||
|
defer h.hosts.RUnlock() |
||||||
|
i, ok := h.hosts.l[host] |
||||||
|
if ok { |
||||||
|
return i |
||||||
|
} |
||||||
|
return []int{} |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) addHostPositions(host string, pos int) { |
||||||
|
h.hosts.Lock() |
||||||
|
defer h.hosts.Unlock() |
||||||
|
h.hosts.l[host] = append(h.hosts.l[host], pos) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) removeHostPositions(host string, pos int) { |
||||||
|
h.hosts.Lock() |
||||||
|
defer h.hosts.Unlock() |
||||||
|
positions := h.hosts.l[host] |
||||||
|
h.hosts.l[host] = removeFromSliceInt(pos, positions) |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) getIpPositions(ip string) []int { |
||||||
|
h.ips.RLock() |
||||||
|
defer h.ips.RUnlock() |
||||||
|
i, ok := h.ips.l[ip] |
||||||
|
if ok { |
||||||
|
return i |
||||||
|
} |
||||||
|
|
||||||
|
return []int{} |
||||||
|
} |
||||||
|
|
||||||
|
func (h *Hosts) addIpPosition(ip string, pos int) { |
||||||
|
h.ips.Lock() |
||||||
|
defer h.ips.Unlock() |
||||||
|
h.ips.l[ip] = append(h.ips.l[ip], pos) |
||||||
|
} |
||||||
|
|
||||||
|
func buildRawLine(ip string, hosts []string) string { |
||||||
|
output := ip |
||||||
|
for _, host := range hosts { |
||||||
|
output = fmt.Sprintf("%s %s", output, host) |
||||||
|
} |
||||||
|
|
||||||
|
return output |
||||||
|
} |
@ -0,0 +1,108 @@ |
|||||||
|
package hostsfile |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"sort" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type HostsLine struct { |
||||||
|
IP string |
||||||
|
Hosts []string |
||||||
|
Raw string |
||||||
|
Err error |
||||||
|
Comment string |
||||||
|
} |
||||||
|
|
||||||
|
const commentChar string = "#" |
||||||
|
|
||||||
|
// NewHostsLine return a new instance of ```HostsLine```.
|
||||||
|
func NewHostsLine(raw string) HostsLine { |
||||||
|
output := HostsLine{Raw: raw} |
||||||
|
if output.IsComment() { //whole line is comment
|
||||||
|
return output |
||||||
|
} |
||||||
|
|
||||||
|
if output.HasComment() { //trailing comment
|
||||||
|
commentSplit := strings.Split(output.Raw, commentChar) |
||||||
|
raw = commentSplit[0] |
||||||
|
output.Comment = commentSplit[1] |
||||||
|
} |
||||||
|
|
||||||
|
fields := strings.Fields(raw) |
||||||
|
if len(fields) == 0 { |
||||||
|
return output |
||||||
|
} |
||||||
|
|
||||||
|
rawIP := fields[0] |
||||||
|
if net.ParseIP(rawIP) == nil { |
||||||
|
output.Err = fmt.Errorf("bad hosts line: %q", raw) |
||||||
|
} |
||||||
|
|
||||||
|
output.IP = rawIP |
||||||
|
output.Hosts = fields[1:] |
||||||
|
|
||||||
|
return output |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) ToRaw() string { |
||||||
|
var comment string |
||||||
|
if l.IsComment() { //Whole line is comment
|
||||||
|
return l.Raw |
||||||
|
} |
||||||
|
|
||||||
|
if l.Comment != "" { |
||||||
|
comment = fmt.Sprintf(" %s%s", commentChar, l.Comment) |
||||||
|
} |
||||||
|
|
||||||
|
return fmt.Sprintf("%s %s%s", l.IP, strings.Join(l.Hosts, " "), comment) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) RemoveDuplicateHosts() { |
||||||
|
unique := make(map[string]struct{}) |
||||||
|
for _, h := range l.Hosts { |
||||||
|
unique[h] = struct{}{} |
||||||
|
} |
||||||
|
|
||||||
|
l.Hosts = []string{} |
||||||
|
for k := range unique { |
||||||
|
l.Hosts = append(l.Hosts, k) |
||||||
|
} |
||||||
|
l.Raw = l.ToRaw() |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) Combine(hostline HostsLine) { |
||||||
|
l.Hosts = append(l.Hosts, hostline.Hosts...) |
||||||
|
if l.Comment == "" { |
||||||
|
l.Comment = hostline.Comment |
||||||
|
} else { |
||||||
|
l.Comment = fmt.Sprintf("%s %s", l.Comment, hostline.Comment) |
||||||
|
} |
||||||
|
l.Raw = l.ToRaw() |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) SortHosts() { |
||||||
|
sort.Strings(l.Hosts) |
||||||
|
l.Raw = l.ToRaw() |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) IsComment() bool { |
||||||
|
return strings.HasPrefix(strings.TrimSpace(l.Raw), commentChar) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) HasComment() bool { |
||||||
|
return strings.Contains(l.Raw, commentChar) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) IsValid() bool { |
||||||
|
return l.IP != "" |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) IsMalformed() bool { |
||||||
|
return l.Err != nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *HostsLine) RegenRaw() { |
||||||
|
l.Raw = fmt.Sprintf("%s %s", l.IP, strings.Join(l.Hosts, " ")) |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
@echo off |
||||||
|
SETLOCAL ENABLEDELAYEDEXPANSION |
||||||
|
set LF=^ |
||||||
|
|
||||||
|
echo %1% |
||||||
|
if "%1%"=="" ( |
||||||
|
set cmd=ci |
||||||
|
) else ( |
||||||
|
set cmd=%1% |
||||||
|
) |
||||||
|
|
||||||
|
if "%cmd%" == "ci" ( |
||||||
|
for /F %%i in ('goimports -l .') do ( |
||||||
|
set "line=%%i" |
||||||
|
set goimports=%goimports%!line!!LF! |
||||||
|
) |
||||||
|
|
||||||
|
if not "!goimports!" == "" ( |
||||||
|
goimports -d . |
||||||
|
goto :eof |
||||||
|
) |
||||||
|
|
||||||
|
golangci-lint run || goto :eof |
||||||
|
go test -v . || goto :eof |
||||||
|
|
||||||
|
goto :eof |
||||||
|
) |
@ -0,0 +1,55 @@ |
|||||||
|
package hostsfile |
||||||
|
|
||||||
|
func itemInSliceString(item string, list []string) bool { |
||||||
|
for _, i := range list { |
||||||
|
if i == item { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func itemInSliceInt(item int, list []int) bool { |
||||||
|
for _, i := range list { |
||||||
|
if i == item { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func removeFromSliceString(s string, slice []string) []string { |
||||||
|
pos := findPositionInSliceString(s, slice) |
||||||
|
for pos > -1 { |
||||||
|
slice = append(slice[:pos], slice[pos+1:]...) |
||||||
|
pos = findPositionInSliceString(s, slice) |
||||||
|
} |
||||||
|
return slice |
||||||
|
} |
||||||
|
|
||||||
|
func findPositionInSliceString(s string, slice []string) int { |
||||||
|
for index, v := range slice { |
||||||
|
if v == s { |
||||||
|
return index |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
} |
||||||
|
|
||||||
|
func removeFromSliceInt(s int, slice []int) []int { |
||||||
|
pos := findPositionInSliceInt(s, slice) |
||||||
|
for pos > -1 { |
||||||
|
slice = append(slice[:pos], slice[pos+1:]...) |
||||||
|
pos = findPositionInSliceInt(s, slice) |
||||||
|
} |
||||||
|
return slice |
||||||
|
} |
||||||
|
|
||||||
|
func findPositionInSliceInt(s int, slice []int) int { |
||||||
|
for index, v := range slice { |
||||||
|
if v == s { |
||||||
|
return index |
||||||
|
} |
||||||
|
} |
||||||
|
return -1 |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
//go:build !windows
|
||||||
|
//+build !windows
|
||||||
|
|
||||||
|
package hostsfile |
||||||
|
|
||||||
|
var ( |
||||||
|
HostsPerLine = -1 // unlimited
|
||||||
|
HostsFilePath = "/etc/hosts" |
||||||
|
eol = "\n" |
||||||
|
) |
||||||
|
|
||||||
|
func (h *Hosts) preFlushClean() {} // no op
|
@ -0,0 +1,12 @@ |
|||||||
|
package hostsfile |
||||||
|
|
||||||
|
var ( |
||||||
|
HostsPerLine = 9 |
||||||
|
HostsFilePath = "${SystemRoot}/System32/drivers/etc/hosts" |
||||||
|
eol = "\r\n" |
||||||
|
) |
||||||
|
|
||||||
|
func (h *Hosts) preFlushClean() { |
||||||
|
// need to force hosts per line always on windows see https://github.com/goodhosts/hostsfile/issues/18
|
||||||
|
h.HostsPerLine(HostsPerLine) |
||||||
|
} |
Loading…
Reference in new issue