aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/unknwon
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/unknwon')
-rw-r--r--vendor/github.com/unknwon/cae/.gitignore25
-rw-r--r--vendor/github.com/unknwon/cae/LICENSE191
-rw-r--r--vendor/github.com/unknwon/cae/README.md37
-rw-r--r--vendor/github.com/unknwon/cae/README_ZH.md29
-rw-r--r--vendor/github.com/unknwon/cae/cae.go108
-rw-r--r--vendor/github.com/unknwon/cae/zip/read.go67
-rw-r--r--vendor/github.com/unknwon/cae/zip/stream.go77
-rw-r--r--vendor/github.com/unknwon/cae/zip/write.go364
-rw-r--r--vendor/github.com/unknwon/cae/zip/zip.go238
-rw-r--r--vendor/github.com/unknwon/com/.gitignore24
-rw-r--r--vendor/github.com/unknwon/com/.travis.yml15
-rw-r--r--vendor/github.com/unknwon/com/LICENSE191
-rw-r--r--vendor/github.com/unknwon/com/README.md20
-rw-r--r--vendor/github.com/unknwon/com/cmd.go161
-rw-r--r--vendor/github.com/unknwon/com/convert.go167
-rw-r--r--vendor/github.com/unknwon/com/dir.go218
-rw-r--r--vendor/github.com/unknwon/com/file.go145
-rw-r--r--vendor/github.com/unknwon/com/go.mod8
-rw-r--r--vendor/github.com/unknwon/com/go.sum8
-rw-r--r--vendor/github.com/unknwon/com/html.go60
-rw-r--r--vendor/github.com/unknwon/com/http.go201
-rw-r--r--vendor/github.com/unknwon/com/math.go29
-rw-r--r--vendor/github.com/unknwon/com/path.go80
-rw-r--r--vendor/github.com/unknwon/com/regex.go56
-rw-r--r--vendor/github.com/unknwon/com/slice.go87
-rw-r--r--vendor/github.com/unknwon/com/string.go253
-rw-r--r--vendor/github.com/unknwon/com/time.go115
-rw-r--r--vendor/github.com/unknwon/com/url.go41
-rw-r--r--vendor/github.com/unknwon/i18n/.gitignore22
-rw-r--r--vendor/github.com/unknwon/i18n/LICENSE191
-rw-r--r--vendor/github.com/unknwon/i18n/Makefile12
-rw-r--r--vendor/github.com/unknwon/i18n/README.md135
-rw-r--r--vendor/github.com/unknwon/i18n/go.mod9
-rw-r--r--vendor/github.com/unknwon/i18n/go.sum21
-rw-r--r--vendor/github.com/unknwon/i18n/i18n.go231
-rw-r--r--vendor/github.com/unknwon/paginater/.gitignore26
-rw-r--r--vendor/github.com/unknwon/paginater/LICENSE202
-rw-r--r--vendor/github.com/unknwon/paginater/README.md65
-rw-r--r--vendor/github.com/unknwon/paginater/paginater.go192
39 files changed, 4121 insertions, 0 deletions
diff --git a/vendor/github.com/unknwon/cae/.gitignore b/vendor/github.com/unknwon/cae/.gitignore
new file mode 100644
index 0000000000..b6c97b1593
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/.gitignore
@@ -0,0 +1,25 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+.idea
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+cae.iml
+*.exe
+.DS_Store
diff --git a/vendor/github.com/unknwon/cae/LICENSE b/vendor/github.com/unknwon/cae/LICENSE
new file mode 100644
index 0000000000..8405e89a0b
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/LICENSE
@@ -0,0 +1,191 @@
+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:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+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
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/vendor/github.com/unknwon/cae/README.md b/vendor/github.com/unknwon/cae/README.md
new file mode 100644
index 0000000000..ff6f97ec15
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/README.md
@@ -0,0 +1,37 @@
+Compression and Archive Extensions
+==================================
+
+[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/unknwon/cae)
+
+[中文文档](README_ZH.md)
+
+Package cae implements PHP-like Compression and Archive Extensions.
+
+But this package has some modifications depends on Go-style.
+
+Reference: [PHP:Compression and Archive Extensions](http://www.php.net/manual/en/refs.compression.php).
+
+Code Convention: based on [Go Code Convention](https://github.com/unknwon/go-code-convention).
+
+### Implementations
+
+Package `zip`([Go Walker](http://gowalker.org/github.com/unknwon/cae/zip)) and `tz`([Go Walker](http://gowalker.org/github.com/unknwon/cae/tz)) both enable you to transparently read or write ZIP/TAR.GZ compressed archives and the files inside them.
+
+- Features:
+ - Add file or directory from everywhere to archive, no one-to-one limitation.
+ - Extract part of entries, not all at once.
+ - Stream data directly into `io.Writer` without any file system storage.
+
+### Test cases and Coverage
+
+All subpackages use [GoConvey](http://goconvey.co/) to write test cases, and coverage is more than 80 percent.
+
+### Use cases
+
+- [Gogs](https://github.com/gogits/gogs): self hosted Git service in the Go Programming Language.
+- [GoBlog](https://github.com/fuxiaohei/GoBlog): personal blogging application.
+- [GoBuild](https://github.com/shxsun/gobuild/): online Go cross-platform compilation and download service.
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. \ No newline at end of file
diff --git a/vendor/github.com/unknwon/cae/README_ZH.md b/vendor/github.com/unknwon/cae/README_ZH.md
new file mode 100644
index 0000000000..d5c361e3dc
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/README_ZH.md
@@ -0,0 +1,29 @@
+压缩与打包扩展
+=============
+
+[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/unknwon/cae)
+
+包 cae 实现了 PHP 风格的压缩与打包扩展。
+
+但本包依据 Go 语言的风格进行了一些修改。
+
+引用:[PHP:Compression and Archive Extensions](http://www.php.net/manual/en/refs.compression.php)
+
+编码规范:基于 [Go 编码规范](https://github.com/unknwon/go-code-convention)
+
+### 实现
+
+包 `zip`([Go Walker](http://gowalker.org/github.com/unknwon/cae/zip)) 和 `tz`([Go Walker](http://gowalker.org/github.com/unknwon/cae/tz)) 都允许你轻易的读取或写入 ZIP/TAR.GZ 压缩档案和其内部文件。
+
+- 特性:
+ - 将任意位置的文件或目录加入档案,没有一对一的操作限制。
+ - 只解压部分文件,而非一次性解压全部。
+ - 将数据以流的形式直接写入 `io.Writer` 而不需经过文件系统的存储。
+
+### 测试用例与覆盖率
+
+所有子包均采用 [GoConvey](http://goconvey.co/) 来书写测试用例,覆盖率均超过 80%。
+
+## 授权许可
+
+本项目采用 Apache v2 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。 \ No newline at end of file
diff --git a/vendor/github.com/unknwon/cae/cae.go b/vendor/github.com/unknwon/cae/cae.go
new file mode 100644
index 0000000000..60e295a9cb
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/cae.go
@@ -0,0 +1,108 @@
+// Copyright 2013 Unknown
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package cae implements PHP-like Compression and Archive Extensions.
+package cae
+
+import (
+ "io"
+ "os"
+ "strings"
+)
+
+// A Streamer describes an streamable archive object.
+type Streamer interface {
+ StreamFile(string, os.FileInfo, []byte) error
+ StreamReader(string, os.FileInfo, io.Reader) error
+ Close() error
+}
+
+// A HookFunc represents a middleware for packing and extracting archive.
+type HookFunc func(string, os.FileInfo) error
+
+// HasPrefix returns true if name has any string in given slice as prefix.
+func HasPrefix(name string, prefixes []string) bool {
+ for _, prefix := range prefixes {
+ if strings.HasPrefix(name, prefix) {
+ return true
+ }
+ }
+ return false
+}
+
+// IsEntry returns true if name equals to any string in given slice.
+func IsEntry(name string, entries []string) bool {
+ for _, e := range entries {
+ if e == name {
+ return true
+ }
+ }
+ return false
+}
+
+// IsFilter returns true if given name matches any of global filter rule.
+func IsFilter(name string) bool {
+ if strings.Contains(name, ".DS_Store") {
+ return true
+ }
+ return false
+}
+
+// IsExist returns true if given path is a file or directory.
+func IsExist(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil || os.IsExist(err)
+}
+
+// Copy copies file from source to target path.
+func Copy(dest, src string) error {
+ // Gather file information to set back later.
+ si, err := os.Lstat(src)
+ if err != nil {
+ return err
+ }
+
+ // Handle symbolic link.
+ if si.Mode()&os.ModeSymlink != 0 {
+ target, err := os.Readlink(src)
+ if err != nil {
+ return err
+ }
+ // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link,
+ // which will lead "no such file or directory" error.
+ return os.Symlink(target, dest)
+ }
+
+ sr, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer sr.Close()
+
+ dw, err := os.Create(dest)
+ if err != nil {
+ return err
+ }
+ defer dw.Close()
+
+ if _, err = io.Copy(dw, sr); err != nil {
+ return err
+ }
+
+ // Set back file information.
+ if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil {
+ return err
+ }
+ return os.Chmod(dest, si.Mode())
+}
diff --git a/vendor/github.com/unknwon/cae/zip/read.go b/vendor/github.com/unknwon/cae/zip/read.go
new file mode 100644
index 0000000000..c4825ef6d5
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/zip/read.go
@@ -0,0 +1,67 @@
+// Copyright 2013 Unknown
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package zip
+
+import (
+ "archive/zip"
+ "os"
+ "strings"
+)
+
+// OpenFile is the generalized open call; most users will use Open
+// instead. It opens the named zip file with specified flag
+// (O_RDONLY etc.) if applicable. If successful,
+// methods on the returned ZipArchive can be used for I/O.
+// If there is an error, it will be of type *PathError.
+func (z *ZipArchive) Open(name string, flag int, perm os.FileMode) error {
+ // Create a new archive if it's specified and not exist.
+ if flag&os.O_CREATE != 0 {
+ f, err := os.Create(name)
+ if err != nil {
+ return err
+ }
+ zw := zip.NewWriter(f)
+ if err = zw.Close(); err != nil {
+ return err
+ }
+ }
+
+ rc, err := zip.OpenReader(name)
+ if err != nil {
+ return err
+ }
+
+ z.ReadCloser = rc
+ z.FileName = name
+ z.Comment = rc.Comment
+ z.NumFiles = len(rc.File)
+ z.Flag = flag
+ z.Permission = perm
+ z.isHasChanged = false
+
+ z.files = make([]*File, z.NumFiles)
+ for i, f := range rc.File {
+ z.files[i] = &File{}
+ z.files[i].FileHeader, err = zip.FileInfoHeader(f.FileInfo())
+ if err != nil {
+ return err
+ }
+ z.files[i].Name = strings.Replace(f.Name, "\\", "/", -1)
+ if f.FileInfo().IsDir() && !strings.HasSuffix(z.files[i].Name, "/") {
+ z.files[i].Name += "/"
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/unknwon/cae/zip/stream.go b/vendor/github.com/unknwon/cae/zip/stream.go
new file mode 100644
index 0000000000..18d8cc2250
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/zip/stream.go
@@ -0,0 +1,77 @@
+// Copyright 2014 Unknown
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package zip
+
+import (
+ "archive/zip"
+ "io"
+ "os"
+ "path/filepath"
+)
+
+// A StreamArchive represents a streamable archive.
+type StreamArchive struct {
+ *zip.Writer
+}
+
+// NewStreamArachive returns a new streamable archive with given io.Writer.
+// It's caller's responsibility to close io.Writer and streamer after operation.
+func NewStreamArachive(w io.Writer) *StreamArchive {
+ return &StreamArchive{zip.NewWriter(w)}
+}
+
+// StreamFile streams a file or directory entry into StreamArchive.
+func (s *StreamArchive) StreamFile(relPath string, fi os.FileInfo, data []byte) error {
+ if fi.IsDir() {
+ fh, err := zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ fh.Name = relPath + "/"
+ if _, err = s.Writer.CreateHeader(fh); err != nil {
+ return err
+ }
+ } else {
+ fh, err := zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ fh.Name = filepath.Join(relPath, fi.Name())
+ fh.Method = zip.Deflate
+ fw, err := s.Writer.CreateHeader(fh)
+ if err != nil {
+ return err
+ } else if _, err = fw.Write(data); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// StreamReader streams data from io.Reader to StreamArchive.
+func (s *StreamArchive) StreamReader(relPath string, fi os.FileInfo, r io.Reader) (err error) {
+ fh, err := zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ fh.Name = filepath.Join(relPath, fi.Name())
+
+ fw, err := s.Writer.CreateHeader(fh)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(fw, r)
+ return err
+}
diff --git a/vendor/github.com/unknwon/cae/zip/write.go b/vendor/github.com/unknwon/cae/zip/write.go
new file mode 100644
index 0000000000..5d4679602c
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/zip/write.go
@@ -0,0 +1,364 @@
+// Copyright 2013 Unknown
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package zip
+
+import (
+ "archive/zip"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/unknwon/cae"
+)
+
+// Switcher of printing trace information when pack and extract.
+var Verbose = true
+
+// extractFile extracts zip.File to file system.
+func extractFile(f *zip.File, destPath string) error {
+ filePath := path.Join(destPath, f.Name)
+ os.MkdirAll(path.Dir(filePath), os.ModePerm)
+
+ rc, err := f.Open()
+ if err != nil {
+ return err
+ }
+ defer rc.Close()
+
+ fw, err := os.Create(filePath)
+ if err != nil {
+ return err
+ }
+ defer fw.Close()
+
+ if _, err = io.Copy(fw, rc); err != nil {
+ return err
+ }
+
+ // Skip symbolic links.
+ if f.FileInfo().Mode()&os.ModeSymlink != 0 {
+ return nil
+ }
+ // Set back file information.
+ if err = os.Chtimes(filePath, f.ModTime(), f.ModTime()); err != nil {
+ return err
+ }
+ return os.Chmod(filePath, f.FileInfo().Mode())
+}
+
+var defaultExtractFunc = func(fullName string, fi os.FileInfo) error {
+ if !Verbose {
+ return nil
+ }
+
+ fmt.Println("Extracting file..." + fullName)
+ return nil
+}
+
+// ExtractToFunc extracts the whole archive or the given files to the
+// specified destination.
+// It accepts a function as a middleware for custom operations.
+func (z *ZipArchive) ExtractToFunc(destPath string, fn cae.HookFunc, entries ...string) (err error) {
+ destPath = strings.Replace(destPath, "\\", "/", -1)
+ isHasEntry := len(entries) > 0
+ if Verbose {
+ fmt.Println("Unzipping " + z.FileName + "...")
+ }
+ os.MkdirAll(destPath, os.ModePerm)
+ for _, f := range z.File {
+ f.Name = strings.Replace(f.Name, "\\", "/", -1)
+
+ // Directory.
+ if strings.HasSuffix(f.Name, "/") {
+ if isHasEntry {
+ if cae.IsEntry(f.Name, entries) {
+ if err = fn(f.Name, f.FileInfo()); err != nil {
+ continue
+ }
+ os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm)
+ }
+ continue
+ }
+ if err = fn(f.Name, f.FileInfo()); err != nil {
+ continue
+ }
+ os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm)
+ continue
+ }
+
+ // File.
+ if isHasEntry {
+ if cae.IsEntry(f.Name, entries) {
+ if err = fn(f.Name, f.FileInfo()); err != nil {
+ continue
+ }
+ err = extractFile(f, destPath)
+ }
+ } else {
+ if err = fn(f.Name, f.FileInfo()); err != nil {
+ continue
+ }
+ err = extractFile(f, destPath)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ExtractToFunc extracts the whole archive or the given files to the
+// specified destination.
+// It accepts a function as a middleware for custom operations.
+func ExtractToFunc(srcPath, destPath string, fn cae.HookFunc, entries ...string) (err error) {
+ z, err := Open(srcPath)
+ if err != nil {
+ return err
+ }
+ defer z.Close()
+ return z.ExtractToFunc(destPath, fn, entries...)
+}
+
+// ExtractTo extracts the whole archive or the given files to the
+// specified destination.
+// Call Flush() to apply changes before this.
+func (z *ZipArchive) ExtractTo(destPath string, entries ...string) (err error) {
+ return z.ExtractToFunc(destPath, defaultExtractFunc, entries...)
+}
+
+// ExtractTo extracts given archive or the given files to the
+// specified destination.
+func ExtractTo(srcPath, destPath string, entries ...string) (err error) {
+ return ExtractToFunc(srcPath, destPath, defaultExtractFunc, entries...)
+}
+
+// extractFile extracts file from ZipArchive to file system.
+func (z *ZipArchive) extractFile(f *File) error {
+ if !z.isHasWriter {
+ for _, zf := range z.ReadCloser.File {
+ if f.Name == zf.Name {
+ return extractFile(zf, path.Dir(f.tmpPath))
+ }
+ }
+ }
+ return cae.Copy(f.tmpPath, f.absPath)
+}
+
+// Flush saves changes to original zip file if any.
+func (z *ZipArchive) Flush() error {
+ if !z.isHasChanged || (z.ReadCloser == nil && !z.isHasWriter) {
+ return nil
+ }
+
+ // Extract to tmp path and pack back.
+ tmpPath := path.Join(os.TempDir(), "cae", path.Base(z.FileName))
+ os.RemoveAll(tmpPath)
+ defer os.RemoveAll(tmpPath)
+
+ for _, f := range z.files {
+ if strings.HasSuffix(f.Name, "/") {
+ os.MkdirAll(path.Join(tmpPath, f.Name), os.ModePerm)
+ continue
+ }
+
+ // Relative path inside zip temporary changed.
+ f.tmpPath = path.Join(tmpPath, f.Name)
+ if err := z.extractFile(f); err != nil {
+ return err
+ }
+ }
+
+ if z.isHasWriter {
+ return packToWriter(tmpPath, z.writer, defaultPackFunc, true)
+ }
+
+ if err := PackTo(tmpPath, z.FileName); err != nil {
+ return err
+ }
+ return z.Open(z.FileName, os.O_RDWR|os.O_TRUNC, z.Permission)
+}
+
+// packFile packs a file or directory to zip.Writer.
+func packFile(srcFile string, recPath string, zw *zip.Writer, fi os.FileInfo) error {
+ if fi.IsDir() {
+ fh, err := zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ fh.Name = recPath + "/"
+ if _, err = zw.CreateHeader(fh); err != nil {
+ return err
+ }
+ } else {
+ fh, err := zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ fh.Name = recPath
+ fh.Method = zip.Deflate
+
+ fw, err := zw.CreateHeader(fh)
+ if err != nil {
+ return err
+ }
+
+ if fi.Mode()&os.ModeSymlink != 0 {
+ target, err := os.Readlink(srcFile)
+ if err != nil {
+ return err
+ }
+ if _, err = fw.Write([]byte(target)); err != nil {
+ return err
+ }
+ } else {
+ f, err := os.Open(srcFile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ if _, err = io.Copy(fw, f); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// packDir packs a directory and its subdirectories and files
+// recursively to zip.Writer.
+func packDir(srcPath string, recPath string, zw *zip.Writer, fn cae.HookFunc) error {
+ dir, err := os.Open(srcPath)
+ if err != nil {
+ return err
+ }
+ defer dir.Close()
+
+ fis, err := dir.Readdir(0)
+ if err != nil {
+ return err
+ }
+ for _, fi := range fis {
+ if cae.IsFilter(fi.Name()) {
+ continue
+ }
+ curPath := srcPath + "/" + fi.Name()
+ tmpRecPath := filepath.Join(recPath, fi.Name())
+ if err = fn(curPath, fi); err != nil {
+ continue
+ }
+
+ if fi.IsDir() {
+ if err = packFile(srcPath, tmpRecPath, zw, fi); err != nil {
+ return err
+ }
+ err = packDir(curPath, tmpRecPath, zw, fn)
+ } else {
+ err = packFile(curPath, tmpRecPath, zw, fi)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// packToWriter packs given path object to io.Writer.
+func packToWriter(srcPath string, w io.Writer, fn func(fullName string, fi os.FileInfo) error, includeDir bool) error {
+ zw := zip.NewWriter(w)
+ defer zw.Close()
+
+ f, err := os.Open(srcPath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ fi, err := f.Stat()
+ if err != nil {
+ return err
+ }
+
+ basePath := path.Base(srcPath)
+ if fi.IsDir() {
+ if includeDir {
+ if err = packFile(srcPath, basePath, zw, fi); err != nil {
+ return err
+ }
+ } else {
+ basePath = ""
+ }
+ return packDir(srcPath, basePath, zw, fn)
+ }
+ return packFile(srcPath, basePath, zw, fi)
+}
+
+// packTo packs given source path object to target path.
+func packTo(srcPath, destPath string, fn cae.HookFunc, includeDir bool) error {
+ fw, err := os.Create(destPath)
+ if err != nil {
+ return err
+ }
+ defer fw.Close()
+
+ return packToWriter(srcPath, fw, fn, includeDir)
+}
+
+// PackToFunc packs the complete archive to the specified destination.
+// It accepts a function as a middleware for custom operations.
+func PackToFunc(srcPath, destPath string, fn func(fullName string, fi os.FileInfo) error, includeDir ...bool) error {
+ isIncludeDir := false
+ if len(includeDir) > 0 && includeDir[0] {
+ isIncludeDir = true
+ }
+ return packTo(srcPath, destPath, fn, isIncludeDir)
+}
+
+var defaultPackFunc = func(fullName string, fi os.FileInfo) error {
+ if !Verbose {
+ return nil
+ }
+
+ if fi.IsDir() {
+ fmt.Printf("Adding dir...%s\n", fullName)
+ } else {
+ fmt.Printf("Adding file...%s\n", fullName)
+ }
+ return nil
+}
+
+// PackTo packs the whole archive to the specified destination.
+// Call Flush() will automatically call this in the end.
+func PackTo(srcPath, destPath string, includeDir ...bool) error {
+ return PackToFunc(srcPath, destPath, defaultPackFunc, includeDir...)
+}
+
+// Close opens or creates archive and save changes.
+func (z *ZipArchive) Close() (err error) {
+ if err = z.Flush(); err != nil {
+ return err
+ }
+
+ if z.ReadCloser != nil {
+ if err = z.ReadCloser.Close(); err != nil {
+ return err
+ }
+ z.ReadCloser = nil
+ }
+ return nil
+}
diff --git a/vendor/github.com/unknwon/cae/zip/zip.go b/vendor/github.com/unknwon/cae/zip/zip.go
new file mode 100644
index 0000000000..b5d7d1071c
--- /dev/null
+++ b/vendor/github.com/unknwon/cae/zip/zip.go
@@ -0,0 +1,238 @@
+// Copyright 2013 Unknown
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package zip enables you to transparently read or write ZIP compressed archives and the files inside them.
+package zip
+
+import (
+ "archive/zip"
+ "errors"
+ "io"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/unknwon/cae"
+)
+
+// A File represents a file or directory entry in archive.
+type File struct {
+ *zip.FileHeader
+ oldName string // NOTE: unused, for future change name feature.
+ oldComment string // NOTE: unused, for future change comment feature.
+ absPath string // Absolute path of local file system.
+ tmpPath string
+}
+
+// A ZipArchive represents a file archive, compressed with Zip.
+type ZipArchive struct {
+ *zip.ReadCloser
+ FileName string
+ Comment string
+ NumFiles int
+ Flag int
+ Permission os.FileMode
+
+ files []*File
+ isHasChanged bool
+
+ // For supporting flushing to io.Writer.
+ writer io.Writer
+ isHasWriter bool
+}
+
+// OpenFile is the generalized open call; most users will use Open
+// instead. It opens the named zip file with specified flag
+// (O_RDONLY etc.) if applicable. If successful,
+// methods on the returned ZipArchive can be used for I/O.
+// If there is an error, it will be of type *PathError.
+func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) {
+ z := new(ZipArchive)
+ err := z.Open(name, flag, perm)
+ return z, err
+}
+
+// Create creates the named zip file, truncating
+// it if it already exists. If successful, methods on the returned
+// ZipArchive can be used for I/O; the associated file descriptor has mode
+// O_RDWR.
+// If there is an error, it will be of type *PathError.
+func Create(name string) (*ZipArchive, error) {
+ os.MkdirAll(path.Dir(name), os.ModePerm)
+ return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+}
+
+// Open opens the named zip file for reading. If successful, methods on
+// the returned ZipArchive can be used for reading; the associated file
+// descriptor has mode O_RDONLY.
+// If there is an error, it will be of type *PathError.
+func Open(name string) (*ZipArchive, error) {
+ return OpenFile(name, os.O_RDONLY, 0)
+}
+
+// New accepts a variable that implemented interface io.Writer
+// for write-only purpose operations.
+func New(w io.Writer) *ZipArchive {
+ return &ZipArchive{
+ writer: w,
+ isHasWriter: true,
+ }
+}
+
+// List returns a string slice of files' name in ZipArchive.
+// Specify prefixes will be used as filters.
+func (z *ZipArchive) List(prefixes ...string) []string {
+ isHasPrefix := len(prefixes) > 0
+ names := make([]string, 0, z.NumFiles)
+ for _, f := range z.files {
+ if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) {
+ continue
+ }
+ names = append(names, f.Name)
+ }
+ return names
+}
+
+// AddEmptyDir adds a raw directory entry to ZipArchive,
+// it returns false if same directory enry already existed.
+func (z *ZipArchive) AddEmptyDir(dirPath string) bool {
+ dirPath = strings.Replace(dirPath, "\\", "/", -1)
+
+ if !strings.HasSuffix(dirPath, "/") {
+ dirPath += "/"
+ }
+
+ for _, f := range z.files {
+ if dirPath == f.Name {
+ return false
+ }
+ }
+
+ dirPath = strings.TrimSuffix(dirPath, "/")
+ if strings.Contains(dirPath, "/") {
+ // Auto add all upper level directories.
+ z.AddEmptyDir(path.Dir(dirPath))
+ }
+ z.files = append(z.files, &File{
+ FileHeader: &zip.FileHeader{
+ Name: dirPath + "/",
+ UncompressedSize: 0,
+ },
+ })
+ z.updateStat()
+ return true
+}
+
+// AddDir adds a directory and subdirectories entries to ZipArchive.
+func (z *ZipArchive) AddDir(dirPath, absPath string) error {
+ dir, err := os.Open(absPath)
+ if err != nil {
+ return err
+ }
+ defer dir.Close()
+
+ // Make sure we have all upper level directories.
+ z.AddEmptyDir(dirPath)
+
+ fis, err := dir.Readdir(0)
+ if err != nil {
+ return err
+ }
+ for _, fi := range fis {
+ curPath := absPath + "/" + fi.Name()
+ tmpRecPath := path.Join(dirPath, fi.Name())
+ if fi.IsDir() {
+ if err = z.AddDir(tmpRecPath, curPath); err != nil {
+ return err
+ }
+ } else {
+ if err = z.AddFile(tmpRecPath, curPath); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// updateStat should be called after every change for rebuilding statistic.
+func (z *ZipArchive) updateStat() {
+ z.NumFiles = len(z.files)
+ z.isHasChanged = true
+}
+
+// AddFile adds a file entry to ZipArchive.
+func (z *ZipArchive) AddFile(fileName, absPath string) error {
+ fileName = strings.Replace(fileName, "\\", "/", -1)
+ absPath = strings.Replace(absPath, "\\", "/", -1)
+
+ if cae.IsFilter(absPath) {
+ return nil
+ }
+
+ f, err := os.Open(absPath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ fi, err := f.Stat()
+ if err != nil {
+ return err
+ }
+
+ file := new(File)
+ file.FileHeader, err = zip.FileInfoHeader(fi)
+ if err != nil {
+ return err
+ }
+ file.Name = fileName
+ file.absPath = absPath
+
+ z.AddEmptyDir(path.Dir(fileName))
+
+ isExist := false
+ for _, f := range z.files {
+ if fileName == f.Name {
+ f = file
+ isExist = true
+ break
+ }
+ }
+ if !isExist {
+ z.files = append(z.files, file)
+ }
+
+ z.updateStat()
+ return nil
+}
+
+// DeleteIndex deletes an entry in the archive by its index.
+func (z *ZipArchive) DeleteIndex(idx int) error {
+ if idx >= z.NumFiles {
+ return errors.New("index out of range of number of files")
+ }
+
+ z.files = append(z.files[:idx], z.files[idx+1:]...)
+ return nil
+}
+
+// DeleteName deletes an entry in the archive by its name.
+func (z *ZipArchive) DeleteName(name string) error {
+ for i, f := range z.files {
+ if f.Name == name {
+ return z.DeleteIndex(i)
+ }
+ }
+ return errors.New("entry with given name not found")
+}
diff --git a/vendor/github.com/unknwon/com/.gitignore b/vendor/github.com/unknwon/com/.gitignore
new file mode 100644
index 0000000000..0da157fe9c
--- /dev/null
+++ b/vendor/github.com/unknwon/com/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+.idea
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.iml
diff --git a/vendor/github.com/unknwon/com/.travis.yml b/vendor/github.com/unknwon/com/.travis.yml
new file mode 100644
index 0000000000..b36cd5c197
--- /dev/null
+++ b/vendor/github.com/unknwon/com/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+
+go:
+ - 1.3.x
+ - 1.4.x
+ - 1.5.x
+ - 1.6.x
+ - 1.7.x
+ - 1.8.x
+ - 1.9.x
+ - 1.10.x
+ - 1.11.x
+ - 1.12.x
+
+install: go get -v -t
diff --git a/vendor/github.com/unknwon/com/LICENSE b/vendor/github.com/unknwon/com/LICENSE
new file mode 100644
index 0000000000..8405e89a0b
--- /dev/null
+++ b/vendor/github.com/unknwon/com/LICENSE
@@ -0,0 +1,191 @@
+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:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+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
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/vendor/github.com/unknwon/com/README.md b/vendor/github.com/unknwon/com/README.md
new file mode 100644
index 0000000000..da0a23bc99
--- /dev/null
+++ b/vendor/github.com/unknwon/com/README.md
@@ -0,0 +1,20 @@
+Common Functions
+================
+
+[![Build Status](https://travis-ci.org/unknwon/com.svg)](https://travis-ci.org/unknwon/com) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/unknwon/com)
+
+This is an open source project for commonly used functions for the Go programming language.
+
+This package need >= **go 1.3**
+
+Code Convention: based on [Go Code Convention](https://github.com/unknwon/go-code-convention).
+
+## Contribute
+
+Your contribute is welcome, but you have to check following steps after you added some functions and commit them:
+
+1. Make sure you wrote user-friendly comments for **all functions** .
+2. Make sure you wrote test cases with any possible condition for **all functions** in file `*_test.go`.
+3. Make sure you wrote benchmarks for **all functions** in file `*_test.go`.
+4. Make sure you wrote useful examples for **all functions** in file `example_test.go`.
+5. Make sure you ran `go test` and got **PASS** .
diff --git a/vendor/github.com/unknwon/com/cmd.go b/vendor/github.com/unknwon/com/cmd.go
new file mode 100644
index 0000000000..5b4bbaee97
--- /dev/null
+++ b/vendor/github.com/unknwon/com/cmd.go
@@ -0,0 +1,161 @@
+// +build go1.3
+
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package com is an open source project for commonly used functions for the Go programming language.
+package com
+
+import (
+ "bytes"
+ "fmt"
+ "os/exec"
+ "runtime"
+ "strings"
+)
+
+// ExecCmdDirBytes executes system command in given directory
+// and return stdout, stderr in bytes type, along with possible error.
+func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) {
+ bufOut := new(bytes.Buffer)
+ bufErr := new(bytes.Buffer)
+
+ cmd := exec.Command(cmdName, args...)
+ cmd.Dir = dir
+ cmd.Stdout = bufOut
+ cmd.Stderr = bufErr
+
+ err := cmd.Run()
+ return bufOut.Bytes(), bufErr.Bytes(), err
+}
+
+// ExecCmdBytes executes system command
+// and return stdout, stderr in bytes type, along with possible error.
+func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) {
+ return ExecCmdDirBytes("", cmdName, args...)
+}
+
+// ExecCmdDir executes system command in given directory
+// and return stdout, stderr in string type, along with possible error.
+func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) {
+ bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...)
+ return string(bufOut), string(bufErr), err
+}
+
+// ExecCmd executes system command
+// and return stdout, stderr in string type, along with possible error.
+func ExecCmd(cmdName string, args ...string) (string, string, error) {
+ return ExecCmdDir("", cmdName, args...)
+}
+
+// _________ .__ .____
+// \_ ___ \ ____ | | ___________ | | ____ ____
+// / \ \/ / _ \| | / _ \_ __ \ | | / _ \ / ___\
+// \ \___( <_> ) |_( <_> ) | \/ | |__( <_> ) /_/ >
+// \______ /\____/|____/\____/|__| |_______ \____/\___ /
+// \/ \/ /_____/
+
+// Color number constants.
+const (
+ Gray = uint8(iota + 90)
+ Red
+ Green
+ Yellow
+ Blue
+ Magenta
+ //NRed = uint8(31) // Normal
+ EndColor = "\033[0m"
+)
+
+// getColorLevel returns colored level string by given level.
+func getColorLevel(level string) string {
+ level = strings.ToUpper(level)
+ switch level {
+ case "TRAC":
+ return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level)
+ case "ERRO":
+ return fmt.Sprintf("\033[%dm%s\033[0m", Red, level)
+ case "WARN":
+ return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level)
+ case "SUCC":
+ return fmt.Sprintf("\033[%dm%s\033[0m", Green, level)
+ default:
+ return level
+ }
+}
+
+// ColorLogS colors log and return colored content.
+// Log format: <level> <content [highlight][path]> [ error ].
+// Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default.
+// Content: default; path: yellow; error -> red.
+// Level has to be surrounded by "[" and "]".
+// Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted.
+// Paths have to be surrounded by "( " and " )"(space).
+// Errors have to be surrounded by "[ " and " ]"(space).
+// Note: it hasn't support windows yet, contribute is welcome.
+func ColorLogS(format string, a ...interface{}) string {
+ log := fmt.Sprintf(format, a...)
+
+ var clog string
+
+ if runtime.GOOS != "windows" {
+ // Level.
+ i := strings.Index(log, "]")
+ if log[0] == '[' && i > -1 {
+ clog += "[" + getColorLevel(log[1:i]) + "]"
+ }
+
+ log = log[i+1:]
+
+ // Error.
+ log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1)
+ log = strings.Replace(log, " ]", EndColor+"]", -1)
+
+ // Path.
+ log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1)
+ log = strings.Replace(log, " )", EndColor+")", -1)
+
+ // Highlights.
+ log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1)
+ log = strings.Replace(log, " #", EndColor, -1)
+
+ } else {
+ // Level.
+ i := strings.Index(log, "]")
+ if log[0] == '[' && i > -1 {
+ clog += "[" + log[1:i] + "]"
+ }
+
+ log = log[i+1:]
+
+ // Error.
+ log = strings.Replace(log, "[ ", "[", -1)
+ log = strings.Replace(log, " ]", "]", -1)
+
+ // Path.
+ log = strings.Replace(log, "( ", "(", -1)
+ log = strings.Replace(log, " )", ")", -1)
+
+ // Highlights.
+ log = strings.Replace(log, "# ", "", -1)
+ log = strings.Replace(log, " #", "", -1)
+ }
+ return clog + log
+}
+
+// ColorLog prints colored log to stdout.
+// See color rules in function 'ColorLogS'.
+func ColorLog(format string, a ...interface{}) {
+ fmt.Print(ColorLogS(format, a...))
+}
diff --git a/vendor/github.com/unknwon/com/convert.go b/vendor/github.com/unknwon/com/convert.go
new file mode 100644
index 0000000000..bf24aa8bc3
--- /dev/null
+++ b/vendor/github.com/unknwon/com/convert.go
@@ -0,0 +1,167 @@
+// Copyright 2014 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "fmt"
+ "strconv"
+)
+
+// Convert string to specify type.
+type StrTo string
+
+func (f StrTo) Exist() bool {
+ return string(f) != string(0x1E)
+}
+
+func (f StrTo) Uint8() (uint8, error) {
+ v, err := strconv.ParseUint(f.String(), 10, 8)
+ return uint8(v), err
+}
+
+func (f StrTo) Int() (int, error) {
+ v, err := strconv.ParseInt(f.String(), 10, 0)
+ return int(v), err
+}
+
+func (f StrTo) Int64() (int64, error) {
+ v, err := strconv.ParseInt(f.String(), 10, 64)
+ return int64(v), err
+}
+
+func (f StrTo) Float64() (float64, error) {
+ v, err := strconv.ParseFloat(f.String(), 64)
+ return float64(v), err
+}
+
+func (f StrTo) MustUint8() uint8 {
+ v, _ := f.Uint8()
+ return v
+}
+
+func (f StrTo) MustInt() int {
+ v, _ := f.Int()
+ return v
+}
+
+func (f StrTo) MustInt64() int64 {
+ v, _ := f.Int64()
+ return v
+}
+
+func (f StrTo) MustFloat64() float64 {
+ v, _ := f.Float64()
+ return v
+}
+
+func (f StrTo) String() string {
+ if f.Exist() {
+ return string(f)
+ }
+ return ""
+}
+
+// Convert any type to string.
+func ToStr(value interface{}, args ...int) (s string) {
+ switch v := value.(type) {
+ case bool:
+ s = strconv.FormatBool(v)
+ case float32:
+ s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
+ case float64:
+ s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
+ case int:
+ s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+ case int8:
+ s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+ case int16:
+ s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+ case int32:
+ s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
+ case int64:
+ s = strconv.FormatInt(v, argInt(args).Get(0, 10))
+ case uint:
+ s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+ case uint8:
+ s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+ case uint16:
+ s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+ case uint32:
+ s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
+ case uint64:
+ s = strconv.FormatUint(v, argInt(args).Get(0, 10))
+ case string:
+ s = v
+ case []byte:
+ s = string(v)
+ default:
+ s = fmt.Sprintf("%v", v)
+ }
+ return s
+}
+
+type argInt []int
+
+func (a argInt) Get(i int, args ...int) (r int) {
+ if i >= 0 && i < len(a) {
+ r = a[i]
+ } else if len(args) > 0 {
+ r = args[0]
+ }
+ return
+}
+
+// HexStr2int converts hex format string to decimal number.
+func HexStr2int(hexStr string) (int, error) {
+ num := 0
+ length := len(hexStr)
+ for i := 0; i < length; i++ {
+ char := hexStr[length-i-1]
+ factor := -1
+
+ switch {
+ case char >= '0' && char <= '9':
+ factor = int(char) - '0'
+ case char >= 'a' && char <= 'f':
+ factor = int(char) - 'a' + 10
+ default:
+ return -1, fmt.Errorf("invalid hex: %s", string(char))
+ }
+
+ num += factor * PowInt(16, i)
+ }
+ return num, nil
+}
+
+// Int2HexStr converts decimal number to hex format string.
+func Int2HexStr(num int) (hex string) {
+ if num == 0 {
+ return "0"
+ }
+
+ for num > 0 {
+ r := num % 16
+
+ c := "?"
+ if r >= 0 && r <= 9 {
+ c = string(r + '0')
+ } else {
+ c = string(r + 'a' - 10)
+ }
+ hex = c + hex
+ num = num / 16
+ }
+ return hex
+}
diff --git a/vendor/github.com/unknwon/com/dir.go b/vendor/github.com/unknwon/com/dir.go
new file mode 100644
index 0000000000..c16e9de333
--- /dev/null
+++ b/vendor/github.com/unknwon/com/dir.go
@@ -0,0 +1,218 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path"
+ "strings"
+)
+
+// IsDir returns true if given path is a directory,
+// or returns false when it's a file or does not exist.
+func IsDir(dir string) bool {
+ f, e := os.Stat(dir)
+ if e != nil {
+ return false
+ }
+ return f.IsDir()
+}
+
+func statDir(dirPath, recPath string, includeDir, isDirOnly, followSymlinks bool) ([]string, error) {
+ dir, err := os.Open(dirPath)
+ if err != nil {
+ return nil, err
+ }
+ defer dir.Close()
+
+ fis, err := dir.Readdir(0)
+ if err != nil {
+ return nil, err
+ }
+
+ statList := make([]string, 0)
+ for _, fi := range fis {
+ if strings.Contains(fi.Name(), ".DS_Store") {
+ continue
+ }
+
+ relPath := path.Join(recPath, fi.Name())
+ curPath := path.Join(dirPath, fi.Name())
+ if fi.IsDir() {
+ if includeDir {
+ statList = append(statList, relPath+"/")
+ }
+ s, err := statDir(curPath, relPath, includeDir, isDirOnly, followSymlinks)
+ if err != nil {
+ return nil, err
+ }
+ statList = append(statList, s...)
+ } else if !isDirOnly {
+ statList = append(statList, relPath)
+ } else if followSymlinks && fi.Mode()&os.ModeSymlink != 0 {
+ link, err := os.Readlink(curPath)
+ if err != nil {
+ return nil, err
+ }
+
+ if IsDir(link) {
+ if includeDir {
+ statList = append(statList, relPath+"/")
+ }
+ s, err := statDir(curPath, relPath, includeDir, isDirOnly, followSymlinks)
+ if err != nil {
+ return nil, err
+ }
+ statList = append(statList, s...)
+ }
+ }
+ }
+ return statList, nil
+}
+
+// StatDir gathers information of given directory by depth-first.
+// It returns slice of file list and includes subdirectories if enabled;
+// it returns error and nil slice when error occurs in underlying functions,
+// or given path is not a directory or does not exist.
+//
+// Slice does not include given path itself.
+// If subdirectories is enabled, they will have suffix '/'.
+func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
+ if !IsDir(rootPath) {
+ return nil, errors.New("not a directory or does not exist: " + rootPath)
+ }
+
+ isIncludeDir := false
+ if len(includeDir) >= 1 {
+ isIncludeDir = includeDir[0]
+ }
+ return statDir(rootPath, "", isIncludeDir, false, false)
+}
+
+// LstatDir gathers information of given directory by depth-first.
+// It returns slice of file list, follows symbolic links and includes subdirectories if enabled;
+// it returns error and nil slice when error occurs in underlying functions,
+// or given path is not a directory or does not exist.
+//
+// Slice does not include given path itself.
+// If subdirectories is enabled, they will have suffix '/'.
+func LstatDir(rootPath string, includeDir ...bool) ([]string, error) {
+ if !IsDir(rootPath) {
+ return nil, errors.New("not a directory or does not exist: " + rootPath)
+ }
+
+ isIncludeDir := false
+ if len(includeDir) >= 1 {
+ isIncludeDir = includeDir[0]
+ }
+ return statDir(rootPath, "", isIncludeDir, false, true)
+}
+
+// GetAllSubDirs returns all subdirectories of given root path.
+// Slice does not include given path itself.
+func GetAllSubDirs(rootPath string) ([]string, error) {
+ if !IsDir(rootPath) {
+ return nil, errors.New("not a directory or does not exist: " + rootPath)
+ }
+ return statDir(rootPath, "", true, true, false)
+}
+
+// LgetAllSubDirs returns all subdirectories of given root path, including
+// following symbolic links, if any.
+// Slice does not include given path itself.
+func LgetAllSubDirs(rootPath string) ([]string, error) {
+ if !IsDir(rootPath) {
+ return nil, errors.New("not a directory or does not exist: " + rootPath)
+ }
+ return statDir(rootPath, "", true, true, true)
+}
+
+// GetFileListBySuffix returns an ordered list of file paths.
+// It recognize if given path is a file, and don't do recursive find.
+func GetFileListBySuffix(dirPath, suffix string) ([]string, error) {
+ if !IsExist(dirPath) {
+ return nil, fmt.Errorf("given path does not exist: %s", dirPath)
+ } else if IsFile(dirPath) {
+ return []string{dirPath}, nil
+ }
+
+ // Given path is a directory.
+ dir, err := os.Open(dirPath)
+ if err != nil {
+ return nil, err
+ }
+
+ fis, err := dir.Readdir(0)
+ if err != nil {
+ return nil, err
+ }
+
+ files := make([]string, 0, len(fis))
+ for _, fi := range fis {
+ if strings.HasSuffix(fi.Name(), suffix) {
+ files = append(files, path.Join(dirPath, fi.Name()))
+ }
+ }
+
+ return files, nil
+}
+
+// CopyDir copy files recursively from source to target directory.
+//
+// The filter accepts a function that process the path info.
+// and should return true for need to filter.
+//
+// It returns error when error occurs in underlying functions.
+func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error {
+ // Check if target directory exists.
+ if IsExist(destPath) {
+ return errors.New("file or directory alreay exists: " + destPath)
+ }
+
+ err := os.MkdirAll(destPath, os.ModePerm)
+ if err != nil {
+ return err
+ }
+
+ // Gather directory info.
+ infos, err := StatDir(srcPath, true)
+ if err != nil {
+ return err
+ }
+
+ var filter func(filePath string) bool
+ if len(filters) > 0 {
+ filter = filters[0]
+ }
+
+ for _, info := range infos {
+ if filter != nil && filter(info) {
+ continue
+ }
+
+ curPath := path.Join(destPath, info)
+ if strings.HasSuffix(info, "/") {
+ err = os.MkdirAll(curPath, os.ModePerm)
+ } else {
+ err = Copy(path.Join(srcPath, info), curPath)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/unknwon/com/file.go b/vendor/github.com/unknwon/com/file.go
new file mode 100644
index 0000000000..b51502c917
--- /dev/null
+++ b/vendor/github.com/unknwon/com/file.go
@@ -0,0 +1,145 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "math"
+ "os"
+ "path"
+)
+
+// Storage unit constants.
+const (
+ Byte = 1
+ KByte = Byte * 1024
+ MByte = KByte * 1024
+ GByte = MByte * 1024
+ TByte = GByte * 1024
+ PByte = TByte * 1024
+ EByte = PByte * 1024
+)
+
+func logn(n, b float64) float64 {
+ return math.Log(n) / math.Log(b)
+}
+
+func humanateBytes(s uint64, base float64, sizes []string) string {
+ if s < 10 {
+ return fmt.Sprintf("%dB", s)
+ }
+ e := math.Floor(logn(float64(s), base))
+ suffix := sizes[int(e)]
+ val := float64(s) / math.Pow(base, math.Floor(e))
+ f := "%.0f"
+ if val < 10 {
+ f = "%.1f"
+ }
+
+ return fmt.Sprintf(f+"%s", val, suffix)
+}
+
+// HumaneFileSize calculates the file size and generate user-friendly string.
+func HumaneFileSize(s uint64) string {
+ sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
+ return humanateBytes(s, 1024, sizes)
+}
+
+// FileMTime returns file modified time and possible error.
+func FileMTime(file string) (int64, error) {
+ f, err := os.Stat(file)
+ if err != nil {
+ return 0, err
+ }
+ return f.ModTime().Unix(), nil
+}
+
+// FileSize returns file size in bytes and possible error.
+func FileSize(file string) (int64, error) {
+ f, err := os.Stat(file)
+ if err != nil {
+ return 0, err
+ }
+ return f.Size(), nil
+}
+
+// Copy copies file from source to target path.
+func Copy(src, dest string) error {
+ // Gather file information to set back later.
+ si, err := os.Lstat(src)
+ if err != nil {
+ return err
+ }
+
+ // Handle symbolic link.
+ if si.Mode()&os.ModeSymlink != 0 {
+ target, err := os.Readlink(src)
+ if err != nil {
+ return err
+ }
+ // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link,
+ // which will lead "no such file or directory" error.
+ return os.Symlink(target, dest)
+ }
+
+ sr, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer sr.Close()
+
+ dw, err := os.Create(dest)
+ if err != nil {
+ return err
+ }
+ defer dw.Close()
+
+ if _, err = io.Copy(dw, sr); err != nil {
+ return err
+ }
+
+ // Set back file information.
+ if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil {
+ return err
+ }
+ return os.Chmod(dest, si.Mode())
+}
+
+// WriteFile writes data to a file named by filename.
+// If the file does not exist, WriteFile creates it
+// and its upper level paths.
+func WriteFile(filename string, data []byte) error {
+ os.MkdirAll(path.Dir(filename), os.ModePerm)
+ return ioutil.WriteFile(filename, data, 0655)
+}
+
+// IsFile returns true if given path is a file,
+// or returns false when it's a directory or does not exist.
+func IsFile(filePath string) bool {
+ f, e := os.Stat(filePath)
+ if e != nil {
+ return false
+ }
+ return !f.IsDir()
+}
+
+// IsExist checks whether a file or directory exists.
+// It returns false when the file or directory does not exist.
+func IsExist(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil || os.IsExist(err)
+}
diff --git a/vendor/github.com/unknwon/com/go.mod b/vendor/github.com/unknwon/com/go.mod
new file mode 100644
index 0000000000..43834a963c
--- /dev/null
+++ b/vendor/github.com/unknwon/com/go.mod
@@ -0,0 +1,8 @@
+module github.com/unknwon/com
+
+require (
+ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
+ github.com/jtolds/gls v4.2.1+incompatible // indirect
+ github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
+ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c
+)
diff --git a/vendor/github.com/unknwon/com/go.sum b/vendor/github.com/unknwon/com/go.sum
new file mode 100644
index 0000000000..3fcc30358b
--- /dev/null
+++ b/vendor/github.com/unknwon/com/go.sum
@@ -0,0 +1,8 @@
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
diff --git a/vendor/github.com/unknwon/com/html.go b/vendor/github.com/unknwon/com/html.go
new file mode 100644
index 0000000000..8a99df4e08
--- /dev/null
+++ b/vendor/github.com/unknwon/com/html.go
@@ -0,0 +1,60 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "html"
+ "regexp"
+ "strings"
+)
+
+// Html2JS converts []byte type of HTML content into JS format.
+func Html2JS(data []byte) []byte {
+ s := string(data)
+ s = strings.Replace(s, `\`, `\\`, -1)
+ s = strings.Replace(s, "\n", `\n`, -1)
+ s = strings.Replace(s, "\r", "", -1)
+ s = strings.Replace(s, "\"", `\"`, -1)
+ s = strings.Replace(s, "<table>", "&lt;table>", -1)
+ return []byte(s)
+}
+
+// encode html chars to string
+func HtmlEncode(str string) string {
+ return html.EscapeString(str)
+}
+
+// HtmlDecode decodes string to html chars
+func HtmlDecode(str string) string {
+ return html.UnescapeString(str)
+}
+
+// strip tags in html string
+func StripTags(src string) string {
+ //去除style,script,html tag
+ re := regexp.MustCompile(`(?s)<(?:style|script)[^<>]*>.*?</(?:style|script)>|</?[a-z][a-z0-9]*[^<>]*>|<!--.*?-->`)
+ src = re.ReplaceAllString(src, "")
+
+ //trim all spaces(2+) into \n
+ re = regexp.MustCompile(`\s{2,}`)
+ src = re.ReplaceAllString(src, "\n")
+
+ return strings.TrimSpace(src)
+}
+
+// change \n to <br/>
+func Nl2br(str string) string {
+ return strings.Replace(str, "\n", "<br/>", -1)
+}
diff --git a/vendor/github.com/unknwon/com/http.go b/vendor/github.com/unknwon/com/http.go
new file mode 100644
index 0000000000..cf0820f378
--- /dev/null
+++ b/vendor/github.com/unknwon/com/http.go
@@ -0,0 +1,201 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "path"
+)
+
+type NotFoundError struct {
+ Message string
+}
+
+func (e NotFoundError) Error() string {
+ return e.Message
+}
+
+type RemoteError struct {
+ Host string
+ Err error
+}
+
+func (e *RemoteError) Error() string {
+ return e.Err.Error()
+}
+
+var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36"
+
+// HttpCall makes HTTP method call.
+func HttpCall(client *http.Client, method, url string, header http.Header, body io.Reader) (io.ReadCloser, error) {
+ req, err := http.NewRequest(method, url, body)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("User-Agent", UserAgent)
+ for k, vs := range header {
+ req.Header[k] = vs
+ }
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode == 200 {
+ return resp.Body, nil
+ }
+ resp.Body.Close()
+ if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 {
+ err = fmt.Errorf("resource not found: %s", url)
+ } else {
+ err = fmt.Errorf("%s %s -> %d", method, url, resp.StatusCode)
+ }
+ return nil, err
+}
+
+// HttpGet gets the specified resource.
+// ErrNotFound is returned if the server responds with status 404.
+func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) {
+ return HttpCall(client, "GET", url, header, nil)
+}
+
+// HttpPost posts the specified resource.
+// ErrNotFound is returned if the server responds with status 404.
+func HttpPost(client *http.Client, url string, header http.Header, body []byte) (io.ReadCloser, error) {
+ return HttpCall(client, "POST", url, header, bytes.NewBuffer(body))
+}
+
+// HttpGetToFile gets the specified resource and writes to file.
+// ErrNotFound is returned if the server responds with status 404.
+func HttpGetToFile(client *http.Client, url string, header http.Header, fileName string) error {
+ rc, err := HttpGet(client, url, header)
+ if err != nil {
+ return err
+ }
+ defer rc.Close()
+
+ os.MkdirAll(path.Dir(fileName), os.ModePerm)
+ f, err := os.Create(fileName)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = io.Copy(f, rc)
+ return err
+}
+
+// HttpGetBytes gets the specified resource. ErrNotFound is returned if the server
+// responds with status 404.
+func HttpGetBytes(client *http.Client, url string, header http.Header) ([]byte, error) {
+ rc, err := HttpGet(client, url, header)
+ if err != nil {
+ return nil, err
+ }
+ defer rc.Close()
+ return ioutil.ReadAll(rc)
+}
+
+// HttpGetJSON gets the specified resource and mapping to struct.
+// ErrNotFound is returned if the server responds with status 404.
+func HttpGetJSON(client *http.Client, url string, v interface{}) error {
+ rc, err := HttpGet(client, url, nil)
+ if err != nil {
+ return err
+ }
+ defer rc.Close()
+ err = json.NewDecoder(rc).Decode(v)
+ if _, ok := err.(*json.SyntaxError); ok {
+ return fmt.Errorf("JSON syntax error at %s", url)
+ }
+ return nil
+}
+
+// HttpPostJSON posts the specified resource with struct values,
+// and maps results to struct.
+// ErrNotFound is returned if the server responds with status 404.
+func HttpPostJSON(client *http.Client, url string, body, v interface{}) error {
+ data, err := json.Marshal(body)
+ if err != nil {
+ return err
+ }
+ rc, err := HttpPost(client, url, http.Header{"content-type": []string{"application/json"}}, data)
+ if err != nil {
+ return err
+ }
+ defer rc.Close()
+ err = json.NewDecoder(rc).Decode(v)
+ if _, ok := err.(*json.SyntaxError); ok {
+ return fmt.Errorf("JSON syntax error at %s", url)
+ }
+ return nil
+}
+
+// A RawFile describes a file that can be downloaded.
+type RawFile interface {
+ Name() string
+ RawUrl() string
+ Data() []byte
+ SetData([]byte)
+}
+
+// FetchFiles fetches files specified by the rawURL field in parallel.
+func FetchFiles(client *http.Client, files []RawFile, header http.Header) error {
+ ch := make(chan error, len(files))
+ for i := range files {
+ go func(i int) {
+ p, err := HttpGetBytes(client, files[i].RawUrl(), nil)
+ if err != nil {
+ ch <- err
+ return
+ }
+ files[i].SetData(p)
+ ch <- nil
+ }(i)
+ }
+ for _ = range files {
+ if err := <-ch; err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// FetchFilesCurl uses command `curl` to fetch files specified by the rawURL field in parallel.
+func FetchFilesCurl(files []RawFile, curlOptions ...string) error {
+ ch := make(chan error, len(files))
+ for i := range files {
+ go func(i int) {
+ stdout, _, err := ExecCmd("curl", append(curlOptions, files[i].RawUrl())...)
+ if err != nil {
+ ch <- err
+ return
+ }
+
+ files[i].SetData([]byte(stdout))
+ ch <- nil
+ }(i)
+ }
+ for _ = range files {
+ if err := <-ch; err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/vendor/github.com/unknwon/com/math.go b/vendor/github.com/unknwon/com/math.go
new file mode 100644
index 0000000000..62b77e87c8
--- /dev/null
+++ b/vendor/github.com/unknwon/com/math.go
@@ -0,0 +1,29 @@
+// Copyright 2014 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+// PowInt is int type of math.Pow function.
+func PowInt(x int, y int) int {
+ if y <= 0 {
+ return 1
+ } else {
+ if y%2 == 0 {
+ sqrt := PowInt(x, y/2)
+ return sqrt * sqrt
+ } else {
+ return PowInt(x, y-1) * x
+ }
+ }
+}
diff --git a/vendor/github.com/unknwon/com/path.go b/vendor/github.com/unknwon/com/path.go
new file mode 100644
index 0000000000..b1e860def4
--- /dev/null
+++ b/vendor/github.com/unknwon/com/path.go
@@ -0,0 +1,80 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+// GetGOPATHs returns all paths in GOPATH variable.
+func GetGOPATHs() []string {
+ gopath := os.Getenv("GOPATH")
+ var paths []string
+ if runtime.GOOS == "windows" {
+ gopath = strings.Replace(gopath, "\\", "/", -1)
+ paths = strings.Split(gopath, ";")
+ } else {
+ paths = strings.Split(gopath, ":")
+ }
+ return paths
+}
+
+// GetSrcPath returns app. source code path.
+// It only works when you have src. folder in GOPATH,
+// it returns error not able to locate source folder path.
+func GetSrcPath(importPath string) (appPath string, err error) {
+ paths := GetGOPATHs()
+ for _, p := range paths {
+ if IsExist(p + "/src/" + importPath + "/") {
+ appPath = p + "/src/" + importPath + "/"
+ break
+ }
+ }
+
+ if len(appPath) == 0 {
+ return "", errors.New("Unable to locate source folder path")
+ }
+
+ appPath = filepath.Dir(appPath) + "/"
+ if runtime.GOOS == "windows" {
+ // Replace all '\' to '/'.
+ appPath = strings.Replace(appPath, "\\", "/", -1)
+ }
+
+ return appPath, nil
+}
+
+// HomeDir returns path of '~'(in Linux) on Windows,
+// it returns error when the variable does not exist.
+func HomeDir() (home string, err error) {
+ if runtime.GOOS == "windows" {
+ home = os.Getenv("USERPROFILE")
+ if len(home) == 0 {
+ home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
+ }
+ } else {
+ home = os.Getenv("HOME")
+ }
+
+ if len(home) == 0 {
+ return "", errors.New("Cannot specify home directory because it's empty")
+ }
+
+ return home, nil
+}
diff --git a/vendor/github.com/unknwon/com/regex.go b/vendor/github.com/unknwon/com/regex.go
new file mode 100644
index 0000000000..14926474e0
--- /dev/null
+++ b/vendor/github.com/unknwon/com/regex.go
@@ -0,0 +1,56 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import "regexp"
+
+const (
+ regex_email_pattern = `(?i)[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}`
+ regex_strict_email_pattern = `(?i)[A-Z0-9!#$%&'*+/=?^_{|}~-]+` +
+ `(?:\.[A-Z0-9!#$%&'*+/=?^_{|}~-]+)*` +
+ `@(?:[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?\.)+` +
+ `[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?`
+ regex_url_pattern = `(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?`
+)
+
+var (
+ regex_email *regexp.Regexp
+ regex_strict_email *regexp.Regexp
+ regex_url *regexp.Regexp
+)
+
+func init() {
+ regex_email = regexp.MustCompile(regex_email_pattern)
+ regex_strict_email = regexp.MustCompile(regex_strict_email_pattern)
+ regex_url = regexp.MustCompile(regex_url_pattern)
+}
+
+// IsEmail validates string is an email address, if not return false
+// basically validation can match 99% cases
+func IsEmail(email string) bool {
+ return regex_email.MatchString(email)
+}
+
+// IsEmailRFC validates string is an email address, if not return false
+// this validation omits RFC 2822
+func IsEmailRFC(email string) bool {
+ return regex_strict_email.MatchString(email)
+}
+
+// IsUrl validates string is a url link, if not return false
+// simple validation can match 99% cases
+func IsUrl(url string) bool {
+ return regex_url.MatchString(url)
+}
diff --git a/vendor/github.com/unknwon/com/slice.go b/vendor/github.com/unknwon/com/slice.go
new file mode 100644
index 0000000000..c3c9ab2e72
--- /dev/null
+++ b/vendor/github.com/unknwon/com/slice.go
@@ -0,0 +1,87 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "strings"
+)
+
+// AppendStr appends string to slice with no duplicates.
+func AppendStr(strs []string, str string) []string {
+ for _, s := range strs {
+ if s == str {
+ return strs
+ }
+ }
+ return append(strs, str)
+}
+
+// CompareSliceStr compares two 'string' type slices.
+// It returns true if elements and order are both the same.
+func CompareSliceStr(s1, s2 []string) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+
+ for i := range s1 {
+ if s1[i] != s2[i] {
+ return false
+ }
+ }
+
+ return true
+}
+
+// CompareSliceStrU compares two 'string' type slices.
+// It returns true if elements are the same, and ignores the order.
+func CompareSliceStrU(s1, s2 []string) bool {
+ if len(s1) != len(s2) {
+ return false
+ }
+
+ for i := range s1 {
+ for j := len(s2) - 1; j >= 0; j-- {
+ if s1[i] == s2[j] {
+ s2 = append(s2[:j], s2[j+1:]...)
+ break
+ }
+ }
+ }
+ if len(s2) > 0 {
+ return false
+ }
+ return true
+}
+
+// IsSliceContainsStr returns true if the string exists in given slice, ignore case.
+func IsSliceContainsStr(sl []string, str string) bool {
+ str = strings.ToLower(str)
+ for _, s := range sl {
+ if strings.ToLower(s) == str {
+ return true
+ }
+ }
+ return false
+}
+
+// IsSliceContainsInt64 returns true if the int64 exists in given slice.
+func IsSliceContainsInt64(sl []int64, i int64) bool {
+ for _, s := range sl {
+ if s == i {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/unknwon/com/string.go b/vendor/github.com/unknwon/com/string.go
new file mode 100644
index 0000000000..7080d174a8
--- /dev/null
+++ b/vendor/github.com/unknwon/com/string.go
@@ -0,0 +1,253 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "errors"
+ r "math/rand"
+ "strconv"
+ "strings"
+ "time"
+ "unicode"
+ "unicode/utf8"
+)
+
+// AESGCMEncrypt encrypts plaintext with the given key using AES in GCM mode.
+func AESGCMEncrypt(key, plaintext []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+
+ nonce := make([]byte, gcm.NonceSize())
+ if _, err := rand.Read(nonce); err != nil {
+ return nil, err
+ }
+
+ ciphertext := gcm.Seal(nil, nonce, plaintext, nil)
+ return append(nonce, ciphertext...), nil
+}
+
+// AESGCMDecrypt decrypts ciphertext with the given key using AES in GCM mode.
+func AESGCMDecrypt(key, ciphertext []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
+
+ size := gcm.NonceSize()
+ if len(ciphertext)-size <= 0 {
+ return nil, errors.New("Ciphertext is empty")
+ }
+
+ nonce := ciphertext[:size]
+ ciphertext = ciphertext[size:]
+
+ plainText, err := gcm.Open(nil, nonce, ciphertext, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return plainText, nil
+}
+
+// IsLetter returns true if the 'l' is an English letter.
+func IsLetter(l uint8) bool {
+ n := (l | 0x20) - 'a'
+ if n >= 0 && n < 26 {
+ return true
+ }
+ return false
+}
+
+// Expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match.
+func Expand(template string, match map[string]string, subs ...string) string {
+ var p []byte
+ var i int
+ for {
+ i = strings.Index(template, "{")
+ if i < 0 {
+ break
+ }
+ p = append(p, template[:i]...)
+ template = template[i+1:]
+ i = strings.Index(template, "}")
+ if s, ok := match[template[:i]]; ok {
+ p = append(p, s...)
+ } else {
+ j, _ := strconv.Atoi(template[:i])
+ if j >= len(subs) {
+ p = append(p, []byte("Missing")...)
+ } else {
+ p = append(p, subs[j]...)
+ }
+ }
+ template = template[i+1:]
+ }
+ p = append(p, template...)
+ return string(p)
+}
+
+// Reverse s string, support unicode
+func Reverse(s string) string {
+ n := len(s)
+ runes := make([]rune, n)
+ for _, rune := range s {
+ n--
+ runes[n] = rune
+ }
+ return string(runes[n:])
+}
+
+// RandomCreateBytes generate random []byte by specify chars.
+func RandomCreateBytes(n int, alphabets ...byte) []byte {
+ const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ var bytes = make([]byte, n)
+ var randby bool
+ if num, err := rand.Read(bytes); num != n || err != nil {
+ r.Seed(time.Now().UnixNano())
+ randby = true
+ }
+ for i, b := range bytes {
+ if len(alphabets) == 0 {
+ if randby {
+ bytes[i] = alphanum[r.Intn(len(alphanum))]
+ } else {
+ bytes[i] = alphanum[b%byte(len(alphanum))]
+ }
+ } else {
+ if randby {
+ bytes[i] = alphabets[r.Intn(len(alphabets))]
+ } else {
+ bytes[i] = alphabets[b%byte(len(alphabets))]
+ }
+ }
+ }
+ return bytes
+}
+
+// ToSnakeCase can convert all upper case characters in a string to
+// underscore 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.
+//
+// From https://github.com/huandu/xstrings
+func ToSnakeCase(str string) string {
+ if len(str) == 0 {
+ return ""
+ }
+
+ buf := &bytes.Buffer{}
+ var prev, r0, r1 rune
+ var size int
+
+ r0 = '_'
+
+ for len(str) > 0 {
+ prev = r0
+ r0, size = utf8.DecodeRuneInString(str)
+ str = str[size:]
+
+ switch {
+ case r0 == utf8.RuneError:
+ buf.WriteByte(byte(str[0]))
+
+ case unicode.IsUpper(r0):
+ if prev != '_' {
+ buf.WriteRune('_')
+ }
+
+ 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 `_` 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.WriteByte(byte(str[0]))
+ break
+ }
+
+ if !unicode.IsUpper(r0) {
+ if r0 == '_' || r0 == ' ' || r0 == '-' {
+ r0 = '_'
+
+ buf.WriteRune(unicode.ToLower(r1))
+ } else {
+ buf.WriteRune('_')
+ buf.WriteRune(unicode.ToLower(r1))
+ buf.WriteRune(r0)
+ }
+
+ break
+ }
+
+ buf.WriteRune(unicode.ToLower(r1))
+ }
+
+ if len(str) == 0 || r0 == '_' {
+ buf.WriteRune(unicode.ToLower(r0))
+ break
+ }
+
+ default:
+ if r0 == ' ' || r0 == '-' {
+ r0 = '_'
+ }
+
+ buf.WriteRune(r0)
+ }
+ }
+
+ return buf.String()
+}
diff --git a/vendor/github.com/unknwon/com/time.go b/vendor/github.com/unknwon/com/time.go
new file mode 100644
index 0000000000..dd1cdbcf2a
--- /dev/null
+++ b/vendor/github.com/unknwon/com/time.go
@@ -0,0 +1,115 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Format unix time int64 to string
+func Date(ti int64, format string) string {
+ t := time.Unix(int64(ti), 0)
+ return DateT(t, format)
+}
+
+// Format unix time string to string
+func DateS(ts string, format string) string {
+ i, _ := strconv.ParseInt(ts, 10, 64)
+ return Date(i, format)
+}
+
+// Format time.Time struct to string
+// MM - month - 01
+// M - month - 1, single bit
+// DD - day - 02
+// D - day 2
+// YYYY - year - 2006
+// YY - year - 06
+// HH - 24 hours - 03
+// H - 24 hours - 3
+// hh - 12 hours - 03
+// h - 12 hours - 3
+// mm - minute - 04
+// m - minute - 4
+// ss - second - 05
+// s - second = 5
+func DateT(t time.Time, format string) string {
+ res := strings.Replace(format, "MM", t.Format("01"), -1)
+ res = strings.Replace(res, "M", t.Format("1"), -1)
+ res = strings.Replace(res, "DD", t.Format("02"), -1)
+ res = strings.Replace(res, "D", t.Format("2"), -1)
+ res = strings.Replace(res, "YYYY", t.Format("2006"), -1)
+ res = strings.Replace(res, "YY", t.Format("06"), -1)
+ res = strings.Replace(res, "HH", fmt.Sprintf("%02d", t.Hour()), -1)
+ res = strings.Replace(res, "H", fmt.Sprintf("%d", t.Hour()), -1)
+ res = strings.Replace(res, "hh", t.Format("03"), -1)
+ res = strings.Replace(res, "h", t.Format("3"), -1)
+ res = strings.Replace(res, "mm", t.Format("04"), -1)
+ res = strings.Replace(res, "m", t.Format("4"), -1)
+ res = strings.Replace(res, "ss", t.Format("05"), -1)
+ res = strings.Replace(res, "s", t.Format("5"), -1)
+ return res
+}
+
+// DateFormat pattern rules.
+var datePatterns = []string{
+ // year
+ "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
+ "y", "06", //A two digit representation of a year Examples: 99 or 03
+
+ // month
+ "m", "01", // Numeric representation of a month, with leading zeros 01 through 12
+ "n", "1", // Numeric representation of a month, without leading zeros 1 through 12
+ "M", "Jan", // A short textual representation of a month, three letters Jan through Dec
+ "F", "January", // A full textual representation of a month, such as January or March January through December
+
+ // day
+ "d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
+ "j", "2", // Day of the month without leading zeros 1 to 31
+
+ // week
+ "D", "Mon", // A textual representation of a day, three letters Mon through Sun
+ "l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
+
+ // time
+ "g", "3", // 12-hour format of an hour without leading zeros 1 through 12
+ "G", "15", // 24-hour format of an hour without leading zeros 0 through 23
+ "h", "03", // 12-hour format of an hour with leading zeros 01 through 12
+ "H", "15", // 24-hour format of an hour with leading zeros 00 through 23
+
+ "a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm
+ "A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM
+
+ "i", "04", // Minutes with leading zeros 00 to 59
+ "s", "05", // Seconds, with leading zeros 00 through 59
+
+ // time zone
+ "T", "MST",
+ "P", "-07:00",
+ "O", "-0700",
+
+ // RFC 2822
+ "r", time.RFC1123Z,
+}
+
+// Parse Date use PHP time format.
+func DateParse(dateString, format string) (time.Time, error) {
+ replacer := strings.NewReplacer(datePatterns...)
+ format = replacer.Replace(format)
+ return time.ParseInLocation(format, dateString, time.Local)
+}
diff --git a/vendor/github.com/unknwon/com/url.go b/vendor/github.com/unknwon/com/url.go
new file mode 100644
index 0000000000..b0b7c0e12b
--- /dev/null
+++ b/vendor/github.com/unknwon/com/url.go
@@ -0,0 +1,41 @@
+// Copyright 2013 com authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package com
+
+import (
+ "encoding/base64"
+ "net/url"
+)
+
+// url encode string, is + not %20
+func UrlEncode(str string) string {
+ return url.QueryEscape(str)
+}
+
+// url decode string
+func UrlDecode(str string) (string, error) {
+ return url.QueryUnescape(str)
+}
+
+// base64 encode
+func Base64Encode(str string) string {
+ return base64.StdEncoding.EncodeToString([]byte(str))
+}
+
+// base64 decode
+func Base64Decode(str string) (string, error) {
+ s, e := base64.StdEncoding.DecodeString(str)
+ return string(s), e
+}
diff --git a/vendor/github.com/unknwon/i18n/.gitignore b/vendor/github.com/unknwon/i18n/.gitignore
new file mode 100644
index 0000000000..00268614f0
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/.gitignore
@@ -0,0 +1,22 @@
+# 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
diff --git a/vendor/github.com/unknwon/i18n/LICENSE b/vendor/github.com/unknwon/i18n/LICENSE
new file mode 100644
index 0000000000..8405e89a0b
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/LICENSE
@@ -0,0 +1,191 @@
+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:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+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
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/vendor/github.com/unknwon/i18n/Makefile b/vendor/github.com/unknwon/i18n/Makefile
new file mode 100644
index 0000000000..8ff1ac4399
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/Makefile
@@ -0,0 +1,12 @@
+.PHONY: build test bench vet
+
+build: vet bench
+
+test:
+ go test -v -cover
+
+bench:
+ go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+ go vet \ No newline at end of file
diff --git a/vendor/github.com/unknwon/i18n/README.md b/vendor/github.com/unknwon/i18n/README.md
new file mode 100644
index 0000000000..4e3bff7526
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/README.md
@@ -0,0 +1,135 @@
+# i18n [![GoDoc](https://godoc.org/github.com/unknwon/i18n?status.svg)](https://godoc.org/github.com/unknwon/i18n)
+
+Package i18n is for app Internationalization and Localization.
+
+## Introduction
+
+This package provides multiple-language options to improve user experience. Sites like [Go Walker](http://gowalker.org) and [gogs.io](http://gogs.io) are using this module to implement Chinese and English user interfaces.
+
+You can use following command to install this module:
+
+ go get github.com/Unknwon/i18n
+
+## Usage
+
+First of all, you have to import this package:
+
+```go
+import "github.com/unknwon/i18n"
+```
+
+The format of locale files is very like INI format configuration file, which is basically key-value pairs. But this module has some improvements. Every language corresponding to a locale file, for example, suppose there are two files called `locale_en-US.ini` and `locale_zh-CN.ini`.
+
+The name and extensions of locale files can be anything, but we strongly recommend you to follow the style of gogsweb.
+
+## Minimal example
+
+Here are two simplest locale file examples:
+
+File `locale_en-US.ini`:
+
+```ini
+hi = hello, %s
+bye = goodbye
+```
+
+File `locale_zh-CN.ini`:
+
+```ini
+hi = 您好,%s
+bye = 再见
+```
+
+### Do Translation
+
+There are two ways to do translation depends on which way is the best fit for your application or framework.
+
+Directly use package function to translate:
+
+```go
+i18n.Tr("en-US", "hi", "Unknwon")
+i18n.Tr("en-US", "bye")
+```
+
+Or create a struct and embed it:
+
+```go
+type MyController struct{
+ // ...other fields
+ i18n.Locale
+}
+
+//...
+
+func ... {
+ c := &MyController{
+ Locale: i18n.Locale{"en-US"},
+ }
+ _ = c.Tr("hi", "Unknwon")
+ _ = c.Tr("bye")
+}
+```
+
+Code above will produce correspondingly:
+
+- English `en-US`:`hello, Unknwon`, `goodbye`
+- Chinese `zh-CN`:`您好,Unknwon`, `再见`
+
+## Section
+
+For different pages, one key may map to different values. Therefore, i18n module also uses the section feature of INI format configuration to achieve section.
+
+For example, the key name is `about`, and we want to show `About` in the home page and `About Us` in about page. Then you can do following:
+
+Content in locale file:
+
+```ini
+about = About
+
+[about]
+about = About Us
+```
+
+Get `about` in home page:
+
+```go
+i18n.Tr("en-US", "about")
+```
+
+Get `about` in about page:
+
+```go
+i18n.Tr("en-US", "about.about")
+```
+
+### Ambiguity
+
+Because dot `.` is sign of section in both [INI parser](https://github.com/go-ini/ini) and locale files, so when your key name contains `.` will cause ambiguity. At this point, you just need to add one more `.` in front of the key.
+
+For example, the key name is `about.`, then we can use:
+
+```go
+i18n.Tr("en-US", ".about.")
+```
+
+to get expect result.
+
+## Helper tool
+
+Module i18n provides a command line helper tool beei18n for simplify steps of your development. You can install it as follows:
+
+ go get github.com/unknwon/i18n/ui18n
+
+### Sync locale files
+
+Command `sync` allows you use a exist local file as the template to create or sync other locale files:
+
+ ui18n sync srouce_file.ini other1.ini other2.ini
+
+This command can operate 1 or more files in one command.
+
+## More information
+
+- The first locale you load to the module is considered as **default locale**.
+- When matching non-default locale and didn't find the string, i18n will have a second try on default locale.
+- If i18n still cannot find string in the default locale, raw string will be returned. For instance, when the string is `hi` and it does not exist in locale file, simply return `hi` as output.
diff --git a/vendor/github.com/unknwon/i18n/go.mod b/vendor/github.com/unknwon/i18n/go.mod
new file mode 100644
index 0000000000..4d252d68ea
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/go.mod
@@ -0,0 +1,9 @@
+module github.com/unknwon/i18n
+
+go 1.12
+
+require (
+ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
+ github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
+ gopkg.in/ini.v1 v1.46.0
+)
diff --git a/vendor/github.com/unknwon/i18n/go.sum b/vendor/github.com/unknwon/i18n/go.sum
new file mode 100644
index 0000000000..8bb9e76563
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/go.sum
@@ -0,0 +1,21 @@
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
+gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/vendor/github.com/unknwon/i18n/i18n.go b/vendor/github.com/unknwon/i18n/i18n.go
new file mode 100644
index 0000000000..38f0899dd3
--- /dev/null
+++ b/vendor/github.com/unknwon/i18n/i18n.go
@@ -0,0 +1,231 @@
+// Copyright 2013 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package i18n is for app Internationalization and Localization.
+package i18n
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+
+ "gopkg.in/ini.v1"
+)
+
+var (
+ ErrLangAlreadyExist = errors.New("Lang already exists")
+
+ locales = &localeStore{store: make(map[string]*locale)}
+)
+
+type locale struct {
+ id int
+ lang string
+ langDesc string
+ message *ini.File
+}
+
+type localeStore struct {
+ langs []string
+ langDescs []string
+ store map[string]*locale
+ defaultLang string
+}
+
+// Get target language string
+func (d *localeStore) Get(lang, section, format string) (string, bool) {
+ if locale, ok := d.store[lang]; ok {
+ if key, err := locale.message.Section(section).GetKey(format); err == nil {
+ return key.Value(), true
+ }
+ }
+
+ if len(d.defaultLang) > 0 && lang != d.defaultLang {
+ return d.Get(d.defaultLang, section, format)
+ }
+
+ return "", false
+}
+
+func (d *localeStore) Add(lc *locale) bool {
+ if _, ok := d.store[lc.lang]; ok {
+ return false
+ }
+
+ lc.id = len(d.langs)
+ d.langs = append(d.langs, lc.lang)
+ d.langDescs = append(d.langDescs, lc.langDesc)
+ d.store[lc.lang] = lc
+
+ return true
+}
+
+func (d *localeStore) Reload(langs ...string) (err error) {
+ if len(langs) == 0 {
+ for _, lc := range d.store {
+ if err = lc.message.Reload(); err != nil {
+ return err
+ }
+ }
+ } else {
+ for _, lang := range langs {
+ if lc, ok := d.store[lang]; ok {
+ if err = lc.message.Reload(); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// SetDefaultLang sets default language which is a indicator that
+// when target language is not found, try find in default language again.
+func SetDefaultLang(lang string) {
+ locales.defaultLang = lang
+}
+
+// ReloadLangs reloads locale files.
+func ReloadLangs(langs ...string) error {
+ return locales.Reload(langs...)
+}
+
+// Count returns number of languages that are registered.
+func Count() int {
+ return len(locales.langs)
+}
+
+// ListLangs returns list of all locale languages.
+func ListLangs() []string {
+ langs := make([]string, len(locales.langs))
+ copy(langs, locales.langs)
+ return langs
+}
+
+func ListLangDescs() []string {
+ langDescs := make([]string, len(locales.langDescs))
+ copy(langDescs, locales.langDescs)
+ return langDescs
+}
+
+// IsExist returns true if given language locale exists.
+func IsExist(lang string) bool {
+ _, ok := locales.store[lang]
+ return ok
+}
+
+// IndexLang returns index of language locale,
+// it returns -1 if locale not exists.
+func IndexLang(lang string) int {
+ if lc, ok := locales.store[lang]; ok {
+ return lc.id
+ }
+ return -1
+}
+
+// GetLangByIndex return language by given index.
+func GetLangByIndex(index int) string {
+ if index < 0 || index >= len(locales.langs) {
+ return ""
+ }
+ return locales.langs[index]
+}
+
+func GetDescriptionByIndex(index int) string {
+ if index < 0 || index >= len(locales.langDescs) {
+ return ""
+ }
+
+ return locales.langDescs[index]
+}
+
+func GetDescriptionByLang(lang string) string {
+ return GetDescriptionByIndex(IndexLang(lang))
+}
+
+func SetMessageWithDesc(lang, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error {
+ message, err := ini.LoadSources(ini.LoadOptions{
+ IgnoreInlineComment: true,
+ UnescapeValueCommentSymbols: true,
+ }, localeFile, otherLocaleFiles...)
+ if err == nil {
+ message.BlockMode = false
+ lc := new(locale)
+ lc.lang = lang
+ lc.langDesc = langDesc
+ lc.message = message
+
+ if locales.Add(lc) == false {
+ return ErrLangAlreadyExist
+ }
+ }
+ return err
+}
+
+// SetMessage sets the message file for localization.
+func SetMessage(lang string, localeFile interface{}, otherLocaleFiles ...interface{}) error {
+ return SetMessageWithDesc(lang, lang, localeFile, otherLocaleFiles...)
+}
+
+// Locale represents the information of localization.
+type Locale struct {
+ Lang string
+}
+
+// Tr translates content to target language.
+func (l Locale) Tr(format string, args ...interface{}) string {
+ return Tr(l.Lang, format, args...)
+}
+
+// Index returns lang index of LangStore.
+func (l Locale) Index() int {
+ return IndexLang(l.Lang)
+}
+
+// Tr translates content to target language.
+func Tr(lang, format string, args ...interface{}) string {
+ var section string
+
+ idx := strings.IndexByte(format, '.')
+ if idx > 0 {
+ section = format[:idx]
+ format = format[idx+1:]
+ }
+
+ value, ok := locales.Get(lang, section, format)
+ if ok {
+ format = value
+ }
+
+ if len(args) > 0 {
+ params := make([]interface{}, 0, len(args))
+ for _, arg := range args {
+ if arg == nil {
+ continue
+ }
+
+ val := reflect.ValueOf(arg)
+ if val.Kind() == reflect.Slice {
+ for i := 0; i < val.Len(); i++ {
+ params = append(params, val.Index(i).Interface())
+ }
+ } else {
+ params = append(params, arg)
+ }
+ }
+ return fmt.Sprintf(format, params...)
+ }
+ return format
+}
diff --git a/vendor/github.com/unknwon/paginater/.gitignore b/vendor/github.com/unknwon/paginater/.gitignore
new file mode 100644
index 0000000000..e947a9a78b
--- /dev/null
+++ b/vendor/github.com/unknwon/paginater/.gitignore
@@ -0,0 +1,26 @@
+# 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
+paginater.sublime-project
+paginater.sublime-workspace
diff --git a/vendor/github.com/unknwon/paginater/LICENSE b/vendor/github.com/unknwon/paginater/LICENSE
new file mode 100644
index 0000000000..8f71f43fee
--- /dev/null
+++ b/vendor/github.com/unknwon/paginater/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/vendor/github.com/unknwon/paginater/README.md b/vendor/github.com/unknwon/paginater/README.md
new file mode 100644
index 0000000000..8e608ed3ea
--- /dev/null
+++ b/vendor/github.com/unknwon/paginater/README.md
@@ -0,0 +1,65 @@
+Paginater [![Build Status](https://drone.io/github.com/Unknwon/paginater/status.png)](https://drone.io/github.com/Unknwon/paginater/latest) [![](http://gocover.io/_badge/github.com/Unknwon/paginater)](http://gocover.io/github.com/Unknwon/paginater)
+=========
+
+Package paginater is a helper module for custom pagination calculation.
+
+## Installation
+
+ go get github.com/Unknwon/paginater
+
+## Getting Started
+
+The following code shows an example of how to use paginater:
+
+```go
+package main
+
+import "github.com/Unknwon/paginater"
+
+func main() {
+ // Arguments:
+ // - Total number of rows
+ // - Number of rows in one page
+ // - Current page number
+ // - Number of page links
+ p := paginater.New(45, 10, 3, 3)
+
+ // Then use p as a template object named "Page" in "demo.html"
+ // ...
+}
+```
+
+`demo.html`
+
+```html
+{{if not .Page.IsFirst}}[First](1){{end}}
+{{if .Page.HasPrevious}}[Previous]({{.Page.Previous}}){{end}}
+
+{{range .Page.Pages}}
+ {{if eq .Num -1}}
+ ...
+ {{else}}
+ {{.Num}}{{if .IsCurrent}}(current){{end}}
+ {{end}}
+{{end}}
+
+{{if .Page.HasNext}}[Next]({{.Page.Next}}){{end}}
+{{if not .Page.IsLast}}[Last]({{.Page.TotalPages}}){{end}}
+```
+
+Possible output:
+
+```
+[First](1) [Previous](2) ... 2 3(current) 4 ... [Next](4) [Last](5)
+```
+
+As you may guess, if the `Page` value is `-1`, you should print `...` in the HTML as common practice.
+
+## Getting Help
+
+- [API Documentation](https://gowalker.org/github.com/Unknwon/paginater)
+- [File An Issue](https://github.com/Unknwon/paginater/issues/new)
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. \ No newline at end of file
diff --git a/vendor/github.com/unknwon/paginater/paginater.go b/vendor/github.com/unknwon/paginater/paginater.go
new file mode 100644
index 0000000000..ca52a39980
--- /dev/null
+++ b/vendor/github.com/unknwon/paginater/paginater.go
@@ -0,0 +1,192 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package paginater is a helper module for custom pagination calculation.
+package paginater
+
+// Paginater represents a set of results of pagination calculations.
+type Paginater struct {
+ total int
+ pagingNum int
+ current int
+ numPages int
+}
+
+// New initialize a new pagination calculation and returns a Paginater as result.
+func New(total, pagingNum, current, numPages int) *Paginater {
+ if pagingNum <= 0 {
+ pagingNum = 1
+ }
+ if current <= 0 {
+ current = 1
+ }
+ p := &Paginater{total, pagingNum, current, numPages}
+ if p.current > p.TotalPages() {
+ p.current = p.TotalPages()
+ }
+ return p
+}
+
+// IsFirst returns true if current page is the first page.
+func (p *Paginater) IsFirst() bool {
+ return p.current == 1
+}
+
+// HasPrevious returns true if there is a previous page relative to current page.
+func (p *Paginater) HasPrevious() bool {
+ return p.current > 1
+}
+
+func (p *Paginater) Previous() int {
+ if !p.HasPrevious() {
+ return p.current
+ }
+ return p.current - 1
+}
+
+// HasNext returns true if there is a next page relative to current page.
+func (p *Paginater) HasNext() bool {
+ return p.total > p.current*p.pagingNum
+}
+
+func (p *Paginater) Next() int {
+ if !p.HasNext() {
+ return p.current
+ }
+ return p.current + 1
+}
+
+// IsLast returns true if current page is the last page.
+func (p *Paginater) IsLast() bool {
+ if p.total == 0 {
+ return true
+ }
+ return p.total > (p.current-1)*p.pagingNum && !p.HasNext()
+}
+
+// Total returns number of total rows.
+func (p *Paginater) Total() int {
+ return p.total
+}
+
+// TotalPage returns number of total pages.
+func (p *Paginater) TotalPages() int {
+ if p.total == 0 {
+ return 1
+ }
+ if p.total%p.pagingNum == 0 {
+ return p.total / p.pagingNum
+ }
+ return p.total/p.pagingNum + 1
+}
+
+// Current returns current page number.
+func (p *Paginater) Current() int {
+ return p.current
+}
+
+// Page presents a page in the paginater.
+type Page struct {
+ num int
+ isCurrent bool
+}
+
+func (p *Page) Num() int {
+ return p.num
+}
+
+func (p *Page) IsCurrent() bool {
+ return p.isCurrent
+}
+
+func getMiddleIdx(numPages int) int {
+ if numPages%2 == 0 {
+ return numPages / 2
+ }
+ return numPages/2 + 1
+}
+
+// Pages returns a list of nearby page numbers relative to current page.
+// If value is -1 means "..." that more pages are not showing.
+func (p *Paginater) Pages() []*Page {
+ if p.numPages == 0 {
+ return []*Page{}
+ } else if p.numPages == 1 && p.TotalPages() == 1 {
+ // Only show current page.
+ return []*Page{{1, true}}
+ }
+
+ // Total page number is less or equal.
+ if p.TotalPages() <= p.numPages {
+ pages := make([]*Page, p.TotalPages())
+ for i := range pages {
+ pages[i] = &Page{i + 1, i+1 == p.current}
+ }
+ return pages
+ }
+
+ numPages := p.numPages
+ maxIdx := numPages - 1
+ offsetIdx := 0
+ hasMoreNext := false
+
+ // Check more previous and next pages.
+ previousNum := getMiddleIdx(p.numPages) - 1
+ if previousNum > p.current-1 {
+ previousNum -= previousNum - (p.current - 1)
+ }
+ nextNum := p.numPages - previousNum - 1
+ if p.current+nextNum > p.TotalPages() {
+ delta := nextNum - (p.TotalPages() - p.current)
+ nextNum -= delta
+ previousNum += delta
+ }
+
+ offsetVal := p.current - previousNum
+ if offsetVal > 1 {
+ numPages++
+ maxIdx++
+ offsetIdx = 1
+ }
+
+ if p.current+nextNum < p.TotalPages() {
+ numPages++
+ hasMoreNext = true
+ }
+
+ pages := make([]*Page, numPages)
+
+ // There are more previous pages.
+ if offsetIdx == 1 {
+ pages[0] = &Page{-1, false}
+ }
+ // There are more next pages.
+ if hasMoreNext {
+ pages[len(pages)-1] = &Page{-1, false}
+ }
+
+ // Check previous pages.
+ for i := 0; i < previousNum; i++ {
+ pages[offsetIdx+i] = &Page{i + offsetVal, false}
+ }
+
+ pages[offsetIdx+previousNum] = &Page{p.current, true}
+
+ // Check next pages.
+ for i := 1; i <= nextNum; i++ {
+ pages[offsetIdx+previousNum+i] = &Page{p.current + i, false}
+ }
+
+ return pages
+}