mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	More expansions in template repositories (#10021)
* Super expansion * Explain which features are in 1.11 vs 1.12 * Move imports Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
		@@ -1,23 +1,20 @@
 | 
			
		||||
---
 | 
			
		||||
date: "2019-11-28:00:00+02:00"
 | 
			
		||||
title: "The .gitea Directory"
 | 
			
		||||
slug: "gitea-directory"
 | 
			
		||||
weight: 40
 | 
			
		||||
title: "Template Repositories"
 | 
			
		||||
slug: "template-repositories"
 | 
			
		||||
weight: 14
 | 
			
		||||
toc: true
 | 
			
		||||
draft: false
 | 
			
		||||
menu:
 | 
			
		||||
  sidebar:
 | 
			
		||||
    parent: "features"
 | 
			
		||||
    name: "The .gitea Directory"
 | 
			
		||||
    weight: 50
 | 
			
		||||
    identifier: "gitea-directory"
 | 
			
		||||
    parent: "usage"
 | 
			
		||||
    name: "Template Repositories"
 | 
			
		||||
    weight: 14
 | 
			
		||||
    identifier: "template-repositories"
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# The .gitea directory
 | 
			
		||||
Gitea repositories can include a `.gitea` directory at their base which will store settings/configurations for certain features.
 | 
			
		||||
 | 
			
		||||
## Templates
 | 
			
		||||
Gitea includes template repositories, and one feature implemented with them is auto-expansion of specific variables within your template files.  
 | 
			
		||||
## Template Repositories
 | 
			
		||||
Gitea `1.11.0` and above includes template repositories, and one feature implemented with them is auto-expansion of specific variables within your template files.  
 | 
			
		||||
To tell Gitea which files to expand, you must include a `template` file inside the `.gitea` directory of the template repository.  
 | 
			
		||||
Gitea uses [gobwas/glob](https://github.com/gobwas/glob) for its glob syntax. It closely resembles a traditional `.gitignore`, however there may be slight differences.
 | 
			
		||||
 | 
			
		||||
@@ -42,15 +39,34 @@ a/b/c/d.json
 | 
			
		||||
In any file matched by the above globs, certain variables will be expanded.  
 | 
			
		||||
All variables must be of the form `$VAR` or `${VAR}`. To escape an expansion, use a double `$$`, such as `$$VAR` or `$${VAR}`
 | 
			
		||||
 | 
			
		||||
| Variable             | Expands To                                          |
 | 
			
		||||
|----------------------|-----------------------------------------------------|
 | 
			
		||||
| REPO_NAME            | The name of the generated repository                |
 | 
			
		||||
| TEMPLATE_NAME        | The name of the template repository                 |
 | 
			
		||||
| REPO_DESCRIPTION     | The description of the generated repository         |
 | 
			
		||||
| TEMPLATE_DESCRIPTION | The description of the template repository          |
 | 
			
		||||
| REPO_LINK            | The URL to the generated repository                 |
 | 
			
		||||
| TEMPLATE_LINK        | The URL to the template repository                  |
 | 
			
		||||
| REPO_HTTPS_URL       | The HTTP(S) clone link for the generated repository |
 | 
			
		||||
| TEMPLATE_HTTPS_URL   | The HTTP(S) clone link for the template repository  |
 | 
			
		||||
| REPO_SSH_URL         | The SSH clone link for the generated repository     |
 | 
			
		||||
| TEMPLATE_SSH_URL     | The SSH clone link for the template repository      |
 | 
			
		||||
| Variable             | Expands To                                          | Transformable |
 | 
			
		||||
|----------------------|-----------------------------------------------------|---------------|
 | 
			
		||||
| REPO_NAME            | The name of the generated repository                | ✓             |
 | 
			
		||||
| TEMPLATE_NAME        | The name of the template repository                 | ✓             |
 | 
			
		||||
| REPO_DESCRIPTION     | The description of the generated repository         | ✘             |
 | 
			
		||||
| TEMPLATE_DESCRIPTION | The description of the template repository          | ✘             |
 | 
			
		||||
| REPO_OWNER           | The owner of the generated repository               | ✓             |
 | 
			
		||||
| TEMPLATE_OWNER       | The owner of the template repository                | ✓             |
 | 
			
		||||
| REPO_LINK            | The URL to the generated repository                 | ✘             |
 | 
			
		||||
| TEMPLATE_LINK        | The URL to the template repository                  | ✘             |
 | 
			
		||||
| REPO_HTTPS_URL       | The HTTP(S) clone link for the generated repository | ✘             |
 | 
			
		||||
| TEMPLATE_HTTPS_URL   | The HTTP(S) clone link for the template repository  | ✘             |
 | 
			
		||||
| REPO_SSH_URL         | The SSH clone link for the generated repository     | ✘             |
 | 
			
		||||
| TEMPLATE_SSH_URL     | The SSH clone link for the template repository      | ✘             |
 | 
			
		||||
 | 
			
		||||
### Transformers :robot:
 | 
			
		||||
Gitea `1.12.0` adds a few transformers to some of the applicable variables above.  
 | 
			
		||||
For example, to get `REPO_NAME` in `PASCAL`-case, your template would use `${REPO_NAME_PASCAL}`
 | 
			
		||||
 | 
			
		||||
Feeding `go-sdk` to the available transformers yields...
 | 
			
		||||
 | 
			
		||||
| Transformer | Effect     |
 | 
			
		||||
|-------------|------------|
 | 
			
		||||
| SNAKE       | go_sdk     |
 | 
			
		||||
| KEBAB       | go-sdk     |
 | 
			
		||||
| CAMEL       | goSdk      |
 | 
			
		||||
| PASCAL      | GoSdk      |
 | 
			
		||||
| LOWER       | go-sdk     |
 | 
			
		||||
| UPPER       | GO-SDK     |
 | 
			
		||||
| TITLE       | Go-Sdk     |
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -50,6 +50,7 @@ require (
 | 
			
		||||
	github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
 | 
			
		||||
	github.com/google/go-github/v24 v24.0.1
 | 
			
		||||
	github.com/gorilla/context v1.1.1
 | 
			
		||||
	github.com/huandu/xstrings v1.3.0
 | 
			
		||||
	github.com/issue9/assert v1.3.2 // indirect
 | 
			
		||||
	github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c
 | 
			
		||||
	github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -306,6 +306,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo=
 | 
			
		||||
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/issue9/assert v1.3.2 h1:IaTa37u4m1fUuTH9K9ldO5IONKVDXjLiUO1T9vj0OF0=
 | 
			
		||||
github.com/issue9/assert v1.3.2/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio=
 | 
			
		||||
 
 | 
			
		||||
@@ -16,38 +16,62 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/huandu/xstrings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type transformer struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Transform func(string) string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type expansion struct {
 | 
			
		||||
	Name         string
 | 
			
		||||
	Value        string
 | 
			
		||||
	Transformers []transformer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var defaultTransformers = []transformer{
 | 
			
		||||
	{Name: "SNAKE", Transform: xstrings.ToSnakeCase},
 | 
			
		||||
	{Name: "KEBAB", Transform: xstrings.ToKebabCase},
 | 
			
		||||
	{Name: "CAMEL", Transform: func(str string) string {
 | 
			
		||||
		return xstrings.FirstRuneToLower(xstrings.ToCamelCase(str))
 | 
			
		||||
	}},
 | 
			
		||||
	{Name: "PASCAL", Transform: xstrings.ToCamelCase},
 | 
			
		||||
	{Name: "LOWER", Transform: strings.ToLower},
 | 
			
		||||
	{Name: "UPPER", Transform: strings.ToUpper},
 | 
			
		||||
	{Name: "TITLE", Transform: strings.Title},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func generateExpansion(src string, templateRepo, generateRepo *models.Repository) string {
 | 
			
		||||
	return os.Expand(src, func(key string) string {
 | 
			
		||||
		switch key {
 | 
			
		||||
		case "REPO_NAME":
 | 
			
		||||
			return generateRepo.Name
 | 
			
		||||
		case "TEMPLATE_NAME":
 | 
			
		||||
			return templateRepo.Name
 | 
			
		||||
		case "REPO_DESCRIPTION":
 | 
			
		||||
			return generateRepo.Description
 | 
			
		||||
		case "TEMPLATE_DESCRIPTION":
 | 
			
		||||
			return templateRepo.Description
 | 
			
		||||
		case "REPO_OWNER":
 | 
			
		||||
			return generateRepo.OwnerName
 | 
			
		||||
		case "TEMPLATE_OWNER":
 | 
			
		||||
			return templateRepo.OwnerName
 | 
			
		||||
		case "REPO_LINK":
 | 
			
		||||
			return generateRepo.Link()
 | 
			
		||||
		case "TEMPLATE_LINK":
 | 
			
		||||
			return templateRepo.Link()
 | 
			
		||||
		case "REPO_HTTPS_URL":
 | 
			
		||||
			return generateRepo.CloneLink().HTTPS
 | 
			
		||||
		case "TEMPLATE_HTTPS_URL":
 | 
			
		||||
			return templateRepo.CloneLink().HTTPS
 | 
			
		||||
		case "REPO_SSH_URL":
 | 
			
		||||
			return generateRepo.CloneLink().SSH
 | 
			
		||||
		case "TEMPLATE_SSH_URL":
 | 
			
		||||
			return templateRepo.CloneLink().SSH
 | 
			
		||||
		default:
 | 
			
		||||
			return key
 | 
			
		||||
	expansions := []expansion{
 | 
			
		||||
		{Name: "REPO_NAME", Value: generateRepo.Name, Transformers: defaultTransformers},
 | 
			
		||||
		{Name: "TEMPLATE_NAME", Value: templateRepo.Name, Transformers: defaultTransformers},
 | 
			
		||||
		{Name: "REPO_DESCRIPTION", Value: generateRepo.Description, Transformers: nil},
 | 
			
		||||
		{Name: "TEMPLATE_DESCRIPTION", Value: templateRepo.Description, Transformers: nil},
 | 
			
		||||
		{Name: "REPO_OWNER", Value: generateRepo.OwnerName, Transformers: defaultTransformers},
 | 
			
		||||
		{Name: "TEMPLATE_OWNER", Value: templateRepo.OwnerName, Transformers: defaultTransformers},
 | 
			
		||||
		{Name: "REPO_LINK", Value: generateRepo.Link(), Transformers: nil},
 | 
			
		||||
		{Name: "TEMPLATE_LINK", Value: templateRepo.Link(), Transformers: nil},
 | 
			
		||||
		{Name: "REPO_HTTPS_URL", Value: generateRepo.CloneLink().HTTPS, Transformers: nil},
 | 
			
		||||
		{Name: "TEMPLATE_HTTPS_URL", Value: templateRepo.CloneLink().HTTPS, Transformers: nil},
 | 
			
		||||
		{Name: "REPO_SSH_URL", Value: generateRepo.CloneLink().SSH, Transformers: nil},
 | 
			
		||||
		{Name: "TEMPLATE_SSH_URL", Value: templateRepo.CloneLink().SSH, Transformers: nil},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var expansionMap = make(map[string]string)
 | 
			
		||||
	for _, e := range expansions {
 | 
			
		||||
		expansionMap[e.Name] = e.Value
 | 
			
		||||
		for _, tr := range e.Transformers {
 | 
			
		||||
			expansionMap[fmt.Sprintf("%s_%s", e.Name, tr.Name)] = tr.Transform(e.Value)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return os.Expand(src, func(key string) string {
 | 
			
		||||
		if expansion, ok := expansionMap[key]; ok {
 | 
			
		||||
			return expansion
 | 
			
		||||
		}
 | 
			
		||||
		return key
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -104,6 +128,7 @@ func generateRepoCommit(repo, templateRepo, generateRepo *models.Repository, tmp
 | 
			
		||||
		return fmt.Errorf("checkGiteaTemplate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if gt != nil {
 | 
			
		||||
		if err := os.Remove(gt.Path); err != nil {
 | 
			
		||||
			return fmt.Errorf("remove .giteatemplate: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -141,6 +166,7 @@ func generateRepoCommit(repo, templateRepo, generateRepo *models.Repository, tmp
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := git.InitRepository(tmpDir, false); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								vendor/github.com/huandu/xstrings/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/huandu/xstrings/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
 | 
			
		||||
*.o
 | 
			
		||||
*.a
 | 
			
		||||
*.so
 | 
			
		||||
 | 
			
		||||
# Folders
 | 
			
		||||
_obj
 | 
			
		||||
_test
 | 
			
		||||
 | 
			
		||||
# Architecture specific extensions/prefixes
 | 
			
		||||
*.[568vq]
 | 
			
		||||
[568vq].out
 | 
			
		||||
 | 
			
		||||
*.cgo1.go
 | 
			
		||||
*.cgo2.c
 | 
			
		||||
_cgo_defun.c
 | 
			
		||||
_cgo_gotypes.go
 | 
			
		||||
_cgo_export.*
 | 
			
		||||
 | 
			
		||||
_testmain.go
 | 
			
		||||
 | 
			
		||||
*.exe
 | 
			
		||||
*.test
 | 
			
		||||
*.prof
 | 
			
		||||
							
								
								
									
										7
									
								
								vendor/github.com/huandu/xstrings/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/huandu/xstrings/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
language: go
 | 
			
		||||
install:
 | 
			
		||||
  - go get golang.org/x/tools/cmd/cover
 | 
			
		||||
  - go get github.com/mattn/goveralls
 | 
			
		||||
script:
 | 
			
		||||
  - go test -v -covermode=count -coverprofile=coverage.out
 | 
			
		||||
  - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ ! -z "$COVERALLS_TOKEN" ]; then $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN; fi'
 | 
			
		||||
							
								
								
									
										23
									
								
								vendor/github.com/huandu/xstrings/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/huandu/xstrings/CONTRIBUTING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
# Contributing #
 | 
			
		||||
 | 
			
		||||
Thanks for your contribution in advance. No matter what you will contribute to this project, pull request or bug report or feature discussion, it's always highly appreciated.
 | 
			
		||||
 | 
			
		||||
## New API or feature ##
 | 
			
		||||
 | 
			
		||||
I want to speak more about how to add new functions to this package.
 | 
			
		||||
 | 
			
		||||
Package `xstring` is a collection of useful string functions which should be implemented in Go. It's a bit subject to say which function should be included and which should not. I set up following rules in order to make it clear and as objective as possible.
 | 
			
		||||
 | 
			
		||||
* Rule 1: Only string algorithm, which takes string as input, can be included.
 | 
			
		||||
* Rule 2: If a function has been implemented in package `string`, it must not be included.
 | 
			
		||||
* Rule 3: If a function is not language neutral, it must not be included.
 | 
			
		||||
* Rule 4: If a function is a part of standard library in other languages, it can be included.
 | 
			
		||||
* Rule 5: If a function is quite useful in some famous framework or library, it can be included.
 | 
			
		||||
 | 
			
		||||
New function must be discussed in project issues before submitting any code. If a pull request with new functions is sent without any ref issue, it will be rejected.
 | 
			
		||||
 | 
			
		||||
## Pull request ##
 | 
			
		||||
 | 
			
		||||
Pull request is always welcome. Just make sure you have run `go fmt` and all test cases passed before submit.
 | 
			
		||||
 | 
			
		||||
If the pull request is to add a new API or feature, don't forget to update README.md and add new API in function list.
 | 
			
		||||
							
								
								
									
										22
									
								
								vendor/github.com/huandu/xstrings/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/huandu/xstrings/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2015 Huan Du
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										117
									
								
								vendor/github.com/huandu/xstrings/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								vendor/github.com/huandu/xstrings/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
# xstrings #
 | 
			
		||||
 | 
			
		||||
[](https://travis-ci.org/huandu/xstrings)
 | 
			
		||||
[](https://godoc.org/github.com/huandu/xstrings)
 | 
			
		||||
[](https://goreportcard.com/report/github.com/huandu/xstrings)
 | 
			
		||||
[](https://coveralls.io/github/huandu/xstrings?branch=master)
 | 
			
		||||
 | 
			
		||||
Go package [xstrings](https://godoc.org/github.com/huandu/xstrings) is a collection of string functions, which are widely used in other languages but absent in Go package [strings](http://golang.org/pkg/strings).
 | 
			
		||||
 | 
			
		||||
All functions are well tested and carefully tuned for performance.
 | 
			
		||||
 | 
			
		||||
## Propose a new function ##
 | 
			
		||||
 | 
			
		||||
Please review [contributing guideline](CONTRIBUTING.md) and [create new issue](https://github.com/huandu/xstrings/issues) to state why it should be included.
 | 
			
		||||
 | 
			
		||||
## Install ##
 | 
			
		||||
 | 
			
		||||
Use `go get` to install this library.
 | 
			
		||||
 | 
			
		||||
    go get github.com/huandu/xstrings
 | 
			
		||||
 | 
			
		||||
## API document ##
 | 
			
		||||
 | 
			
		||||
See [GoDoc](https://godoc.org/github.com/huandu/xstrings) for full document.
 | 
			
		||||
 | 
			
		||||
## Function list ##
 | 
			
		||||
 | 
			
		||||
Go functions have a unique naming style. One, who has experience in other language but new in Go, may have difficulties to find out right string function to use.
 | 
			
		||||
 | 
			
		||||
Here is a list of functions in [strings](http://golang.org/pkg/strings) and [xstrings](https://godoc.org/github.com/huandu/xstrings) with enough extra information about how to map these functions to their friends in other languages. Hope this list could be helpful for fresh gophers.
 | 
			
		||||
 | 
			
		||||
### Package `xstrings` functions ###
 | 
			
		||||
 | 
			
		||||
*Keep this table sorted by Function in ascending order.*
 | 
			
		||||
 | 
			
		||||
| Function | Friends | # |
 | 
			
		||||
| -------- | ------- | --- |
 | 
			
		||||
| [Center](https://godoc.org/github.com/huandu/xstrings#Center) | `str.center` in Python; `String#center` in Ruby | [#30](https://github.com/huandu/xstrings/issues/30) |
 | 
			
		||||
| [Count](https://godoc.org/github.com/huandu/xstrings#Count) | `String#count` in Ruby | [#16](https://github.com/huandu/xstrings/issues/16) |
 | 
			
		||||
| [Delete](https://godoc.org/github.com/huandu/xstrings#Delete) | `String#delete` in Ruby | [#17](https://github.com/huandu/xstrings/issues/17) |
 | 
			
		||||
| [ExpandTabs](https://godoc.org/github.com/huandu/xstrings#ExpandTabs) | `str.expandtabs` in Python | [#27](https://github.com/huandu/xstrings/issues/27) |
 | 
			
		||||
| [FirstRuneToLower](https://godoc.org/github.com/huandu/xstrings#FirstRuneToLower) | `lcfirst` in PHP or Perl | [#15](https://github.com/huandu/xstrings/issues/15) |
 | 
			
		||||
| [FirstRuneToUpper](https://godoc.org/github.com/huandu/xstrings#FirstRuneToUpper) | `String#capitalize` in Ruby; `ucfirst` in PHP or Perl | [#15](https://github.com/huandu/xstrings/issues/15) |
 | 
			
		||||
| [Insert](https://godoc.org/github.com/huandu/xstrings#Insert) | `String#insert` in Ruby | [#18](https://github.com/huandu/xstrings/issues/18) |
 | 
			
		||||
| [LastPartition](https://godoc.org/github.com/huandu/xstrings#LastPartition) | `str.rpartition` in Python; `String#rpartition` in Ruby | [#19](https://github.com/huandu/xstrings/issues/19) |
 | 
			
		||||
| [LeftJustify](https://godoc.org/github.com/huandu/xstrings#LeftJustify) | `str.ljust` in Python; `String#ljust` in Ruby | [#28](https://github.com/huandu/xstrings/issues/28) |
 | 
			
		||||
| [Len](https://godoc.org/github.com/huandu/xstrings#Len) | `mb_strlen` in PHP | [#23](https://github.com/huandu/xstrings/issues/23) |
 | 
			
		||||
| [Partition](https://godoc.org/github.com/huandu/xstrings#Partition) | `str.partition` in Python; `String#partition` in Ruby | [#10](https://github.com/huandu/xstrings/issues/10) |
 | 
			
		||||
| [Reverse](https://godoc.org/github.com/huandu/xstrings#Reverse) | `String#reverse` in Ruby; `strrev` in PHP; `reverse` in Perl | [#7](https://github.com/huandu/xstrings/issues/7) |
 | 
			
		||||
| [RightJustify](https://godoc.org/github.com/huandu/xstrings#RightJustify) | `str.rjust` in Python; `String#rjust` in Ruby | [#29](https://github.com/huandu/xstrings/issues/29) |
 | 
			
		||||
| [RuneWidth](https://godoc.org/github.com/huandu/xstrings#RuneWidth) | - | [#27](https://github.com/huandu/xstrings/issues/27) |
 | 
			
		||||
| [Scrub](https://godoc.org/github.com/huandu/xstrings#Scrub) | `String#scrub` in Ruby | [#20](https://github.com/huandu/xstrings/issues/20) |
 | 
			
		||||
| [Shuffle](https://godoc.org/github.com/huandu/xstrings#Shuffle) | `str_shuffle` in PHP | [#13](https://github.com/huandu/xstrings/issues/13) |
 | 
			
		||||
| [ShuffleSource](https://godoc.org/github.com/huandu/xstrings#ShuffleSource) | `str_shuffle` in PHP | [#13](https://github.com/huandu/xstrings/issues/13) |
 | 
			
		||||
| [Slice](https://godoc.org/github.com/huandu/xstrings#Slice) | `mb_substr` in PHP | [#9](https://github.com/huandu/xstrings/issues/9) |
 | 
			
		||||
| [Squeeze](https://godoc.org/github.com/huandu/xstrings#Squeeze) | `String#squeeze` in Ruby | [#11](https://github.com/huandu/xstrings/issues/11) |
 | 
			
		||||
| [Successor](https://godoc.org/github.com/huandu/xstrings#Successor) | `String#succ` or `String#next` in Ruby | [#22](https://github.com/huandu/xstrings/issues/22) |
 | 
			
		||||
| [SwapCase](https://godoc.org/github.com/huandu/xstrings#SwapCase) | `str.swapcase` in Python; `String#swapcase` in Ruby | [#12](https://github.com/huandu/xstrings/issues/12) |
 | 
			
		||||
| [ToCamelCase](https://godoc.org/github.com/huandu/xstrings#ToCamelCase) | `String#camelize` in RoR | [#1](https://github.com/huandu/xstrings/issues/1) |
 | 
			
		||||
| [ToKebab](https://godoc.org/github.com/huandu/xstrings#ToKebabCase) | - | [#41](https://github.com/huandu/xstrings/issues/41) |
 | 
			
		||||
| [ToSnakeCase](https://godoc.org/github.com/huandu/xstrings#ToSnakeCase) | `String#underscore` in RoR | [#1](https://github.com/huandu/xstrings/issues/1) |
 | 
			
		||||
| [Translate](https://godoc.org/github.com/huandu/xstrings#Translate) | `str.translate` in Python; `String#tr` in Ruby; `strtr` in PHP; `tr///` in Perl | [#21](https://github.com/huandu/xstrings/issues/21) |
 | 
			
		||||
| [Width](https://godoc.org/github.com/huandu/xstrings#Width) | `mb_strwidth` in PHP | [#26](https://github.com/huandu/xstrings/issues/26) |
 | 
			
		||||
| [WordCount](https://godoc.org/github.com/huandu/xstrings#WordCount) | `str_word_count` in PHP | [#14](https://github.com/huandu/xstrings/issues/14) |
 | 
			
		||||
| [WordSplit](https://godoc.org/github.com/huandu/xstrings#WordSplit) | - | [#14](https://github.com/huandu/xstrings/issues/14) |
 | 
			
		||||
 | 
			
		||||
### Package `strings` functions ###
 | 
			
		||||
 | 
			
		||||
*Keep this table sorted by Function in ascending order.*
 | 
			
		||||
 | 
			
		||||
| Function | Friends |
 | 
			
		||||
| -------- | ------- |
 | 
			
		||||
| [Contains](http://golang.org/pkg/strings/#Contains) | `String#include?` in Ruby |
 | 
			
		||||
| [ContainsAny](http://golang.org/pkg/strings/#ContainsAny) | - |
 | 
			
		||||
| [ContainsRune](http://golang.org/pkg/strings/#ContainsRune) | - |
 | 
			
		||||
| [Count](http://golang.org/pkg/strings/#Count) | `str.count` in Python; `substr_count` in PHP |
 | 
			
		||||
| [EqualFold](http://golang.org/pkg/strings/#EqualFold) | `stricmp` in PHP; `String#casecmp` in Ruby |
 | 
			
		||||
| [Fields](http://golang.org/pkg/strings/#Fields) | `str.split` in Python; `split` in Perl; `String#split` in Ruby |
 | 
			
		||||
| [FieldsFunc](http://golang.org/pkg/strings/#FieldsFunc) | - |
 | 
			
		||||
| [HasPrefix](http://golang.org/pkg/strings/#HasPrefix) | `str.startswith` in Python; `String#start_with?` in Ruby |
 | 
			
		||||
| [HasSuffix](http://golang.org/pkg/strings/#HasSuffix) | `str.endswith` in Python; `String#end_with?` in Ruby |
 | 
			
		||||
| [Index](http://golang.org/pkg/strings/#Index) | `str.index` in Python; `String#index` in Ruby; `strpos` in PHP; `index` in Perl |
 | 
			
		||||
| [IndexAny](http://golang.org/pkg/strings/#IndexAny) | - |
 | 
			
		||||
| [IndexByte](http://golang.org/pkg/strings/#IndexByte) | - |
 | 
			
		||||
| [IndexFunc](http://golang.org/pkg/strings/#IndexFunc) | - |
 | 
			
		||||
| [IndexRune](http://golang.org/pkg/strings/#IndexRune) | - |
 | 
			
		||||
| [Join](http://golang.org/pkg/strings/#Join) | `str.join` in Python; `Array#join` in Ruby; `implode` in PHP; `join` in Perl |
 | 
			
		||||
| [LastIndex](http://golang.org/pkg/strings/#LastIndex) | `str.rindex` in Python; `String#rindex`; `strrpos` in PHP; `rindex` in Perl |
 | 
			
		||||
| [LastIndexAny](http://golang.org/pkg/strings/#LastIndexAny) | - |
 | 
			
		||||
| [LastIndexFunc](http://golang.org/pkg/strings/#LastIndexFunc) | - |
 | 
			
		||||
| [Map](http://golang.org/pkg/strings/#Map) | `String#each_codepoint` in Ruby |
 | 
			
		||||
| [Repeat](http://golang.org/pkg/strings/#Repeat) | operator `*` in Python and Ruby; `str_repeat` in PHP |
 | 
			
		||||
| [Replace](http://golang.org/pkg/strings/#Replace) | `str.replace` in Python; `String#sub` in Ruby; `str_replace` in PHP |
 | 
			
		||||
| [Split](http://golang.org/pkg/strings/#Split) | `str.split` in Python; `String#split` in Ruby; `explode` in PHP; `split` in Perl |
 | 
			
		||||
| [SplitAfter](http://golang.org/pkg/strings/#SplitAfter) | - |
 | 
			
		||||
| [SplitAfterN](http://golang.org/pkg/strings/#SplitAfterN) | - |
 | 
			
		||||
| [SplitN](http://golang.org/pkg/strings/#SplitN) | `str.split` in Python; `String#split` in Ruby; `explode` in PHP; `split` in Perl |
 | 
			
		||||
| [Title](http://golang.org/pkg/strings/#Title) | `str.title` in Python |
 | 
			
		||||
| [ToLower](http://golang.org/pkg/strings/#ToLower) | `str.lower` in Python; `String#downcase` in Ruby; `strtolower` in PHP; `lc` in Perl |
 | 
			
		||||
| [ToLowerSpecial](http://golang.org/pkg/strings/#ToLowerSpecial) | - |
 | 
			
		||||
| [ToTitle](http://golang.org/pkg/strings/#ToTitle) | - |
 | 
			
		||||
| [ToTitleSpecial](http://golang.org/pkg/strings/#ToTitleSpecial) | - |
 | 
			
		||||
| [ToUpper](http://golang.org/pkg/strings/#ToUpper) | `str.upper` in Python; `String#upcase` in Ruby; `strtoupper` in PHP; `uc` in Perl |
 | 
			
		||||
| [ToUpperSpecial](http://golang.org/pkg/strings/#ToUpperSpecial) | - |
 | 
			
		||||
| [Trim](http://golang.org/pkg/strings/#Trim) | `str.strip` in Python; `String#strip` in Ruby; `trim` in PHP |
 | 
			
		||||
| [TrimFunc](http://golang.org/pkg/strings/#TrimFunc) | - |
 | 
			
		||||
| [TrimLeft](http://golang.org/pkg/strings/#TrimLeft) | `str.lstrip` in Python; `String#lstrip` in Ruby; `ltrim` in PHP |
 | 
			
		||||
| [TrimLeftFunc](http://golang.org/pkg/strings/#TrimLeftFunc) | - |
 | 
			
		||||
| [TrimPrefix](http://golang.org/pkg/strings/#TrimPrefix) | - |
 | 
			
		||||
| [TrimRight](http://golang.org/pkg/strings/#TrimRight) | `str.rstrip` in Python; `String#rstrip` in Ruby; `rtrim` in PHP |
 | 
			
		||||
| [TrimRightFunc](http://golang.org/pkg/strings/#TrimRightFunc) | - |
 | 
			
		||||
| [TrimSpace](http://golang.org/pkg/strings/#TrimSpace) | `str.strip` in Python; `String#strip` in Ruby; `trim` in PHP |
 | 
			
		||||
| [TrimSuffix](http://golang.org/pkg/strings/#TrimSuffix) | `String#chomp` in Ruby; `chomp` in Perl |
 | 
			
		||||
 | 
			
		||||
## License ##
 | 
			
		||||
 | 
			
		||||
This library is licensed under MIT license. See LICENSE for details.
 | 
			
		||||
							
								
								
									
										25
									
								
								vendor/github.com/huandu/xstrings/common.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/huandu/xstrings/common.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package xstrings
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const bufferMaxInitGrowSize = 2048
 | 
			
		||||
 | 
			
		||||
// Lazy initialize a buffer.
 | 
			
		||||
func allocBuffer(orig, cur string) *bytes.Buffer {
 | 
			
		||||
	output := &bytes.Buffer{}
 | 
			
		||||
	maxSize := len(orig) * 4
 | 
			
		||||
 | 
			
		||||
	// Avoid to reserve too much memory at once.
 | 
			
		||||
	if maxSize > bufferMaxInitGrowSize {
 | 
			
		||||
		maxSize = bufferMaxInitGrowSize
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	output.Grow(maxSize)
 | 
			
		||||
	output.WriteString(orig[:len(orig)-len(cur)])
 | 
			
		||||
	return output
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										404
									
								
								vendor/github.com/huandu/xstrings/convert.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								vendor/github.com/huandu/xstrings/convert.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,404 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package xstrings
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"unicode"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ToCamelCase is to convert words separated by space, underscore and hyphen to camel case.
 | 
			
		||||
//
 | 
			
		||||
// Some samples.
 | 
			
		||||
//     "some_words"      => "SomeWords"
 | 
			
		||||
//     "http_server"     => "HttpServer"
 | 
			
		||||
//     "no_https"        => "NoHttps"
 | 
			
		||||
//     "_complex__case_" => "_Complex_Case_"
 | 
			
		||||
//     "some words"      => "SomeWords"
 | 
			
		||||
func ToCamelCase(str string) string {
 | 
			
		||||
	if len(str) == 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	var r0, r1 rune
 | 
			
		||||
	var size int
 | 
			
		||||
 | 
			
		||||
	// leading connector will appear in output.
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r0, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
 | 
			
		||||
		if !isConnector(r0) {
 | 
			
		||||
			r0 = unicode.ToUpper(r0)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		buf.WriteRune(r0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(str) == 0 {
 | 
			
		||||
		// A special case for a string contains only 1 rune.
 | 
			
		||||
		if size != 0 {
 | 
			
		||||
			buf.WriteRune(r0)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return buf.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r1 = r0
 | 
			
		||||
		r0, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
 | 
			
		||||
		if isConnector(r0) && isConnector(r1) {
 | 
			
		||||
			buf.WriteRune(r1)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if isConnector(r1) {
 | 
			
		||||
			r0 = unicode.ToUpper(r0)
 | 
			
		||||
		} else {
 | 
			
		||||
			r0 = unicode.ToLower(r0)
 | 
			
		||||
			buf.WriteRune(r1)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf.WriteRune(r0)
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToSnakeCase can convert all upper case characters in a string to
 | 
			
		||||
// snake case format.
 | 
			
		||||
//
 | 
			
		||||
// Some samples.
 | 
			
		||||
//     "FirstName"  => "first_name"
 | 
			
		||||
//     "HTTPServer" => "http_server"
 | 
			
		||||
//     "NoHTTPS"    => "no_https"
 | 
			
		||||
//     "GO_PATH"    => "go_path"
 | 
			
		||||
//     "GO PATH"    => "go_path"      // space is converted to underscore.
 | 
			
		||||
//     "GO-PATH"    => "go_path"      // hyphen is converted to underscore.
 | 
			
		||||
//     "HTTP2XX"    => "http_2xx"     // insert an underscore before a number and after an alphabet.
 | 
			
		||||
//     "http2xx"    => "http_2xx"
 | 
			
		||||
//     "HTTP20xOK"  => "http_20x_ok"
 | 
			
		||||
func ToSnakeCase(str string) string {
 | 
			
		||||
	return camelCaseToLowerCase(str, '_')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToKebabCase can convert all upper case characters in a string to
 | 
			
		||||
// kebab case format.
 | 
			
		||||
//
 | 
			
		||||
// Some samples.
 | 
			
		||||
//     "FirstName"  => "first-name"
 | 
			
		||||
//     "HTTPServer" => "http-server"
 | 
			
		||||
//     "NoHTTPS"    => "no-https"
 | 
			
		||||
//     "GO_PATH"    => "go-path"
 | 
			
		||||
//     "GO PATH"    => "go-path"      // space is converted to '-'.
 | 
			
		||||
//     "GO-PATH"    => "go-path"      // hyphen is converted to '-'.
 | 
			
		||||
//     "HTTP2XX"    => "http-2xx"     // insert a '-' before a number and after an alphabet.
 | 
			
		||||
//     "http2xx"    => "http-2xx"
 | 
			
		||||
//     "HTTP20xOK"  => "http-20x-ok"
 | 
			
		||||
func ToKebabCase(str string) string {
 | 
			
		||||
	return camelCaseToLowerCase(str, '-')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func camelCaseToLowerCase(str string, connector rune) string {
 | 
			
		||||
	if len(str) == 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	var prev, r0, r1 rune
 | 
			
		||||
	var size int
 | 
			
		||||
 | 
			
		||||
	r0 = connector
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		prev = r0
 | 
			
		||||
		r0, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case r0 == utf8.RuneError:
 | 
			
		||||
			buf.WriteRune(r0)
 | 
			
		||||
 | 
			
		||||
		case unicode.IsUpper(r0):
 | 
			
		||||
			if prev != connector && !unicode.IsNumber(prev) {
 | 
			
		||||
				buf.WriteRune(connector)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			buf.WriteRune(unicode.ToLower(r0))
 | 
			
		||||
 | 
			
		||||
			if len(str) == 0 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			r0, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
			str = str[size:]
 | 
			
		||||
 | 
			
		||||
			if !unicode.IsUpper(r0) {
 | 
			
		||||
				buf.WriteRune(r0)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// find next non-upper-case character and insert connector properly.
 | 
			
		||||
			// it's designed to convert `HTTPServer` to `http_server`.
 | 
			
		||||
			// if there are more than 2 adjacent upper case characters in a word,
 | 
			
		||||
			// treat them as an abbreviation plus a normal word.
 | 
			
		||||
			for len(str) > 0 {
 | 
			
		||||
				r1 = r0
 | 
			
		||||
				r0, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
				str = str[size:]
 | 
			
		||||
 | 
			
		||||
				if r0 == utf8.RuneError {
 | 
			
		||||
					buf.WriteRune(unicode.ToLower(r1))
 | 
			
		||||
					buf.WriteRune(r0)
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !unicode.IsUpper(r0) {
 | 
			
		||||
					if isConnector(r0) {
 | 
			
		||||
						r0 = connector
 | 
			
		||||
 | 
			
		||||
						buf.WriteRune(unicode.ToLower(r1))
 | 
			
		||||
					} else if unicode.IsNumber(r0) {
 | 
			
		||||
						// treat a number as an upper case rune
 | 
			
		||||
						// so that both `http2xx` and `HTTP2XX` can be converted to `http_2xx`.
 | 
			
		||||
						buf.WriteRune(unicode.ToLower(r1))
 | 
			
		||||
						buf.WriteRune(connector)
 | 
			
		||||
						buf.WriteRune(r0)
 | 
			
		||||
					} else {
 | 
			
		||||
						buf.WriteRune(connector)
 | 
			
		||||
						buf.WriteRune(unicode.ToLower(r1))
 | 
			
		||||
						buf.WriteRune(r0)
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				buf.WriteRune(unicode.ToLower(r1))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(str) == 0 || r0 == connector {
 | 
			
		||||
				buf.WriteRune(unicode.ToLower(r0))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case unicode.IsNumber(r0):
 | 
			
		||||
			if prev != connector && !unicode.IsNumber(prev) {
 | 
			
		||||
				buf.WriteRune(connector)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			buf.WriteRune(r0)
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			if isConnector(r0) {
 | 
			
		||||
				r0 = connector
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			buf.WriteRune(r0)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isConnector(r rune) bool {
 | 
			
		||||
	return r == '-' || r == '_' || unicode.IsSpace(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SwapCase will swap characters case from upper to lower or lower to upper.
 | 
			
		||||
func SwapCase(str string) string {
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size int
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case unicode.IsUpper(r):
 | 
			
		||||
			buf.WriteRune(unicode.ToLower(r))
 | 
			
		||||
 | 
			
		||||
		case unicode.IsLower(r):
 | 
			
		||||
			buf.WriteRune(unicode.ToUpper(r))
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			buf.WriteRune(r)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FirstRuneToUpper converts first rune to upper case if necessary.
 | 
			
		||||
func FirstRuneToUpper(str string) string {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, size := utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
	if !unicode.IsLower(r) {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	buf.WriteRune(unicode.ToUpper(r))
 | 
			
		||||
	buf.WriteString(str[size:])
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FirstRuneToLower converts first rune to lower case if necessary.
 | 
			
		||||
func FirstRuneToLower(str string) string {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, size := utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
	if !unicode.IsUpper(r) {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	buf.WriteRune(unicode.ToLower(r))
 | 
			
		||||
	buf.WriteString(str[size:])
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Shuffle randomizes runes in a string and returns the result.
 | 
			
		||||
// It uses default random source in `math/rand`.
 | 
			
		||||
func Shuffle(str string) string {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	runes := []rune(str)
 | 
			
		||||
	index := 0
 | 
			
		||||
 | 
			
		||||
	for i := len(runes) - 1; i > 0; i-- {
 | 
			
		||||
		index = rand.Intn(i + 1)
 | 
			
		||||
 | 
			
		||||
		if i != index {
 | 
			
		||||
			runes[i], runes[index] = runes[index], runes[i]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(runes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ShuffleSource randomizes runes in a string with given random source.
 | 
			
		||||
func ShuffleSource(str string, src rand.Source) string {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	runes := []rune(str)
 | 
			
		||||
	index := 0
 | 
			
		||||
	r := rand.New(src)
 | 
			
		||||
 | 
			
		||||
	for i := len(runes) - 1; i > 0; i-- {
 | 
			
		||||
		index = r.Intn(i + 1)
 | 
			
		||||
 | 
			
		||||
		if i != index {
 | 
			
		||||
			runes[i], runes[index] = runes[index], runes[i]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(runes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Successor returns the successor to string.
 | 
			
		||||
//
 | 
			
		||||
// If there is one alphanumeric rune is found in string, increase the rune by 1.
 | 
			
		||||
// If increment generates a "carry", the rune to the left of it is incremented.
 | 
			
		||||
// This process repeats until there is no carry, adding an additional rune if necessary.
 | 
			
		||||
//
 | 
			
		||||
// If there is no alphanumeric rune, the rightmost rune will be increased by 1
 | 
			
		||||
// regardless whether the result is a valid rune or not.
 | 
			
		||||
//
 | 
			
		||||
// Only following characters are alphanumeric.
 | 
			
		||||
//     * a - z
 | 
			
		||||
//     * A - Z
 | 
			
		||||
//     * 0 - 9
 | 
			
		||||
//
 | 
			
		||||
// Samples (borrowed from ruby's String#succ document):
 | 
			
		||||
//     "abcd"      => "abce"
 | 
			
		||||
//     "THX1138"   => "THX1139"
 | 
			
		||||
//     "<<koala>>" => "<<koalb>>"
 | 
			
		||||
//     "1999zzz"   => "2000aaa"
 | 
			
		||||
//     "ZZZ9999"   => "AAAA0000"
 | 
			
		||||
//     "***"       => "**+"
 | 
			
		||||
func Successor(str string) string {
 | 
			
		||||
	if str == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var r rune
 | 
			
		||||
	var i int
 | 
			
		||||
	carry := ' '
 | 
			
		||||
	runes := []rune(str)
 | 
			
		||||
	l := len(runes)
 | 
			
		||||
	lastAlphanumeric := l
 | 
			
		||||
 | 
			
		||||
	for i = l - 1; i >= 0; i-- {
 | 
			
		||||
		r = runes[i]
 | 
			
		||||
 | 
			
		||||
		if ('a' <= r && r <= 'y') ||
 | 
			
		||||
			('A' <= r && r <= 'Y') ||
 | 
			
		||||
			('0' <= r && r <= '8') {
 | 
			
		||||
			runes[i]++
 | 
			
		||||
			carry = ' '
 | 
			
		||||
			lastAlphanumeric = i
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch r {
 | 
			
		||||
		case 'z':
 | 
			
		||||
			runes[i] = 'a'
 | 
			
		||||
			carry = 'a'
 | 
			
		||||
			lastAlphanumeric = i
 | 
			
		||||
 | 
			
		||||
		case 'Z':
 | 
			
		||||
			runes[i] = 'A'
 | 
			
		||||
			carry = 'A'
 | 
			
		||||
			lastAlphanumeric = i
 | 
			
		||||
 | 
			
		||||
		case '9':
 | 
			
		||||
			runes[i] = '0'
 | 
			
		||||
			carry = '0'
 | 
			
		||||
			lastAlphanumeric = i
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Needs to add one character for carry.
 | 
			
		||||
	if i < 0 && carry != ' ' {
 | 
			
		||||
		buf := &bytes.Buffer{}
 | 
			
		||||
		buf.Grow(l + 4) // Reserve enough space for write.
 | 
			
		||||
 | 
			
		||||
		if lastAlphanumeric != 0 {
 | 
			
		||||
			buf.WriteString(str[:lastAlphanumeric])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		buf.WriteRune(carry)
 | 
			
		||||
 | 
			
		||||
		for _, r = range runes[lastAlphanumeric:] {
 | 
			
		||||
			buf.WriteRune(r)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return buf.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No alphanumeric character. Simply increase last rune's value.
 | 
			
		||||
	if lastAlphanumeric == l {
 | 
			
		||||
		runes[l-1]++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(runes)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								vendor/github.com/huandu/xstrings/count.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								vendor/github.com/huandu/xstrings/count.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package xstrings
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"unicode"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Len returns str's utf8 rune length.
 | 
			
		||||
func Len(str string) int {
 | 
			
		||||
	return utf8.RuneCountInString(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WordCount returns number of words in a string.
 | 
			
		||||
//
 | 
			
		||||
// Word is defined as a locale dependent string containing alphabetic characters,
 | 
			
		||||
// which may also contain but not start with `'` and `-` characters.
 | 
			
		||||
func WordCount(str string) int {
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size, n int
 | 
			
		||||
 | 
			
		||||
	inWord := false
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case isAlphabet(r):
 | 
			
		||||
			if !inWord {
 | 
			
		||||
				inWord = true
 | 
			
		||||
				n++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case inWord && (r == '\'' || r == '-'):
 | 
			
		||||
			// Still in word.
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			inWord = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const minCJKCharacter = '\u3400'
 | 
			
		||||
 | 
			
		||||
// Checks r is a letter but not CJK character.
 | 
			
		||||
func isAlphabet(r rune) bool {
 | 
			
		||||
	if !unicode.IsLetter(r) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	// Quick check for non-CJK character.
 | 
			
		||||
	case r < minCJKCharacter:
 | 
			
		||||
		return true
 | 
			
		||||
 | 
			
		||||
	// Common CJK characters.
 | 
			
		||||
	case r >= '\u4E00' && r <= '\u9FCC':
 | 
			
		||||
		return false
 | 
			
		||||
 | 
			
		||||
	// Rare CJK characters.
 | 
			
		||||
	case r >= '\u3400' && r <= '\u4D85':
 | 
			
		||||
		return false
 | 
			
		||||
 | 
			
		||||
	// Rare and historic CJK characters.
 | 
			
		||||
	case r >= '\U00020000' && r <= '\U0002B81D':
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Width returns string width in monotype font.
 | 
			
		||||
// Multi-byte characters are usually twice the width of single byte characters.
 | 
			
		||||
//
 | 
			
		||||
// Algorithm comes from `mb_strwidth` in PHP.
 | 
			
		||||
// http://php.net/manual/en/function.mb-strwidth.php
 | 
			
		||||
func Width(str string) int {
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size, n int
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		n += RuneWidth(r)
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RuneWidth returns character width in monotype font.
 | 
			
		||||
// Multi-byte characters are usually twice the width of single byte characters.
 | 
			
		||||
//
 | 
			
		||||
// Algorithm comes from `mb_strwidth` in PHP.
 | 
			
		||||
// http://php.net/manual/en/function.mb-strwidth.php
 | 
			
		||||
func RuneWidth(r rune) int {
 | 
			
		||||
	switch {
 | 
			
		||||
	case r == utf8.RuneError || r < '\x20':
 | 
			
		||||
		return 0
 | 
			
		||||
 | 
			
		||||
	case '\x20' <= r && r < '\u2000':
 | 
			
		||||
		return 1
 | 
			
		||||
 | 
			
		||||
	case '\u2000' <= r && r < '\uFF61':
 | 
			
		||||
		return 2
 | 
			
		||||
 | 
			
		||||
	case '\uFF61' <= r && r < '\uFFA0':
 | 
			
		||||
		return 1
 | 
			
		||||
 | 
			
		||||
	case '\uFFA0' <= r:
 | 
			
		||||
		return 2
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								vendor/github.com/huandu/xstrings/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/huandu/xstrings/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
// Package xstrings is to provide string algorithms which are useful but not included in `strings` package.
 | 
			
		||||
// See project home page for details. https://github.com/huandu/xstrings
 | 
			
		||||
//
 | 
			
		||||
// Package xstrings assumes all strings are encoded in utf8.
 | 
			
		||||
package xstrings
 | 
			
		||||
							
								
								
									
										170
									
								
								vendor/github.com/huandu/xstrings/format.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								vendor/github.com/huandu/xstrings/format.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package xstrings
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ExpandTabs can expand tabs ('\t') rune in str to one or more spaces dpending on
 | 
			
		||||
// current column and tabSize.
 | 
			
		||||
// The column number is reset to zero after each newline ('\n') occurring in the str.
 | 
			
		||||
//
 | 
			
		||||
// ExpandTabs uses RuneWidth to decide rune's width.
 | 
			
		||||
// For example, CJK characters will be treated as two characters.
 | 
			
		||||
//
 | 
			
		||||
// If tabSize <= 0, ExpandTabs panics with error.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     ExpandTabs("a\tbc\tdef\tghij\tk", 4) => "a   bc  def ghij    k"
 | 
			
		||||
//     ExpandTabs("abcdefg\thij\nk\tl", 4)  => "abcdefg hij\nk   l"
 | 
			
		||||
//     ExpandTabs("z中\t文\tw", 4)           => "z中 文  w"
 | 
			
		||||
func ExpandTabs(str string, tabSize int) string {
 | 
			
		||||
	if tabSize <= 0 {
 | 
			
		||||
		panic("tab size must be positive")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var r rune
 | 
			
		||||
	var i, size, column, expand int
 | 
			
		||||
	var output *bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	orig := str
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
		if r == '\t' {
 | 
			
		||||
			expand = tabSize - column%tabSize
 | 
			
		||||
 | 
			
		||||
			if output == nil {
 | 
			
		||||
				output = allocBuffer(orig, str)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for i = 0; i < expand; i++ {
 | 
			
		||||
				output.WriteByte(byte(' '))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			column += expand
 | 
			
		||||
		} else {
 | 
			
		||||
			if r == '\n' {
 | 
			
		||||
				column = 0
 | 
			
		||||
			} else {
 | 
			
		||||
				column += RuneWidth(r)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if output != nil {
 | 
			
		||||
				output.WriteRune(r)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if output == nil {
 | 
			
		||||
		return orig
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return output.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LeftJustify returns a string with pad string at right side if str's rune length is smaller than length.
 | 
			
		||||
// If str's rune length is larger than length, str itself will be returned.
 | 
			
		||||
//
 | 
			
		||||
// If pad is an empty string, str will be returned.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     LeftJustify("hello", 4, " ")    => "hello"
 | 
			
		||||
//     LeftJustify("hello", 10, " ")   => "hello     "
 | 
			
		||||
//     LeftJustify("hello", 10, "123") => "hello12312"
 | 
			
		||||
func LeftJustify(str string, length int, pad string) string {
 | 
			
		||||
	l := Len(str)
 | 
			
		||||
 | 
			
		||||
	if l >= length || pad == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	remains := length - l
 | 
			
		||||
	padLen := Len(pad)
 | 
			
		||||
 | 
			
		||||
	output := &bytes.Buffer{}
 | 
			
		||||
	output.Grow(len(str) + (remains/padLen+1)*len(pad))
 | 
			
		||||
	output.WriteString(str)
 | 
			
		||||
	writePadString(output, pad, padLen, remains)
 | 
			
		||||
	return output.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RightJustify returns a string with pad string at left side if str's rune length is smaller than length.
 | 
			
		||||
// If str's rune length is larger than length, str itself will be returned.
 | 
			
		||||
//
 | 
			
		||||
// If pad is an empty string, str will be returned.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     RightJustify("hello", 4, " ")    => "hello"
 | 
			
		||||
//     RightJustify("hello", 10, " ")   => "     hello"
 | 
			
		||||
//     RightJustify("hello", 10, "123") => "12312hello"
 | 
			
		||||
func RightJustify(str string, length int, pad string) string {
 | 
			
		||||
	l := Len(str)
 | 
			
		||||
 | 
			
		||||
	if l >= length || pad == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	remains := length - l
 | 
			
		||||
	padLen := Len(pad)
 | 
			
		||||
 | 
			
		||||
	output := &bytes.Buffer{}
 | 
			
		||||
	output.Grow(len(str) + (remains/padLen+1)*len(pad))
 | 
			
		||||
	writePadString(output, pad, padLen, remains)
 | 
			
		||||
	output.WriteString(str)
 | 
			
		||||
	return output.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Center returns a string with pad string at both side if str's rune length is smaller than length.
 | 
			
		||||
// If str's rune length is larger than length, str itself will be returned.
 | 
			
		||||
//
 | 
			
		||||
// If pad is an empty string, str will be returned.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     Center("hello", 4, " ")    => "hello"
 | 
			
		||||
//     Center("hello", 10, " ")   => "  hello   "
 | 
			
		||||
//     Center("hello", 10, "123") => "12hello123"
 | 
			
		||||
func Center(str string, length int, pad string) string {
 | 
			
		||||
	l := Len(str)
 | 
			
		||||
 | 
			
		||||
	if l >= length || pad == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	remains := length - l
 | 
			
		||||
	padLen := Len(pad)
 | 
			
		||||
 | 
			
		||||
	output := &bytes.Buffer{}
 | 
			
		||||
	output.Grow(len(str) + (remains/padLen+1)*len(pad))
 | 
			
		||||
	writePadString(output, pad, padLen, remains/2)
 | 
			
		||||
	output.WriteString(str)
 | 
			
		||||
	writePadString(output, pad, padLen, (remains+1)/2)
 | 
			
		||||
	return output.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writePadString(output *bytes.Buffer, pad string, padLen, remains int) {
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size int
 | 
			
		||||
 | 
			
		||||
	repeats := remains / padLen
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < repeats; i++ {
 | 
			
		||||
		output.WriteString(pad)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	remains = remains % padLen
 | 
			
		||||
 | 
			
		||||
	if remains != 0 {
 | 
			
		||||
		for i := 0; i < remains; i++ {
 | 
			
		||||
			r, size = utf8.DecodeRuneInString(pad)
 | 
			
		||||
			output.WriteRune(r)
 | 
			
		||||
			pad = pad[size:]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								vendor/github.com/huandu/xstrings/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/huandu/xstrings/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
module github.com/huandu/xstrings
 | 
			
		||||
 | 
			
		||||
go 1.12
 | 
			
		||||
							
								
								
									
										217
									
								
								vendor/github.com/huandu/xstrings/manipulate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								vendor/github.com/huandu/xstrings/manipulate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,217 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package xstrings
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Reverse a utf8 encoded string.
 | 
			
		||||
func Reverse(str string) string {
 | 
			
		||||
	var size int
 | 
			
		||||
 | 
			
		||||
	tail := len(str)
 | 
			
		||||
	buf := make([]byte, tail)
 | 
			
		||||
	s := buf
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		_, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		tail -= size
 | 
			
		||||
		s = append(s[:tail], []byte(str[:size])...)
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return string(buf)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Slice a string by rune.
 | 
			
		||||
//
 | 
			
		||||
// Start must satisfy 0 <= start <= rune length.
 | 
			
		||||
//
 | 
			
		||||
// End can be positive, zero or negative.
 | 
			
		||||
// If end >= 0, start and end must satisfy start <= end <= rune length.
 | 
			
		||||
// If end < 0, it means slice to the end of string.
 | 
			
		||||
//
 | 
			
		||||
// Otherwise, Slice will panic as out of range.
 | 
			
		||||
func Slice(str string, start, end int) string {
 | 
			
		||||
	var size, startPos, endPos int
 | 
			
		||||
 | 
			
		||||
	origin := str
 | 
			
		||||
 | 
			
		||||
	if start < 0 || end > len(str) || (end >= 0 && start > end) {
 | 
			
		||||
		panic("out of range")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if end >= 0 {
 | 
			
		||||
		end -= start
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for start > 0 && len(str) > 0 {
 | 
			
		||||
		_, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		start--
 | 
			
		||||
		startPos += size
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if end < 0 {
 | 
			
		||||
		return origin[startPos:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	endPos = startPos
 | 
			
		||||
 | 
			
		||||
	for end > 0 && len(str) > 0 {
 | 
			
		||||
		_, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		end--
 | 
			
		||||
		endPos += size
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(str) == 0 && (start > 0 || end > 0) {
 | 
			
		||||
		panic("out of range")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return origin[startPos:endPos]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Partition splits a string by sep into three parts.
 | 
			
		||||
// The return value is a slice of strings with head, match and tail.
 | 
			
		||||
//
 | 
			
		||||
// If str contains sep, for example "hello" and "l", Partition returns
 | 
			
		||||
//     "he", "l", "lo"
 | 
			
		||||
//
 | 
			
		||||
// If str doesn't contain sep, for example "hello" and "x", Partition returns
 | 
			
		||||
//     "hello", "", ""
 | 
			
		||||
func Partition(str, sep string) (head, match, tail string) {
 | 
			
		||||
	index := strings.Index(str, sep)
 | 
			
		||||
 | 
			
		||||
	if index == -1 {
 | 
			
		||||
		head = str
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	head = str[:index]
 | 
			
		||||
	match = str[index : index+len(sep)]
 | 
			
		||||
	tail = str[index+len(sep):]
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LastPartition splits a string by last instance of sep into three parts.
 | 
			
		||||
// The return value is a slice of strings with head, match and tail.
 | 
			
		||||
//
 | 
			
		||||
// If str contains sep, for example "hello" and "l", LastPartition returns
 | 
			
		||||
//     "hel", "l", "o"
 | 
			
		||||
//
 | 
			
		||||
// If str doesn't contain sep, for example "hello" and "x", LastPartition returns
 | 
			
		||||
//     "", "", "hello"
 | 
			
		||||
func LastPartition(str, sep string) (head, match, tail string) {
 | 
			
		||||
	index := strings.LastIndex(str, sep)
 | 
			
		||||
 | 
			
		||||
	if index == -1 {
 | 
			
		||||
		tail = str
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	head = str[:index]
 | 
			
		||||
	match = str[index : index+len(sep)]
 | 
			
		||||
	tail = str[index+len(sep):]
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Insert src into dst at given rune index.
 | 
			
		||||
// Index is counted by runes instead of bytes.
 | 
			
		||||
//
 | 
			
		||||
// If index is out of range of dst, panic with out of range.
 | 
			
		||||
func Insert(dst, src string, index int) string {
 | 
			
		||||
	return Slice(dst, 0, index) + src + Slice(dst, index, -1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scrub scrubs invalid utf8 bytes with repl string.
 | 
			
		||||
// Adjacent invalid bytes are replaced only once.
 | 
			
		||||
func Scrub(str, repl string) string {
 | 
			
		||||
	var buf *bytes.Buffer
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size, pos int
 | 
			
		||||
	var hasError bool
 | 
			
		||||
 | 
			
		||||
	origin := str
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
		if r == utf8.RuneError {
 | 
			
		||||
			if !hasError {
 | 
			
		||||
				if buf == nil {
 | 
			
		||||
					buf = &bytes.Buffer{}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				buf.WriteString(origin[:pos])
 | 
			
		||||
				hasError = true
 | 
			
		||||
			}
 | 
			
		||||
		} else if hasError {
 | 
			
		||||
			hasError = false
 | 
			
		||||
			buf.WriteString(repl)
 | 
			
		||||
 | 
			
		||||
			origin = origin[pos:]
 | 
			
		||||
			pos = 0
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pos += size
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if buf != nil {
 | 
			
		||||
		buf.WriteString(origin)
 | 
			
		||||
		return buf.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No invalid byte.
 | 
			
		||||
	return origin
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WordSplit splits a string into words. Returns a slice of words.
 | 
			
		||||
// If there is no word in a string, return nil.
 | 
			
		||||
//
 | 
			
		||||
// Word is defined as a locale dependent string containing alphabetic characters,
 | 
			
		||||
// which may also contain but not start with `'` and `-` characters.
 | 
			
		||||
func WordSplit(str string) []string {
 | 
			
		||||
	var word string
 | 
			
		||||
	var words []string
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size, pos int
 | 
			
		||||
 | 
			
		||||
	inWord := false
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case isAlphabet(r):
 | 
			
		||||
			if !inWord {
 | 
			
		||||
				inWord = true
 | 
			
		||||
				word = str
 | 
			
		||||
				pos = 0
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case inWord && (r == '\'' || r == '-'):
 | 
			
		||||
			// Still in word.
 | 
			
		||||
 | 
			
		||||
		default:
 | 
			
		||||
			if inWord {
 | 
			
		||||
				inWord = false
 | 
			
		||||
				words = append(words, word[:pos])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pos += size
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if inWord {
 | 
			
		||||
		words = append(words, word[:pos])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return words
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										547
									
								
								vendor/github.com/huandu/xstrings/translate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										547
									
								
								vendor/github.com/huandu/xstrings/translate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,547 @@
 | 
			
		||||
// Copyright 2015 Huan Du. All rights reserved.
 | 
			
		||||
// Licensed under the MIT license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package xstrings
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"unicode"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type runeRangeMap struct {
 | 
			
		||||
	FromLo rune // Lower bound of range map.
 | 
			
		||||
	FromHi rune // An inclusive higher bound of range map.
 | 
			
		||||
	ToLo   rune
 | 
			
		||||
	ToHi   rune
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type runeDict struct {
 | 
			
		||||
	Dict [unicode.MaxASCII + 1]rune
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type runeMap map[rune]rune
 | 
			
		||||
 | 
			
		||||
// Translator can translate string with pre-compiled from and to patterns.
 | 
			
		||||
// If a from/to pattern pair needs to be used more than once, it's recommended
 | 
			
		||||
// to create a Translator and reuse it.
 | 
			
		||||
type Translator struct {
 | 
			
		||||
	quickDict  *runeDict       // A quick dictionary to look up rune by index. Only available for latin runes.
 | 
			
		||||
	runeMap    runeMap         // Rune map for translation.
 | 
			
		||||
	ranges     []*runeRangeMap // Ranges of runes.
 | 
			
		||||
	mappedRune rune            // If mappedRune >= 0, all matched runes are translated to the mappedRune.
 | 
			
		||||
	reverted   bool            // If to pattern is empty, all matched characters will be deleted.
 | 
			
		||||
	hasPattern bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewTranslator creates new Translator through a from/to pattern pair.
 | 
			
		||||
func NewTranslator(from, to string) *Translator {
 | 
			
		||||
	tr := &Translator{}
 | 
			
		||||
 | 
			
		||||
	if from == "" {
 | 
			
		||||
		return tr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reverted := from[0] == '^'
 | 
			
		||||
	deletion := len(to) == 0
 | 
			
		||||
 | 
			
		||||
	if reverted {
 | 
			
		||||
		from = from[1:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var fromStart, fromEnd, fromRangeStep rune
 | 
			
		||||
	var toStart, toEnd, toRangeStep rune
 | 
			
		||||
	var fromRangeSize, toRangeSize rune
 | 
			
		||||
	var singleRunes []rune
 | 
			
		||||
 | 
			
		||||
	// Update the to rune range.
 | 
			
		||||
	updateRange := func() {
 | 
			
		||||
		// No more rune to read in the to rune pattern.
 | 
			
		||||
		if toEnd == utf8.RuneError {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if toRangeStep == 0 {
 | 
			
		||||
			to, toStart, toEnd, toRangeStep = nextRuneRange(to, toEnd)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Current range is not empty. Consume 1 rune from start.
 | 
			
		||||
		if toStart != toEnd {
 | 
			
		||||
			toStart += toRangeStep
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// No more rune. Repeat the last rune.
 | 
			
		||||
		if to == "" {
 | 
			
		||||
			toEnd = utf8.RuneError
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Both start and end are used. Read two more runes from the to pattern.
 | 
			
		||||
		to, toStart, toEnd, toRangeStep = nextRuneRange(to, utf8.RuneError)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if deletion {
 | 
			
		||||
		toStart = utf8.RuneError
 | 
			
		||||
		toEnd = utf8.RuneError
 | 
			
		||||
	} else {
 | 
			
		||||
		// If from pattern is reverted, only the last rune in the to pattern will be used.
 | 
			
		||||
		if reverted {
 | 
			
		||||
			var size int
 | 
			
		||||
 | 
			
		||||
			for len(to) > 0 {
 | 
			
		||||
				toStart, size = utf8.DecodeRuneInString(to)
 | 
			
		||||
				to = to[size:]
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			toEnd = utf8.RuneError
 | 
			
		||||
		} else {
 | 
			
		||||
			to, toStart, toEnd, toRangeStep = nextRuneRange(to, utf8.RuneError)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fromEnd = utf8.RuneError
 | 
			
		||||
 | 
			
		||||
	for len(from) > 0 {
 | 
			
		||||
		from, fromStart, fromEnd, fromRangeStep = nextRuneRange(from, fromEnd)
 | 
			
		||||
 | 
			
		||||
		// fromStart is a single character. Just map it with a rune in the to pattern.
 | 
			
		||||
		if fromRangeStep == 0 {
 | 
			
		||||
			singleRunes = tr.addRune(fromStart, toStart, singleRunes)
 | 
			
		||||
			updateRange()
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for toEnd != utf8.RuneError && fromStart != fromEnd {
 | 
			
		||||
			// If mapped rune is a single character instead of a range, simply shift first
 | 
			
		||||
			// rune in the range.
 | 
			
		||||
			if toRangeStep == 0 {
 | 
			
		||||
				singleRunes = tr.addRune(fromStart, toStart, singleRunes)
 | 
			
		||||
				updateRange()
 | 
			
		||||
				fromStart += fromRangeStep
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fromRangeSize = (fromEnd - fromStart) * fromRangeStep
 | 
			
		||||
			toRangeSize = (toEnd - toStart) * toRangeStep
 | 
			
		||||
 | 
			
		||||
			// Not enough runes in the to pattern. Need to read more.
 | 
			
		||||
			if fromRangeSize > toRangeSize {
 | 
			
		||||
				fromStart, toStart = tr.addRuneRange(fromStart, fromStart+toRangeSize*fromRangeStep, toStart, toEnd, singleRunes)
 | 
			
		||||
				fromStart += fromRangeStep
 | 
			
		||||
				updateRange()
 | 
			
		||||
 | 
			
		||||
				// Edge case: If fromRangeSize == toRangeSize + 1, the last fromStart value needs be considered
 | 
			
		||||
				// as a single rune.
 | 
			
		||||
				if fromStart == fromEnd {
 | 
			
		||||
					singleRunes = tr.addRune(fromStart, toStart, singleRunes)
 | 
			
		||||
					updateRange()
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fromStart, toStart = tr.addRuneRange(fromStart, fromEnd, toStart, toStart+fromRangeSize*toRangeStep, singleRunes)
 | 
			
		||||
			updateRange()
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if fromStart == fromEnd {
 | 
			
		||||
			fromEnd = utf8.RuneError
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fromStart, toStart = tr.addRuneRange(fromStart, fromEnd, toStart, toStart, singleRunes)
 | 
			
		||||
		fromEnd = utf8.RuneError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fromEnd != utf8.RuneError {
 | 
			
		||||
		singleRunes = tr.addRune(fromEnd, toStart, singleRunes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tr.reverted = reverted
 | 
			
		||||
	tr.mappedRune = -1
 | 
			
		||||
	tr.hasPattern = true
 | 
			
		||||
 | 
			
		||||
	// Translate RuneError only if in deletion or reverted mode.
 | 
			
		||||
	if deletion || reverted {
 | 
			
		||||
		tr.mappedRune = toStart
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tr *Translator) addRune(from, to rune, singleRunes []rune) []rune {
 | 
			
		||||
	if from <= unicode.MaxASCII {
 | 
			
		||||
		if tr.quickDict == nil {
 | 
			
		||||
			tr.quickDict = &runeDict{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tr.quickDict.Dict[from] = to
 | 
			
		||||
	} else {
 | 
			
		||||
		if tr.runeMap == nil {
 | 
			
		||||
			tr.runeMap = make(runeMap)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tr.runeMap[from] = to
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	singleRunes = append(singleRunes, from)
 | 
			
		||||
	return singleRunes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tr *Translator) addRuneRange(fromLo, fromHi, toLo, toHi rune, singleRunes []rune) (rune, rune) {
 | 
			
		||||
	var r rune
 | 
			
		||||
	var rrm *runeRangeMap
 | 
			
		||||
 | 
			
		||||
	if fromLo < fromHi {
 | 
			
		||||
		rrm = &runeRangeMap{
 | 
			
		||||
			FromLo: fromLo,
 | 
			
		||||
			FromHi: fromHi,
 | 
			
		||||
			ToLo:   toLo,
 | 
			
		||||
			ToHi:   toHi,
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		rrm = &runeRangeMap{
 | 
			
		||||
			FromLo: fromHi,
 | 
			
		||||
			FromHi: fromLo,
 | 
			
		||||
			ToLo:   toHi,
 | 
			
		||||
			ToHi:   toLo,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If there is any single rune conflicts with this rune range, clear single rune record.
 | 
			
		||||
	for _, r = range singleRunes {
 | 
			
		||||
		if rrm.FromLo <= r && r <= rrm.FromHi {
 | 
			
		||||
			if r <= unicode.MaxASCII {
 | 
			
		||||
				tr.quickDict.Dict[r] = 0
 | 
			
		||||
			} else {
 | 
			
		||||
				delete(tr.runeMap, r)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tr.ranges = append(tr.ranges, rrm)
 | 
			
		||||
	return fromHi, toHi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func nextRuneRange(str string, last rune) (remaining string, start, end rune, rangeStep rune) {
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size int
 | 
			
		||||
 | 
			
		||||
	remaining = str
 | 
			
		||||
	escaping := false
 | 
			
		||||
	isRange := false
 | 
			
		||||
 | 
			
		||||
	for len(remaining) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(remaining)
 | 
			
		||||
		remaining = remaining[size:]
 | 
			
		||||
 | 
			
		||||
		// Parse special characters.
 | 
			
		||||
		if !escaping {
 | 
			
		||||
			if r == '\\' {
 | 
			
		||||
				escaping = true
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if r == '-' {
 | 
			
		||||
				// Ignore slash at beginning of string.
 | 
			
		||||
				if last == utf8.RuneError {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				start = last
 | 
			
		||||
				isRange = true
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		escaping = false
 | 
			
		||||
 | 
			
		||||
		if last != utf8.RuneError {
 | 
			
		||||
			// This is a range which start and end are the same.
 | 
			
		||||
			// Considier it as a normal character.
 | 
			
		||||
			if isRange && last == r {
 | 
			
		||||
				isRange = false
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			start = last
 | 
			
		||||
			end = r
 | 
			
		||||
 | 
			
		||||
			if isRange {
 | 
			
		||||
				if start < end {
 | 
			
		||||
					rangeStep = 1
 | 
			
		||||
				} else {
 | 
			
		||||
					rangeStep = -1
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		last = r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start = last
 | 
			
		||||
	end = utf8.RuneError
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Translate str with a from/to pattern pair.
 | 
			
		||||
//
 | 
			
		||||
// See comment in Translate function for usage and samples.
 | 
			
		||||
func (tr *Translator) Translate(str string) string {
 | 
			
		||||
	if !tr.hasPattern || str == "" {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size int
 | 
			
		||||
	var needTr bool
 | 
			
		||||
 | 
			
		||||
	orig := str
 | 
			
		||||
 | 
			
		||||
	var output *bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		r, needTr = tr.TranslateRune(r)
 | 
			
		||||
 | 
			
		||||
		if needTr && output == nil {
 | 
			
		||||
			output = allocBuffer(orig, str)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r != utf8.RuneError && output != nil {
 | 
			
		||||
			output.WriteRune(r)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No character is translated.
 | 
			
		||||
	if output == nil {
 | 
			
		||||
		return orig
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return output.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TranslateRune return translated rune and true if r matches the from pattern.
 | 
			
		||||
// If r doesn't match the pattern, original r is returned and translated is false.
 | 
			
		||||
func (tr *Translator) TranslateRune(r rune) (result rune, translated bool) {
 | 
			
		||||
	switch {
 | 
			
		||||
	case tr.quickDict != nil:
 | 
			
		||||
		if r <= unicode.MaxASCII {
 | 
			
		||||
			result = tr.quickDict.Dict[r]
 | 
			
		||||
 | 
			
		||||
			if result != 0 {
 | 
			
		||||
				translated = true
 | 
			
		||||
 | 
			
		||||
				if tr.mappedRune >= 0 {
 | 
			
		||||
					result = tr.mappedRune
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fallthrough
 | 
			
		||||
 | 
			
		||||
	case tr.runeMap != nil:
 | 
			
		||||
		var ok bool
 | 
			
		||||
 | 
			
		||||
		if result, ok = tr.runeMap[r]; ok {
 | 
			
		||||
			translated = true
 | 
			
		||||
 | 
			
		||||
			if tr.mappedRune >= 0 {
 | 
			
		||||
				result = tr.mappedRune
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fallthrough
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		var rrm *runeRangeMap
 | 
			
		||||
		ranges := tr.ranges
 | 
			
		||||
 | 
			
		||||
		for i := len(ranges) - 1; i >= 0; i-- {
 | 
			
		||||
			rrm = ranges[i]
 | 
			
		||||
 | 
			
		||||
			if rrm.FromLo <= r && r <= rrm.FromHi {
 | 
			
		||||
				translated = true
 | 
			
		||||
 | 
			
		||||
				if tr.mappedRune >= 0 {
 | 
			
		||||
					result = tr.mappedRune
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if rrm.ToLo < rrm.ToHi {
 | 
			
		||||
					result = rrm.ToLo + r - rrm.FromLo
 | 
			
		||||
				} else if rrm.ToLo > rrm.ToHi {
 | 
			
		||||
					// ToHi can be smaller than ToLo if range is from higher to lower.
 | 
			
		||||
					result = rrm.ToLo - r + rrm.FromLo
 | 
			
		||||
				} else {
 | 
			
		||||
					result = rrm.ToLo
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if tr.reverted {
 | 
			
		||||
		if !translated {
 | 
			
		||||
			result = tr.mappedRune
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		translated = !translated
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !translated {
 | 
			
		||||
		result = r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HasPattern returns true if Translator has one pattern at least.
 | 
			
		||||
func (tr *Translator) HasPattern() bool {
 | 
			
		||||
	return tr.hasPattern
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Translate str with the characters defined in from replaced by characters defined in to.
 | 
			
		||||
//
 | 
			
		||||
// From and to are patterns representing a set of characters. Pattern is defined as following.
 | 
			
		||||
//
 | 
			
		||||
//     * Special characters
 | 
			
		||||
//       * '-' means a range of runes, e.g.
 | 
			
		||||
//         * "a-z" means all characters from 'a' to 'z' inclusive;
 | 
			
		||||
//         * "z-a" means all characters from 'z' to 'a' inclusive.
 | 
			
		||||
//       * '^' as first character means a set of all runes excepted listed, e.g.
 | 
			
		||||
//         * "^a-z" means all characters except 'a' to 'z' inclusive.
 | 
			
		||||
//       * '\' escapes special characters.
 | 
			
		||||
//     * Normal character represents itself, e.g. "abc" is a set including 'a', 'b' and 'c'.
 | 
			
		||||
//
 | 
			
		||||
// Translate will try to find a 1:1 mapping from from to to.
 | 
			
		||||
// If to is smaller than from, last rune in to will be used to map "out of range" characters in from.
 | 
			
		||||
//
 | 
			
		||||
// Note that '^' only works in the from pattern. It will be considered as a normal character in the to pattern.
 | 
			
		||||
//
 | 
			
		||||
// If the to pattern is an empty string, Translate works exactly the same as Delete.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     Translate("hello", "aeiou", "12345")    => "h2ll4"
 | 
			
		||||
//     Translate("hello", "a-z", "A-Z")        => "HELLO"
 | 
			
		||||
//     Translate("hello", "z-a", "a-z")        => "svool"
 | 
			
		||||
//     Translate("hello", "aeiou", "*")        => "h*ll*"
 | 
			
		||||
//     Translate("hello", "^l", "*")           => "**ll*"
 | 
			
		||||
//     Translate("hello ^ world", `\^lo`, "*") => "he*** * w*r*d"
 | 
			
		||||
func Translate(str, from, to string) string {
 | 
			
		||||
	tr := NewTranslator(from, to)
 | 
			
		||||
	return tr.Translate(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete runes in str matching the pattern.
 | 
			
		||||
// Pattern is defined in Translate function.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     Delete("hello", "aeiou") => "hll"
 | 
			
		||||
//     Delete("hello", "a-k")   => "llo"
 | 
			
		||||
//     Delete("hello", "^a-k")  => "he"
 | 
			
		||||
func Delete(str, pattern string) string {
 | 
			
		||||
	tr := NewTranslator(pattern, "")
 | 
			
		||||
	return tr.Translate(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Count how many runes in str match the pattern.
 | 
			
		||||
// Pattern is defined in Translate function.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     Count("hello", "aeiou") => 3
 | 
			
		||||
//     Count("hello", "a-k")   => 3
 | 
			
		||||
//     Count("hello", "^a-k")  => 2
 | 
			
		||||
func Count(str, pattern string) int {
 | 
			
		||||
	if pattern == "" || str == "" {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var r rune
 | 
			
		||||
	var size int
 | 
			
		||||
	var matched bool
 | 
			
		||||
 | 
			
		||||
	tr := NewTranslator(pattern, "")
 | 
			
		||||
	cnt := 0
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
 | 
			
		||||
		if _, matched = tr.TranslateRune(r); matched {
 | 
			
		||||
			cnt++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cnt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Squeeze deletes adjacent repeated runes in str.
 | 
			
		||||
// If pattern is not empty, only runes matching the pattern will be squeezed.
 | 
			
		||||
//
 | 
			
		||||
// Samples:
 | 
			
		||||
//     Squeeze("hello", "")             => "helo"
 | 
			
		||||
//     Squeeze("hello", "m-z")          => "hello"
 | 
			
		||||
//     Squeeze("hello   world", " ")    => "hello world"
 | 
			
		||||
func Squeeze(str, pattern string) string {
 | 
			
		||||
	var last, r rune
 | 
			
		||||
	var size int
 | 
			
		||||
	var skipSqueeze, matched bool
 | 
			
		||||
	var tr *Translator
 | 
			
		||||
	var output *bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	orig := str
 | 
			
		||||
	last = -1
 | 
			
		||||
 | 
			
		||||
	if len(pattern) > 0 {
 | 
			
		||||
		tr = NewTranslator(pattern, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for len(str) > 0 {
 | 
			
		||||
		r, size = utf8.DecodeRuneInString(str)
 | 
			
		||||
 | 
			
		||||
		// Need to squeeze the str.
 | 
			
		||||
		if last == r && !skipSqueeze {
 | 
			
		||||
			if tr != nil {
 | 
			
		||||
				if _, matched = tr.TranslateRune(r); !matched {
 | 
			
		||||
					skipSqueeze = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if output == nil {
 | 
			
		||||
				output = allocBuffer(orig, str)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if skipSqueeze {
 | 
			
		||||
				output.WriteRune(r)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if output != nil {
 | 
			
		||||
				output.WriteRune(r)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			last = r
 | 
			
		||||
			skipSqueeze = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		str = str[size:]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if output == nil {
 | 
			
		||||
		return orig
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return output.String()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							@@ -237,6 +237,8 @@ github.com/hashicorp/hcl/hcl/token
 | 
			
		||||
github.com/hashicorp/hcl/json/parser
 | 
			
		||||
github.com/hashicorp/hcl/json/scanner
 | 
			
		||||
github.com/hashicorp/hcl/json/token
 | 
			
		||||
# github.com/huandu/xstrings v1.3.0
 | 
			
		||||
github.com/huandu/xstrings
 | 
			
		||||
# github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c
 | 
			
		||||
github.com/issue9/identicon
 | 
			
		||||
# github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user