mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Update html2text lib to use the original repo (#8900)
This commit is contained in:
committed by
GitHub
parent
8883f48788
commit
c8b99b97df
38
Gopkg.lock
generated
38
Gopkg.lock
generated
@@ -43,12 +43,6 @@
|
||||
packages = ["."]
|
||||
revision = "fd153d5969d30bd701e10f3530228f001213047c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dimchansky/utfbom"
|
||||
packages = ["."]
|
||||
revision = "6c6132ff69f0f6c088739067407b5d32c52e1d0f"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/disintegration/imaging"
|
||||
packages = ["."]
|
||||
@@ -232,6 +226,12 @@
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jaytaylor/html2text"
|
||||
packages = ["."]
|
||||
revision = "64a82a6d140778896f13303121a49d8cb8007034"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jehiah/go-strftime"
|
||||
@@ -270,12 +270,6 @@
|
||||
packages = ["."]
|
||||
revision = "520a119fe3536337cf8feeaf76afae0f5ae193f1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattermost/html2text"
|
||||
packages = ["."]
|
||||
revision = "d47a5532a7bc36ad7b2b8ec3eebe24e975154f94"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattermost/rsc"
|
||||
@@ -286,6 +280,12 @@
|
||||
]
|
||||
revision = "bbaefb05eaa0389ea712340066837c8ce4d287f9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
packages = ["."]
|
||||
@@ -340,6 +340,12 @@
|
||||
revision = "0dc1626d56435e9d605a29875701721c54bc9bbd"
|
||||
version = "v1.10.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/olekukonko/tablewriter"
|
||||
packages = ["."]
|
||||
revision = "d4647c9c7a84d847478d890b816b7d8b62b0b279"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pborman/uuid"
|
||||
packages = ["."]
|
||||
@@ -473,6 +479,12 @@
|
||||
revision = "4f5003aa93559718c866d86fbc795439079484f5"
|
||||
source = "https://github.com/mattermost/viper"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/ssor/bom"
|
||||
packages = ["."]
|
||||
revision = "6386211fdfcf24c0bfbdaceafd02849ed9a8a509"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/objx"
|
||||
packages = ["."]
|
||||
@@ -673,6 +685,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "2c81384fe2861bebdef3a2e44059201880756a17e630bc44f592b9f0f5d9fac8"
|
||||
inputs-digest = "463ec98eba9cac751c2702ca1ecae98740d7dd9a1dc8b615b484ea4bdb41f6b5"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -1516,10 +1516,10 @@ MIT license
|
||||
|
||||
## html2text
|
||||
|
||||
This product contains a modified portion of 'html2text', an HTML to raw text converter by Carlos Tadeu Panato Junior based on `html2text`, built by Jay Taylor.
|
||||
This product contains a modified portion of `html2text`, an HTML to raw text converter, built by Jay Taylor.
|
||||
|
||||
* HOMEPAGE
|
||||
* https://github.com/mattermost/html2text
|
||||
* https://github.com/jaytaylor/html2text
|
||||
|
||||
* LICENSE
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/html2text"
|
||||
"github.com/jaytaylor/html2text"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
37
vendor/github.com/dimchansky/utfbom/.gitignore
generated
vendored
37
vendor/github.com/dimchansky/utfbom/.gitignore
generated
vendored
@@ -1,37 +0,0 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.o
|
||||
*.a
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.prof
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
# Gogland
|
||||
.idea/
|
||||
18
vendor/github.com/dimchansky/utfbom/.travis.yml
generated
vendored
18
vendor/github.com/dimchansky/utfbom/.travis.yml
generated
vendored
@@ -1,18 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- tip
|
||||
|
||||
# sudo=false makes the build run using a container
|
||||
sudo: false
|
||||
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go get github.com/golang/lint/golint
|
||||
script:
|
||||
- gofiles=$(find ./ -name '*.go') && [ -z "$gofiles" ] || unformatted=$(goimports -l $gofiles) && [ -z "$unformatted" ] || (echo >&2 "Go files must be formatted with gofmt. Following files has problem:\n $unformatted" && false)
|
||||
- golint ./... # This won't break the build, just show warnings
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
201
vendor/github.com/dimchansky/utfbom/LICENSE
generated
vendored
201
vendor/github.com/dimchansky/utfbom/LICENSE
generated
vendored
@@ -1,201 +0,0 @@
|
||||
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.
|
||||
81
vendor/github.com/dimchansky/utfbom/README.md
generated
vendored
81
vendor/github.com/dimchansky/utfbom/README.md
generated
vendored
@@ -1,81 +0,0 @@
|
||||
# utfbom [](https://godoc.org/github.com/dimchansky/utfbom) [](https://opensource.org/licenses/Apache-2.0) [](https://travis-ci.org/dimchansky/utfbom) [](https://goreportcard.com/report/github.com/dimchansky/utfbom) [](https://coveralls.io/github/dimchansky/utfbom?branch=master)
|
||||
|
||||
The package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary. It can also return the encoding detected by the BOM.
|
||||
|
||||
## Installation
|
||||
|
||||
go get -u github.com/dimchansky/utfbom
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
)
|
||||
|
||||
func main() {
|
||||
trySkip([]byte("\xEF\xBB\xBFhello"))
|
||||
trySkip([]byte("hello"))
|
||||
}
|
||||
|
||||
func trySkip(byteData []byte) {
|
||||
fmt.Println("Input:", byteData)
|
||||
|
||||
// just skip BOM
|
||||
output, err := ioutil.ReadAll(utfbom.SkipOnly(bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("ReadAll with BOM skipping", output)
|
||||
|
||||
// skip BOM and detect encoding
|
||||
sr, enc := utfbom.Skip(bytes.NewReader(byteData))
|
||||
var encStr string
|
||||
switch enc {
|
||||
case utfbom.UTF8:
|
||||
encStr = "UTF8"
|
||||
case utfbom.UTF16BigEndian:
|
||||
encStr = "UTF16 big endian"
|
||||
case utfbom.UTF16LittleEndian:
|
||||
encStr = "UTF16 little endian"
|
||||
case utfbom.UTF32BigEndian:
|
||||
encStr = "UTF32 big endian"
|
||||
case utfbom.UTF32LittleEndian:
|
||||
encStr = "UTF32 little endian"
|
||||
default:
|
||||
encStr = "Unknown, no byte-order mark found"
|
||||
}
|
||||
fmt.Println("Detected encoding:", encStr)
|
||||
output, err = ioutil.ReadAll(sr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("ReadAll with BOM detection and skipping", output)
|
||||
fmt.Println()
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
$ go run main.go
|
||||
Input: [239 187 191 104 101 108 108 111]
|
||||
ReadAll with BOM skipping [104 101 108 108 111]
|
||||
Detected encoding: UTF8
|
||||
ReadAll with BOM detection and skipping [104 101 108 108 111]
|
||||
|
||||
Input: [104 101 108 108 111]
|
||||
ReadAll with BOM skipping [104 101 108 108 111]
|
||||
Detected encoding: Unknown, no byte-order mark found
|
||||
ReadAll with BOM detection and skipping [104 101 108 108 111]
|
||||
```
|
||||
|
||||
|
||||
174
vendor/github.com/dimchansky/utfbom/utfbom.go
generated
vendored
174
vendor/github.com/dimchansky/utfbom/utfbom.go
generated
vendored
@@ -1,174 +0,0 @@
|
||||
// Package utfbom implements the detection of the BOM (Unicode Byte Order Mark) and removing as necessary.
|
||||
// It wraps an io.Reader object, creating another object (Reader) that also implements the io.Reader
|
||||
// interface but provides automatic BOM checking and removing as necessary.
|
||||
package utfbom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encoding is type alias for detected UTF encoding.
|
||||
type Encoding int
|
||||
|
||||
// Constants to identify detected UTF encodings.
|
||||
const (
|
||||
// Unknown encoding, returned when no BOM was detected
|
||||
Unknown Encoding = iota
|
||||
|
||||
// UTF8, BOM bytes: EF BB BF
|
||||
UTF8
|
||||
|
||||
// UTF-16, big-endian, BOM bytes: FE FF
|
||||
UTF16BigEndian
|
||||
|
||||
// UTF-16, little-endian, BOM bytes: FF FE
|
||||
UTF16LittleEndian
|
||||
|
||||
// UTF-32, big-endian, BOM bytes: 00 00 FE FF
|
||||
UTF32BigEndian
|
||||
|
||||
// UTF-32, little-endian, BOM bytes: FF FE 00 00
|
||||
UTF32LittleEndian
|
||||
)
|
||||
|
||||
const maxConsecutiveEmptyReads = 100
|
||||
|
||||
// Skip creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||
// It also returns the encoding detected by the BOM.
|
||||
// If the detected encoding is not needed, you can call the SkipOnly function.
|
||||
func Skip(rd io.Reader) (*Reader, Encoding) {
|
||||
// Is it already a Reader?
|
||||
b, ok := rd.(*Reader)
|
||||
if ok {
|
||||
return b, Unknown
|
||||
}
|
||||
|
||||
enc, left, err := detectUtf(rd)
|
||||
return &Reader{
|
||||
rd: rd,
|
||||
buf: left,
|
||||
err: err,
|
||||
}, enc
|
||||
}
|
||||
|
||||
// SkipOnly creates Reader which automatically detects BOM (Unicode Byte Order Mark) and removes it as necessary.
|
||||
func SkipOnly(rd io.Reader) *Reader {
|
||||
r, _ := Skip(rd)
|
||||
return r
|
||||
}
|
||||
|
||||
// Reader implements automatic BOM (Unicode Byte Order Mark) checking and
|
||||
// removing as necessary for an io.Reader object.
|
||||
type Reader struct {
|
||||
rd io.Reader // reader provided by the client
|
||||
buf []byte // buffered data
|
||||
err error // last error
|
||||
}
|
||||
|
||||
// Read is an implementation of io.Reader interface.
|
||||
// The bytes are taken from the underlying Reader, but it checks for BOMs, removing them as necessary.
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if r.buf == nil {
|
||||
if r.err != nil {
|
||||
return 0, r.readErr()
|
||||
}
|
||||
|
||||
return r.rd.Read(p)
|
||||
}
|
||||
|
||||
// copy as much as we can
|
||||
n = copy(p, r.buf)
|
||||
r.buf = nilIfEmpty(r.buf[n:])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *Reader) readErr() error {
|
||||
err := r.err
|
||||
r.err = nil
|
||||
return err
|
||||
}
|
||||
|
||||
var errNegativeRead = errors.New("utfbom: reader returned negative count from Read")
|
||||
|
||||
func detectUtf(rd io.Reader) (enc Encoding, buf []byte, err error) {
|
||||
buf, err = readBOM(rd)
|
||||
|
||||
if len(buf) >= 4 {
|
||||
if isUTF32BigEndianBOM4(buf) {
|
||||
return UTF32BigEndian, nilIfEmpty(buf[4:]), err
|
||||
}
|
||||
if isUTF32LittleEndianBOM4(buf) {
|
||||
return UTF32LittleEndian, nilIfEmpty(buf[4:]), err
|
||||
}
|
||||
}
|
||||
|
||||
if len(buf) > 2 && isUTF8BOM3(buf) {
|
||||
return UTF8, nilIfEmpty(buf[3:]), err
|
||||
}
|
||||
|
||||
if (err != nil && err != io.EOF) || (len(buf) < 2) {
|
||||
return Unknown, nilIfEmpty(buf), err
|
||||
}
|
||||
|
||||
if isUTF16BigEndianBOM2(buf) {
|
||||
return UTF16BigEndian, nilIfEmpty(buf[2:]), err
|
||||
}
|
||||
if isUTF16LittleEndianBOM2(buf) {
|
||||
return UTF16LittleEndian, nilIfEmpty(buf[2:]), err
|
||||
}
|
||||
|
||||
return Unknown, nilIfEmpty(buf), err
|
||||
}
|
||||
|
||||
func readBOM(rd io.Reader) (buf []byte, err error) {
|
||||
const maxBOMSize = 4
|
||||
var bom [maxBOMSize]byte // used to read BOM
|
||||
|
||||
// read as many bytes as possible
|
||||
for nEmpty, n := 0, 0; err == nil && len(buf) < maxBOMSize; buf = bom[:len(buf)+n] {
|
||||
if n, err = rd.Read(bom[len(buf):]); n < 0 {
|
||||
panic(errNegativeRead)
|
||||
}
|
||||
if n > 0 {
|
||||
nEmpty = 0
|
||||
} else {
|
||||
nEmpty++
|
||||
if nEmpty >= maxConsecutiveEmptyReads {
|
||||
err = io.ErrNoProgress
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isUTF32BigEndianBOM4(buf []byte) bool {
|
||||
return buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF
|
||||
}
|
||||
|
||||
func isUTF32LittleEndianBOM4(buf []byte) bool {
|
||||
return buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00
|
||||
}
|
||||
|
||||
func isUTF8BOM3(buf []byte) bool {
|
||||
return buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF
|
||||
}
|
||||
|
||||
func isUTF16BigEndianBOM2(buf []byte) bool {
|
||||
return buf[0] == 0xFE && buf[1] == 0xFF
|
||||
}
|
||||
|
||||
func isUTF16LittleEndianBOM2(buf []byte) bool {
|
||||
return buf[0] == 0xFF && buf[1] == 0xFE
|
||||
}
|
||||
|
||||
func nilIfEmpty(buf []byte) (res []byte) {
|
||||
if len(buf) > 0 {
|
||||
res = buf
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Jay Taylor
|
||||
Modified work: Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
137
vendor/github.com/jaytaylor/html2text/README.md
generated
vendored
Normal file
137
vendor/github.com/jaytaylor/html2text/README.md
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
# html2text
|
||||
|
||||
[](https://godoc.org/github.com/jaytaylor/html2text)
|
||||
[](https://travis-ci.org/jaytaylor/html2text)
|
||||
[](https://goreportcard.com/report/github.com/jaytaylor/html2text)
|
||||
|
||||
### Converts HTML into text of the markdown-flavored variety
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
Ensure your emails are readable by all!
|
||||
|
||||
Turns HTML into raw text, useful for sending fancy HTML emails with an equivalently nicely formatted TXT document as a fallback (e.g. for people who don't allow HTML emails or have other display issues).
|
||||
|
||||
html2text is a simple golang package for rendering HTML into plaintext.
|
||||
|
||||
There are still lots of improvements to be had, but FWIW this has worked fine for my [basic] HTML-2-text needs.
|
||||
|
||||
It requires go 1.x or newer ;)
|
||||
|
||||
|
||||
## Download the package
|
||||
|
||||
```bash
|
||||
go get github.com/jaytaylor/html2text
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jaytaylor/html2text"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inputHTML := `
|
||||
<html>
|
||||
<head>
|
||||
<title>My Mega Service</title>
|
||||
<link rel=\"stylesheet\" href=\"main.css\">
|
||||
<style type=\"text/css\">body { color: #fff; }</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="logo">
|
||||
<a href="http://jaytaylor.com/"><img src="/logo-image.jpg" alt="Mega Service"/></a>
|
||||
</div>
|
||||
|
||||
<h1>Welcome to your new account on my service!</h1>
|
||||
|
||||
<p>
|
||||
Here is some more information:
|
||||
|
||||
<ul>
|
||||
<li>Link 1: <a href="https://example.com">Example.com</a></li>
|
||||
<li>Link 2: <a href="https://example2.com">Example2.com</a></li>
|
||||
<li>Something else</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Header 1</th><th>Header 2</th></tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<tr><td>Footer 1</td><td>Footer 2</td></tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
<tr><td>Row 1 Col 1</td><td>Row 1 Col 2</td></tr>
|
||||
<tr><td>Row 2 Col 1</td><td>Row 2 Col 2</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
text, err := html2text.FromString(inputHTML, html2text.Options{PrettyTables: true})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(text)
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Mega Service ( http://jaytaylor.com/ )
|
||||
|
||||
******************************************
|
||||
Welcome to your new account on my service!
|
||||
******************************************
|
||||
|
||||
Here is some more information:
|
||||
|
||||
* Link 1: Example.com ( https://example.com )
|
||||
* Link 2: Example2.com ( https://example2.com )
|
||||
* Something else
|
||||
|
||||
+-------------+-------------+
|
||||
| HEADER 1 | HEADER 2 |
|
||||
+-------------+-------------+
|
||||
| Row 1 Col 1 | Row 1 Col 2 |
|
||||
| Row 2 Col 1 | Row 2 Col 2 |
|
||||
+-------------+-------------+
|
||||
| FOOTER 1 | FOOTER 2 |
|
||||
+-------------+-------------+
|
||||
```
|
||||
|
||||
|
||||
## Unit-tests
|
||||
|
||||
Running the unit-tests is straightforward and standard:
|
||||
|
||||
```bash
|
||||
go test
|
||||
```
|
||||
|
||||
|
||||
# License
|
||||
|
||||
Permissive MIT license.
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
You are more than welcome to open issues and send pull requests if you find a bug or want a new feature.
|
||||
|
||||
If you appreciate this library please feel free to drop me a line and tell me! It's always nice to hear from people who have benefitted from my work.
|
||||
|
||||
Email: jay at (my github username).com
|
||||
|
||||
Twitter: [@jtaylor](https://twitter.com/jtaylor)
|
||||
|
||||
473
vendor/github.com/jaytaylor/html2text/html2text.go
generated
vendored
Normal file
473
vendor/github.com/jaytaylor/html2text/html2text.go
generated
vendored
Normal file
@@ -0,0 +1,473 @@
|
||||
package html2text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/ssor/bom"
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
// Options provide toggles and overrides to control specific rendering behaviors.
|
||||
type Options struct {
|
||||
PrettyTables bool // Turns on pretty ASCII rendering for table elements.
|
||||
OmitLinks bool // Turns on omitting links
|
||||
}
|
||||
|
||||
// FromHTMLNode renders text output from a pre-parsed HTML document.
|
||||
func FromHTMLNode(doc *html.Node, o ...Options) (string, error) {
|
||||
var options Options
|
||||
if len(o) > 0 {
|
||||
options = o[0]
|
||||
}
|
||||
|
||||
ctx := textifyTraverseContext{
|
||||
buf: bytes.Buffer{},
|
||||
options: options,
|
||||
}
|
||||
if err := ctx.traverse(doc); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(newlineRe.ReplaceAllString(
|
||||
strings.Replace(ctx.buf.String(), "\n ", "\n", -1), "\n\n"),
|
||||
)
|
||||
return text, nil
|
||||
}
|
||||
|
||||
// FromReader renders text output after parsing HTML for the specified
|
||||
// io.Reader.
|
||||
func FromReader(reader io.Reader, options ...Options) (string, error) {
|
||||
newReader, err := bom.NewReaderWithoutBom(reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
doc, err := html.Parse(newReader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return FromHTMLNode(doc, options...)
|
||||
}
|
||||
|
||||
// FromString parses HTML from the input string, then renders the text form.
|
||||
func FromString(input string, options ...Options) (string, error) {
|
||||
bs := bom.CleanBom([]byte(input))
|
||||
text, err := FromReader(bytes.NewReader(bs), options...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
var (
|
||||
spacingRe = regexp.MustCompile(`[ \r\n\t]+`)
|
||||
newlineRe = regexp.MustCompile(`\n\n+`)
|
||||
)
|
||||
|
||||
// traverseTableCtx holds text-related context.
|
||||
type textifyTraverseContext struct {
|
||||
buf bytes.Buffer
|
||||
|
||||
prefix string
|
||||
tableCtx tableTraverseContext
|
||||
options Options
|
||||
endsWithSpace bool
|
||||
justClosedDiv bool
|
||||
blockquoteLevel int
|
||||
lineLength int
|
||||
isPre bool
|
||||
}
|
||||
|
||||
// tableTraverseContext holds table ASCII-form related context.
|
||||
type tableTraverseContext struct {
|
||||
header []string
|
||||
body [][]string
|
||||
footer []string
|
||||
tmpRow int
|
||||
isInFooter bool
|
||||
}
|
||||
|
||||
func (tableCtx *tableTraverseContext) init() {
|
||||
tableCtx.body = [][]string{}
|
||||
tableCtx.header = []string{}
|
||||
tableCtx.footer = []string{}
|
||||
tableCtx.isInFooter = false
|
||||
tableCtx.tmpRow = 0
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseContext) handleElement(node *html.Node) error {
|
||||
ctx.justClosedDiv = false
|
||||
|
||||
switch node.DataAtom {
|
||||
case atom.Br:
|
||||
return ctx.emit("\n")
|
||||
|
||||
case atom.H1, atom.H2, atom.H3:
|
||||
subCtx := textifyTraverseContext{}
|
||||
if err := subCtx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str := subCtx.buf.String()
|
||||
dividerLen := 0
|
||||
for _, line := range strings.Split(str, "\n") {
|
||||
if lineLen := len([]rune(line)); lineLen-1 > dividerLen {
|
||||
dividerLen = lineLen - 1
|
||||
}
|
||||
}
|
||||
var divider string
|
||||
if node.DataAtom == atom.H1 {
|
||||
divider = strings.Repeat("*", dividerLen)
|
||||
} else {
|
||||
divider = strings.Repeat("-", dividerLen)
|
||||
}
|
||||
|
||||
if node.DataAtom == atom.H3 {
|
||||
return ctx.emit("\n\n" + str + "\n" + divider + "\n\n")
|
||||
}
|
||||
return ctx.emit("\n\n" + divider + "\n" + str + "\n" + divider + "\n\n")
|
||||
|
||||
case atom.Blockquote:
|
||||
ctx.blockquoteLevel++
|
||||
ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel) + " "
|
||||
if err := ctx.emit("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.blockquoteLevel == 1 {
|
||||
if err := ctx.emit("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.blockquoteLevel--
|
||||
ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel)
|
||||
if ctx.blockquoteLevel > 0 {
|
||||
ctx.prefix += " "
|
||||
}
|
||||
return ctx.emit("\n\n")
|
||||
|
||||
case atom.Div:
|
||||
if ctx.lineLength > 0 {
|
||||
if err := ctx.emit("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
if !ctx.justClosedDiv {
|
||||
err = ctx.emit("\n")
|
||||
}
|
||||
ctx.justClosedDiv = true
|
||||
return err
|
||||
|
||||
case atom.Li:
|
||||
if err := ctx.emit("* "); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.emit("\n")
|
||||
|
||||
case atom.B, atom.Strong:
|
||||
subCtx := textifyTraverseContext{}
|
||||
subCtx.endsWithSpace = true
|
||||
if err := subCtx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
str := subCtx.buf.String()
|
||||
return ctx.emit("*" + str + "*")
|
||||
|
||||
case atom.A:
|
||||
linkText := ""
|
||||
// For simple link element content with single text node only, peek at the link text.
|
||||
if node.FirstChild != nil && node.FirstChild.NextSibling == nil && node.FirstChild.Type == html.TextNode {
|
||||
linkText = node.FirstChild.Data
|
||||
}
|
||||
|
||||
// If image is the only child, take its alt text as the link text.
|
||||
if img := node.FirstChild; img != nil && node.LastChild == img && img.DataAtom == atom.Img {
|
||||
if altText := getAttrVal(img, "alt"); altText != "" {
|
||||
if err := ctx.emit(altText); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hrefLink := ""
|
||||
if attrVal := getAttrVal(node, "href"); attrVal != "" {
|
||||
attrVal = ctx.normalizeHrefLink(attrVal)
|
||||
// Don't print link href if it matches link element content or if the link is empty.
|
||||
if !ctx.options.OmitLinks && attrVal != "" && linkText != attrVal {
|
||||
hrefLink = "( " + attrVal + " )"
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.emit(hrefLink)
|
||||
|
||||
case atom.P, atom.Ul:
|
||||
return ctx.paragraphHandler(node)
|
||||
|
||||
case atom.Table, atom.Tfoot, atom.Th, atom.Tr, atom.Td:
|
||||
if ctx.options.PrettyTables {
|
||||
return ctx.handleTableElement(node)
|
||||
} else if node.DataAtom == atom.Table {
|
||||
return ctx.paragraphHandler(node)
|
||||
}
|
||||
return ctx.traverseChildren(node)
|
||||
|
||||
case atom.Pre:
|
||||
ctx.isPre = true
|
||||
err := ctx.traverseChildren(node)
|
||||
ctx.isPre = false
|
||||
return err
|
||||
|
||||
case atom.Style, atom.Script, atom.Head:
|
||||
// Ignore the subtree.
|
||||
return nil
|
||||
|
||||
default:
|
||||
return ctx.traverseChildren(node)
|
||||
}
|
||||
}
|
||||
|
||||
// paragraphHandler renders node children surrounded by double newlines.
|
||||
func (ctx *textifyTraverseContext) paragraphHandler(node *html.Node) error {
|
||||
if err := ctx.emit("\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.emit("\n\n")
|
||||
}
|
||||
|
||||
// handleTableElement is only to be invoked when options.PrettyTables is active.
|
||||
func (ctx *textifyTraverseContext) handleTableElement(node *html.Node) error {
|
||||
if !ctx.options.PrettyTables {
|
||||
panic("handleTableElement invoked when PrettyTables not active")
|
||||
}
|
||||
|
||||
switch node.DataAtom {
|
||||
case atom.Table:
|
||||
if err := ctx.emit("\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Re-intialize all table context.
|
||||
ctx.tableCtx.init()
|
||||
|
||||
// Browse children, enriching context with table data.
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
table := tablewriter.NewWriter(buf)
|
||||
table.SetHeader(ctx.tableCtx.header)
|
||||
table.SetFooter(ctx.tableCtx.footer)
|
||||
table.AppendBulk(ctx.tableCtx.body)
|
||||
|
||||
// Render the table using ASCII.
|
||||
table.Render()
|
||||
if err := ctx.emit(buf.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.emit("\n\n")
|
||||
|
||||
case atom.Tfoot:
|
||||
ctx.tableCtx.isInFooter = true
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.tableCtx.isInFooter = false
|
||||
|
||||
case atom.Tr:
|
||||
ctx.tableCtx.body = append(ctx.tableCtx.body, []string{})
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.tableCtx.tmpRow++
|
||||
|
||||
case atom.Th:
|
||||
res, err := ctx.renderEachChild(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.tableCtx.header = append(ctx.tableCtx.header, res)
|
||||
|
||||
case atom.Td:
|
||||
res, err := ctx.renderEachChild(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.tableCtx.isInFooter {
|
||||
ctx.tableCtx.footer = append(ctx.tableCtx.footer, res)
|
||||
} else {
|
||||
ctx.tableCtx.body[ctx.tableCtx.tmpRow] = append(ctx.tableCtx.body[ctx.tableCtx.tmpRow], res)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseContext) traverse(node *html.Node) error {
|
||||
switch node.Type {
|
||||
default:
|
||||
return ctx.traverseChildren(node)
|
||||
|
||||
case html.TextNode:
|
||||
var data string
|
||||
if ctx.isPre {
|
||||
data = node.Data
|
||||
} else {
|
||||
data = strings.Trim(spacingRe.ReplaceAllString(node.Data, " "), " ")
|
||||
}
|
||||
return ctx.emit(data)
|
||||
|
||||
case html.ElementNode:
|
||||
return ctx.handleElement(node)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseContext) traverseChildren(node *html.Node) error {
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||
if err := ctx.traverse(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseContext) emit(data string) error {
|
||||
if data == "" {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
lines = ctx.breakLongLines(data)
|
||||
err error
|
||||
)
|
||||
for _, line := range lines {
|
||||
runes := []rune(line)
|
||||
startsWithSpace := unicode.IsSpace(runes[0])
|
||||
if !startsWithSpace && !ctx.endsWithSpace && !strings.HasPrefix(data, ".") {
|
||||
if err = ctx.buf.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.lineLength++
|
||||
}
|
||||
ctx.endsWithSpace = unicode.IsSpace(runes[len(runes)-1])
|
||||
for _, c := range line {
|
||||
if _, err = ctx.buf.WriteString(string(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.lineLength++
|
||||
if c == '\n' {
|
||||
ctx.lineLength = 0
|
||||
if ctx.prefix != "" {
|
||||
if _, err = ctx.buf.WriteString(ctx.prefix); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxLineLen = 74
|
||||
|
||||
func (ctx *textifyTraverseContext) breakLongLines(data string) []string {
|
||||
// Only break lines when in blockquotes.
|
||||
if ctx.blockquoteLevel == 0 {
|
||||
return []string{data}
|
||||
}
|
||||
var (
|
||||
ret = []string{}
|
||||
runes = []rune(data)
|
||||
l = len(runes)
|
||||
existing = ctx.lineLength
|
||||
)
|
||||
if existing >= maxLineLen {
|
||||
ret = append(ret, "\n")
|
||||
existing = 0
|
||||
}
|
||||
for l+existing > maxLineLen {
|
||||
i := maxLineLen - existing
|
||||
for i >= 0 && !unicode.IsSpace(runes[i]) {
|
||||
i--
|
||||
}
|
||||
if i == -1 {
|
||||
// No spaces, so go the other way.
|
||||
i = maxLineLen - existing
|
||||
for i < l && !unicode.IsSpace(runes[i]) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
ret = append(ret, string(runes[:i])+"\n")
|
||||
for i < l && unicode.IsSpace(runes[i]) {
|
||||
i++
|
||||
}
|
||||
runes = runes[i:]
|
||||
l = len(runes)
|
||||
existing = 0
|
||||
}
|
||||
if len(runes) > 0 {
|
||||
ret = append(ret, string(runes))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseContext) normalizeHrefLink(link string) string {
|
||||
link = strings.TrimSpace(link)
|
||||
link = strings.TrimPrefix(link, "mailto:")
|
||||
return link
|
||||
}
|
||||
|
||||
// renderEachChild visits each direct child of a node and collects the sequence of
|
||||
// textuual representaitons separated by a single newline.
|
||||
func (ctx *textifyTraverseContext) renderEachChild(node *html.Node) (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||
s, err := FromHTMLNode(c, ctx.options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err = buf.WriteString(s); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if c.NextSibling != nil {
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func getAttrVal(node *html.Node, attrName string) string {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == attrName {
|
||||
return attr.Val
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
108
vendor/github.com/mattermost/html2text/README.md
generated
vendored
108
vendor/github.com/mattermost/html2text/README.md
generated
vendored
@@ -1,108 +0,0 @@
|
||||
# html2text
|
||||
|
||||
[](https://godoc.org/github.com/cpanato/html2text)
|
||||
[](https://travis-ci.org/cpanato/html2text)
|
||||
[](https://goreportcard.com/report/github.com/cpanato/html2text)
|
||||
|
||||
### Initial information
|
||||
This project was forked from [github.com/jaytaylor/html2text](https://github.com/jaytaylor/html2text) in order to use another clean bom library due the original one has no license.
|
||||
|
||||
|
||||
### Converts HTML into text
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
Ensure your emails are readable by all!
|
||||
|
||||
Turns HTML into raw text, useful for sending fancy HTML emails with a equivalently nicely formatted TXT document as a fallback (e.g. for people who don't allow HTML emails or have other display issues).
|
||||
|
||||
html2text is a simple golang package for rendering HTML into plaintext.
|
||||
|
||||
There are still lots of improvements to be had, but FWIW this has worked fine for my [basic] HTML-2-text needs.
|
||||
|
||||
It requires go 1.x or newer ;)
|
||||
|
||||
|
||||
## Download the package
|
||||
|
||||
```bash
|
||||
go get github.com/cpanato/html2text
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cpanato/html2text"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inputHtml := `
|
||||
<html>
|
||||
<head>
|
||||
<title>My Mega Service</title>
|
||||
<link rel=\"stylesheet\" href=\"main.css\">
|
||||
<style type=\"text/css\">body { color: #fff; }</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="logo">
|
||||
<a href="http://mymegaservice.com/"><img src="/logo-image.jpg" alt="Mega Service"/></a>
|
||||
</div>
|
||||
|
||||
<h1>Welcome to your new account on my service!</h1>
|
||||
|
||||
<p>
|
||||
Here is some more information:
|
||||
|
||||
<ul>
|
||||
<li>Link 1: <a href="https://example.com">Example.com</a></li>
|
||||
<li>Link 2: <a href="https://example2.com">Example2.com</a></li>
|
||||
<li>Something else</li>
|
||||
</ul>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
text, err := html2text.FromString(inputHtml)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(text)
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Mega Service ( http://mymegaservice.com/ )
|
||||
|
||||
******************************************
|
||||
Welcome to your new account on my service!
|
||||
******************************************
|
||||
|
||||
Here is some more information:
|
||||
|
||||
* Link 1: Example.com ( https://example.com )
|
||||
* Link 2: Example2.com ( https://example2.com )
|
||||
* Something else
|
||||
```
|
||||
|
||||
|
||||
## Unit-tests
|
||||
|
||||
Running the unit-tests is straightforward and standard:
|
||||
|
||||
```bash
|
||||
go test
|
||||
```
|
||||
|
||||
|
||||
# License
|
||||
|
||||
Permissive MIT license.
|
||||
312
vendor/github.com/mattermost/html2text/html2text.go
generated
vendored
312
vendor/github.com/mattermost/html2text/html2text.go
generated
vendored
@@ -1,312 +0,0 @@
|
||||
package html2text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
var (
|
||||
spacingRe = regexp.MustCompile(`[ \r\n\t]+`)
|
||||
newlineRe = regexp.MustCompile(`\n\n+`)
|
||||
)
|
||||
|
||||
type textifyTraverseCtx struct {
|
||||
Buf bytes.Buffer
|
||||
|
||||
prefix string
|
||||
blockquoteLevel int
|
||||
lineLength int
|
||||
endsWithSpace bool
|
||||
endsWithNewline bool
|
||||
justClosedDiv bool
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseCtx) traverse(node *html.Node) error {
|
||||
switch node.Type {
|
||||
default:
|
||||
return ctx.traverseChildren(node)
|
||||
|
||||
case html.TextNode:
|
||||
data := strings.Trim(spacingRe.ReplaceAllString(node.Data, " "), " ")
|
||||
return ctx.emit(data)
|
||||
|
||||
case html.ElementNode:
|
||||
return ctx.handleElementNode(node)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseCtx) handleElementNode(node *html.Node) error {
|
||||
ctx.justClosedDiv = false
|
||||
switch node.DataAtom {
|
||||
case atom.Br:
|
||||
return ctx.emit("\n")
|
||||
|
||||
case atom.H1, atom.H2, atom.H3:
|
||||
subCtx := textifyTraverseCtx{}
|
||||
if err := subCtx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
str := subCtx.Buf.String()
|
||||
dividerLen := 0
|
||||
for _, line := range strings.Split(str, "\n") {
|
||||
if lineLen := len([]rune(line)); lineLen-1 > dividerLen {
|
||||
dividerLen = lineLen - 1
|
||||
}
|
||||
}
|
||||
divider := ""
|
||||
if node.DataAtom == atom.H1 {
|
||||
divider = strings.Repeat("*", dividerLen)
|
||||
} else {
|
||||
divider = strings.Repeat("-", dividerLen)
|
||||
}
|
||||
|
||||
if node.DataAtom == atom.H3 {
|
||||
return ctx.emit("\n\n" + str + "\n" + divider + "\n\n")
|
||||
}
|
||||
return ctx.emit("\n\n" + divider + "\n" + str + "\n" + divider + "\n\n")
|
||||
|
||||
case atom.Blockquote:
|
||||
ctx.blockquoteLevel++
|
||||
ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel) + " "
|
||||
if err := ctx.emit("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.blockquoteLevel == 1 {
|
||||
if err := ctx.emit("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.blockquoteLevel--
|
||||
ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel)
|
||||
if ctx.blockquoteLevel > 0 {
|
||||
ctx.prefix += " "
|
||||
}
|
||||
return ctx.emit("\n\n")
|
||||
|
||||
case atom.Div:
|
||||
if ctx.lineLength > 0 {
|
||||
if err := ctx.emit("\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
if ctx.justClosedDiv == false {
|
||||
err = ctx.emit("\n")
|
||||
}
|
||||
ctx.justClosedDiv = true
|
||||
return err
|
||||
|
||||
case atom.Li:
|
||||
if err := ctx.emit("* "); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.emit("\n")
|
||||
|
||||
case atom.B, atom.Strong:
|
||||
subCtx := textifyTraverseCtx{}
|
||||
subCtx.endsWithSpace = true
|
||||
if err := subCtx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
str := subCtx.Buf.String()
|
||||
return ctx.emit("*" + str + "*")
|
||||
|
||||
case atom.A:
|
||||
// If image is the only child, take its alt text as the link text
|
||||
if img := node.FirstChild; img != nil && node.LastChild == img && img.DataAtom == atom.Img {
|
||||
if altText := getAttrVal(img, "alt"); altText != "" {
|
||||
ctx.emit(altText)
|
||||
}
|
||||
} else if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hrefLink := ""
|
||||
if attrVal := getAttrVal(node, "href"); attrVal != "" {
|
||||
attrVal = ctx.normalizeHrefLink(attrVal)
|
||||
if attrVal != "" {
|
||||
hrefLink = "( " + attrVal + " )"
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.emit(hrefLink)
|
||||
|
||||
case atom.P, atom.Ul, atom.Table:
|
||||
if err := ctx.emit("\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.emit("\n\n")
|
||||
|
||||
case atom.Tr:
|
||||
if err := ctx.traverseChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.emit("\n")
|
||||
|
||||
case atom.Style, atom.Script, atom.Head:
|
||||
// Ignore the subtree
|
||||
return nil
|
||||
|
||||
default:
|
||||
return ctx.traverseChildren(node)
|
||||
}
|
||||
}
|
||||
func (ctx *textifyTraverseCtx) traverseChildren(node *html.Node) error {
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||
if err := ctx.traverse(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseCtx) emit(data string) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
lines := ctx.breakLongLines(data)
|
||||
var err error
|
||||
for _, line := range lines {
|
||||
runes := []rune(line)
|
||||
startsWithSpace := unicode.IsSpace(runes[0])
|
||||
if !startsWithSpace && !ctx.endsWithSpace {
|
||||
ctx.Buf.WriteByte(' ')
|
||||
ctx.lineLength++
|
||||
}
|
||||
ctx.endsWithSpace = unicode.IsSpace(runes[len(runes)-1])
|
||||
for _, c := range line {
|
||||
_, err = ctx.Buf.WriteString(string(c))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.lineLength++
|
||||
if c == '\n' {
|
||||
ctx.lineLength = 0
|
||||
if ctx.prefix != "" {
|
||||
_, err = ctx.Buf.WriteString(ctx.prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseCtx) breakLongLines(data string) []string {
|
||||
// only break lines when we are in blockquotes
|
||||
if ctx.blockquoteLevel == 0 {
|
||||
return []string{data}
|
||||
}
|
||||
var ret []string
|
||||
runes := []rune(data)
|
||||
l := len(runes)
|
||||
existing := ctx.lineLength
|
||||
if existing >= 74 {
|
||||
ret = append(ret, "\n")
|
||||
existing = 0
|
||||
}
|
||||
for l+existing > 74 {
|
||||
i := 74 - existing
|
||||
for i >= 0 && !unicode.IsSpace(runes[i]) {
|
||||
i--
|
||||
}
|
||||
if i == -1 {
|
||||
// no spaces, so go the other way
|
||||
i = 74 - existing
|
||||
for i < l && !unicode.IsSpace(runes[i]) {
|
||||
i++
|
||||
}
|
||||
}
|
||||
ret = append(ret, string(runes[:i])+"\n")
|
||||
for i < l && unicode.IsSpace(runes[i]) {
|
||||
i++
|
||||
}
|
||||
runes = runes[i:]
|
||||
l = len(runes)
|
||||
existing = 0
|
||||
}
|
||||
if len(runes) > 0 {
|
||||
ret = append(ret, string(runes))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (ctx *textifyTraverseCtx) normalizeHrefLink(link string) string {
|
||||
link = strings.TrimSpace(link)
|
||||
link = strings.TrimPrefix(link, "mailto:")
|
||||
return link
|
||||
}
|
||||
|
||||
func getAttrVal(node *html.Node, attrName string) string {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == attrName {
|
||||
return attr.Val
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func FromHtmlNode(doc *html.Node) (string, error) {
|
||||
ctx := textifyTraverseCtx{
|
||||
Buf: bytes.Buffer{},
|
||||
}
|
||||
if err := ctx.traverse(doc); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(newlineRe.ReplaceAllString(
|
||||
strings.Replace(ctx.Buf.String(), "\n ", "\n", -1), "\n\n"))
|
||||
return text, nil
|
||||
|
||||
}
|
||||
|
||||
func FromReader(reader io.Reader) (string, error) {
|
||||
bs, err := ioutil.ReadAll(reader)
|
||||
newReader, _ := utfbom.Skip(bytes.NewReader(bs))
|
||||
|
||||
doc, err := html.Parse(newReader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return FromHtmlNode(doc)
|
||||
}
|
||||
|
||||
func FromString(input string) (string, error) {
|
||||
bs := utfbom.SkipOnly(bytes.NewReader([]byte(input)))
|
||||
text, err := FromReader(bs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
8
vendor/github.com/mattn/go-runewidth/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/mattn/go-runewidth/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken lAKAWPzcGsD3A8yBX3BGGtRUdJ6CaGERL
|
||||
21
vendor/github.com/mattn/go-runewidth/LICENSE
generated
vendored
Normal file
21
vendor/github.com/mattn/go-runewidth/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
27
vendor/github.com/mattn/go-runewidth/README.mkd
generated
vendored
Normal file
27
vendor/github.com/mattn/go-runewidth/README.mkd
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
go-runewidth
|
||||
============
|
||||
|
||||
[](https://travis-ci.org/mattn/go-runewidth)
|
||||
[](https://coveralls.io/r/mattn/go-runewidth?branch=HEAD)
|
||||
[](http://godoc.org/github.com/mattn/go-runewidth)
|
||||
[](https://goreportcard.com/report/github.com/mattn/go-runewidth)
|
||||
|
||||
Provides functions to get fixed width of the character or string.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```go
|
||||
runewidth.StringWidth("つのだ☆HIRO") == 12
|
||||
```
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Yasuhiro Matsumoto
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2013
|
||||
1223
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
Normal file
1223
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8
vendor/github.com/mattn/go-runewidth/runewidth_js.go
generated
vendored
Normal file
8
vendor/github.com/mattn/go-runewidth/runewidth_js.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build js
|
||||
|
||||
package runewidth
|
||||
|
||||
func IsEastAsian() bool {
|
||||
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
|
||||
return false
|
||||
}
|
||||
77
vendor/github.com/mattn/go-runewidth/runewidth_posix.go
generated
vendored
Normal file
77
vendor/github.com/mattn/go-runewidth/runewidth_posix.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// +build !windows,!js
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
|
||||
|
||||
var mblenTable = map[string]int{
|
||||
"utf-8": 6,
|
||||
"utf8": 6,
|
||||
"jis": 8,
|
||||
"eucjp": 3,
|
||||
"euckr": 2,
|
||||
"euccn": 2,
|
||||
"sjis": 2,
|
||||
"cp932": 2,
|
||||
"cp51932": 2,
|
||||
"cp936": 2,
|
||||
"cp949": 2,
|
||||
"cp950": 2,
|
||||
"big5": 2,
|
||||
"gbk": 2,
|
||||
"gb2312": 2,
|
||||
}
|
||||
|
||||
func isEastAsian(locale string) bool {
|
||||
charset := strings.ToLower(locale)
|
||||
r := reLoc.FindStringSubmatch(locale)
|
||||
if len(r) == 2 {
|
||||
charset = strings.ToLower(r[1])
|
||||
}
|
||||
|
||||
if strings.HasSuffix(charset, "@cjk_narrow") {
|
||||
return false
|
||||
}
|
||||
|
||||
for pos, b := range []byte(charset) {
|
||||
if b == '@' {
|
||||
charset = charset[:pos]
|
||||
break
|
||||
}
|
||||
}
|
||||
max := 1
|
||||
if m, ok := mblenTable[charset]; ok {
|
||||
max = m
|
||||
}
|
||||
if max > 1 && (charset[0] != 'u' ||
|
||||
strings.HasPrefix(locale, "ja") ||
|
||||
strings.HasPrefix(locale, "ko") ||
|
||||
strings.HasPrefix(locale, "zh")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
locale := os.Getenv("LC_CTYPE")
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LANG")
|
||||
}
|
||||
|
||||
// ignore C locale
|
||||
if locale == "POSIX" || locale == "C" {
|
||||
return false
|
||||
}
|
||||
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
|
||||
return false
|
||||
}
|
||||
|
||||
return isEastAsian(locale)
|
||||
}
|
||||
25
vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
Normal file
25
vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
|
||||
)
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
r1, _, _ := procGetConsoleOutputCP.Call()
|
||||
if r1 == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch int(r1) {
|
||||
case 932, 51932, 936, 949, 950:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
15
vendor/github.com/olekukonko/tablewriter/.gitignore
generated
vendored
Normal file
15
vendor/github.com/olekukonko/tablewriter/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
12
vendor/github.com/olekukonko/tablewriter/.travis.yml
generated
vendored
Normal file
12
vendor/github.com/olekukonko/tablewriter/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
||||
19
vendor/github.com/olekukonko/tablewriter/LICENCE.md
generated
vendored
Normal file
19
vendor/github.com/olekukonko/tablewriter/LICENCE.md
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014 by Oleku Konko
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
277
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
Normal file
277
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
ASCII Table Writer
|
||||
=========
|
||||
|
||||
[](https://travis-ci.org/olekukonko/tablewriter)
|
||||
[](https://sourcegraph.com/github.com/olekukonko/tablewriter)
|
||||
[](https://godoc.org/github.com/olekukonko/tablewriter)
|
||||
|
||||
Generate ASCII table on the fly ... Installation is simple as
|
||||
|
||||
go get github.com/olekukonko/tablewriter
|
||||
|
||||
|
||||
#### Features
|
||||
- Automatic Padding
|
||||
- Support Multiple Lines
|
||||
- Supports Alignment
|
||||
- Support Custom Separators
|
||||
- Automatic Alignment of numbers & percentage
|
||||
- Write directly to http , file etc via `io.Writer`
|
||||
- Read directly from CSV file
|
||||
- Optional row line via `SetRowLine`
|
||||
- Normalise table header
|
||||
- Make CSV Headers optional
|
||||
- Enable or disable table border
|
||||
- Set custom footer support
|
||||
- Optional identical cells merging
|
||||
- Set custom caption
|
||||
- Optional reflowing of paragrpahs in multi-line cells.
|
||||
|
||||
#### Example 1 - Basic
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"A", "The Good", "500"},
|
||||
[]string{"B", "The Very very Bad Man", "288"},
|
||||
[]string{"C", "The Ugly", "120"},
|
||||
[]string{"D", "The Gopher", "800"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Name", "Sign", "Rating"})
|
||||
|
||||
for _, v := range data {
|
||||
table.Append(v)
|
||||
}
|
||||
table.Render() // Send output
|
||||
```
|
||||
|
||||
##### Output 1
|
||||
```
|
||||
+------+-----------------------+--------+
|
||||
| NAME | SIGN | RATING |
|
||||
+------+-----------------------+--------+
|
||||
| A | The Good | 500 |
|
||||
| B | The Very very Bad Man | 288 |
|
||||
| C | The Ugly | 120 |
|
||||
| D | The Gopher | 800 |
|
||||
+------+-----------------------+--------+
|
||||
```
|
||||
|
||||
#### Example 2 - Without Border / Footer / Bulk Append
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
|
||||
table.SetBorder(false) // Set Border to false
|
||||
table.AppendBulk(data) // Add Bulk Data
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 2
|
||||
```
|
||||
|
||||
DATE | DESCRIPTION | CV2 | AMOUNT
|
||||
+----------+--------------------------+-------+---------+
|
||||
1/1/2014 | Domain name | 2233 | $10.98
|
||||
1/1/2014 | January Hosting | 2233 | $54.95
|
||||
1/4/2014 | February Hosting | 2233 | $51.00
|
||||
1/4/2014 | February Extra Bandwidth | 2233 | $30.00
|
||||
+----------+--------------------------+-------+---------+
|
||||
TOTAL | $146 93
|
||||
+-------+---------+
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Example 3 - CSV
|
||||
```go
|
||||
table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test_info.csv", true)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT) // Set Alignment
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 3
|
||||
```
|
||||
+----------+--------------+------+-----+---------+----------------+
|
||||
| FIELD | TYPE | NULL | KEY | DEFAULT | EXTRA |
|
||||
+----------+--------------+------+-----+---------+----------------+
|
||||
| user_id | smallint(5) | NO | PRI | NULL | auto_increment |
|
||||
| username | varchar(10) | NO | | NULL | |
|
||||
| password | varchar(100) | NO | | NULL | |
|
||||
+----------+--------------+------+-----+---------+----------------+
|
||||
```
|
||||
|
||||
#### Example 4 - Custom Separator
|
||||
```go
|
||||
table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test.csv", true)
|
||||
table.SetRowLine(true) // Enable row line
|
||||
|
||||
// Change table lines
|
||||
table.SetCenterSeparator("*")
|
||||
table.SetColumnSeparator("‡")
|
||||
table.SetRowSeparator("-")
|
||||
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 4
|
||||
```
|
||||
*------------*-----------*---------*
|
||||
╪ FIRST NAME ╪ LAST NAME ╪ SSN ╪
|
||||
*------------*-----------*---------*
|
||||
╪ John ╪ Barry ╪ 123456 ╪
|
||||
*------------*-----------*---------*
|
||||
╪ Kathy ╪ Smith ╪ 687987 ╪
|
||||
*------------*-----------*---------*
|
||||
╪ Bob ╪ McCornick ╪ 3979870 ╪
|
||||
*------------*-----------*---------*
|
||||
```
|
||||
|
||||
#### Example 5 - Markdown Format
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
|
||||
table.SetCenterSeparator("|")
|
||||
table.AppendBulk(data) // Add Bulk Data
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 5
|
||||
```
|
||||
| DATE | DESCRIPTION | CV2 | AMOUNT |
|
||||
|----------|--------------------------|------|--------|
|
||||
| 1/1/2014 | Domain name | 2233 | $10.98 |
|
||||
| 1/1/2014 | January Hosting | 2233 | $54.95 |
|
||||
| 1/4/2014 | February Hosting | 2233 | $51.00 |
|
||||
| 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 |
|
||||
```
|
||||
|
||||
#### Example 6 - Identical cells merging
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "1234", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2345", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "3456", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"})
|
||||
table.SetAutoMergeCells(true)
|
||||
table.SetRowLine(true)
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
##### Output 6
|
||||
```
|
||||
+----------+--------------------------+-------+---------+
|
||||
| DATE | DESCRIPTION | CV2 | AMOUNT |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/1/2014 | Domain name | 1234 | $10.98 |
|
||||
+ +--------------------------+-------+---------+
|
||||
| | January Hosting | 2345 | $54.95 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| 1/4/2014 | February Hosting | 3456 | $51.00 |
|
||||
+ +--------------------------+-------+---------+
|
||||
| | February Extra Bandwidth | 4567 | $30.00 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
| TOTAL | $146 93 |
|
||||
+----------+--------------------------+-------+---------+
|
||||
```
|
||||
|
||||
|
||||
#### Table with color
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
|
||||
[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
|
||||
[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
|
||||
[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
|
||||
table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
|
||||
table.SetBorder(false) // Set Border to false
|
||||
|
||||
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
|
||||
tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
|
||||
tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
|
||||
tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
|
||||
|
||||
table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
|
||||
|
||||
table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
|
||||
tablewriter.Colors{tablewriter.Bold},
|
||||
tablewriter.Colors{tablewriter.FgHiRedColor})
|
||||
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
```
|
||||
|
||||
#### Table with color Output
|
||||

|
||||
|
||||
#### Example 6 - Set table caption
|
||||
```go
|
||||
data := [][]string{
|
||||
[]string{"A", "The Good", "500"},
|
||||
[]string{"B", "The Very very Bad Man", "288"},
|
||||
[]string{"C", "The Ugly", "120"},
|
||||
[]string{"D", "The Gopher", "800"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"Name", "Sign", "Rating"})
|
||||
table.SetCaption(true, "Movie ratings.")
|
||||
|
||||
for _, v := range data {
|
||||
table.Append(v)
|
||||
}
|
||||
table.Render() // Send output
|
||||
```
|
||||
|
||||
Note: Caption text will wrap with total width of rendered table.
|
||||
|
||||
##### Output 6
|
||||
```
|
||||
+------+-----------------------+--------+
|
||||
| NAME | SIGN | RATING |
|
||||
+------+-----------------------+--------+
|
||||
| A | The Good | 500 |
|
||||
| B | The Very very Bad Man | 288 |
|
||||
| C | The Ugly | 120 |
|
||||
| D | The Gopher | 800 |
|
||||
+------+-----------------------+--------+
|
||||
Movie ratings.
|
||||
```
|
||||
|
||||
#### TODO
|
||||
- ~~Import Directly from CSV~~ - `done`
|
||||
- ~~Support for `SetFooter`~~ - `done`
|
||||
- ~~Support for `SetBorder`~~ - `done`
|
||||
- ~~Support table with uneven rows~~ - `done`
|
||||
- ~~Support custom alignment~~
|
||||
- General Improvement & Optimisation
|
||||
- `NewHTML` Parse table from HTML
|
||||
52
vendor/github.com/olekukonko/tablewriter/csv.go
generated
vendored
Normal file
52
vendor/github.com/olekukonko/tablewriter/csv.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Start A new table by importing from a CSV file
|
||||
// Takes io.Writer and csv File name
|
||||
func NewCSV(writer io.Writer, fileName string, hasHeader bool) (*Table, error) {
|
||||
file, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return &Table{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
csvReader := csv.NewReader(file)
|
||||
t, err := NewCSVReader(writer, csvReader, hasHeader)
|
||||
return t, err
|
||||
}
|
||||
|
||||
// Start a New Table Writer with csv.Reader
|
||||
// This enables customisation such as reader.Comma = ';'
|
||||
// See http://golang.org/src/pkg/encoding/csv/reader.go?s=3213:3671#L94
|
||||
func NewCSVReader(writer io.Writer, csvReader *csv.Reader, hasHeader bool) (*Table, error) {
|
||||
t := NewWriter(writer)
|
||||
if hasHeader {
|
||||
// Read the first row
|
||||
headers, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return &Table{}, err
|
||||
}
|
||||
t.SetHeader(headers)
|
||||
}
|
||||
for {
|
||||
record, err := csvReader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return &Table{}, err
|
||||
}
|
||||
t.Append(record)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
839
vendor/github.com/olekukonko/tablewriter/table.go
generated
vendored
Normal file
839
vendor/github.com/olekukonko/tablewriter/table.go
generated
vendored
Normal file
@@ -0,0 +1,839 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
// Create & Generate text based table
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MAX_ROW_WIDTH = 30
|
||||
)
|
||||
|
||||
const (
|
||||
CENTER = "+"
|
||||
ROW = "-"
|
||||
COLUMN = "|"
|
||||
SPACE = " "
|
||||
NEWLINE = "\n"
|
||||
)
|
||||
|
||||
const (
|
||||
ALIGN_DEFAULT = iota
|
||||
ALIGN_CENTER
|
||||
ALIGN_RIGHT
|
||||
ALIGN_LEFT
|
||||
)
|
||||
|
||||
var (
|
||||
decimal = regexp.MustCompile(`^-*\d*\.?\d*$`)
|
||||
percent = regexp.MustCompile(`^-*\d*\.?\d*$%$`)
|
||||
)
|
||||
|
||||
type Border struct {
|
||||
Left bool
|
||||
Right bool
|
||||
Top bool
|
||||
Bottom bool
|
||||
}
|
||||
|
||||
type Table struct {
|
||||
out io.Writer
|
||||
rows [][]string
|
||||
lines [][][]string
|
||||
cs map[int]int
|
||||
rs map[int]int
|
||||
headers [][]string
|
||||
footers [][]string
|
||||
caption bool
|
||||
captionText string
|
||||
autoFmt bool
|
||||
autoWrap bool
|
||||
reflowText bool
|
||||
mW int
|
||||
pCenter string
|
||||
pRow string
|
||||
pColumn string
|
||||
tColumn int
|
||||
tRow int
|
||||
hAlign int
|
||||
fAlign int
|
||||
align int
|
||||
newLine string
|
||||
rowLine bool
|
||||
autoMergeCells bool
|
||||
hdrLine bool
|
||||
borders Border
|
||||
colSize int
|
||||
headerParams []string
|
||||
columnsParams []string
|
||||
footerParams []string
|
||||
columnsAlign []int
|
||||
}
|
||||
|
||||
// Start New Table
|
||||
// Take io.Writer Directly
|
||||
func NewWriter(writer io.Writer) *Table {
|
||||
t := &Table{
|
||||
out: writer,
|
||||
rows: [][]string{},
|
||||
lines: [][][]string{},
|
||||
cs: make(map[int]int),
|
||||
rs: make(map[int]int),
|
||||
headers: [][]string{},
|
||||
footers: [][]string{},
|
||||
caption: false,
|
||||
captionText: "Table caption.",
|
||||
autoFmt: true,
|
||||
autoWrap: true,
|
||||
reflowText: true,
|
||||
mW: MAX_ROW_WIDTH,
|
||||
pCenter: CENTER,
|
||||
pRow: ROW,
|
||||
pColumn: COLUMN,
|
||||
tColumn: -1,
|
||||
tRow: -1,
|
||||
hAlign: ALIGN_DEFAULT,
|
||||
fAlign: ALIGN_DEFAULT,
|
||||
align: ALIGN_DEFAULT,
|
||||
newLine: NEWLINE,
|
||||
rowLine: false,
|
||||
hdrLine: true,
|
||||
borders: Border{Left: true, Right: true, Bottom: true, Top: true},
|
||||
colSize: -1,
|
||||
headerParams: []string{},
|
||||
columnsParams: []string{},
|
||||
footerParams: []string{},
|
||||
columnsAlign: []int{}}
|
||||
return t
|
||||
}
|
||||
|
||||
// Render table output
|
||||
func (t *Table) Render() {
|
||||
if t.borders.Top {
|
||||
t.printLine(true)
|
||||
}
|
||||
t.printHeading()
|
||||
if t.autoMergeCells {
|
||||
t.printRowsMergeCells()
|
||||
} else {
|
||||
t.printRows()
|
||||
}
|
||||
if !t.rowLine && t.borders.Bottom {
|
||||
t.printLine(true)
|
||||
}
|
||||
t.printFooter()
|
||||
|
||||
if t.caption {
|
||||
t.printCaption()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
headerRowIdx = -1
|
||||
footerRowIdx = -2
|
||||
)
|
||||
|
||||
// Set table header
|
||||
func (t *Table) SetHeader(keys []string) {
|
||||
t.colSize = len(keys)
|
||||
for i, v := range keys {
|
||||
lines := t.parseDimension(v, i, headerRowIdx)
|
||||
t.headers = append(t.headers, lines)
|
||||
}
|
||||
}
|
||||
|
||||
// Set table Footer
|
||||
func (t *Table) SetFooter(keys []string) {
|
||||
//t.colSize = len(keys)
|
||||
for i, v := range keys {
|
||||
lines := t.parseDimension(v, i, footerRowIdx)
|
||||
t.footers = append(t.footers, lines)
|
||||
}
|
||||
}
|
||||
|
||||
// Set table Caption
|
||||
func (t *Table) SetCaption(caption bool, captionText ...string) {
|
||||
t.caption = caption
|
||||
if len(captionText) == 1 {
|
||||
t.captionText = captionText[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Turn header autoformatting on/off. Default is on (true).
|
||||
func (t *Table) SetAutoFormatHeaders(auto bool) {
|
||||
t.autoFmt = auto
|
||||
}
|
||||
|
||||
// Turn automatic multiline text adjustment on/off. Default is on (true).
|
||||
func (t *Table) SetAutoWrapText(auto bool) {
|
||||
t.autoWrap = auto
|
||||
}
|
||||
|
||||
// Turn automatic reflowing of multiline text when rewrapping. Default is on (true).
|
||||
func (t *Table) SetReflowDuringAutoWrap(auto bool) {
|
||||
t.reflowText = auto
|
||||
}
|
||||
|
||||
// Set the Default column width
|
||||
func (t *Table) SetColWidth(width int) {
|
||||
t.mW = width
|
||||
}
|
||||
|
||||
// Set the minimal width for a column
|
||||
func (t *Table) SetColMinWidth(column int, width int) {
|
||||
t.cs[column] = width
|
||||
}
|
||||
|
||||
// Set the Column Separator
|
||||
func (t *Table) SetColumnSeparator(sep string) {
|
||||
t.pColumn = sep
|
||||
}
|
||||
|
||||
// Set the Row Separator
|
||||
func (t *Table) SetRowSeparator(sep string) {
|
||||
t.pRow = sep
|
||||
}
|
||||
|
||||
// Set the center Separator
|
||||
func (t *Table) SetCenterSeparator(sep string) {
|
||||
t.pCenter = sep
|
||||
}
|
||||
|
||||
// Set Header Alignment
|
||||
func (t *Table) SetHeaderAlignment(hAlign int) {
|
||||
t.hAlign = hAlign
|
||||
}
|
||||
|
||||
// Set Footer Alignment
|
||||
func (t *Table) SetFooterAlignment(fAlign int) {
|
||||
t.fAlign = fAlign
|
||||
}
|
||||
|
||||
// Set Table Alignment
|
||||
func (t *Table) SetAlignment(align int) {
|
||||
t.align = align
|
||||
}
|
||||
|
||||
func (t *Table) SetColumnAlignment(keys []int) {
|
||||
for _, v := range keys {
|
||||
switch v {
|
||||
case ALIGN_CENTER:
|
||||
break
|
||||
case ALIGN_LEFT:
|
||||
break
|
||||
case ALIGN_RIGHT:
|
||||
break
|
||||
default:
|
||||
v = ALIGN_DEFAULT
|
||||
}
|
||||
t.columnsAlign = append(t.columnsAlign, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Set New Line
|
||||
func (t *Table) SetNewLine(nl string) {
|
||||
t.newLine = nl
|
||||
}
|
||||
|
||||
// Set Header Line
|
||||
// This would enable / disable a line after the header
|
||||
func (t *Table) SetHeaderLine(line bool) {
|
||||
t.hdrLine = line
|
||||
}
|
||||
|
||||
// Set Row Line
|
||||
// This would enable / disable a line on each row of the table
|
||||
func (t *Table) SetRowLine(line bool) {
|
||||
t.rowLine = line
|
||||
}
|
||||
|
||||
// Set Auto Merge Cells
|
||||
// This would enable / disable the merge of cells with identical values
|
||||
func (t *Table) SetAutoMergeCells(auto bool) {
|
||||
t.autoMergeCells = auto
|
||||
}
|
||||
|
||||
// Set Table Border
|
||||
// This would enable / disable line around the table
|
||||
func (t *Table) SetBorder(border bool) {
|
||||
t.SetBorders(Border{border, border, border, border})
|
||||
}
|
||||
|
||||
func (t *Table) SetBorders(border Border) {
|
||||
t.borders = border
|
||||
}
|
||||
|
||||
// Append row to table
|
||||
func (t *Table) Append(row []string) {
|
||||
rowSize := len(t.headers)
|
||||
if rowSize > t.colSize {
|
||||
t.colSize = rowSize
|
||||
}
|
||||
|
||||
n := len(t.lines)
|
||||
line := [][]string{}
|
||||
for i, v := range row {
|
||||
|
||||
// Detect string width
|
||||
// Detect String height
|
||||
// Break strings into words
|
||||
out := t.parseDimension(v, i, n)
|
||||
|
||||
// Append broken words
|
||||
line = append(line, out)
|
||||
}
|
||||
t.lines = append(t.lines, line)
|
||||
}
|
||||
|
||||
// Allow Support for Bulk Append
|
||||
// Eliminates repeated for loops
|
||||
func (t *Table) AppendBulk(rows [][]string) {
|
||||
for _, row := range rows {
|
||||
t.Append(row)
|
||||
}
|
||||
}
|
||||
|
||||
// NumLines to get the number of lines
|
||||
func (t *Table) NumLines() int {
|
||||
return len(t.lines)
|
||||
}
|
||||
|
||||
// Clear rows
|
||||
func (t *Table) ClearRows() {
|
||||
t.lines = [][][]string{}
|
||||
}
|
||||
|
||||
// Clear footer
|
||||
func (t *Table) ClearFooter() {
|
||||
t.footers = [][]string{}
|
||||
}
|
||||
|
||||
// Print line based on row width
|
||||
func (t *Table) printLine(nl bool) {
|
||||
fmt.Fprint(t.out, t.pCenter)
|
||||
for i := 0; i < len(t.cs); i++ {
|
||||
v := t.cs[i]
|
||||
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||
t.pRow,
|
||||
strings.Repeat(string(t.pRow), v),
|
||||
t.pRow,
|
||||
t.pCenter)
|
||||
}
|
||||
if nl {
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Print line based on row width with our without cell separator
|
||||
func (t *Table) printLineOptionalCellSeparators(nl bool, displayCellSeparator []bool) {
|
||||
fmt.Fprint(t.out, t.pCenter)
|
||||
for i := 0; i < len(t.cs); i++ {
|
||||
v := t.cs[i]
|
||||
if i > len(displayCellSeparator) || displayCellSeparator[i] {
|
||||
// Display the cell separator
|
||||
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||
t.pRow,
|
||||
strings.Repeat(string(t.pRow), v),
|
||||
t.pRow,
|
||||
t.pCenter)
|
||||
} else {
|
||||
// Don't display the cell separator for this cell
|
||||
fmt.Fprintf(t.out, "%s%s",
|
||||
strings.Repeat(" ", v+2),
|
||||
t.pCenter)
|
||||
}
|
||||
}
|
||||
if nl {
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the PadRight function if align is left, PadLeft if align is right,
|
||||
// and Pad by default
|
||||
func pad(align int) func(string, string, int) string {
|
||||
padFunc := Pad
|
||||
switch align {
|
||||
case ALIGN_LEFT:
|
||||
padFunc = PadRight
|
||||
case ALIGN_RIGHT:
|
||||
padFunc = PadLeft
|
||||
}
|
||||
return padFunc
|
||||
}
|
||||
|
||||
// Print heading information
|
||||
func (t *Table) printHeading() {
|
||||
// Check if headers is available
|
||||
if len(t.headers) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Identify last column
|
||||
end := len(t.cs) - 1
|
||||
|
||||
// Get pad function
|
||||
padFunc := pad(t.hAlign)
|
||||
|
||||
// Checking for ANSI escape sequences for header
|
||||
is_esc_seq := false
|
||||
if len(t.headerParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
|
||||
// Maximum height.
|
||||
max := t.rs[headerRowIdx]
|
||||
|
||||
// Print Heading
|
||||
for x := 0; x < max; x++ {
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||
|
||||
for y := 0; y <= end; y++ {
|
||||
v := t.cs[y]
|
||||
h := ""
|
||||
if y < len(t.headers) && x < len(t.headers[y]) {
|
||||
h = t.headers[y][x]
|
||||
}
|
||||
if t.autoFmt {
|
||||
h = Title(h)
|
||||
}
|
||||
pad := ConditionString((y == end && !t.borders.Left), SPACE, t.pColumn)
|
||||
|
||||
if is_esc_seq {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
format(padFunc(h, SPACE, v),
|
||||
t.headerParams[y]), pad)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
padFunc(h, SPACE, v),
|
||||
pad)
|
||||
}
|
||||
}
|
||||
// Next line
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
if t.hdrLine {
|
||||
t.printLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Print heading information
|
||||
func (t *Table) printFooter() {
|
||||
// Check if headers is available
|
||||
if len(t.footers) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// Only print line if border is not set
|
||||
if !t.borders.Bottom {
|
||||
t.printLine(true)
|
||||
}
|
||||
|
||||
// Identify last column
|
||||
end := len(t.cs) - 1
|
||||
|
||||
// Get pad function
|
||||
padFunc := pad(t.fAlign)
|
||||
|
||||
// Checking for ANSI escape sequences for header
|
||||
is_esc_seq := false
|
||||
if len(t.footerParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
|
||||
// Maximum height.
|
||||
max := t.rs[footerRowIdx]
|
||||
|
||||
// Print Footer
|
||||
erasePad := make([]bool, len(t.footers))
|
||||
for x := 0; x < max; x++ {
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
fmt.Fprint(t.out, ConditionString(t.borders.Bottom, t.pColumn, SPACE))
|
||||
|
||||
for y := 0; y <= end; y++ {
|
||||
v := t.cs[y]
|
||||
f := ""
|
||||
if y < len(t.footers) && x < len(t.footers[y]) {
|
||||
f = t.footers[y][x]
|
||||
}
|
||||
if t.autoFmt {
|
||||
f = Title(f)
|
||||
}
|
||||
pad := ConditionString((y == end && !t.borders.Top), SPACE, t.pColumn)
|
||||
|
||||
if erasePad[y] || (x == 0 && len(f) == 0) {
|
||||
pad = SPACE
|
||||
erasePad[y] = true
|
||||
}
|
||||
|
||||
if is_esc_seq {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
format(padFunc(f, SPACE, v),
|
||||
t.footerParams[y]), pad)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, " %s %s",
|
||||
padFunc(f, SPACE, v),
|
||||
pad)
|
||||
}
|
||||
|
||||
//fmt.Fprintf(t.out, " %s %s",
|
||||
// padFunc(f, SPACE, v),
|
||||
// pad)
|
||||
}
|
||||
// Next line
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
//t.printLine(true)
|
||||
}
|
||||
|
||||
hasPrinted := false
|
||||
|
||||
for i := 0; i <= end; i++ {
|
||||
v := t.cs[i]
|
||||
pad := t.pRow
|
||||
center := t.pCenter
|
||||
length := len(t.footers[i][0])
|
||||
|
||||
if length > 0 {
|
||||
hasPrinted = true
|
||||
}
|
||||
|
||||
// Set center to be space if length is 0
|
||||
if length == 0 && !t.borders.Right {
|
||||
center = SPACE
|
||||
}
|
||||
|
||||
// Print first junction
|
||||
if i == 0 {
|
||||
fmt.Fprint(t.out, center)
|
||||
}
|
||||
|
||||
// Pad With space of length is 0
|
||||
if length == 0 {
|
||||
pad = SPACE
|
||||
}
|
||||
// Ignore left space of it has printed before
|
||||
if hasPrinted || t.borders.Left {
|
||||
pad = t.pRow
|
||||
center = t.pCenter
|
||||
}
|
||||
|
||||
// Change Center start position
|
||||
if center == SPACE {
|
||||
if i < end && len(t.footers[i+1][0]) != 0 {
|
||||
center = t.pCenter
|
||||
}
|
||||
}
|
||||
|
||||
// Print the footer
|
||||
fmt.Fprintf(t.out, "%s%s%s%s",
|
||||
pad,
|
||||
strings.Repeat(string(pad), v),
|
||||
pad,
|
||||
center)
|
||||
|
||||
}
|
||||
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
|
||||
// Print caption text
|
||||
func (t Table) printCaption() {
|
||||
width := t.getTableWidth()
|
||||
paragraph, _ := WrapString(t.captionText, width)
|
||||
for linecount := 0; linecount < len(paragraph); linecount++ {
|
||||
fmt.Fprintln(t.out, paragraph[linecount])
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the total number of characters in a row
|
||||
func (t Table) getTableWidth() int {
|
||||
var chars int
|
||||
for _, v := range t.cs {
|
||||
chars += v
|
||||
}
|
||||
|
||||
// Add chars, spaces, seperators to calculate the total width of the table.
|
||||
// ncols := t.colSize
|
||||
// spaces := ncols * 2
|
||||
// seps := ncols + 1
|
||||
|
||||
return (chars + (3 * t.colSize) + 2)
|
||||
}
|
||||
|
||||
func (t Table) printRows() {
|
||||
for i, lines := range t.lines {
|
||||
t.printRow(lines, i)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) fillAlignment(num int) {
|
||||
if len(t.columnsAlign) < num {
|
||||
t.columnsAlign = make([]int, num)
|
||||
for i := range t.columnsAlign {
|
||||
t.columnsAlign[i] = t.align
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print Row Information
|
||||
// Adjust column alignment based on type
|
||||
|
||||
func (t *Table) printRow(columns [][]string, rowIdx int) {
|
||||
// Get Maximum Height
|
||||
max := t.rs[rowIdx]
|
||||
total := len(columns)
|
||||
|
||||
// TODO Fix uneven col size
|
||||
// if total < t.colSize {
|
||||
// for n := t.colSize - total; n < t.colSize ; n++ {
|
||||
// columns = append(columns, []string{SPACE})
|
||||
// t.cs[n] = t.mW
|
||||
// }
|
||||
//}
|
||||
|
||||
// Pad Each Height
|
||||
pads := []int{}
|
||||
|
||||
// Checking for ANSI escape sequences for columns
|
||||
is_esc_seq := false
|
||||
if len(t.columnsParams) > 0 {
|
||||
is_esc_seq = true
|
||||
}
|
||||
t.fillAlignment(total)
|
||||
|
||||
for i, line := range columns {
|
||||
length := len(line)
|
||||
pad := max - length
|
||||
pads = append(pads, pad)
|
||||
for n := 0; n < pad; n++ {
|
||||
columns[i] = append(columns[i], " ")
|
||||
}
|
||||
}
|
||||
//fmt.Println(max, "\n")
|
||||
for x := 0; x < max; x++ {
|
||||
for y := 0; y < total; y++ {
|
||||
|
||||
// Check if border is set
|
||||
fmt.Fprint(t.out, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
|
||||
|
||||
fmt.Fprintf(t.out, SPACE)
|
||||
str := columns[y][x]
|
||||
|
||||
// Embedding escape sequence with column value
|
||||
if is_esc_seq {
|
||||
str = format(str, t.columnsParams[y])
|
||||
}
|
||||
|
||||
// This would print alignment
|
||||
// Default alignment would use multiple configuration
|
||||
switch t.columnsAlign[y] {
|
||||
case ALIGN_CENTER: //
|
||||
fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
|
||||
case ALIGN_RIGHT:
|
||||
fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
case ALIGN_LEFT:
|
||||
fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
default:
|
||||
if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
|
||||
fmt.Fprintf(t.out, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
} else {
|
||||
fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
|
||||
// TODO Custom alignment per column
|
||||
//if max == 1 || pads[y] > 0 {
|
||||
// fmt.Fprintf(t.out, "%s", Pad(str, SPACE, t.cs[y]))
|
||||
//} else {
|
||||
// fmt.Fprintf(t.out, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(t.out, SPACE)
|
||||
}
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
fmt.Fprint(t.out, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||
fmt.Fprint(t.out, t.newLine)
|
||||
}
|
||||
|
||||
if t.rowLine {
|
||||
t.printLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Print the rows of the table and merge the cells that are identical
|
||||
func (t *Table) printRowsMergeCells() {
|
||||
var previousLine []string
|
||||
var displayCellBorder []bool
|
||||
var tmpWriter bytes.Buffer
|
||||
for i, lines := range t.lines {
|
||||
// We store the display of the current line in a tmp writer, as we need to know which border needs to be print above
|
||||
previousLine, displayCellBorder = t.printRowMergeCells(&tmpWriter, lines, i, previousLine)
|
||||
if i > 0 { //We don't need to print borders above first line
|
||||
if t.rowLine {
|
||||
t.printLineOptionalCellSeparators(true, displayCellBorder)
|
||||
}
|
||||
}
|
||||
tmpWriter.WriteTo(t.out)
|
||||
}
|
||||
//Print the end of the table
|
||||
if t.rowLine {
|
||||
t.printLine(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Print Row Information to a writer and merge identical cells.
|
||||
// Adjust column alignment based on type
|
||||
|
||||
func (t *Table) printRowMergeCells(writer io.Writer, columns [][]string, rowIdx int, previousLine []string) ([]string, []bool) {
|
||||
// Get Maximum Height
|
||||
max := t.rs[rowIdx]
|
||||
total := len(columns)
|
||||
|
||||
// Pad Each Height
|
||||
pads := []int{}
|
||||
|
||||
for i, line := range columns {
|
||||
length := len(line)
|
||||
pad := max - length
|
||||
pads = append(pads, pad)
|
||||
for n := 0; n < pad; n++ {
|
||||
columns[i] = append(columns[i], " ")
|
||||
}
|
||||
}
|
||||
|
||||
var displayCellBorder []bool
|
||||
t.fillAlignment(total)
|
||||
for x := 0; x < max; x++ {
|
||||
for y := 0; y < total; y++ {
|
||||
|
||||
// Check if border is set
|
||||
fmt.Fprint(writer, ConditionString((!t.borders.Left && y == 0), SPACE, t.pColumn))
|
||||
|
||||
fmt.Fprintf(writer, SPACE)
|
||||
|
||||
str := columns[y][x]
|
||||
|
||||
if t.autoMergeCells {
|
||||
//Store the full line to merge mutli-lines cells
|
||||
fullLine := strings.Join(columns[y], " ")
|
||||
if len(previousLine) > y && fullLine == previousLine[y] && fullLine != "" {
|
||||
// If this cell is identical to the one above but not empty, we don't display the border and keep the cell empty.
|
||||
displayCellBorder = append(displayCellBorder, false)
|
||||
str = ""
|
||||
} else {
|
||||
// First line or different content, keep the content and print the cell border
|
||||
displayCellBorder = append(displayCellBorder, true)
|
||||
}
|
||||
}
|
||||
|
||||
// This would print alignment
|
||||
// Default alignment would use multiple configuration
|
||||
switch t.columnsAlign[y] {
|
||||
case ALIGN_CENTER: //
|
||||
fmt.Fprintf(writer, "%s", Pad(str, SPACE, t.cs[y]))
|
||||
case ALIGN_RIGHT:
|
||||
fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
case ALIGN_LEFT:
|
||||
fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
default:
|
||||
if decimal.MatchString(strings.TrimSpace(str)) || percent.MatchString(strings.TrimSpace(str)) {
|
||||
fmt.Fprintf(writer, "%s", PadLeft(str, SPACE, t.cs[y]))
|
||||
} else {
|
||||
fmt.Fprintf(writer, "%s", PadRight(str, SPACE, t.cs[y]))
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(writer, SPACE)
|
||||
}
|
||||
// Check if border is set
|
||||
// Replace with space if not set
|
||||
fmt.Fprint(writer, ConditionString(t.borders.Left, t.pColumn, SPACE))
|
||||
fmt.Fprint(writer, t.newLine)
|
||||
}
|
||||
|
||||
//The new previous line is the current one
|
||||
previousLine = make([]string, total)
|
||||
for y := 0; y < total; y++ {
|
||||
previousLine[y] = strings.Join(columns[y], " ") //Store the full line for multi-lines cells
|
||||
}
|
||||
//Returns the newly added line and wether or not a border should be displayed above.
|
||||
return previousLine, displayCellBorder
|
||||
}
|
||||
|
||||
func (t *Table) parseDimension(str string, colKey, rowKey int) []string {
|
||||
var (
|
||||
raw []string
|
||||
maxWidth int
|
||||
)
|
||||
|
||||
raw = getLines(str)
|
||||
maxWidth = 0
|
||||
for _, line := range raw {
|
||||
if w := DisplayWidth(line); w > maxWidth {
|
||||
maxWidth = w
|
||||
}
|
||||
}
|
||||
|
||||
// If wrapping, ensure that all paragraphs in the cell fit in the
|
||||
// specified width.
|
||||
if t.autoWrap {
|
||||
// If there's a maximum allowed width for wrapping, use that.
|
||||
if maxWidth > t.mW {
|
||||
maxWidth = t.mW
|
||||
}
|
||||
|
||||
// In the process of doing so, we need to recompute maxWidth. This
|
||||
// is because perhaps a word in the cell is longer than the
|
||||
// allowed maximum width in t.mW.
|
||||
newMaxWidth := maxWidth
|
||||
newRaw := make([]string, 0, len(raw))
|
||||
|
||||
if t.reflowText {
|
||||
// Make a single paragraph of everything.
|
||||
raw = []string{strings.Join(raw, " ")}
|
||||
}
|
||||
for i, para := range raw {
|
||||
paraLines, _ := WrapString(para, maxWidth)
|
||||
for _, line := range paraLines {
|
||||
if w := DisplayWidth(line); w > newMaxWidth {
|
||||
newMaxWidth = w
|
||||
}
|
||||
}
|
||||
if i > 0 {
|
||||
newRaw = append(newRaw, " ")
|
||||
}
|
||||
newRaw = append(newRaw, paraLines...)
|
||||
}
|
||||
raw = newRaw
|
||||
maxWidth = newMaxWidth
|
||||
}
|
||||
|
||||
// Store the new known maximum width.
|
||||
v, ok := t.cs[colKey]
|
||||
if !ok || v < maxWidth || v == 0 {
|
||||
t.cs[colKey] = maxWidth
|
||||
}
|
||||
|
||||
// Remember the number of lines for the row printer.
|
||||
h := len(raw)
|
||||
v, ok = t.rs[rowKey]
|
||||
|
||||
if !ok || v < h || v == 0 {
|
||||
t.rs[rowKey] = h
|
||||
}
|
||||
//fmt.Printf("Raw %+v %d\n", raw, len(raw))
|
||||
return raw
|
||||
}
|
||||
134
vendor/github.com/olekukonko/tablewriter/table_with_color.go
generated
vendored
Normal file
134
vendor/github.com/olekukonko/tablewriter/table_with_color.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ESC = "\033"
|
||||
const SEP = ";"
|
||||
|
||||
const (
|
||||
BgBlackColor int = iota + 40
|
||||
BgRedColor
|
||||
BgGreenColor
|
||||
BgYellowColor
|
||||
BgBlueColor
|
||||
BgMagentaColor
|
||||
BgCyanColor
|
||||
BgWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
FgBlackColor int = iota + 30
|
||||
FgRedColor
|
||||
FgGreenColor
|
||||
FgYellowColor
|
||||
FgBlueColor
|
||||
FgMagentaColor
|
||||
FgCyanColor
|
||||
FgWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
BgHiBlackColor int = iota + 100
|
||||
BgHiRedColor
|
||||
BgHiGreenColor
|
||||
BgHiYellowColor
|
||||
BgHiBlueColor
|
||||
BgHiMagentaColor
|
||||
BgHiCyanColor
|
||||
BgHiWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
FgHiBlackColor int = iota + 90
|
||||
FgHiRedColor
|
||||
FgHiGreenColor
|
||||
FgHiYellowColor
|
||||
FgHiBlueColor
|
||||
FgHiMagentaColor
|
||||
FgHiCyanColor
|
||||
FgHiWhiteColor
|
||||
)
|
||||
|
||||
const (
|
||||
Normal = 0
|
||||
Bold = 1
|
||||
UnderlineSingle = 4
|
||||
Italic
|
||||
)
|
||||
|
||||
type Colors []int
|
||||
|
||||
func startFormat(seq string) string {
|
||||
return fmt.Sprintf("%s[%sm", ESC, seq)
|
||||
}
|
||||
|
||||
func stopFormat() string {
|
||||
return fmt.Sprintf("%s[%dm", ESC, Normal)
|
||||
}
|
||||
|
||||
// Making the SGR (Select Graphic Rendition) sequence.
|
||||
func makeSequence(codes []int) string {
|
||||
codesInString := []string{}
|
||||
for _, code := range codes {
|
||||
codesInString = append(codesInString, strconv.Itoa(code))
|
||||
}
|
||||
return strings.Join(codesInString, SEP)
|
||||
}
|
||||
|
||||
// Adding ANSI escape sequences before and after string
|
||||
func format(s string, codes interface{}) string {
|
||||
var seq string
|
||||
|
||||
switch v := codes.(type) {
|
||||
|
||||
case string:
|
||||
seq = v
|
||||
case []int:
|
||||
seq = makeSequence(v)
|
||||
default:
|
||||
return s
|
||||
}
|
||||
|
||||
if len(seq) == 0 {
|
||||
return s
|
||||
}
|
||||
return startFormat(seq) + s + stopFormat()
|
||||
}
|
||||
|
||||
// Adding header colors (ANSI codes)
|
||||
func (t *Table) SetHeaderColor(colors ...Colors) {
|
||||
if t.colSize != len(colors) {
|
||||
panic("Number of header colors must be equal to number of headers.")
|
||||
}
|
||||
for i := 0; i < len(colors); i++ {
|
||||
t.headerParams = append(t.headerParams, makeSequence(colors[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// Adding column colors (ANSI codes)
|
||||
func (t *Table) SetColumnColor(colors ...Colors) {
|
||||
if t.colSize != len(colors) {
|
||||
panic("Number of column colors must be equal to number of headers.")
|
||||
}
|
||||
for i := 0; i < len(colors); i++ {
|
||||
t.columnsParams = append(t.columnsParams, makeSequence(colors[i]))
|
||||
}
|
||||
}
|
||||
|
||||
// Adding column colors (ANSI codes)
|
||||
func (t *Table) SetFooterColor(colors ...Colors) {
|
||||
if len(t.footers) != len(colors) {
|
||||
panic("Number of footer colors must be equal to number of footer.")
|
||||
}
|
||||
for i := 0; i < len(colors); i++ {
|
||||
t.footerParams = append(t.footerParams, makeSequence(colors[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func Color(colors ...int) []int {
|
||||
return colors
|
||||
}
|
||||
93
vendor/github.com/olekukonko/tablewriter/util.go
generated
vendored
Normal file
93
vendor/github.com/olekukonko/tablewriter/util.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
var ansi = regexp.MustCompile("\033\\[(?:[0-9]{1,3}(?:;[0-9]{1,3})*)?[m|K]")
|
||||
|
||||
func DisplayWidth(str string) int {
|
||||
return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
|
||||
}
|
||||
|
||||
// Simple Condition for string
|
||||
// Returns value based on condition
|
||||
func ConditionString(cond bool, valid, inValid string) string {
|
||||
if cond {
|
||||
return valid
|
||||
}
|
||||
return inValid
|
||||
}
|
||||
|
||||
func isNumOrSpace(r rune) bool {
|
||||
return ('0' <= r && r <= '9') || r == ' '
|
||||
}
|
||||
|
||||
// Format Table Header
|
||||
// Replace _ , . and spaces
|
||||
func Title(name string) string {
|
||||
origLen := len(name)
|
||||
rs := []rune(name)
|
||||
for i, r := range rs {
|
||||
switch r {
|
||||
case '_':
|
||||
rs[i] = ' '
|
||||
case '.':
|
||||
// ignore floating number 0.0
|
||||
if (i != 0 && !isNumOrSpace(rs[i-1])) || (i != len(rs)-1 && !isNumOrSpace(rs[i+1])) {
|
||||
rs[i] = ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
name = string(rs)
|
||||
name = strings.TrimSpace(name)
|
||||
if len(name) == 0 && origLen > 0 {
|
||||
// Keep at least one character. This is important to preserve
|
||||
// empty lines in multi-line headers/footers.
|
||||
name = " "
|
||||
}
|
||||
return strings.ToUpper(name)
|
||||
}
|
||||
|
||||
// Pad String
|
||||
// Attempts to play string in the center
|
||||
func Pad(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
if gap > 0 {
|
||||
gapLeft := int(math.Ceil(float64(gap / 2)))
|
||||
gapRight := gap - gapLeft
|
||||
return strings.Repeat(string(pad), gapLeft) + s + strings.Repeat(string(pad), gapRight)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pad String Right position
|
||||
// This would pace string at the left side fo the screen
|
||||
func PadRight(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
if gap > 0 {
|
||||
return s + strings.Repeat(string(pad), gap)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Pad String Left position
|
||||
// This would pace string at the right side fo the screen
|
||||
func PadLeft(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
if gap > 0 {
|
||||
return strings.Repeat(string(pad), gap) + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
99
vendor/github.com/olekukonko/tablewriter/wrap.go
generated
vendored
Normal file
99
vendor/github.com/olekukonko/tablewriter/wrap.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2014 Oleku Konko All rights reserved.
|
||||
// Use of this source code is governed by a MIT
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This module is a Table Writer API for the Go Programming Language.
|
||||
// The protocols were written in pure Go and works on windows and unix systems
|
||||
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
var (
|
||||
nl = "\n"
|
||||
sp = " "
|
||||
)
|
||||
|
||||
const defaultPenalty = 1e5
|
||||
|
||||
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
||||
// raggedness.
|
||||
func WrapString(s string, lim int) ([]string, int) {
|
||||
words := strings.Split(strings.Replace(s, nl, sp, -1), sp)
|
||||
var lines []string
|
||||
max := 0
|
||||
for _, v := range words {
|
||||
max = runewidth.StringWidth(v)
|
||||
if max > lim {
|
||||
lim = max
|
||||
}
|
||||
}
|
||||
for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
|
||||
lines = append(lines, strings.Join(line, sp))
|
||||
}
|
||||
return lines, lim
|
||||
}
|
||||
|
||||
// WrapWords is the low-level line-breaking algorithm, useful if you need more
|
||||
// control over the details of the text wrapping process. For most uses,
|
||||
// WrapString will be sufficient and more convenient.
|
||||
//
|
||||
// WrapWords splits a list of words into lines with minimal "raggedness",
|
||||
// treating each rune as one unit, accounting for spc units between adjacent
|
||||
// words on each line, and attempting to limit lines to lim units. Raggedness
|
||||
// is the total error over all lines, where error is the square of the
|
||||
// difference of the length of the line and lim. Too-long lines (which only
|
||||
// happen when a single word is longer than lim units) have pen penalty units
|
||||
// added to the error.
|
||||
func WrapWords(words []string, spc, lim, pen int) [][]string {
|
||||
n := len(words)
|
||||
|
||||
length := make([][]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
length[i] = make([]int, n)
|
||||
length[i][i] = runewidth.StringWidth(words[i])
|
||||
for j := i + 1; j < n; j++ {
|
||||
length[i][j] = length[i][j-1] + spc + runewidth.StringWidth(words[j])
|
||||
}
|
||||
}
|
||||
nbrk := make([]int, n)
|
||||
cost := make([]int, n)
|
||||
for i := range cost {
|
||||
cost[i] = math.MaxInt32
|
||||
}
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if length[i][n-1] <= lim {
|
||||
cost[i] = 0
|
||||
nbrk[i] = n
|
||||
} else {
|
||||
for j := i + 1; j < n; j++ {
|
||||
d := lim - length[i][j-1]
|
||||
c := d*d + cost[j]
|
||||
if length[i][j-1] > lim {
|
||||
c += pen // too-long lines get a worse penalty
|
||||
}
|
||||
if c < cost[i] {
|
||||
cost[i] = c
|
||||
nbrk[i] = j
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var lines [][]string
|
||||
i := 0
|
||||
for i < n {
|
||||
lines = append(lines, words[i:nbrk[i]])
|
||||
i = nbrk[i]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// getLines decomposes a multiline string into a slice of strings.
|
||||
func getLines(s string) []string {
|
||||
return strings.Split(s, nl)
|
||||
}
|
||||
14
vendor/github.com/ssor/bom/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/ssor/bom/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
- 1.8
|
||||
- 1.7
|
||||
- 1.6
|
||||
- 1.5
|
||||
- 1.4
|
||||
- 1.3
|
||||
- 1.2
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
21
vendor/github.com/ssor/bom/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ssor/bom/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Asher
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
23
vendor/github.com/ssor/bom/README.md
generated
vendored
Normal file
23
vendor/github.com/ssor/bom/README.md
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# bom
|
||||
small tools for cleaning bom from byte array or reader
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
$ go get github.com/ssor/bom
|
||||
```
|
||||
|
||||
## How to Use
|
||||
|
||||
|
||||
```
|
||||
bs := []byte{bom0, bom1, bom2, 0x11}
|
||||
result := CleanBom(bs)
|
||||
```
|
||||
|
||||
```
|
||||
bs := []byte{bom0, bom1, bom2, 0x11}
|
||||
result := NewReaderWithoutBom(bytes.NewReader(bs))
|
||||
|
||||
```
|
||||
34
vendor/github.com/ssor/bom/bom.go
generated
vendored
Normal file
34
vendor/github.com/ssor/bom/bom.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package bom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
const (
|
||||
bom0 = 0xef
|
||||
bom1 = 0xbb
|
||||
bom2 = 0xbf
|
||||
)
|
||||
|
||||
// CleanBom returns b with the 3 byte BOM stripped off the front if it is present.
|
||||
// If the BOM is not present, then b is returned.
|
||||
func CleanBom(b []byte) []byte {
|
||||
if len(b) >= 3 &&
|
||||
b[0] == bom0 &&
|
||||
b[1] == bom1 &&
|
||||
b[2] == bom2 {
|
||||
return b[3:]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// NewReaderWithoutBom returns an io.Reader that will skip over initial UTF-8 byte order marks.
|
||||
func NewReaderWithoutBom(r io.Reader) (io.Reader, error) {
|
||||
bs, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewReader(CleanBom(bs)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user