mirror of
https://gitea.com/gitea/tea
synced 2024-05-12 18:56:06 +02:00
Detect markdown line width, resolve relative URLs (#332)
~~this is semi-blocked by https://github.com/charmbracelet/glamour/pull/96, but behaviour isn't really worse than the previous behaviour (most links work, some are still broken)~~ #### testcase for link resolver ``` tea pr 332 tea checkout 332 && make install && tea pr 332 ``` - [rel](./332) - [abs](/gitea/tea/pulls/332) - [full](https://gitea.com/gitea/tea/pulls/332) Co-authored-by: Norwin Roosen <git@nroo.de> Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/332 Reviewed-by: 6543 <6543@obermui.de> Reviewed-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Norwin <noerw@noreply.gitea.io> Co-committed-by: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
parent
cb404b53b5
commit
222d0501df
2
go.mod
2
go.mod
|
@ -34,3 +34,5 @@ require (
|
|||
golang.org/x/tools v0.1.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
replace github.com/charmbracelet/glamour => github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2
|
||||
|
|
30
go.sum
30
go.sum
|
@ -18,8 +18,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBb
|
|||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI=
|
||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
|
||||
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
|
@ -31,8 +31,10 @@ github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e h1:OjdSMCht0ZVX7
|
|||
github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg=
|
||||
github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
|
@ -60,7 +62,8 @@ github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2Jg
|
|||
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
|
@ -101,17 +104,18 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC
|
|||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM=
|
||||
github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I=
|
||||
github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA=
|
||||
github.com/muesli/reflow v0.2.0 h1:2o0UBJPHHH4fa2GCXU4Rg4DwOtWPMekCeyc5EWbAQp0=
|
||||
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
|
||||
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2 h1:ACjOTGUGi7rt3JQU9GIFFs8sueFGShy6GcGjQhMmKjs=
|
||||
github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2/go.mod h1:WIVFX8Y2VIK1Y/1qtXYL/Vvzqlcbo3VgVop9i2piPkE=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
|
@ -151,9 +155,11 @@ github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0B
|
|||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I=
|
||||
github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
|
|
@ -13,20 +13,27 @@ import (
|
|||
|
||||
// Comments renders a list of comments to stdout
|
||||
func Comments(comments []*gitea.Comment) {
|
||||
var baseURL string
|
||||
if len(comments) != 0 {
|
||||
baseURL = comments[0].HTMLURL
|
||||
}
|
||||
|
||||
var out = make([]string, len(comments))
|
||||
for i, c := range comments {
|
||||
out[i] = formatComment(c)
|
||||
baseURL = comments[i].HTMLURL
|
||||
}
|
||||
|
||||
outputMarkdown(fmt.Sprintf(
|
||||
// this will become a heading by means of the first --- from a comment
|
||||
"Comments\n%s",
|
||||
strings.Join(out, "\n"),
|
||||
))
|
||||
), baseURL)
|
||||
}
|
||||
|
||||
// Comment renders a comment to stdout
|
||||
func Comment(c *gitea.Comment) {
|
||||
outputMarkdown(formatComment(c))
|
||||
outputMarkdown(formatComment(c), c.HTMLURL)
|
||||
}
|
||||
|
||||
func formatComment(c *gitea.Comment) string {
|
||||
|
|
|
@ -21,7 +21,7 @@ func IssueDetails(issue *gitea.Issue) {
|
|||
issue.Poster.UserName,
|
||||
FormatTime(issue.Created),
|
||||
issue.Body,
|
||||
))
|
||||
), issue.HTMLURL)
|
||||
}
|
||||
|
||||
// IssuesPullsList prints a listing of issues & pulls
|
||||
|
|
|
@ -28,7 +28,7 @@ func LoginDetails(login *config.Login) {
|
|||
}
|
||||
in += fmt.Sprintf("\nCreated: %s", time.Unix(login.Created, 0).Format(time.RFC822))
|
||||
|
||||
outputMarkdown(in)
|
||||
outputMarkdown(in, "")
|
||||
}
|
||||
|
||||
// LoginsList prints a listing of logins
|
||||
|
|
|
@ -6,15 +6,27 @@ package print
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// outputMarkdown prints markdown to stdout, formatted for terminals.
|
||||
// If the input could not be parsed, it is printed unformatted, the error
|
||||
// is returned anyway.
|
||||
func outputMarkdown(markdown string) error {
|
||||
out, err := glamour.Render(markdown, "auto")
|
||||
func outputMarkdown(markdown string, baseURL string) error {
|
||||
renderer, err := glamour.NewTermRenderer(
|
||||
glamour.WithAutoStyle(),
|
||||
glamour.WithBaseURL(baseURL),
|
||||
glamour.WithWordWrap(getWordWrap()),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf(markdown)
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := renderer.Render(markdown)
|
||||
if err != nil {
|
||||
fmt.Printf(markdown)
|
||||
return err
|
||||
|
@ -22,3 +34,18 @@ func outputMarkdown(markdown string) error {
|
|||
fmt.Print(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
// stolen from https://github.com/charmbracelet/glow/blob/e9d728c/main.go#L152-L165
|
||||
func getWordWrap() int {
|
||||
fd := int(os.Stdout.Fd())
|
||||
width := 80
|
||||
if terminal.IsTerminal(fd) {
|
||||
if w, _, err := terminal.GetSize(fd); err == nil {
|
||||
width = w
|
||||
}
|
||||
}
|
||||
if width > 120 {
|
||||
width = 120
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ func PullDetails(pr *gitea.PullRequest, reviews []*gitea.PullReview, ciStatus *g
|
|||
}
|
||||
}
|
||||
|
||||
outputMarkdown(out)
|
||||
outputMarkdown(out, pr.HTMLURL)
|
||||
}
|
||||
|
||||
func formatReviews(reviews []*gitea.PullReview) string {
|
||||
|
|
|
@ -87,7 +87,7 @@ func RepoDetails(repo *gitea.Repository, topics []string) {
|
|||
urls,
|
||||
perm,
|
||||
tops,
|
||||
))
|
||||
), repo.HTMLURL)
|
||||
}
|
||||
|
||||
// RepoFields are the available fields to print with ReposList()
|
||||
|
|
|
@ -20,6 +20,11 @@ linters:
|
|||
- wsl
|
||||
- gomnd
|
||||
- gocognit
|
||||
- goerr113
|
||||
- nolintlint
|
||||
- testpackage
|
||||
- godot
|
||||
- nestif
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
|
|
|
@ -4,7 +4,7 @@ go:
|
|||
- "1.13.x"
|
||||
script:
|
||||
- go test -v ./...
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.22.2
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.26.0
|
||||
- ./bin/golangci-lint run
|
||||
- git clean -fdx .
|
||||
after_success:
|
||||
|
|
|
@ -30,14 +30,14 @@ var Awk = internal.Register(MustNewLexer(
|
|||
"root": {
|
||||
{`^(?=\s|/)`, Text, Push("slashstartsregex")},
|
||||
Include("commentsandwhitespace"),
|
||||
{`\+\+|--|\|\||&&|in\b|\$|!?~|(\*\*|[-<>+*%\^/!=|])=?`, Operator, Push("slashstartsregex")},
|
||||
{`\+\+|--|\|\||&&|in\b|\$|!?~|\|&|(\*\*|[-<>+*%\^/!=|])=?`, Operator, Push("slashstartsregex")},
|
||||
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
||||
{`[})\].]`, Punctuation, nil},
|
||||
{`(break|continue|do|while|exit|for|if|else|return)\b`, Keyword, Push("slashstartsregex")},
|
||||
{`(break|continue|do|while|exit|for|if|else|return|switch|case|default)\b`, Keyword, Push("slashstartsregex")},
|
||||
{`function\b`, KeywordDeclaration, Push("slashstartsregex")},
|
||||
{`(atan2|cos|exp|int|log|rand|sin|sqrt|srand|gensub|gsub|index|length|match|split|sprintf|sub|substr|tolower|toupper|close|fflush|getline|next|nextfile|print|printf|strftime|systime|delete|system)\b`, KeywordReserved, nil},
|
||||
{`(ARGC|ARGIND|ARGV|BEGIN|CONVFMT|ENVIRON|END|ERRNO|FIELDWIDTHS|FILENAME|FNR|FS|IGNORECASE|NF|NR|OFMT|OFS|ORFS|RLENGTH|RS|RSTART|RT|SUBSEP)\b`, NameBuiltin, nil},
|
||||
{`[$a-zA-Z_]\w*`, NameOther, nil},
|
||||
{`(atan2|cos|exp|int|log|rand|sin|sqrt|srand|gensub|gsub|index|length|match|split|patsplit|sprintf|sub|substr|tolower|toupper|close|fflush|getline|next(file)|print|printf|strftime|systime|mktime|delete|system|strtonum|and|compl|lshift|or|rshift|asorti?|isarray|bindtextdomain|dcn?gettext|@(include|load|namespace))\b`, KeywordReserved, nil},
|
||||
{`(ARGC|ARGIND|ARGV|BEGIN(FILE)?|BINMODE|CONVFMT|ENVIRON|END(FILE)?|ERRNO|FIELDWIDTHS|FILENAME|FNR|FPAT|FS|IGNORECASE|LINT|NF|NR|OFMT|OFS|ORS|PROCINFO|RLENGTH|RS|RSTART|RT|SUBSEP|TEXTDOMAIN)\b`, NameBuiltin, nil},
|
||||
{`[@$a-zA-Z_]\w*`, NameOther, nil},
|
||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
|
|
|
@ -36,7 +36,7 @@ var Bash = internal.Register(MustNewLexer(
|
|||
{`\b(if|fi|else|while|do|done|for|then|return|function|case|select|continue|until|esac|elif)(\s*)\b`, ByGroups(Keyword, Text), nil},
|
||||
{"\\b(alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|declare|dirs|disown|echo|enable|eval|exec|exit|export|false|fc|fg|getopts|hash|help|history|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|set|shift|shopt|source|suspend|test|time|times|trap|true|type|typeset|ulimit|umask|unalias|unset|wait)(?=[\\s)`])", NameBuiltin, nil},
|
||||
{`\A#!.+\n`, CommentPreproc, nil},
|
||||
{`#.*\S`, CommentSingle, nil},
|
||||
{`#.*(\S|$)`, CommentSingle, nil},
|
||||
{`\\[\w\W]`, LiteralStringEscape, nil},
|
||||
{`(\b\w+)(\s*)(\+?=)`, ByGroups(NameVariable, Text, Operator), nil},
|
||||
{`[\[\]{}()=]`, Operator, nil},
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// caddyfileCommon are the rules common to both of the lexer variants
|
||||
var caddyfileCommon = Rules{
|
||||
"site_block_common": {
|
||||
// Import keyword
|
||||
{`(import)(\s+)([^\s]+)`, ByGroups(Keyword, Text, NameVariableMagic), nil},
|
||||
// Matcher definition
|
||||
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||
// Matcher token stub for docs
|
||||
{`\[\<matcher\>\]`, NameDecorator, Push("matcher")},
|
||||
// These cannot have matchers but may have things that look like
|
||||
// matchers in their arguments, so we just parse as a subdirective.
|
||||
{`try_files`, Keyword, Push("subdirective")},
|
||||
// These are special, they can nest more directives
|
||||
{`handle_errors|handle|route|handle_path|not`, Keyword, Push("nested_directive")},
|
||||
// Any other directive
|
||||
{`[^\s#]+`, Keyword, Push("directive")},
|
||||
Include("base"),
|
||||
},
|
||||
"matcher": {
|
||||
{`\{`, Punctuation, Push("block")},
|
||||
// Not can be one-liner
|
||||
{`not`, Keyword, Push("deep_not_matcher")},
|
||||
// Any other same-line matcher
|
||||
{`[^\s#]+`, Keyword, Push("arguments")},
|
||||
// Terminators
|
||||
{`\n`, Text, Pop(1)},
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
Include("base"),
|
||||
},
|
||||
"block": {
|
||||
{`\}`, Punctuation, Pop(2)},
|
||||
// Not can be one-liner
|
||||
{`not`, Keyword, Push("not_matcher")},
|
||||
// Any other subdirective
|
||||
{`[^\s#]+`, Keyword, Push("subdirective")},
|
||||
Include("base"),
|
||||
},
|
||||
"nested_block": {
|
||||
{`\}`, Punctuation, Pop(2)},
|
||||
// Matcher definition
|
||||
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||
// Something that starts with literally < is probably a docs stub
|
||||
{`\<[^#]+\>`, Keyword, Push("nested_directive")},
|
||||
// Any other directive
|
||||
{`[^\s#]+`, Keyword, Push("nested_directive")},
|
||||
Include("base"),
|
||||
},
|
||||
"not_matcher": {
|
||||
{`\}`, Punctuation, Pop(2)},
|
||||
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||
{`[^\s#]+`, Keyword, Push("arguments")},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
"deep_not_matcher": {
|
||||
{`\}`, Punctuation, Pop(2)},
|
||||
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||
{`[^\s#]+`, Keyword, Push("deep_subdirective")},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
"directive": {
|
||||
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||
Include("matcher_token"),
|
||||
Include("comments_pop_1"),
|
||||
{`\n`, Text, Pop(1)},
|
||||
Include("base"),
|
||||
},
|
||||
"nested_directive": {
|
||||
{`\{(?=\s)`, Punctuation, Push("nested_block")},
|
||||
Include("matcher_token"),
|
||||
Include("comments_pop_1"),
|
||||
{`\n`, Text, Pop(1)},
|
||||
Include("base"),
|
||||
},
|
||||
"subdirective": {
|
||||
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||
Include("comments_pop_1"),
|
||||
{`\n`, Text, Pop(1)},
|
||||
Include("base"),
|
||||
},
|
||||
"arguments": {
|
||||
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||
Include("comments_pop_2"),
|
||||
{`\\\n`, Text, nil}, // Skip escaped newlines
|
||||
{`\n`, Text, Pop(2)},
|
||||
Include("base"),
|
||||
},
|
||||
"deep_subdirective": {
|
||||
{`\{(?=\s)`, Punctuation, Push("block")},
|
||||
Include("comments_pop_3"),
|
||||
{`\n`, Text, Pop(3)},
|
||||
Include("base"),
|
||||
},
|
||||
"matcher_token": {
|
||||
{`@[^\s]+`, NameDecorator, Push("arguments")}, // Named matcher
|
||||
{`/[^\s]+`, NameDecorator, Push("arguments")}, // Path matcher
|
||||
{`\*`, NameDecorator, Push("arguments")}, // Wildcard path matcher
|
||||
{`\[\<matcher\>\]`, NameDecorator, Push("arguments")}, // Matcher token stub for docs
|
||||
},
|
||||
"comments": {
|
||||
{`^#.*\n`, CommentSingle, nil}, // Comment at start of line
|
||||
{`\s+#.*\n`, CommentSingle, nil}, // Comment preceded by whitespace
|
||||
},
|
||||
"comments_pop_1": {
|
||||
{`^#.*\n`, CommentSingle, Pop(1)}, // Comment at start of line
|
||||
{`\s+#.*\n`, CommentSingle, Pop(1)}, // Comment preceded by whitespace
|
||||
},
|
||||
"comments_pop_2": {
|
||||
{`^#.*\n`, CommentSingle, Pop(2)}, // Comment at start of line
|
||||
{`\s+#.*\n`, CommentSingle, Pop(2)}, // Comment preceded by whitespace
|
||||
},
|
||||
"comments_pop_3": {
|
||||
{`^#.*\n`, CommentSingle, Pop(3)}, // Comment at start of line
|
||||
{`\s+#.*\n`, CommentSingle, Pop(3)}, // Comment preceded by whitespace
|
||||
},
|
||||
"base": {
|
||||
Include("comments"),
|
||||
{`(on|off|first|last|before|after|internal|strip_prefix|strip_suffix|replace)\b`, NameConstant, nil},
|
||||
{`(https?://)?([a-z0-9.-]+)(:)([0-9]+)`, ByGroups(Name, Name, Punctuation, LiteralNumberInteger), nil},
|
||||
{`[a-z-]+/[a-z-+]+`, LiteralString, nil},
|
||||
{`[0-9]+[km]?\b`, LiteralNumberInteger, nil},
|
||||
{`\{[\w+.\$-]+\}`, LiteralStringEscape, nil}, // Placeholder
|
||||
{`\[(?=[^#{}$]+\])`, Punctuation, nil},
|
||||
{`\]|\|`, Punctuation, nil},
|
||||
{`[^\s#{}$\]]+`, LiteralString, nil},
|
||||
{`/[^\s#]*`, Name, nil},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
}
|
||||
|
||||
// Caddyfile lexer.
|
||||
var Caddyfile = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Caddyfile",
|
||||
Aliases: []string{"caddyfile", "caddy"},
|
||||
Filenames: []string{"Caddyfile*"},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
Include("comments"),
|
||||
// Global options block
|
||||
{`^\s*(\{)\s*$`, ByGroups(Punctuation), Push("globals")},
|
||||
// Snippets
|
||||
{`(\([^\s#]+\))(\s*)(\{)`, ByGroups(NameVariableAnonymous, Text, Punctuation), Push("snippet")},
|
||||
// Site label
|
||||
{`[^#{(\s,]+`, GenericHeading, Push("label")},
|
||||
// Site label with placeholder
|
||||
{`\{[\w+.\$-]+\}`, LiteralStringEscape, Push("label")},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
"globals": {
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
{`[^\s#]+`, Keyword, Push("directive")},
|
||||
Include("base"),
|
||||
},
|
||||
"snippet": {
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
// Matcher definition
|
||||
{`@[^\s]+(?=\s)`, NameDecorator, Push("matcher")},
|
||||
// Any directive
|
||||
{`[^\s#]+`, Keyword, Push("directive")},
|
||||
Include("base"),
|
||||
},
|
||||
"label": {
|
||||
// Allow multiple labels, comma separated, newlines after
|
||||
// a comma means another label is coming
|
||||
{`,\s*\n?`, Text, nil},
|
||||
{` `, Text, nil},
|
||||
// Site label with placeholder
|
||||
{`\{[\w+.\$-]+\}`, LiteralStringEscape, nil},
|
||||
// Site label
|
||||
{`[^#{(\s,]+`, GenericHeading, nil},
|
||||
// Comment after non-block label (hack because comments end in \n)
|
||||
{`#.*\n`, CommentSingle, Push("site_block")},
|
||||
// Note: if \n, we'll never pop out of the site_block, it's valid
|
||||
{`\{(?=\s)|\n`, Punctuation, Push("site_block")},
|
||||
},
|
||||
"site_block": {
|
||||
{`\}`, Punctuation, Pop(2)},
|
||||
Include("site_block_common"),
|
||||
},
|
||||
}.Merge(caddyfileCommon),
|
||||
))
|
||||
|
||||
// Caddyfile directive-only lexer.
|
||||
var CaddyfileDirectives = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Caddyfile Directives",
|
||||
Aliases: []string{"caddyfile-directives", "caddyfile-d", "caddy-d"},
|
||||
Filenames: []string{},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
// Same as "site_block" in Caddyfile
|
||||
"root": {
|
||||
Include("site_block_common"),
|
||||
},
|
||||
}.Merge(caddyfileCommon),
|
||||
))
|
|
@ -1,15 +1,12 @@
|
|||
package circular
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/h"
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// PHP lexer.
|
||||
var PHP = internal.Register(DelegatingLexer(h.HTML, MustNewLexer(
|
||||
// PHP lexer for pure PHP code (not embedded in HTML).
|
||||
var PHP = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "PHP",
|
||||
Aliases: []string{"php", "php3", "php4", "php5"},
|
||||
|
@ -19,73 +16,65 @@ var PHP = internal.Register(DelegatingLexer(h.HTML, MustNewLexer(
|
|||
CaseInsensitive: true,
|
||||
EnsureNL: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`<\?(php)?`, CommentPreproc, Push("php")},
|
||||
{`[^<]+`, Other, nil},
|
||||
{`<`, Other, nil},
|
||||
},
|
||||
"php": {
|
||||
{`\?>`, CommentPreproc, Pop(1)},
|
||||
{`(<<<)([\'"]?)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)(\2\n.*?\n\s*)(\3)(;?)(\n)`, ByGroups(LiteralString, LiteralString, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, Punctuation, Text), nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`#.*?\n`, CommentSingle, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*\*/`, CommentMultiline, nil},
|
||||
{`/\*\*.*?\*/`, LiteralStringDoc, nil},
|
||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||
{`(->|::)(\s*)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Operator, Text, NameAttribute), nil},
|
||||
{`[~!%^&*+=|:.<>/@-]+`, Operator, nil},
|
||||
{`\?`, Operator, nil},
|
||||
{`[\[\]{}();,]+`, Punctuation, nil},
|
||||
{`(class)(\s+)`, ByGroups(Keyword, Text), Push("classname")},
|
||||
{`(function)(\s*)(?=\()`, ByGroups(Keyword, Text), nil},
|
||||
{`(function)(\s+)(&?)(\s*)`, ByGroups(Keyword, Text, Operator, Text), Push("functionname")},
|
||||
{`(const)(\s+)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Keyword, Text, NameConstant), nil},
|
||||
{`(and|E_PARSE|old_function|E_ERROR|or|as|E_WARNING|parent|eval|PHP_OS|break|exit|case|extends|PHP_VERSION|cfunction|FALSE|print|for|require|continue|foreach|require_once|declare|return|default|static|do|switch|die|stdClass|echo|else|TRUE|elseif|var|empty|if|xor|enddeclare|include|virtual|endfor|include_once|while|endforeach|global|endif|list|endswitch|new|endwhile|not|array|E_ALL|NULL|final|php_user_filter|interface|implements|public|private|protected|abstract|clone|try|catch|throw|this|use|namespace|trait|yield|finally)\b`, Keyword, nil},
|
||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||
Include("magicconstants"),
|
||||
{`\$\{\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*\}`, NameVariable, nil},
|
||||
{`\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameVariable, nil},
|
||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameOther, nil},
|
||||
{`(\d+\.\d*|\d*\.\d+)(e[+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||
{`\d+e[+-]?[0-9]+`, LiteralNumberFloat, nil},
|
||||
{`0[0-7]+`, LiteralNumberOct, nil},
|
||||
{`0x[a-f0-9]+`, LiteralNumberHex, nil},
|
||||
{`\d+`, LiteralNumberInteger, nil},
|
||||
{`0b[01]+`, LiteralNumberBin, nil},
|
||||
{`'([^'\\]*(?:\\.[^'\\]*)*)'`, LiteralStringSingle, nil},
|
||||
{"`([^`\\\\]*(?:\\\\.[^`\\\\]*)*)`", LiteralStringBacktick, nil},
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
},
|
||||
"magicfuncs": {
|
||||
{Words(``, `\b`, `__construct`, `__destruct`, `__call`, `__callStatic`, `__get`, `__set`, `__isset`, `__unset`, `__sleep`, `__wakeup`, `__toString`, `__invoke`, `__set_state`, `__clone`, `__debugInfo`), NameFunctionMagic, nil},
|
||||
},
|
||||
"magicconstants": {
|
||||
{Words(``, `\b`, `__LINE__`, `__FILE__`, `__DIR__`, `__FUNCTION__`, `__CLASS__`, `__TRAIT__`, `__METHOD__`, `__NAMESPACE__`), NameConstant, nil},
|
||||
},
|
||||
"classname": {
|
||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameClass, Pop(1)},
|
||||
},
|
||||
"functionname": {
|
||||
Include("magicfuncs"),
|
||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameFunction, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralStringDouble, Pop(1)},
|
||||
{`[^{$"\\]+`, LiteralStringDouble, nil},
|
||||
{`\\([nrt"$\\]|[0-7]{1,3}|x[0-9a-f]{1,2})`, LiteralStringEscape, nil},
|
||||
{`\$(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*(\[\S+?\]|->(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)?`, LiteralStringInterpol, nil},
|
||||
{`(\{\$\{)(.*?)(\}\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
||||
{`(\{)(\$.*?)(\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
||||
{`(\$\{)(\S+)(\})`, ByGroups(LiteralStringInterpol, NameVariable, LiteralStringInterpol), nil},
|
||||
{`[${\\]`, LiteralStringDouble, nil},
|
||||
},
|
||||
phpCommonRules.Rename("php", "root"),
|
||||
))
|
||||
|
||||
var phpCommonRules = Rules{
|
||||
"php": {
|
||||
{`\?>`, CommentPreproc, Pop(1)},
|
||||
{`(<<<)([\'"]?)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)(\2\n.*?\n\s*)(\3)(;?)(\n)`, ByGroups(LiteralString, LiteralString, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, Punctuation, Text), nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`#.*?\n`, CommentSingle, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*\*/`, CommentMultiline, nil},
|
||||
{`/\*\*.*?\*/`, LiteralStringDoc, nil},
|
||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||
{`(->|::)(\s*)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Operator, Text, NameAttribute), nil},
|
||||
{`[~!%^&*+=|:.<>/@-]+`, Operator, nil},
|
||||
{`\?`, Operator, nil},
|
||||
{`[\[\]{}();,]+`, Punctuation, nil},
|
||||
{`(class)(\s+)`, ByGroups(Keyword, Text), Push("classname")},
|
||||
{`(function)(\s*)(?=\()`, ByGroups(Keyword, Text), nil},
|
||||
{`(function)(\s+)(&?)(\s*)`, ByGroups(Keyword, Text, Operator, Text), Push("functionname")},
|
||||
{`(const)(\s+)((?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)`, ByGroups(Keyword, Text, NameConstant), nil},
|
||||
{`(and|E_PARSE|old_function|E_ERROR|or|as|E_WARNING|parent|eval|PHP_OS|break|exit|case|extends|PHP_VERSION|cfunction|FALSE|print|for|require|continue|foreach|require_once|declare|return|default|static|do|switch|die|stdClass|echo|else|TRUE|elseif|var|empty|if|xor|enddeclare|include|virtual|endfor|include_once|while|endforeach|global|endif|list|endswitch|new|endwhile|not|array|E_ALL|NULL|final|php_user_filter|interface|implements|public|private|protected|abstract|clone|try|catch|throw|this|use|namespace|trait|yield|finally)\b`, Keyword, nil},
|
||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||
Include("magicconstants"),
|
||||
{`\$\{\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*\}`, NameVariable, nil},
|
||||
{`\$+(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameVariable, nil},
|
||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameOther, nil},
|
||||
{`(\d+\.\d*|\d*\.\d+)(e[+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||
{`\d+e[+-]?[0-9]+`, LiteralNumberFloat, nil},
|
||||
{`0[0-7]+`, LiteralNumberOct, nil},
|
||||
{`0x[a-f0-9]+`, LiteralNumberHex, nil},
|
||||
{`\d+`, LiteralNumberInteger, nil},
|
||||
{`0b[01]+`, LiteralNumberBin, nil},
|
||||
{`'([^'\\]*(?:\\.[^'\\]*)*)'`, LiteralStringSingle, nil},
|
||||
{"`([^`\\\\]*(?:\\\\.[^`\\\\]*)*)`", LiteralStringBacktick, nil},
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
},
|
||||
).SetAnalyser(func(text string) float32 {
|
||||
if strings.Contains(text, "<?php") {
|
||||
return 0.5
|
||||
}
|
||||
return 0.0
|
||||
})))
|
||||
"magicfuncs": {
|
||||
{Words(``, `\b`, `__construct`, `__destruct`, `__call`, `__callStatic`, `__get`, `__set`, `__isset`, `__unset`, `__sleep`, `__wakeup`, `__toString`, `__invoke`, `__set_state`, `__clone`, `__debugInfo`), NameFunctionMagic, nil},
|
||||
},
|
||||
"magicconstants": {
|
||||
{Words(``, `\b`, `__LINE__`, `__FILE__`, `__DIR__`, `__FUNCTION__`, `__CLASS__`, `__TRAIT__`, `__METHOD__`, `__NAMESPACE__`), NameConstant, nil},
|
||||
},
|
||||
"classname": {
|
||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameClass, Pop(1)},
|
||||
},
|
||||
"functionname": {
|
||||
Include("magicfuncs"),
|
||||
{`(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*`, NameFunction, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralStringDouble, Pop(1)},
|
||||
{`[^{$"\\]+`, LiteralStringDouble, nil},
|
||||
{`\\([nrt"$\\]|[0-7]{1,3}|x[0-9a-f]{1,2})`, LiteralStringEscape, nil},
|
||||
{`\$(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*(\[\S+?\]|->(?:[\\_a-z]|[^\x00-\x7f])(?:[\\\w]|[^\x00-\x7f])*)?`, LiteralStringInterpol, nil},
|
||||
{`(\{\$\{)(.*?)(\}\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
||||
{`(\{)(\$.*?)(\})`, ByGroups(LiteralStringInterpol, UsingSelf("root"), LiteralStringInterpol), nil},
|
||||
{`(\$\{)(\S+)(\})`, ByGroups(LiteralStringInterpol, NameVariable, LiteralStringInterpol), nil},
|
||||
{`[${\\]`, LiteralStringDouble, nil},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package circular
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/h"
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// PHTML lexer is PHP in HTML.
|
||||
var PHTML = internal.Register(DelegatingLexer(h.HTML, MustNewLexer(
|
||||
&Config{
|
||||
Name: "PHTML",
|
||||
Aliases: []string{"phtml"},
|
||||
Filenames: []string{"*.phtml"},
|
||||
MimeTypes: []string{"application/x-php", "application/x-httpd-php", "application/x-httpd-php3", "application/x-httpd-php4", "application/x-httpd-php5"},
|
||||
DotAll: true,
|
||||
CaseInsensitive: true,
|
||||
EnsureNL: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`<\?(php)?`, CommentPreproc, Push("php")},
|
||||
{`[^<]+`, Other, nil},
|
||||
{`<`, Other, nil},
|
||||
},
|
||||
}.Merge(phpCommonRules),
|
||||
).SetAnalyser(func(text string) float32 {
|
||||
if strings.Contains(text, "<?php") {
|
||||
return 0.5
|
||||
}
|
||||
return 0.0
|
||||
})))
|
|
@ -28,6 +28,13 @@ var Elixir = internal.Register(MustNewLexer(
|
|||
{`:"`, LiteralStringSymbol, Push("string_double_atom")},
|
||||
{`:'`, LiteralStringSymbol, Push("string_single_atom")},
|
||||
{`((?:\.\.\.|<<>>|%\{\}|%|\{\})|(?:(?:\.\.\.|[a-z_]\w*[!?]?)|[A-Z]\w*(?:\.[A-Z]\w*)*|(?:\<\<\<|\>\>\>|\|\|\||\&\&\&|\^\^\^|\~\~\~|\=\=\=|\!\=\=|\~\>\>|\<\~\>|\|\~\>|\<\|\>|\=\=|\!\=|\<\=|\>\=|\&\&|\|\||\<\>|\+\+|\-\-|\|\>|\=\~|\-\>|\<\-|\||\.|\=|\~\>|\<\~|\<|\>|\+|\-|\*|\/|\!|\^|\&)))(:)(?=\s|\n)`, ByGroups(LiteralStringSymbol, Punctuation), nil},
|
||||
{`(fn|do|end|after|else|rescue|catch)\b`, Keyword, nil},
|
||||
{`(not|and|or|when|in)\b`, OperatorWord, nil},
|
||||
{`(case|cond|for|if|unless|try|receive|raise|quote|unquote|unquote_splicing|throw|super|while)\b`, Keyword, nil},
|
||||
{`(def|defp|defmodule|defprotocol|defmacro|defmacrop|defdelegate|defexception|defstruct|defimpl|defcallback)\b`, KeywordDeclaration, nil},
|
||||
{`(import|require|use|alias)\b`, KeywordNamespace, nil},
|
||||
{`(nil|true|false)\b`, NameConstant, nil},
|
||||
{`(_|__MODULE__|__DIR__|__ENV__|__CALLER__)\b`, NamePseudo, nil},
|
||||
{`@(?:\.\.\.|[a-z_]\w*[!?]?)`, NameAttribute, nil},
|
||||
{`(?:\.\.\.|[a-z_]\w*[!?]?)`, Name, nil},
|
||||
{`(%?)([A-Z]\w*(?:\.[A-Z]\w*)*)`, ByGroups(Punctuation, NameClass), nil},
|
||||
|
|
|
@ -15,6 +15,7 @@ var Go = internal.Register(MustNewLexer(
|
|||
Aliases: []string{"go", "golang"},
|
||||
Filenames: []string{"*.go"},
|
||||
MimeTypes: []string{"text/x-gosrc"},
|
||||
EnsureNL: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
|
|
|
@ -19,8 +19,8 @@ var HTTP = internal.Register(httpBodyContentTypeLexer(MustNewLexer(
|
|||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`(GET|POST|PUT|DELETE|HEAD|OPTIONS|TRACE|PATCH|CONNECT)( +)([^ ]+)( +)(HTTP)(/)(1\.[01])(\r?\n|\Z)`, ByGroups(NameFunction, Text, NameNamespace, Text, KeywordReserved, Operator, LiteralNumber, Text), Push("headers")},
|
||||
{`(HTTP)(/)(1\.[01])( +)(\d{3})( +)([^\r\n]+)(\r?\n|\Z)`, ByGroups(KeywordReserved, Operator, LiteralNumber, Text, LiteralNumber, Text, NameException, Text), Push("headers")},
|
||||
{`(GET|POST|PUT|DELETE|HEAD|OPTIONS|TRACE|PATCH|CONNECT)( +)([^ ]+)( +)(HTTP)(/)([12]\.[01])(\r?\n|\Z)`, ByGroups(NameFunction, Text, NameNamespace, Text, KeywordReserved, Operator, LiteralNumber, Text), Push("headers")},
|
||||
{`(HTTP)(/)([12]\.[01])( +)(\d{3})( +)([^\r\n]+)(\r?\n|\Z)`, ByGroups(KeywordReserved, Operator, LiteralNumber, Text, LiteralNumber, Text, NameException, Text), Push("headers")},
|
||||
},
|
||||
"headers": {
|
||||
{`([^\s:]+)( *)(:)( *)([^\r\n]+)(\r?\n|\Z)`, EmitterFunc(httpHeaderBlock), nil},
|
||||
|
|
|
@ -10,7 +10,7 @@ var Ini = internal.Register(MustNewLexer(
|
|||
&Config{
|
||||
Name: "INI",
|
||||
Aliases: []string{"ini", "cfg", "dosini"},
|
||||
Filenames: []string{"*.ini", "*.cfg", "*.inf", ".gitconfig"},
|
||||
Filenames: []string{"*.ini", "*.cfg", "*.inf", ".gitconfig", ".editorconfig"},
|
||||
MimeTypes: []string{"text/x-ini", "text/inf"},
|
||||
},
|
||||
Rules{
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -24,32 +24,71 @@ var Kotlin = internal.Register(MustNewLexer(
|
|||
{`//[^\n]*\n?`, CommentSingle, nil},
|
||||
{`/[*].*?[*]/`, CommentMultiline, nil},
|
||||
{`\n`, Text, nil},
|
||||
{`::|!!|\?[:.]`, Operator, nil},
|
||||
{`[~!%^&*()+=|\[\]:;,.<>/?-]`, Punctuation, nil},
|
||||
{`!==|!in|!is|===`, Operator, nil},
|
||||
{`%=|&&|\*=|\+\+|\+=|--|-=|->|\.\.|\/=|::|<=|==|>=|!!|!=|\|\||\?[:.]`, Operator, nil},
|
||||
{`[~!%^&*()+=|\[\]:;,.<>\/?-]`, Punctuation, nil},
|
||||
{`[{}]`, Punctuation, nil},
|
||||
{`"""[^"]*"""`, LiteralString, nil},
|
||||
{`"(\\\\|\\"|[^"\n])*["\n]`, LiteralString, nil},
|
||||
{`"""`, LiteralString, Push("rawstring")},
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
{`(')(\\u[0-9a-fA-F]{4})(')`, ByGroups(LiteralStringChar, LiteralStringEscape, LiteralStringChar), nil},
|
||||
{`'\\.'|'[^\\]'`, LiteralStringChar, nil},
|
||||
{`0[xX][0-9a-fA-F]+[Uu]?[Ll]?|[0-9]+(\.[0-9]*)?([eE][+-][0-9]+)?[fF]?[Uu]?[Ll]?`, LiteralNumber, nil},
|
||||
{`(companion)(\s+)(object)`, ByGroups(Keyword, Text, Keyword), nil},
|
||||
{`(class|interface|object)(\s+)`, ByGroups(Keyword, Text), Push("class")},
|
||||
{`(package|import)(\s+)`, ByGroups(Keyword, Text), Push("package")},
|
||||
{`(val|var)(\s+)`, ByGroups(Keyword, Text), Push("property")},
|
||||
{`(fun)(\s+)(<[^>]*>\s+)?`, ByGroups(Keyword, Text, Text), Push("function")},
|
||||
{`(abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|false|final|finally|for|fun|get|if|import|in|infix|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|true|try|val|var|vararg|when|where|while)\b`, Keyword, nil},
|
||||
{"(@?[" + kotlinIdentifier + "]*`)", Name, nil},
|
||||
{`(fun)(\s+)`, ByGroups(Keyword, Text), Push("function")},
|
||||
{`(abstract|actual|annotation|as|as\?|break|by|catch|class|companion|const|constructor|continue|crossinline|data|delegate|do|dynamic|else|enum|expect|external|false|field|file|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|it|lateinit|noinline|null|object|open|operator|out|override|package|param|private|property|protected|public|receiver|reified|return|sealed|set|setparam|super|suspend|tailrec|this|throw|true|try|typealias|typeof|val|var|vararg|when|where|while)\b`, Keyword, nil},
|
||||
{`@[` + kotlinIdentifier + `]+`, NameDecorator, nil},
|
||||
{`[` + kotlinIdentifier + `]+`, Name, nil},
|
||||
},
|
||||
"package": {
|
||||
{`\S+`, NameNamespace, Pop(1)},
|
||||
},
|
||||
"class": {
|
||||
{"(@?[" + kotlinIdentifier + "]*`)", NameClass, Pop(1)},
|
||||
// \x60 is the back tick character (`)
|
||||
{`\x60[^\x60]+?\x60`, NameClass, Pop(1)},
|
||||
{`[` + kotlinIdentifier + `]+`, NameClass, Pop(1)},
|
||||
},
|
||||
"property": {
|
||||
{"(@?[" + kotlinIdentifier + " ]*`)", NameProperty, Pop(1)},
|
||||
{`\x60[^\x60]+?\x60`, NameProperty, Pop(1)},
|
||||
{`[` + kotlinIdentifier + `]+`, NameProperty, Pop(1)},
|
||||
},
|
||||
"generics-specification": {
|
||||
{`<`, Punctuation, Push("generics-specification")}, // required for generics inside generics e.g. <T : List<Int> >
|
||||
{`>`, Punctuation, Pop(1)},
|
||||
{`[,:*?]`, Punctuation, nil},
|
||||
{`(in|out|reified)`, Keyword, nil},
|
||||
{`\x60[^\x60]+?\x60`, NameClass, nil},
|
||||
{`[` + kotlinIdentifier + `]+`, NameClass, nil},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
"function": {
|
||||
{"(@?[" + kotlinIdentifier + " ]*`)", NameFunction, Pop(1)},
|
||||
{`<`, Punctuation, Push("generics-specification")},
|
||||
{`\x60[^\x60]+?\x60`, NameFunction, Pop(1)},
|
||||
{`[` + kotlinIdentifier + `]+`, NameFunction, Pop(1)},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
"rawstring": {
|
||||
// raw strings don't allow character escaping
|
||||
{`"""`, LiteralString, Pop(1)},
|
||||
{`(?:[^$"]+|\"{1,2}[^"])+`, LiteralString, nil},
|
||||
Include("string-interpol"),
|
||||
// remaining dollar signs are just a string
|
||||
{`\$`, LiteralString, nil},
|
||||
},
|
||||
"string": {
|
||||
{`\\[tbnr'"\\\$]`, LiteralStringEscape, nil},
|
||||
{`\\u[0-9a-fA-F]{4}`, LiteralStringEscape, nil},
|
||||
{`"`, LiteralStringDouble, Pop(1)},
|
||||
Include("string-interpol"),
|
||||
{`[^\n\\"$]+`, LiteralStringDouble, nil},
|
||||
// remaining dollar signs are just a string
|
||||
{`\$`, LiteralStringDouble, nil},
|
||||
},
|
||||
"string-interpol": {
|
||||
{`\$[` + kotlinIdentifier + `]+`, LiteralStringInterpol, nil},
|
||||
{`\${[^}\n]*}`, LiteralStringInterpol, nil},
|
||||
},
|
||||
},
|
||||
))
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
_ "github.com/alecthomas/chroma/lexers/w"
|
||||
_ "github.com/alecthomas/chroma/lexers/x"
|
||||
_ "github.com/alecthomas/chroma/lexers/y"
|
||||
_ "github.com/alecthomas/chroma/lexers/z"
|
||||
)
|
||||
|
||||
// Registry of Lexers.
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package p
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Pony lexer.
|
||||
var Pony = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Pony",
|
||||
Aliases: []string{"pony"},
|
||||
Filenames: []string{"*.pony"},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\n`, Text, nil},
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`//.*\n`, CommentSingle, nil},
|
||||
{`/\*`, CommentMultiline, Push("nested_comment")},
|
||||
{`"""(?:.|\n)*?"""`, LiteralStringDoc, nil},
|
||||
{`"`, LiteralString, Push("string")},
|
||||
{`\'.*\'`, LiteralStringChar, nil},
|
||||
{`=>|[]{}:().~;,|&!^?[]`, Punctuation, nil},
|
||||
{Words(``, `\b`, `addressof`, `and`, `as`, `consume`, `digestof`, `is`, `isnt`, `not`, `or`), OperatorWord, nil},
|
||||
{`!=|==|<<|>>|[-+/*%=<>]`, Operator, nil},
|
||||
{Words(``, `\b`, `box`, `break`, `compile_error`, `compile_intrinsic`, `continue`, `do`, `else`, `elseif`, `embed`, `end`, `error`, `for`, `if`, `ifdef`, `in`, `iso`, `lambda`, `let`, `match`, `object`, `recover`, `ref`, `repeat`, `return`, `tag`, `then`, `this`, `trn`, `try`, `until`, `use`, `var`, `val`, `where`, `while`, `with`, `#any`, `#read`, `#send`, `#share`), Keyword, nil},
|
||||
{`(actor|class|struct|primitive|interface|trait|type)((?:\s)+)`, ByGroups(Keyword, Text), Push("typename")},
|
||||
{`(new|fun|be)((?:\s)+)`, ByGroups(Keyword, Text), Push("methodname")},
|
||||
{Words(``, `\b`, `U8`, `U16`, `U32`, `U64`, `ULong`, `USize`, `U128`, `Unsigned`, `Stringable`, `String`, `StringBytes`, `StringRunes`, `InputNotify`, `InputStream`, `Stdin`, `ByteSeq`, `ByteSeqIter`, `OutStream`, `StdStream`, `SourceLoc`, `I8`, `I16`, `I32`, `I64`, `ILong`, `ISize`, `I128`, `Signed`, `Seq`, `RuntimeOptions`, `Real`, `Integer`, `SignedInteger`, `UnsignedInteger`, `FloatingPoint`, `Number`, `Int`, `ReadSeq`, `ReadElement`, `Pointer`, `Platform`, `NullablePointer`, `None`, `Iterator`, `F32`, `F64`, `Float`, `Env`, `DoNotOptimise`, `DisposableActor`, `Less`, `Equal`, `Greater`, `Compare`, `HasEq`, `Equatable`, `Comparable`, `Bool`, `AsioEventID`, `AsioEventNotify`, `AsioEvent`, `Array`, `ArrayKeys`, `ArrayValues`, `ArrayPairs`, `Any`, `AmbientAuth`), KeywordType, nil},
|
||||
{`_?[A-Z]\w*`, NameClass, nil},
|
||||
{`string\(\)`, NameOther, nil},
|
||||
{`(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`\d+`, LiteralNumberInteger, nil},
|
||||
{`(true|false)\b`, Keyword, nil},
|
||||
{`_\d*`, Name, nil},
|
||||
{`_?[a-z][\w\'_]*`, Name, nil},
|
||||
},
|
||||
"typename": {
|
||||
{`(iso|trn|ref|val|box|tag)?((?:\s)*)(_?[A-Z]\w*)`, ByGroups(Keyword, Text, NameClass), Pop(1)},
|
||||
},
|
||||
"methodname": {
|
||||
{`(iso|trn|ref|val|box|tag)?((?:\s)*)(_?[a-z]\w*)`, ByGroups(Keyword, Text, NameFunction), Pop(1)},
|
||||
},
|
||||
"nested_comment": {
|
||||
{`[^*/]+`, CommentMultiline, nil},
|
||||
{`/\*`, CommentMultiline, Push()},
|
||||
{`\*/`, CommentMultiline, Pop(1)},
|
||||
{`[*/]`, CommentMultiline, nil},
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralString, Pop(1)},
|
||||
{`\\"`, LiteralString, nil},
|
||||
{`[^\\"]+`, LiteralString, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -22,7 +22,7 @@ var TOML = internal.Register(MustNewLexer(
|
|||
{`[+-]?[0-9](_?\d)*`, LiteralNumberInteger, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, StringDouble, nil},
|
||||
{`'(\\\\|\\'|[^'])*'`, StringSingle, nil},
|
||||
{`[.,=\[\]]`, Punctuation, nil},
|
||||
{`[.,=\[\]{}]`, Punctuation, nil},
|
||||
{`[^\W\d]\w*`, NameOther, nil},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -38,14 +38,14 @@ var TypeScript = internal.Register(MustNewLexer(
|
|||
{`\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|(<<|>>>?|==?|!=?|[-<>+*%&|^/])=?`, Operator, Push("slashstartsregex")},
|
||||
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
||||
{`[})\].]`, Punctuation, nil},
|
||||
{`(for|in|while|do|break|return|continue|switch|case|default|if|else|throw|try|catch|finally|new|delete|typeof|instanceof|void|this)\b`, Keyword, Push("slashstartsregex")},
|
||||
{`(for|in|of|while|do|break|return|yield|continue|switch|case|default|if|else|throw|try|catch|finally|new|delete|typeof|instanceof|keyof|asserts|is|infer|await|void|this)\b`, Keyword, Push("slashstartsregex")},
|
||||
{`(var|let|with|function)\b`, KeywordDeclaration, Push("slashstartsregex")},
|
||||
{`(abstract|boolean|byte|char|class|const|debugger|double|enum|export|extends|final|float|goto|implements|import|int|interface|long|native|package|private|protected|public|short|static|super|synchronized|throws|transient|volatile)\b`, KeywordReserved, nil},
|
||||
{`(abstract|async|boolean|class|const|debugger|enum|export|extends|from|get|global|goto|implements|import|interface|namespace|package|private|protected|public|readonly|require|set|static|super|type)\b`, KeywordReserved, nil},
|
||||
{`(true|false|null|NaN|Infinity|undefined)\b`, KeywordConstant, nil},
|
||||
{`(Array|Boolean|Date|Error|Function|Math|netscape|Number|Object|Packages|RegExp|String|sun|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|window)\b`, NameBuiltin, nil},
|
||||
{`(Array|Boolean|Date|Error|Function|Math|Number|Object|Packages|RegExp|String|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt|document|this|window)\b`, NameBuiltin, nil},
|
||||
{`\b(module)(\s*)(\s*[\w?.$][\w?.$]*)(\s*)`, ByGroups(KeywordReserved, Text, NameOther, Text), Push("slashstartsregex")},
|
||||
{`\b(string|bool|number)\b`, KeywordType, nil},
|
||||
{`\b(constructor|declare|interface|as|AS)\b`, KeywordReserved, nil},
|
||||
{`\b(string|bool|number|any|never|object|symbol|unique|unknown|bigint)\b`, KeywordType, nil},
|
||||
{`\b(constructor|declare|interface|as)\b`, KeywordReserved, nil},
|
||||
{`(super)(\s*)(\([\w,?.$\s]+\s*\))`, ByGroups(KeywordReserved, Text), Push("slashstartsregex")},
|
||||
{`([a-zA-Z_?.$][\w?.$]*)\(\) \{`, NameOther, Push("slashstartsregex")},
|
||||
{`([\w?.$][\w?.$]*)(\s*:\s*)([\w?.$][\w?.$]*)`, ByGroups(NameOther, Text, KeywordType), nil},
|
||||
|
|
|
@ -15,32 +15,36 @@ var YAML = internal.Register(MustNewLexer(
|
|||
Rules{
|
||||
"root": {
|
||||
Include("whitespace"),
|
||||
{`^---`, Text, nil},
|
||||
{`^---`, NameNamespace, nil},
|
||||
{`^\.\.\.`, NameNamespace, nil},
|
||||
{`[\n?]?\s*- `, Text, nil},
|
||||
{`#.*$`, Comment, nil},
|
||||
{`!![^\s]+`, CommentPreproc, nil},
|
||||
{`&[^\s]+`, CommentPreproc, nil},
|
||||
{`\*[^\s]+`, CommentPreproc, nil},
|
||||
{`^%include\s+[^\n\r]+`, CommentPreproc, nil},
|
||||
{`([>|+-]\s+)(\s+)((?:(?:.*?$)(?:[\n\r]*?)?)*)`, ByGroups(StringDoc, StringDoc, StringDoc), nil},
|
||||
Include("key"),
|
||||
Include("value"),
|
||||
{`[?:,\[\]]`, Punctuation, nil},
|
||||
{`.`, Text, nil},
|
||||
},
|
||||
"value": {
|
||||
{Words(``, `\b`, "true", "false", "null"), KeywordConstant, nil},
|
||||
{`([>|](?:[+-])?)(\n(^ {1,})(?:.*\n*(?:^\3 *).*)*)`, ByGroups(Punctuation, StringDoc, Whitespace), nil},
|
||||
{Words(``, `\b`, "true", "True", "TRUE", "false", "False", "FALSE", "null",
|
||||
"y", "Y", "yes", "Yes", "YES", "n", "N", "no", "No", "NO",
|
||||
"on", "On", "ON", "off", "Off", "OFF"), KeywordConstant, nil},
|
||||
{`"(?:\\.|[^"])*"`, StringDouble, nil},
|
||||
{`'(?:\\.|[^'])*'`, StringSingle, nil},
|
||||
{`\d\d\d\d-\d\d-\d\d([T ]\d\d:\d\d:\d\d(\.\d+)?(Z|\s+[-+]\d+)?)?`, LiteralDate, nil},
|
||||
{`\b[+\-]?(0x[\da-f]+|0o[0-7]+|(\d+\.?\d*|\.?\d+)(e[\+\-]?\d+)?|\.inf|\.nan)\b`, Number, nil},
|
||||
{`\b[\w]+\b`, Text, nil},
|
||||
{`([^\{\}\[\]\?,\:\!\-\*&\@].*)( )+(#.*)`, ByGroups(Literal, Whitespace, Comment), nil},
|
||||
{`[^\{\}\[\]\?,\:\!\-\*&\@].*`, Literal, nil},
|
||||
},
|
||||
"key": {
|
||||
{`"[^"\n].*": `, Keyword, nil},
|
||||
{`(-)( )([^"\n{]*)(:)( )`, ByGroups(Punctuation, Whitespace, Keyword, Punctuation, Whitespace), nil},
|
||||
{`([^"\n{]*)(:)( )`, ByGroups(Keyword, Punctuation, Whitespace), nil},
|
||||
{`([^"\n{]*)(:)(\n)`, ByGroups(Keyword, Punctuation, Whitespace), nil},
|
||||
{`"[^"\n].*": `, NameTag, nil},
|
||||
{`(-)( )([^"\n{]*)(:)( )`, ByGroups(Punctuation, Whitespace, NameTag, Punctuation, Whitespace), nil},
|
||||
{`([^"\n{]*)(:)( )`, ByGroups(NameTag, Punctuation, Whitespace), nil},
|
||||
{`([^"\n{]*)(:)(\n)`, ByGroups(NameTag, Punctuation, Whitespace), nil},
|
||||
},
|
||||
"whitespace": {
|
||||
{`\s+`, Whitespace, nil},
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package z
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Zig lexer.
|
||||
var Zig = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Zig",
|
||||
Aliases: []string{"zig"},
|
||||
Filenames: []string{"*.zig"},
|
||||
MimeTypes: []string{"text/zig"},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\n`, TextWhitespace, nil},
|
||||
{`\s+`, TextWhitespace, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{Words(``, `\b`, `break`, `return`, `continue`, `asm`, `defer`, `errdefer`, `unreachable`, `try`, `catch`, `async`, `await`, `suspend`, `resume`, `cancel`), Keyword, nil},
|
||||
{Words(``, `\b`, `const`, `var`, `extern`, `packed`, `export`, `pub`, `noalias`, `inline`, `comptime`, `nakedcc`, `stdcallcc`, `volatile`, `allowzero`, `align`, `linksection`, `threadlocal`), KeywordReserved, nil},
|
||||
{Words(``, `\b`, `struct`, `enum`, `union`, `error`), Keyword, nil},
|
||||
{Words(``, `\b`, `while`, `for`), Keyword, nil},
|
||||
{Words(``, `\b`, `bool`, `f16`, `f32`, `f64`, `f128`, `void`, `noreturn`, `type`, `anyerror`, `promise`, `i0`, `u0`, `isize`, `usize`, `comptime_int`, `comptime_float`, `c_short`, `c_ushort`, `c_int`, `c_uint`, `c_long`, `c_ulong`, `c_longlong`, `c_ulonglong`, `c_longdouble`, `c_voidi8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`), KeywordType, nil},
|
||||
{Words(``, `\b`, `true`, `false`, `null`, `undefined`), KeywordConstant, nil},
|
||||
{Words(``, `\b`, `if`, `else`, `switch`, `and`, `or`, `orelse`), Keyword, nil},
|
||||
{Words(``, `\b`, `fn`, `usingnamespace`, `test`), Keyword, nil},
|
||||
{`0x[0-9a-fA-F]+\.[0-9a-fA-F]+([pP][\-+]?[0-9a-fA-F]+)?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+\.?[pP][\-+]?[0-9a-fA-F]+`, LiteralNumberFloat, nil},
|
||||
{`[0-9]+\.[0-9]+([eE][-+]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||
{`[0-9]+\.?[eE][-+]?[0-9]+`, LiteralNumberFloat, nil},
|
||||
{`0b[01]+`, LiteralNumberBin, nil},
|
||||
{`0o[0-7]+`, LiteralNumberOct, nil},
|
||||
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`@[a-zA-Z_]\w*`, NameBuiltin, nil},
|
||||
{`[a-zA-Z_]\w*`, Name, nil},
|
||||
{`\'\\\'\'`, LiteralStringEscape, nil},
|
||||
{`\'\\(|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{6}|[nr\\t\'"])\'`, LiteralStringEscape, nil},
|
||||
{`\'[^\\\']\'`, LiteralString, nil},
|
||||
{`\\\\[^\n]*`, LiteralStringHeredoc, nil},
|
||||
{`c\\\\[^\n]*`, LiteralStringHeredoc, nil},
|
||||
{`c?"`, LiteralString, Push("string")},
|
||||
{`[+%=><|^!?/\-*&~:]`, Operator, nil},
|
||||
{`[{}()\[\],.;]`, Punctuation, nil},
|
||||
},
|
||||
"string": {
|
||||
{`\\(x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{6}|[nr\\t\'"])`, LiteralStringEscape, nil},
|
||||
{`[^\\"\n]+`, LiteralString, nil},
|
||||
{`"`, LiteralString, Pop(1)},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -6,6 +6,7 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
|
@ -160,6 +161,14 @@ func Tokenise(lexer Lexer, options *TokeniseOptions, text string) ([]Token, erro
|
|||
// Rules maps from state to a sequence of Rules.
|
||||
type Rules map[string][]Rule
|
||||
|
||||
// Rename clones rules then a rule.
|
||||
func (r Rules) Rename(old, new string) Rules {
|
||||
r = r.Clone()
|
||||
r[new] = r[old]
|
||||
delete(r, old)
|
||||
return r
|
||||
}
|
||||
|
||||
// Clone returns a clone of the Rules.
|
||||
func (r Rules) Clone() Rules {
|
||||
out := map[string][]Rule{}
|
||||
|
@ -170,6 +179,15 @@ func (r Rules) Clone() Rules {
|
|||
return out
|
||||
}
|
||||
|
||||
// Merge creates a clone of "r" then merges "rules" into the clone.
|
||||
func (r Rules) Merge(rules Rules) Rules {
|
||||
out := r.Clone()
|
||||
for k, v := range rules.Clone() {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// MustNewLexer creates a new Lexer or panics.
|
||||
func MustNewLexer(config *Config, rules Rules) *RegexLexer {
|
||||
lexer, err := NewLexer(config, rules)
|
||||
|
@ -376,6 +394,7 @@ func (r *RegexLexer) maybeCompile() (err error) {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err)
|
||||
}
|
||||
rule.Regexp.MatchTimeout = time.Millisecond * 250
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Aymerick JEHANNE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package css
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Declaration represents a parsed style property
|
||||
type Declaration struct {
|
||||
Property string
|
||||
Value string
|
||||
Important bool
|
||||
}
|
||||
|
||||
// NewDeclaration instanciates a new Declaration
|
||||
func NewDeclaration() *Declaration {
|
||||
return &Declaration{}
|
||||
}
|
||||
|
||||
// Returns string representation of the Declaration
|
||||
func (decl *Declaration) String() string {
|
||||
return decl.StringWithImportant(true)
|
||||
}
|
||||
|
||||
// StringWithImportant returns string representation with optional !important part
|
||||
func (decl *Declaration) StringWithImportant(option bool) string {
|
||||
result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
|
||||
|
||||
if option && decl.Important {
|
||||
result += " !important"
|
||||
}
|
||||
|
||||
result += ";"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Equal returns true if both Declarations are equals
|
||||
func (decl *Declaration) Equal(other *Declaration) bool {
|
||||
return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
|
||||
}
|
||||
|
||||
//
|
||||
// DeclarationsByProperty
|
||||
//
|
||||
|
||||
// DeclarationsByProperty represents sortable style declarations
|
||||
type DeclarationsByProperty []*Declaration
|
||||
|
||||
// Implements sort.Interface
|
||||
func (declarations DeclarationsByProperty) Len() int {
|
||||
return len(declarations)
|
||||
}
|
||||
|
||||
// Implements sort.Interface
|
||||
func (declarations DeclarationsByProperty) Swap(i, j int) {
|
||||
declarations[i], declarations[j] = declarations[j], declarations[i]
|
||||
}
|
||||
|
||||
// Implements sort.Interface
|
||||
func (declarations DeclarationsByProperty) Less(i, j int) bool {
|
||||
return declarations[i].Property < declarations[j].Property
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
package css
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
indentSpace = 2
|
||||
)
|
||||
|
||||
// RuleKind represents a Rule kind
|
||||
type RuleKind int
|
||||
|
||||
// Rule kinds
|
||||
const (
|
||||
QualifiedRule RuleKind = iota
|
||||
AtRule
|
||||
)
|
||||
|
||||
// At Rules than have Rules inside their block instead of Declarations
|
||||
var atRulesWithRulesBlock = []string{
|
||||
"@document", "@font-feature-values", "@keyframes", "@media", "@supports",
|
||||
}
|
||||
|
||||
// Rule represents a parsed CSS rule
|
||||
type Rule struct {
|
||||
Kind RuleKind
|
||||
|
||||
// At Rule name (eg: "@media")
|
||||
Name string
|
||||
|
||||
// Raw prelude
|
||||
Prelude string
|
||||
|
||||
// Qualified Rule selectors parsed from prelude
|
||||
Selectors []string
|
||||
|
||||
// Style properties
|
||||
Declarations []*Declaration
|
||||
|
||||
// At Rule embedded rules
|
||||
Rules []*Rule
|
||||
|
||||
// Current rule embedding level
|
||||
EmbedLevel int
|
||||
}
|
||||
|
||||
// NewRule instanciates a new Rule
|
||||
func NewRule(kind RuleKind) *Rule {
|
||||
return &Rule{
|
||||
Kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns string representation of rule kind
|
||||
func (kind RuleKind) String() string {
|
||||
switch kind {
|
||||
case QualifiedRule:
|
||||
return "Qualified Rule"
|
||||
case AtRule:
|
||||
return "At Rule"
|
||||
default:
|
||||
return "WAT"
|
||||
}
|
||||
}
|
||||
|
||||
// EmbedsRules returns true if this rule embeds another rules
|
||||
func (rule *Rule) EmbedsRules() bool {
|
||||
if rule.Kind == AtRule {
|
||||
for _, atRuleName := range atRulesWithRulesBlock {
|
||||
if rule.Name == atRuleName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Equal returns true if both rules are equals
|
||||
func (rule *Rule) Equal(other *Rule) bool {
|
||||
if (rule.Kind != other.Kind) ||
|
||||
(rule.Prelude != other.Prelude) ||
|
||||
(rule.Name != other.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (len(rule.Selectors) != len(other.Selectors)) ||
|
||||
(len(rule.Declarations) != len(other.Declarations)) ||
|
||||
(len(rule.Rules) != len(other.Rules)) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, sel := range rule.Selectors {
|
||||
if sel != other.Selectors[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i, decl := range rule.Declarations {
|
||||
if !decl.Equal(other.Declarations[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i, rule := range rule.Rules {
|
||||
if !rule.Equal(other.Rules[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Diff returns a string representation of rules differences
|
||||
func (rule *Rule) Diff(other *Rule) []string {
|
||||
result := []string{}
|
||||
|
||||
if rule.Kind != other.Kind {
|
||||
result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String()))
|
||||
}
|
||||
|
||||
if rule.Prelude != other.Prelude {
|
||||
result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude))
|
||||
}
|
||||
|
||||
if rule.Name != other.Name {
|
||||
result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name))
|
||||
}
|
||||
|
||||
if len(rule.Selectors) != len(other.Selectors) {
|
||||
result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", ")))
|
||||
} else {
|
||||
for i, sel := range rule.Selectors {
|
||||
if sel != other.Selectors[i] {
|
||||
result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(rule.Declarations) != len(other.Declarations) {
|
||||
result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations)))
|
||||
} else {
|
||||
for i, decl := range rule.Declarations {
|
||||
if !decl.Equal(other.Declarations[i]) {
|
||||
result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(rule.Rules) != len(other.Rules) {
|
||||
result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules)))
|
||||
} else {
|
||||
|
||||
for i, rule := range rule.Rules {
|
||||
if !rule.Equal(other.Rules[i]) {
|
||||
result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the string representation of a rule
|
||||
func (rule *Rule) String() string {
|
||||
result := ""
|
||||
|
||||
if rule.Kind == QualifiedRule {
|
||||
for i, sel := range rule.Selectors {
|
||||
if i != 0 {
|
||||
result += ", "
|
||||
}
|
||||
result += sel
|
||||
}
|
||||
} else {
|
||||
// AtRule
|
||||
result += fmt.Sprintf("%s", rule.Name)
|
||||
|
||||
if rule.Prelude != "" {
|
||||
if result != "" {
|
||||
result += " "
|
||||
}
|
||||
result += fmt.Sprintf("%s", rule.Prelude)
|
||||
}
|
||||
}
|
||||
|
||||
if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) {
|
||||
result += ";"
|
||||
} else {
|
||||
result += " {\n"
|
||||
|
||||
if rule.EmbedsRules() {
|
||||
for _, subRule := range rule.Rules {
|
||||
result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
|
||||
}
|
||||
} else {
|
||||
for _, decl := range rule.Declarations {
|
||||
result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
|
||||
}
|
||||
}
|
||||
|
||||
result += fmt.Sprintf("%s}", rule.indentEndBlock())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns identation spaces for declarations and rules
|
||||
func (rule *Rule) indent() string {
|
||||
result := ""
|
||||
|
||||
for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
|
||||
result += " "
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns identation spaces for end of block character
|
||||
func (rule *Rule) indentEndBlock() string {
|
||||
result := ""
|
||||
|
||||
for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
|
||||
result += " "
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package css
|
||||
|
||||
// Stylesheet represents a parsed stylesheet
|
||||
type Stylesheet struct {
|
||||
Rules []*Rule
|
||||
}
|
||||
|
||||
// NewStylesheet instanciate a new Stylesheet
|
||||
func NewStylesheet() *Stylesheet {
|
||||
return &Stylesheet{}
|
||||
}
|
||||
|
||||
// Returns string representation of the Stylesheet
|
||||
func (sheet *Stylesheet) String() string {
|
||||
result := ""
|
||||
|
||||
for _, rule := range sheet.Rules {
|
||||
if result != "" {
|
||||
result += "\n"
|
||||
}
|
||||
result += rule.String()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
run:
|
||||
tests: false
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- dupl
|
||||
- exportloopref
|
||||
- goconst
|
||||
- godot
|
||||
- godox
|
||||
- goimports
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- misspell
|
||||
- prealloc
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- whitespace
|
|
@ -1,10 +1,13 @@
|
|||
# Glamour
|
||||
|
||||
[![Latest Release](https://img.shields.io/github/release/charmbracelet/glamour.svg)](https://github.com/charmbracelet/glamour/releases)
|
||||
[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://pkg.go.dev/github.com/charmbracelet/glamour?tab=doc)
|
||||
[![Build Status](https://github.com/charmbracelet/glamour/workflows/build/badge.svg)](https://github.com/charmbracelet/glamour/actions)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/charmbracelet/glamour/badge.svg?branch=master)](https://coveralls.io/github/charmbracelet/glamour?branch=master)
|
||||
[![Go ReportCard](http://goreportcard.com/badge/charmbracelet/glamour)](http://goreportcard.com/report/charmbracelet/glamour)
|
||||
<p>
|
||||
<img src="https://stuff.charm.sh/glamour/glamour-github-header.png" width="245" alt="Glamour Title Treatment"><br>
|
||||
<a href="https://github.com/charmbracelet/glamour/releases"><img src="https://img.shields.io/github/release/charmbracelet/glamour.svg" alt="Latest Release"></a>
|
||||
<a href="https://pkg.go.dev/github.com/charmbracelet/glamour?tab=doc"><img src="https://godoc.org/github.com/golang/gddo?status.svg" alt="GoDoc"></a>
|
||||
<a href="https://github.com/charmbracelet/glamour/actions"><img src="https://github.com/charmbracelet/glamour/workflows/build/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://coveralls.io/github/charmbracelet/glamour?branch=master"><img src="https://coveralls.io/repos/github/charmbracelet/glamour/badge.svg?branch=master" alt="Coverage Status"></a>
|
||||
<a href="http://goreportcard.com/report/charmbracelet/glamour"><img src="http://goreportcard.com/badge/charmbracelet/glamour" alt="Go ReportCard"></a>
|
||||
</p>
|
||||
|
||||
Write handsome command-line tools with *glamour*!
|
||||
|
||||
|
@ -63,10 +66,22 @@ There are a few options for using a custom style:
|
|||
|
||||
## Glamourous Projects
|
||||
|
||||
Check out [Glow](https://github.com/charmbracelet/glow), a markdown renderer for
|
||||
the command-line, which uses `glamour`.
|
||||
|
||||
Check out these projects, which use `glamour`:
|
||||
- [Glow](https://github.com/charmbracelet/glow), a markdown renderer for
|
||||
the command-line.
|
||||
- [GitHub CLI](https://github.com/cli/cli), GitHub’s official command line tool.
|
||||
- [GLab](https://github.com/profclems/glab), An open source GitLab command line tool.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/charmbracelet/glamour/raw/master/LICENSE)
|
||||
|
||||
|
||||
***
|
||||
|
||||
Part of [Charm](https://charm.sh).
|
||||
|
||||
<a href="https://charm.sh/"><img alt="the Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
|
||||
|
||||
Charm热爱开源! / Charm loves open source!
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"strings"
|
||||
|
||||
east "github.com/yuin/goldmark-emoji/ast"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
astext "github.com/yuin/goldmark/extension/ast"
|
||||
)
|
||||
|
@ -375,6 +376,14 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
|
|||
case ast.KindTextBlock:
|
||||
return Element{}
|
||||
|
||||
case east.KindEmoji:
|
||||
n := node.(*east.Emoji)
|
||||
return Element{
|
||||
Renderer: &BaseElement{
|
||||
Token: string(n.Value.Unicode),
|
||||
},
|
||||
}
|
||||
|
||||
// Unknown case
|
||||
default:
|
||||
fmt.Println("Warning: unhandled element", node.Kind().String())
|
||||
|
|
|
@ -25,7 +25,7 @@ func (e *ImageElement) Render(w io.Writer, ctx RenderContext) error {
|
|||
}
|
||||
if len(e.URL) > 0 {
|
||||
el := &BaseElement{
|
||||
Token: resolveRelativeURL(e.BaseURL, e.URL),
|
||||
Token: resolveURL(e.BaseURL, e.URL),
|
||||
Prefix: " ",
|
||||
Style: ctx.options.Styles.Image,
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ func (e *LinkElement) Render(w io.Writer, ctx RenderContext) error {
|
|||
}
|
||||
|
||||
el := &BaseElement{
|
||||
Token: resolveRelativeURL(e.BaseURL, e.URL),
|
||||
Token: resolveURL(e.BaseURL, e.URL),
|
||||
Prefix: pre,
|
||||
Style: style,
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@ package ansi
|
|||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
east "github.com/yuin/goldmark-emoji/ast"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
astext "github.com/yuin/goldmark/extension/ast"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
|
@ -72,13 +72,16 @@ func (r *ANSIRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
|||
reg.Register(astext.KindFootnote, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteList, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteLink, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteBackLink, r.renderNode)
|
||||
reg.Register(astext.KindFootnoteBacklink, r.renderNode)
|
||||
|
||||
// checkboxes
|
||||
reg.Register(astext.KindTaskCheckBox, r.renderNode)
|
||||
|
||||
// strikethrough
|
||||
reg.Register(astext.KindStrikethrough, r.renderNode)
|
||||
|
||||
// emoji
|
||||
reg.Register(east.KindEmoji, r.renderNode)
|
||||
}
|
||||
|
||||
func (r *ANSIRenderer) renderNode(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
|
@ -145,7 +148,7 @@ func isChild(node ast.Node) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func resolveRelativeURL(baseURL string, rel string) string {
|
||||
func resolveURL(baseURL string, rel string) string {
|
||||
u, err := url.Parse(rel)
|
||||
if err != nil {
|
||||
return rel
|
||||
|
@ -153,7 +156,6 @@ func resolveRelativeURL(baseURL string, rel string) string {
|
|||
if u.IsAbs() {
|
||||
return rel
|
||||
}
|
||||
u.Path = strings.TrimPrefix(u.Path, "/")
|
||||
|
||||
base, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/yuin/goldmark"
|
||||
emoji "github.com/yuin/goldmark-emoji"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
|
@ -135,20 +136,17 @@ func WithEnvironmentConfig() TermRendererOption {
|
|||
// standard style.
|
||||
func WithStylePath(stylePath string) TermRendererOption {
|
||||
return func(tr *TermRenderer) error {
|
||||
jsonBytes, err := ioutil.ReadFile(stylePath)
|
||||
switch {
|
||||
case err == nil:
|
||||
return json.Unmarshal(jsonBytes, &tr.ansiOptions.Styles)
|
||||
case os.IsNotExist(err):
|
||||
styles, err := getDefaultStyle(stylePath)
|
||||
styles, err := getDefaultStyle(stylePath)
|
||||
if err != nil {
|
||||
jsonBytes, err := ioutil.ReadFile(stylePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.ansiOptions.Styles = *styles
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
|
||||
return json.Unmarshal(jsonBytes, &tr.ansiOptions.Styles)
|
||||
}
|
||||
tr.ansiOptions.Styles = *styles
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,6 +185,14 @@ func WithWordWrap(wordWrap int) TermRendererOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithEmoji sets a TermRenderer's emoji rendering.
|
||||
func WithEmoji() TermRendererOption {
|
||||
return func(tr *TermRenderer) error {
|
||||
emoji.New().Extend(tr.md)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *TermRenderer) Read(b []byte) (int, error) {
|
||||
return tr.renderBuf.Read(b)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ module github.com/charmbracelet/glamour
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma v0.7.3
|
||||
github.com/microcosm-cc/bluemonday v1.0.2
|
||||
github.com/muesli/reflow v0.1.0
|
||||
github.com/muesli/termenv v0.6.0
|
||||
github.com/alecthomas/chroma v0.8.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.4
|
||||
github.com/muesli/reflow v0.2.0
|
||||
github.com/muesli/termenv v0.7.4
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/yuin/goldmark v1.2.0
|
||||
github.com/yuin/goldmark v1.3.1
|
||||
github.com/yuin/goldmark-emoji v1.0.1
|
||||
)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI=
|
||||
github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
|
||||
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -14,8 +18,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
|
@ -25,12 +29,12 @@ github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+tw
|
|||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM=
|
||||
github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I=
|
||||
github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk=
|
||||
github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||
github.com/muesli/reflow v0.2.0 h1:2o0UBJPHHH4fa2GCXU4Rg4DwOtWPMekCeyc5EWbAQp0=
|
||||
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
|
||||
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -43,8 +47,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc=
|
||||
github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I=
|
||||
github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Aymerick JEHANNE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/css/scanner"
|
||||
|
||||
"github.com/aymerick/douceur/css"
|
||||
)
|
||||
|
||||
const (
|
||||
importantSuffixRegexp = `(?i)\s*!important\s*$`
|
||||
)
|
||||
|
||||
var (
|
||||
importantRegexp *regexp.Regexp
|
||||
)
|
||||
|
||||
// Parser represents a CSS parser
|
||||
type Parser struct {
|
||||
scan *scanner.Scanner // Tokenizer
|
||||
|
||||
// Tokens parsed but not consumed yet
|
||||
tokens []*scanner.Token
|
||||
|
||||
// Rule embedding level
|
||||
embedLevel int
|
||||
}
|
||||
|
||||
func init() {
|
||||
importantRegexp = regexp.MustCompile(importantSuffixRegexp)
|
||||
}
|
||||
|
||||
// NewParser instanciates a new parser
|
||||
func NewParser(txt string) *Parser {
|
||||
return &Parser{
|
||||
scan: scanner.New(txt),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses a whole stylesheet
|
||||
func Parse(text string) (*css.Stylesheet, error) {
|
||||
result, err := NewParser(text).ParseStylesheet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseDeclarations parses CSS declarations
|
||||
func ParseDeclarations(text string) ([]*css.Declaration, error) {
|
||||
result, err := NewParser(text).ParseDeclarations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseStylesheet parses a stylesheet
|
||||
func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
|
||||
result := css.NewStylesheet()
|
||||
|
||||
// Parse BOM
|
||||
if _, err := parser.parseBOM(); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Parse list of rules
|
||||
rules, err := parser.ParseRules()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Rules = rules
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseRules parses a list of rules
|
||||
func (parser *Parser) ParseRules() ([]*css.Rule, error) {
|
||||
result := []*css.Rule{}
|
||||
|
||||
inBlock := false
|
||||
if parser.tokenChar("{") {
|
||||
// parsing a block of rules
|
||||
inBlock = true
|
||||
parser.embedLevel++
|
||||
|
||||
parser.shiftToken()
|
||||
}
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenIgnorable() {
|
||||
parser.shiftToken()
|
||||
} else if parser.tokenChar("}") {
|
||||
if !inBlock {
|
||||
errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
|
||||
return result, errors.New(errMsg)
|
||||
}
|
||||
|
||||
parser.shiftToken()
|
||||
parser.embedLevel--
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
rule, err := parser.ParseRule()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
rule.EmbedLevel = parser.embedLevel
|
||||
result = append(result, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// ParseRule parses a rule
|
||||
func (parser *Parser) ParseRule() (*css.Rule, error) {
|
||||
if parser.tokenAtKeyword() {
|
||||
return parser.parseAtRule()
|
||||
}
|
||||
|
||||
return parser.parseQualifiedRule()
|
||||
}
|
||||
|
||||
// ParseDeclarations parses a list of declarations
|
||||
func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
|
||||
result := []*css.Declaration{}
|
||||
|
||||
if parser.tokenChar("{") {
|
||||
parser.shiftToken()
|
||||
}
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenIgnorable() {
|
||||
parser.shiftToken()
|
||||
} else if parser.tokenChar("}") {
|
||||
// end of block
|
||||
parser.shiftToken()
|
||||
break
|
||||
} else {
|
||||
declaration, err := parser.ParseDeclaration()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = append(result, declaration)
|
||||
}
|
||||
}
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// ParseDeclaration parses a declaration
|
||||
func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
|
||||
result := css.NewDeclaration()
|
||||
curValue := ""
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenChar(":") {
|
||||
result.Property = strings.TrimSpace(curValue)
|
||||
curValue = ""
|
||||
|
||||
parser.shiftToken()
|
||||
} else if parser.tokenChar(";") || parser.tokenChar("}") {
|
||||
if result.Property == "" {
|
||||
errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
|
||||
return result, errors.New(errMsg)
|
||||
}
|
||||
|
||||
if importantRegexp.MatchString(curValue) {
|
||||
result.Important = true
|
||||
curValue = importantRegexp.ReplaceAllString(curValue, "")
|
||||
}
|
||||
|
||||
result.Value = strings.TrimSpace(curValue)
|
||||
|
||||
if parser.tokenChar(";") {
|
||||
parser.shiftToken()
|
||||
}
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
token := parser.shiftToken()
|
||||
curValue += token.Value
|
||||
}
|
||||
}
|
||||
|
||||
// log.Printf("[parsed] Declaration: %s", result.String())
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse an At Rule
|
||||
func (parser *Parser) parseAtRule() (*css.Rule, error) {
|
||||
// parse rule name (eg: "@import")
|
||||
token := parser.shiftToken()
|
||||
|
||||
result := css.NewRule(css.AtRule)
|
||||
result.Name = token.Value
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenChar(";") {
|
||||
parser.shiftToken()
|
||||
|
||||
// finished
|
||||
break
|
||||
} else if parser.tokenChar("{") {
|
||||
if result.EmbedsRules() {
|
||||
// parse rules block
|
||||
rules, err := parser.ParseRules()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Rules = rules
|
||||
} else {
|
||||
// parse declarations block
|
||||
declarations, err := parser.ParseDeclarations()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Declarations = declarations
|
||||
}
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
// parse prelude
|
||||
prelude, err := parser.parsePrelude()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Prelude = prelude
|
||||
}
|
||||
}
|
||||
|
||||
// log.Printf("[parsed] Rule: %s", result.String())
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse a Qualified Rule
|
||||
func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
|
||||
result := css.NewRule(css.QualifiedRule)
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenChar("{") {
|
||||
if result.Prelude == "" {
|
||||
errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
|
||||
return result, errors.New(errMsg)
|
||||
}
|
||||
|
||||
// parse declarations block
|
||||
declarations, err := parser.ParseDeclarations()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Declarations = declarations
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
// parse prelude
|
||||
prelude, err := parser.parsePrelude()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Prelude = prelude
|
||||
}
|
||||
}
|
||||
|
||||
result.Selectors = strings.Split(result.Prelude, ",")
|
||||
for i, sel := range result.Selectors {
|
||||
result.Selectors[i] = strings.TrimSpace(sel)
|
||||
}
|
||||
|
||||
// log.Printf("[parsed] Rule: %s", result.String())
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse Rule prelude
|
||||
func (parser *Parser) parsePrelude() (string, error) {
|
||||
result := ""
|
||||
|
||||
for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
|
||||
token := parser.shiftToken()
|
||||
result += token.Value
|
||||
}
|
||||
|
||||
result = strings.TrimSpace(result)
|
||||
|
||||
// log.Printf("[parsed] prelude: %s", result)
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse BOM
|
||||
func (parser *Parser) parseBOM() (bool, error) {
|
||||
if parser.nextToken().Type == scanner.TokenBOM {
|
||||
parser.shiftToken()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, parser.err()
|
||||
}
|
||||
|
||||
// Returns next token without removing it from tokens buffer
|
||||
func (parser *Parser) nextToken() *scanner.Token {
|
||||
if len(parser.tokens) == 0 {
|
||||
// fetch next token
|
||||
nextToken := parser.scan.Next()
|
||||
|
||||
// log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
|
||||
|
||||
// queue it
|
||||
parser.tokens = append(parser.tokens, nextToken)
|
||||
}
|
||||
|
||||
return parser.tokens[0]
|
||||
}
|
||||
|
||||
// Returns next token and remove it from the tokens buffer
|
||||
func (parser *Parser) shiftToken() *scanner.Token {
|
||||
var result *scanner.Token
|
||||
|
||||
result, parser.tokens = parser.tokens[0], parser.tokens[1:]
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns tokenizer error, or nil if no error
|
||||
func (parser *Parser) err() error {
|
||||
if parser.tokenError() {
|
||||
token := parser.nextToken()
|
||||
return fmt.Errorf("Tokenizer error: %s", token.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if next token is Error
|
||||
func (parser *Parser) tokenError() bool {
|
||||
return parser.nextToken().Type == scanner.TokenError
|
||||
}
|
||||
|
||||
// Returns true if next token is EOF
|
||||
func (parser *Parser) tokenEOF() bool {
|
||||
return parser.nextToken().Type == scanner.TokenEOF
|
||||
}
|
||||
|
||||
// Returns true if next token is a whitespace
|
||||
func (parser *Parser) tokenWS() bool {
|
||||
return parser.nextToken().Type == scanner.TokenS
|
||||
}
|
||||
|
||||
// Returns true if next token is a comment
|
||||
func (parser *Parser) tokenComment() bool {
|
||||
return parser.nextToken().Type == scanner.TokenComment
|
||||
}
|
||||
|
||||
// Returns true if next token is a CDO or a CDC
|
||||
func (parser *Parser) tokenCDOorCDC() bool {
|
||||
switch parser.nextToken().Type {
|
||||
case scanner.TokenCDO, scanner.TokenCDC:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if next token is ignorable
|
||||
func (parser *Parser) tokenIgnorable() bool {
|
||||
return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
|
||||
}
|
||||
|
||||
// Returns true if next token is parsable
|
||||
func (parser *Parser) tokenParsable() bool {
|
||||
return !parser.tokenEOF() && !parser.tokenError()
|
||||
}
|
||||
|
||||
// Returns true if next token is an At Rule keyword
|
||||
func (parser *Parser) tokenAtKeyword() bool {
|
||||
return parser.nextToken().Type == scanner.TokenAtKeyword
|
||||
}
|
||||
|
||||
// Returns true if next token is given character
|
||||
func (parser *Parser) tokenChar(value string) bool {
|
||||
token := parser.nextToken()
|
||||
return (token.Type == scanner.TokenChar) && (token.Value == value)
|
||||
}
|
||||
|
||||
// Returns true if next token marks the end of a prelude
|
||||
func (parser *Parser) tokenEndOfPrelude() bool {
|
||||
return parser.tokenChar(";") || parser.tokenChar("{")
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2013, Gorilla web toolkit
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name of the {organization} nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package gorilla/css/scanner generates tokens for a CSS3 input.
|
||||
|
||||
It follows the CSS3 specification located at:
|
||||
|
||||
http://www.w3.org/TR/css3-syntax/
|
||||
|
||||
To use it, create a new scanner for a given CSS string and call Next() until
|
||||
the token returned has type TokenEOF or TokenError:
|
||||
|
||||
s := scanner.New(myCSS)
|
||||
for {
|
||||
token := s.Next()
|
||||
if token.Type == scanner.TokenEOF || token.Type == scanner.TokenError {
|
||||
break
|
||||
}
|
||||
// Do something with the token...
|
||||
}
|
||||
|
||||
Following the CSS3 specification, an error can only occur when the scanner
|
||||
finds an unclosed quote or unclosed comment. In these cases the text becomes
|
||||
"untokenizable". Everything else is tokenizable and it is up to a parser
|
||||
to make sense of the token stream (or ignore nonsensical token sequences).
|
||||
|
||||
Note: the scanner doesn't perform lexical analysis or, in other words, it
|
||||
doesn't care about the token context. It is intended to be used by a
|
||||
lexer or parser.
|
||||
*/
|
||||
package scanner
|
|
@ -0,0 +1,356 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// tokenType identifies the type of lexical tokens.
|
||||
type tokenType int
|
||||
|
||||
// String returns a string representation of the token type.
|
||||
func (t tokenType) String() string {
|
||||
return tokenNames[t]
|
||||
}
|
||||
|
||||
// Token represents a token and the corresponding string.
|
||||
type Token struct {
|
||||
Type tokenType
|
||||
Value string
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
// String returns a string representation of the token.
|
||||
func (t *Token) String() string {
|
||||
if len(t.Value) > 10 {
|
||||
return fmt.Sprintf("%s (line: %d, column: %d): %.10q...",
|
||||
t.Type, t.Line, t.Column, t.Value)
|
||||
}
|
||||
return fmt.Sprintf("%s (line: %d, column: %d): %q",
|
||||
t.Type, t.Line, t.Column, t.Value)
|
||||
}
|
||||
|
||||
// All tokens -----------------------------------------------------------------
|
||||
|
||||
// The complete list of tokens in CSS3.
|
||||
const (
|
||||
// Scanner flags.
|
||||
TokenError tokenType = iota
|
||||
TokenEOF
|
||||
// From now on, only tokens from the CSS specification.
|
||||
TokenIdent
|
||||
TokenAtKeyword
|
||||
TokenString
|
||||
TokenHash
|
||||
TokenNumber
|
||||
TokenPercentage
|
||||
TokenDimension
|
||||
TokenURI
|
||||
TokenUnicodeRange
|
||||
TokenCDO
|
||||
TokenCDC
|
||||
TokenS
|
||||
TokenComment
|
||||
TokenFunction
|
||||
TokenIncludes
|
||||
TokenDashMatch
|
||||
TokenPrefixMatch
|
||||
TokenSuffixMatch
|
||||
TokenSubstringMatch
|
||||
TokenChar
|
||||
TokenBOM
|
||||
)
|
||||
|
||||
// tokenNames maps tokenType's to their names. Used for conversion to string.
|
||||
var tokenNames = map[tokenType]string{
|
||||
TokenError: "error",
|
||||
TokenEOF: "EOF",
|
||||
TokenIdent: "IDENT",
|
||||
TokenAtKeyword: "ATKEYWORD",
|
||||
TokenString: "STRING",
|
||||
TokenHash: "HASH",
|
||||
TokenNumber: "NUMBER",
|
||||
TokenPercentage: "PERCENTAGE",
|
||||
TokenDimension: "DIMENSION",
|
||||
TokenURI: "URI",
|
||||
TokenUnicodeRange: "UNICODE-RANGE",
|
||||
TokenCDO: "CDO",
|
||||
TokenCDC: "CDC",
|
||||
TokenS: "S",
|
||||
TokenComment: "COMMENT",
|
||||
TokenFunction: "FUNCTION",
|
||||
TokenIncludes: "INCLUDES",
|
||||
TokenDashMatch: "DASHMATCH",
|
||||
TokenPrefixMatch: "PREFIXMATCH",
|
||||
TokenSuffixMatch: "SUFFIXMATCH",
|
||||
TokenSubstringMatch: "SUBSTRINGMATCH",
|
||||
TokenChar: "CHAR",
|
||||
TokenBOM: "BOM",
|
||||
}
|
||||
|
||||
// Macros and productions -----------------------------------------------------
|
||||
// http://www.w3.org/TR/css3-syntax/#tokenization
|
||||
|
||||
var macroRegexp = regexp.MustCompile(`\{[a-z]+\}`)
|
||||
|
||||
// macros maps macro names to patterns to be expanded.
|
||||
var macros = map[string]string{
|
||||
// must be escaped: `\.+*?()|[]{}^$`
|
||||
"ident": `-?{nmstart}{nmchar}*`,
|
||||
"name": `{nmchar}+`,
|
||||
"nmstart": `[a-zA-Z_]|{nonascii}|{escape}`,
|
||||
"nonascii": "[\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]",
|
||||
"unicode": `\\[0-9a-fA-F]{1,6}{wc}?`,
|
||||
"escape": "{unicode}|\\\\[\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]",
|
||||
"nmchar": `[a-zA-Z0-9_-]|{nonascii}|{escape}`,
|
||||
"num": `[0-9]*\.[0-9]+|[0-9]+`,
|
||||
"string": `"(?:{stringchar}|')*"|'(?:{stringchar}|")*'`,
|
||||
"stringchar": `{urlchar}|[ ]|\\{nl}`,
|
||||
"nl": `[\n\r\f]|\r\n`,
|
||||
"w": `{wc}*`,
|
||||
"wc": `[\t\n\f\r ]`,
|
||||
|
||||
// urlchar should accept [(ascii characters minus those that need escaping)|{nonascii}|{escape}]
|
||||
// ASCII characters range = `[\u0020-\u007e]`
|
||||
// Skip space \u0020 = `[\u0021-\u007e]`
|
||||
// Skip quotation mark \0022 = `[\u0021\u0023-\u007e]`
|
||||
// Skip apostrophe \u0027 = `[\u0021\u0023-\u0026\u0028-\u007e]`
|
||||
// Skip reverse solidus \u005c = `[\u0021\u0023-\u0026\u0028-\u005b\u005d\u007e]`
|
||||
// Finally, the left square bracket (\u005b) and right (\u005d) needs escaping themselves
|
||||
"urlchar": "[\u0021\u0023-\u0026\u0028-\\\u005b\\\u005d-\u007E]|{nonascii}|{escape}",
|
||||
}
|
||||
|
||||
// productions maps the list of tokens to patterns to be expanded.
|
||||
var productions = map[tokenType]string{
|
||||
// Unused regexps (matched using other methods) are commented out.
|
||||
TokenIdent: `{ident}`,
|
||||
TokenAtKeyword: `@{ident}`,
|
||||
TokenString: `{string}`,
|
||||
TokenHash: `#{name}`,
|
||||
TokenNumber: `{num}`,
|
||||
TokenPercentage: `{num}%`,
|
||||
TokenDimension: `{num}{ident}`,
|
||||
TokenURI: `url\({w}(?:{string}|{urlchar}*?){w}\)`,
|
||||
TokenUnicodeRange: `U\+[0-9A-F\?]{1,6}(?:-[0-9A-F]{1,6})?`,
|
||||
//TokenCDO: `<!--`,
|
||||
TokenCDC: `-->`,
|
||||
TokenS: `{wc}+`,
|
||||
TokenComment: `/\*[^\*]*[\*]+(?:[^/][^\*]*[\*]+)*/`,
|
||||
TokenFunction: `{ident}\(`,
|
||||
//TokenIncludes: `~=`,
|
||||
//TokenDashMatch: `\|=`,
|
||||
//TokenPrefixMatch: `\^=`,
|
||||
//TokenSuffixMatch: `\$=`,
|
||||
//TokenSubstringMatch: `\*=`,
|
||||
//TokenChar: `[^"']`,
|
||||
//TokenBOM: "\uFEFF",
|
||||
}
|
||||
|
||||
// matchers maps the list of tokens to compiled regular expressions.
|
||||
//
|
||||
// The map is filled on init() using the macros and productions defined in
|
||||
// the CSS specification.
|
||||
var matchers = map[tokenType]*regexp.Regexp{}
|
||||
|
||||
// matchOrder is the order to test regexps when first-char shortcuts
|
||||
// can't be used.
|
||||
var matchOrder = []tokenType{
|
||||
TokenURI,
|
||||
TokenFunction,
|
||||
TokenUnicodeRange,
|
||||
TokenIdent,
|
||||
TokenDimension,
|
||||
TokenPercentage,
|
||||
TokenNumber,
|
||||
TokenCDC,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// replace macros and compile regexps for productions.
|
||||
replaceMacro := func(s string) string {
|
||||
return "(?:" + macros[s[1:len(s)-1]] + ")"
|
||||
}
|
||||
for t, s := range productions {
|
||||
for macroRegexp.MatchString(s) {
|
||||
s = macroRegexp.ReplaceAllStringFunc(s, replaceMacro)
|
||||
}
|
||||
matchers[t] = regexp.MustCompile("^(?:" + s + ")")
|
||||
}
|
||||
}
|
||||
|
||||
// Scanner --------------------------------------------------------------------
|
||||
|
||||
// New returns a new CSS scanner for the given input.
|
||||
func New(input string) *Scanner {
|
||||
// Normalize newlines.
|
||||
input = strings.Replace(input, "\r\n", "\n", -1)
|
||||
return &Scanner{
|
||||
input: input,
|
||||
row: 1,
|
||||
col: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Scanner scans an input and emits tokens following the CSS3 specification.
|
||||
type Scanner struct {
|
||||
input string
|
||||
pos int
|
||||
row int
|
||||
col int
|
||||
err *Token
|
||||
}
|
||||
|
||||
// Next returns the next token from the input.
|
||||
//
|
||||
// At the end of the input the token type is TokenEOF.
|
||||
//
|
||||
// If the input can't be tokenized the token type is TokenError. This occurs
|
||||
// in case of unclosed quotation marks or comments.
|
||||
func (s *Scanner) Next() *Token {
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
if s.pos >= len(s.input) {
|
||||
s.err = &Token{TokenEOF, "", s.row, s.col}
|
||||
return s.err
|
||||
}
|
||||
if s.pos == 0 {
|
||||
// Test BOM only once, at the beginning of the file.
|
||||
if strings.HasPrefix(s.input, "\uFEFF") {
|
||||
return s.emitSimple(TokenBOM, "\uFEFF")
|
||||
}
|
||||
}
|
||||
// There's a lot we can guess based on the first byte so we'll take a
|
||||
// shortcut before testing multiple regexps.
|
||||
input := s.input[s.pos:]
|
||||
switch input[0] {
|
||||
case '\t', '\n', '\f', '\r', ' ':
|
||||
// Whitespace.
|
||||
return s.emitToken(TokenS, matchers[TokenS].FindString(input))
|
||||
case '.':
|
||||
// Dot is too common to not have a quick check.
|
||||
// We'll test if this is a Char; if it is followed by a number it is a
|
||||
// dimension/percentage/number, and this will be matched later.
|
||||
if len(input) > 1 && !unicode.IsDigit(rune(input[1])) {
|
||||
return s.emitSimple(TokenChar, ".")
|
||||
}
|
||||
case '#':
|
||||
// Another common one: Hash or Char.
|
||||
if match := matchers[TokenHash].FindString(input); match != "" {
|
||||
return s.emitToken(TokenHash, match)
|
||||
}
|
||||
return s.emitSimple(TokenChar, "#")
|
||||
case '@':
|
||||
// Another common one: AtKeyword or Char.
|
||||
if match := matchers[TokenAtKeyword].FindString(input); match != "" {
|
||||
return s.emitSimple(TokenAtKeyword, match)
|
||||
}
|
||||
return s.emitSimple(TokenChar, "@")
|
||||
case ':', ',', ';', '%', '&', '+', '=', '>', '(', ')', '[', ']', '{', '}':
|
||||
// More common chars.
|
||||
return s.emitSimple(TokenChar, string(input[0]))
|
||||
case '"', '\'':
|
||||
// String or error.
|
||||
match := matchers[TokenString].FindString(input)
|
||||
if match != "" {
|
||||
return s.emitToken(TokenString, match)
|
||||
}
|
||||
|
||||
s.err = &Token{TokenError, "unclosed quotation mark", s.row, s.col}
|
||||
return s.err
|
||||
case '/':
|
||||
// Comment, error or Char.
|
||||
if len(input) > 1 && input[1] == '*' {
|
||||
match := matchers[TokenComment].FindString(input)
|
||||
if match != "" {
|
||||
return s.emitToken(TokenComment, match)
|
||||
} else {
|
||||
s.err = &Token{TokenError, "unclosed comment", s.row, s.col}
|
||||
return s.err
|
||||
}
|
||||
}
|
||||
return s.emitSimple(TokenChar, "/")
|
||||
case '~':
|
||||
// Includes or Char.
|
||||
return s.emitPrefixOrChar(TokenIncludes, "~=")
|
||||
case '|':
|
||||
// DashMatch or Char.
|
||||
return s.emitPrefixOrChar(TokenDashMatch, "|=")
|
||||
case '^':
|
||||
// PrefixMatch or Char.
|
||||
return s.emitPrefixOrChar(TokenPrefixMatch, "^=")
|
||||
case '$':
|
||||
// SuffixMatch or Char.
|
||||
return s.emitPrefixOrChar(TokenSuffixMatch, "$=")
|
||||
case '*':
|
||||
// SubstringMatch or Char.
|
||||
return s.emitPrefixOrChar(TokenSubstringMatch, "*=")
|
||||
case '<':
|
||||
// CDO or Char.
|
||||
return s.emitPrefixOrChar(TokenCDO, "<!--")
|
||||
}
|
||||
// Test all regexps, in order.
|
||||
for _, token := range matchOrder {
|
||||
if match := matchers[token].FindString(input); match != "" {
|
||||
return s.emitToken(token, match)
|
||||
}
|
||||
}
|
||||
// We already handled unclosed quotation marks and comments,
|
||||
// so this can only be a Char.
|
||||
r, width := utf8.DecodeRuneInString(input)
|
||||
token := &Token{TokenChar, string(r), s.row, s.col}
|
||||
s.col += width
|
||||
s.pos += width
|
||||
return token
|
||||
}
|
||||
|
||||
// updatePosition updates input coordinates based on the consumed text.
|
||||
func (s *Scanner) updatePosition(text string) {
|
||||
width := utf8.RuneCountInString(text)
|
||||
lines := strings.Count(text, "\n")
|
||||
s.row += lines
|
||||
if lines == 0 {
|
||||
s.col += width
|
||||
} else {
|
||||
s.col = utf8.RuneCountInString(text[strings.LastIndex(text, "\n"):])
|
||||
}
|
||||
s.pos += len(text) // while col is a rune index, pos is a byte index
|
||||
}
|
||||
|
||||
// emitToken returns a Token for the string v and updates the scanner position.
|
||||
func (s *Scanner) emitToken(t tokenType, v string) *Token {
|
||||
token := &Token{t, v, s.row, s.col}
|
||||
s.updatePosition(v)
|
||||
return token
|
||||
}
|
||||
|
||||
// emitSimple returns a Token for the string v and updates the scanner
|
||||
// position in a simplified manner.
|
||||
//
|
||||
// The string is known to have only ASCII characters and to not have a newline.
|
||||
func (s *Scanner) emitSimple(t tokenType, v string) *Token {
|
||||
token := &Token{t, v, s.row, s.col}
|
||||
s.col += len(v)
|
||||
s.pos += len(v)
|
||||
return token
|
||||
}
|
||||
|
||||
// emitPrefixOrChar returns a Token for type t if the current position
|
||||
// matches the given prefix. Otherwise it returns a Char token using the
|
||||
// first character from the prefix.
|
||||
//
|
||||
// The prefix is known to have only ASCII characters and to not have a newline.
|
||||
func (s *Scanner) emitPrefixOrChar(t tokenType, prefix string) *Token {
|
||||
if strings.HasPrefix(s.input[s.pos:], prefix) {
|
||||
return s.emitSimple(t, prefix)
|
||||
}
|
||||
return s.emitSimple(TokenChar, string(prefix[0]))
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# goland idea folder
|
||||
*.idea
|
|
@ -1,6 +1,5 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.1.x
|
||||
- 1.2.x
|
||||
- 1.3.x
|
||||
- 1.4.x
|
||||
|
@ -11,6 +10,7 @@ go:
|
|||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
1. Andrew Krasichkov @buglloc https://github.com/buglloc
|
||||
1. John Graham-Cumming http://jgc.org/
|
||||
1. Mohammad Gufran https://github.com/Gufran
|
||||
1. Steven Gutzwiller https://github.com/StevenGutzwiller
|
||||
1. Andrew Krasichkov @buglloc https://github.com/buglloc
|
||||
1. Mike Samuel mikesamuel@gmail.com
|
||||
1. Dmitri Shuralyov shurcooL@gmail.com
|
||||
1. https://github.com/opennota
|
||||
1. https://github.com/Gufran
|
||||
1. https://github.com/opennota
|
|
@ -58,10 +58,12 @@ We expect to be supplied with well-formatted HTML (closing elements for every ap
|
|||
|
||||
### Supported Go Versions
|
||||
|
||||
bluemonday is tested against Go 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, and tip.
|
||||
bluemonday is tested against Go 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, and tip.
|
||||
|
||||
We do not support Go 1.0 as we depend on `golang.org/x/net/html` which includes a reference to `io.ErrNoProgress` which did not exist in Go 1.0.
|
||||
|
||||
We support Go 1.1 but Travis no longer tests against it.
|
||||
|
||||
## Is it production ready?
|
||||
|
||||
*Yes*
|
||||
|
@ -90,7 +92,7 @@ func main() {
|
|||
// Do this once for each unique policy, and use the policy for the life of the program
|
||||
// Policy creation/editing is not safe to use in multiple goroutines
|
||||
p := bluemonday.UGCPolicy()
|
||||
|
||||
|
||||
// The policy can then be used to sanitize lots of input and it is safe to use the policy in multiple goroutines
|
||||
html := p.Sanitize(
|
||||
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`,
|
||||
|
@ -167,12 +169,26 @@ To add elements to a policy either add just the elements:
|
|||
p.AllowElements("b", "strong")
|
||||
```
|
||||
|
||||
Or using a regex:
|
||||
|
||||
_Note: if an element is added by name as shown above, any matching regex will be ignored_
|
||||
|
||||
It is also recommended to ensure multiple patterns don't overlap as order of execution is not guaranteed and can result in some rules being missed.
|
||||
```go
|
||||
p.AllowElementsMatching(regex.MustCompile(`^my-element-`))
|
||||
```
|
||||
|
||||
Or add elements as a virtue of adding an attribute:
|
||||
```go
|
||||
// Not the recommended pattern, see the recommendation on using .Matching() below
|
||||
p.AllowAttrs("nowrap").OnElements("td", "th")
|
||||
```
|
||||
|
||||
Again, this also supports a regex pattern match alternative:
|
||||
```go
|
||||
p.AllowAttrs("nowrap").OnElementsMatching(regex.MustCompile(`^my-element-`))
|
||||
```
|
||||
|
||||
Attributes can either be added to all elements:
|
||||
```go
|
||||
p.AllowAttrs("dir").Matching(regexp.MustCompile("(?i)rtl|ltr")).Globally()
|
||||
|
@ -202,6 +218,49 @@ p := bluemonday.UGCPolicy()
|
|||
p.AllowElements("fieldset", "select", "option")
|
||||
```
|
||||
|
||||
### Inline CSS
|
||||
|
||||
Although it's possible to handle inline CSS using `AllowAttrs` with a `Matching` rule, writing a single monolithic regular expression to safely process all inline CSS which you wish to allow is not a trivial task. Instead of attempting to do so, you can whitelist the `style` attribute on whichever element(s) you desire and use style policies to control and sanitize inline styles.
|
||||
|
||||
It is suggested that you use `Matching` (with a suitable regular expression)
|
||||
`MatchingEnum`, or `MatchingHandler` to ensure each style matches your needs,
|
||||
but default handlers are supplied for most widely used styles.
|
||||
|
||||
Similar to attributes, you can allow specific CSS properties to be set inline:
|
||||
```go
|
||||
p.AllowAttrs("style").OnElements("span", "p")
|
||||
// Allow the 'color' property with valid RGB(A) hex values only (on any element allowed a 'style' attribute)
|
||||
p.AllowStyles("color").Matching(regexp.MustCompile("(?i)^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$")).Globally()
|
||||
```
|
||||
|
||||
Additionally, you can allow a CSS property to be set only to an allowed value:
|
||||
```go
|
||||
p.AllowAttrs("style").OnElements("span", "p")
|
||||
// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
|
||||
// on 'span' elements only
|
||||
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElements("span")
|
||||
```
|
||||
|
||||
Or you can specify elements based on a regex patterm match:
|
||||
```go
|
||||
p.AllowAttrs("style").OnElementsMatching(regex.MustCompile(`^my-element-`))
|
||||
// Allow the 'text-decoration' property to be set to 'underline', 'line-through' or 'none'
|
||||
// on 'span' elements only
|
||||
p.AllowStyles("text-decoration").MatchingEnum("underline", "line-through", "none").OnElementsMatching(regex.MustCompile(`^my-element-`))
|
||||
```
|
||||
|
||||
If you need more specific checking, you can create a handler that takes in a string and returns a bool to
|
||||
validate the values for a given property. The string parameter has been
|
||||
converted to lowercase and unicode code points have been converted.
|
||||
```go
|
||||
myHandler := func(value string) bool{
|
||||
return true
|
||||
}
|
||||
p.AllowAttrs("style").OnElements("span", "p")
|
||||
// Allow the 'color' property with values validated by the handler (on any element allowed a 'style' attribute)
|
||||
p.AllowStyles("color").MatchingHandler(myHandler).Globally()
|
||||
```
|
||||
|
||||
### Links
|
||||
|
||||
Links are difficult beasts to sanitise safely and also one of the biggest attack vectors for malicious content.
|
||||
|
@ -236,6 +295,13 @@ Regardless of whether you have enabled parseable URLs, you can force all URLs to
|
|||
p.RequireNoFollowOnLinks(true)
|
||||
```
|
||||
|
||||
Similarly, you can force all URLs to have "noreferrer" in their rel attribute.
|
||||
```go
|
||||
// This applies to "a" "area" "link" elements that have a "href" attribute
|
||||
p.RequireNoReferrerOnLinks(true)
|
||||
```
|
||||
|
||||
|
||||
We provide a convenience method that applies all of the above, but you will still need to whitelist the linkable elements for the URL rules to be applied to:
|
||||
```go
|
||||
p.AllowStandardURLs()
|
||||
|
@ -316,7 +382,6 @@ It is not the job of bluemonday to fix your bad HTML, it is merely the job of bl
|
|||
|
||||
## TODO
|
||||
|
||||
* Add support for CSS sanitisation to allow some CSS properties based on a whitelist, possibly using the [Gorilla CSS3 scanner](http://www.gorillatoolkit.org/pkg/css/scanner) - PRs welcome so long as testing covers XSS and demonstrates safety first
|
||||
* Investigate whether devs want to blacklist elements and attributes. This would allow devs to take an existing policy (such as the `bluemonday.UGCPolicy()` ) that encapsulates 90% of what they're looking for but does more than they need, and to remove the extra things they do not want to make it 100% what they want
|
||||
* Investigate whether devs want a validating HTML mode, in which the HTML elements are not just transformed into a balanced tree (every start tag has a closing tag at the correct depth) but also that elements and character data appear only in their allowed context (i.e. that a `table` element isn't a descendent of a `caption`, that `colgroup`, `thead`, `tbody`, `tfoot` and `tr` are permitted, and that character data is not permitted)
|
||||
|
||||
|
|
|
@ -2,4 +2,9 @@ module github.com/microcosm-cc/bluemonday
|
|||
|
||||
go 1.9
|
||||
|
||||
require golang.org/x/net v0.0.0-20181220203305-927f97764cc3
|
||||
require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/chris-ramon/douceur v0.2.0
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3
|
||||
)
|
||||
|
|
|
@ -1,2 +1,8 @@
|
|||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
|
@ -0,0 +1,2085 @@
|
|||
// Copyright (c) 2019, David Kitchen <david@buro9.com>
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the organisation (Microcosm) nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package bluemonday
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultStyleHandlers = map[string]func(string) bool{
|
||||
"align-content": AlignContentHandler,
|
||||
"align-items": AlignItemsHandler,
|
||||
"align-self": AlignSelfHandler,
|
||||
"all": AllHandler,
|
||||
"animation": AnimationHandler,
|
||||
"animation-delay": AnimationDelayHandler,
|
||||
"animation-direction": AnimationDirectionHandler,
|
||||
"animation-duration": AnimationDurationHandler,
|
||||
"animation-fill-mode": AnimationFillModeHandler,
|
||||
"animation-iteration-count": AnimationIterationCountHandler,
|
||||
"animation-name": AnimationNameHandler,
|
||||
"animation-play-state": AnimationPlayStateHandler,
|
||||
"animation-timing-function": TimingFunctionHandler,
|
||||
"backface-visibility": BackfaceVisibilityHandler,
|
||||
"background": BackgroundHandler,
|
||||
"background-attachment": BackgroundAttachmentHandler,
|
||||
"background-blend-mode": BackgroundBlendModeHandler,
|
||||
"background-clip": BackgroundClipHandler,
|
||||
"background-color": ColorHandler,
|
||||
"background-image": ImageHandler,
|
||||
"background-origin": BackgroundOriginHandler,
|
||||
"background-position": BackgroundPositionHandler,
|
||||
"background-repeat": BackgroundRepeatHandler,
|
||||
"background-size": BackgroundSizeHandler,
|
||||
"border": BorderHandler,
|
||||
"border-bottom": BorderSideHandler,
|
||||
"border-bottom-color": ColorHandler,
|
||||
"border-bottom-left-radius": BorderSideRadiusHandler,
|
||||
"border-bottom-right-radius": BorderSideRadiusHandler,
|
||||
"border-bottom-style": BorderSideStyleHandler,
|
||||
"border-bottom-width": BorderSideWidthHandler,
|
||||
"border-collapse": BorderCollapseHandler,
|
||||
"border-color": ColorHandler,
|
||||
"border-image": BorderImageHandler,
|
||||
"border-image-outset": BorderImageOutsetHandler,
|
||||
"border-image-repeat": BorderImageRepeatHandler,
|
||||
"border-image-slice": BorderImageSliceHandler,
|
||||
"border-image-source": ImageHandler,
|
||||
"border-image-width": BorderImageWidthHandler,
|
||||
"border-left": BorderSideHandler,
|
||||
"border-left-color": ColorHandler,
|
||||
"border-left-style": BorderSideStyleHandler,
|
||||
"border-left-width": BorderSideWidthHandler,
|
||||
"border-radius": BorderRadiusHandler,
|
||||
"border-right": BorderSideHandler,
|
||||
"border-right-color": ColorHandler,
|
||||
"border-right-style": BorderSideStyleHandler,
|
||||
"border-right-width": BorderSideWidthHandler,
|
||||
"border-spacing": BorderSpacingHandler,
|
||||
"border-style": BorderStyleHandler,
|
||||
"border-top": BorderSideHandler,
|
||||
"border-top-color": ColorHandler,
|
||||
"border-top-left-radius": BorderSideRadiusHandler,
|
||||
"border-top-right-radius": BorderSideRadiusHandler,
|
||||
"border-top-style": BorderSideStyleHandler,
|
||||
"border-top-width": BorderSideWidthHandler,
|
||||
"border-width": BorderWidthHandler,
|
||||
"bottom": SideHandler,
|
||||
"box-decoration-break": BoxDecorationBreakHandler,
|
||||
"box-shadow": BoxShadowHandler,
|
||||
"box-sizing": BoxSizingHandler,
|
||||
"break-after": BreakBeforeAfterHandler,
|
||||
"break-before": BreakBeforeAfterHandler,
|
||||
"break-inside": BreakInsideHandler,
|
||||
"caption-side": CaptionSideHandler,
|
||||
"caret-color": CaretColorHandler,
|
||||
"clear": ClearHandler,
|
||||
"clip": ClipHandler,
|
||||
"color": ColorHandler,
|
||||
"column-count": ColumnCountHandler,
|
||||
"column-fill": ColumnFillHandler,
|
||||
"column-gap": ColumnGapHandler,
|
||||
"column-rule": ColumnRuleHandler,
|
||||
"column-rule-color": ColorHandler,
|
||||
"column-rule-style": BorderSideStyleHandler,
|
||||
"column-rule-width": ColumnRuleWidthHandler,
|
||||
"column-span": ColumnSpanHandler,
|
||||
"column-width": ColumnWidthHandler,
|
||||
"columns": ColumnsHandler,
|
||||
"cursor": CursorHandler,
|
||||
"direction": DirectionHandler,
|
||||
"display": DisplayHandler,
|
||||
"empty-cells": EmptyCellsHandler,
|
||||
"filter": FilterHandler,
|
||||
"flex": FlexHandler,
|
||||
"flex-basis": FlexBasisHandler,
|
||||
"flex-direction": FlexDirectionHandler,
|
||||
"flex-flow": FlexFlowHandler,
|
||||
"flex-grow": FlexGrowHandler,
|
||||
"flex-shrink": FlexGrowHandler,
|
||||
"flex-wrap": FlexWrapHandler,
|
||||
"float": FloatHandler,
|
||||
"font": FontHandler,
|
||||
"font-family": FontFamilyHandler,
|
||||
"font-kerning": FontKerningHandler,
|
||||
"font-language-override": FontLanguageOverrideHandler,
|
||||
"font-size": FontSizeHandler,
|
||||
"font-size-adjust": FontSizeAdjustHandler,
|
||||
"font-stretch": FontStretchHandler,
|
||||
"font-style": FontStyleHandler,
|
||||
"font-synthesis": FontSynthesisHandler,
|
||||
"font-variant": FontVariantHandler,
|
||||
"font-variant-caps": FontVariantCapsHandler,
|
||||
"font-variant-position": FontVariantPositionHandler,
|
||||
"font-weight": FontWeightHandler,
|
||||
"grid": GridHandler,
|
||||
"grid-area": GridAreaHandler,
|
||||
"grid-auto-columns": GridAutoColumnsHandler,
|
||||
"grid-auto-flow": GridAutoFlowHandler,
|
||||
"grid-auto-rows": GridAutoColumnsHandler,
|
||||
"grid-column": GridColumnHandler,
|
||||
"grid-column-end": GridAxisStartEndHandler,
|
||||
"grid-column-gap": LengthHandler,
|
||||
"grid-column-start": GridAxisStartEndHandler,
|
||||
"grid-gap": GridGapHandler,
|
||||
"grid-row": GridRowHandler,
|
||||
"grid-row-end": GridAxisStartEndHandler,
|
||||
"grid-row-gap": LengthHandler,
|
||||
"grid-row-start": GridAxisStartEndHandler,
|
||||
"grid-template": GridTemplateHandler,
|
||||
"grid-template-areas": GridTemplateAreasHandler,
|
||||
"grid-template-columns": GridTemplateColumnsHandler,
|
||||
"grid-template-rows": GridTemplateRowsHandler,
|
||||
"hanging-punctuation": HangingPunctuationHandler,
|
||||
"height": HeightHandler,
|
||||
"hyphens": HyphensHandler,
|
||||
"image-rendering": ImageRenderingHandler,
|
||||
"isolation": IsolationHandler,
|
||||
"justify-content": JustifyContentHandler,
|
||||
"left": SideHandler,
|
||||
"letter-spacing": LetterSpacingHandler,
|
||||
"line-break": LineBreakHandler,
|
||||
"line-height": LineHeightHandler,
|
||||
"list-style": ListStyleHandler,
|
||||
"list-style-image": ImageHandler,
|
||||
"list-style-position": ListStylePositionHandler,
|
||||
"list-style-type": ListStyleTypeHandler,
|
||||
"margin": MarginHandler,
|
||||
"margin-bottom": MarginSideHandler,
|
||||
"margin-left": MarginSideHandler,
|
||||
"margin-right": MarginSideHandler,
|
||||
"margin-top": MarginSideHandler,
|
||||
"max-height": MaxHeightWidthHandler,
|
||||
"max-width": MaxHeightWidthHandler,
|
||||
"min-height": MinHeightWidthHandler,
|
||||
"min-width": MinHeightWidthHandler,
|
||||
"mix-blend-mode": MixBlendModeHandler,
|
||||
"object-fit": ObjectFitHandler,
|
||||
"object-position": ObjectPositionHandler,
|
||||
"opacity": OpacityHandler,
|
||||
"order": OrderHandler,
|
||||
"orphans": OrphansHandler,
|
||||
"outline": OutlineHandler,
|
||||
"outline-color": ColorHandler,
|
||||
"outline-offset": OutlineOffsetHandler,
|
||||
"outline-style": OutlineStyleHandler,
|
||||
"outline-width": OutlineWidthHandler,
|
||||
"overflow": OverflowHandler,
|
||||
"overflow-wrap": OverflowWrapHandler,
|
||||
"overflow-x": OverflowXYHandler,
|
||||
"overflow-y": OverflowXYHandler,
|
||||
"padding": PaddingHandler,
|
||||
"padding-bottom": PaddingSideHandler,
|
||||
"padding-left": PaddingSideHandler,
|
||||
"padding-right": PaddingSideHandler,
|
||||
"padding-top": PaddingSideHandler,
|
||||
"page-break-after": PageBreakBeforeAfterHandler,
|
||||
"page-break-before": PageBreakBeforeAfterHandler,
|
||||
"page-break-inside": PageBreakInsideHandler,
|
||||
"perspective": PerspectiveHandler,
|
||||
"perspective-origin": PerspectiveOriginHandler,
|
||||
"pointer-events": PointerEventsHandler,
|
||||
"position": PositionHandler,
|
||||
"quotes": QuotesHandler,
|
||||
"resize": ResizeHandler,
|
||||
"right": SideHandler,
|
||||
"scroll-behavior": ScrollBehaviorHandler,
|
||||
"tab-size": TabSizeHandler,
|
||||
"table-layout": TableLayoutHandler,
|
||||
"text-align": TextAlignHandler,
|
||||
"text-align-last": TextAlignLastHandler,
|
||||
"text-combine-upright": TextCombineUprightHandler,
|
||||
"text-decoration": TextDecorationHandler,
|
||||
"text-decoration-color": ColorHandler,
|
||||
"text-decoration-line": TextDecorationLineHandler,
|
||||
"text-decoration-style": TextDecorationStyleHandler,
|
||||
"text-indent": TextIndentHandler,
|
||||
"text-justify": TextJustifyHandler,
|
||||
"text-orientation": TextOrientationHandler,
|
||||
"text-overflow": TextOverflowHandler,
|
||||
"text-shadow": TextShadowHandler,
|
||||
"text-transform": TextTransformHandler,
|
||||
"top": SideHandler,
|
||||
"transform": TransformHandler,
|
||||
"transform-origin": TransformOriginHandler,
|
||||
"transform-style": TransformStyleHandler,
|
||||
"transition": TransitionHandler,
|
||||
"transition-delay": TransitionDelayHandler,
|
||||
"transition-duration": TransitionDurationHandler,
|
||||
"transition-property": TransitionPropertyHandler,
|
||||
"transition-timing-function": TimingFunctionHandler,
|
||||
"unicode-bidi": UnicodeBidiHandler,
|
||||
"user-select": UserSelectHandler,
|
||||
"vertical-align": VerticalAlignHandler,
|
||||
"visibility": VisiblityHandler,
|
||||
"white-space": WhiteSpaceHandler,
|
||||
"widows": OrphansHandler,
|
||||
"width": WidthHandler,
|
||||
"word-break": WordBreakHandler,
|
||||
"word-spacing": WordSpacingHandler,
|
||||
"word-wrap": WordWrapHandler,
|
||||
"writing-mode": WritingModeHandler,
|
||||
"z-index": ZIndexHandler,
|
||||
}
|
||||
colorValues = []string{"initial", "inherit", "aliceblue", "antiquewhite",
|
||||
"aqua", "aquamarine", "azure", "beige", "bisque", "black",
|
||||
"blanchedalmond", "blue", "blueviolet", "brown", "burlywood",
|
||||
"cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
|
||||
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
|
||||
"darkgray", "darkgrey", "darkgreen", "darkkhaki", "darkmagenta",
|
||||
"darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon",
|
||||
"darkseagreen", "darkslateblue", "darkslategrey", "darkslategray",
|
||||
"darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray",
|
||||
"dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen",
|
||||
"fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
|
||||
"grey", "green", "greenyellow", "honeydew", "hotpink", "indianred",
|
||||
"indigo", "ivory", "khaki", "lavender", "lavenderblush",
|
||||
"lemonchiffon", "lightblue", "lightcoral", "lightcyan",
|
||||
"lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen",
|
||||
"lightpink", "lightsalmon", "lightseagreen", "lightskyblue",
|
||||
"lightslategray", "lightslategrey", "lightsteeelblue", "lightyellow",
|
||||
"lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine",
|
||||
"mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen",
|
||||
"mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
"mediumvioletred", "midnightblue", "mintcream", "mistyrose",
|
||||
"moccasin", "navajowhite", "navy", "oldlace", "olive", "olivedrab",
|
||||
"orange", "orangered", "orchid", "palegoldenrod", "palegreen",
|
||||
"paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
|
||||
"pink", "plum", "powderblue", "purple", "rebeccapurple", "red",
|
||||
"rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown",
|
||||
"seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue",
|
||||
"slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
|
||||
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
|
||||
"whitesmoke", "yellow", "yellowgreen"}
|
||||
)
|
||||
|
||||
func multiSplit(value string, seps ...string) []string {
|
||||
curArray := []string{value}
|
||||
for _, i := range seps {
|
||||
newArray := []string{}
|
||||
for _, j := range curArray {
|
||||
newArray = append(newArray, strings.Split(j, i)...)
|
||||
}
|
||||
curArray = newArray
|
||||
}
|
||||
return curArray
|
||||
}
|
||||
|
||||
func recursiveCheck(value []string, funcs []func(string) bool) bool {
|
||||
for i := 0; i < len(value); i++ {
|
||||
tempVal := strings.Join(value[:i+1], " ")
|
||||
for _, j := range funcs {
|
||||
if j(tempVal) && (len(value[i+1:]) == 0 || recursiveCheck(value[i+1:], funcs)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func in(value []string, arr []string) bool {
|
||||
for _, i := range value {
|
||||
foundString := false
|
||||
for _, j := range arr {
|
||||
if j == i {
|
||||
foundString = true
|
||||
}
|
||||
}
|
||||
if !foundString {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func splitValues(value string) []string {
|
||||
values := strings.Split(value, ",")
|
||||
for _, strippedValue := range values {
|
||||
strippedValue = strings.ToLower(strings.TrimSpace(strippedValue))
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func getDefaultHandler(attr string) func(string) bool {
|
||||
|
||||
if defaultStyleHandlers[attr] != nil {
|
||||
return defaultStyleHandlers[attr]
|
||||
}
|
||||
return BaseHandler
|
||||
}
|
||||
|
||||
func BaseHandler(value string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func AlignContentHandler(value string) bool {
|
||||
values := []string{"stretch", "center", "flex-start",
|
||||
"flex-end", "space-between", "space-around", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AlignItemsHandler(value string) bool {
|
||||
values := []string{"stretch", "center", "flex-start",
|
||||
"flex-end", "baseline", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AlignSelfHandler(value string) bool {
|
||||
values := []string{"auto", "stretch", "center", "flex-start",
|
||||
"flex-end", "baseline", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AllHandler(value string) bool {
|
||||
values := []string{"initial", "inherit", "unset"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AnimationHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
AnimationNameHandler,
|
||||
AnimationDurationHandler,
|
||||
TimingFunctionHandler,
|
||||
AnimationDelayHandler,
|
||||
AnimationIterationCountHandler,
|
||||
AnimationDirectionHandler,
|
||||
AnimationFillModeHandler,
|
||||
AnimationPlayStateHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func AnimationDelayHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[\-]?[0-9]+[\.]?[0-9]*[s|ms]?`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AnimationDirectionHandler(value string) bool {
|
||||
values := []string{"normal", "reverse", "alternate", "alternate-reverse", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AnimationDurationHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9]+[\.]?[0-9]*[s|ms]?`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AnimationFillModeHandler(value string) bool {
|
||||
values := []string{"none", "forwards", "backwards", "both", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AnimationIterationCountHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9]+[\.]?[0-9]*`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"infinite", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func AnimationNameHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[a-z]+`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func AnimationPlayStateHandler(value string) bool {
|
||||
values := []string{"paused", "running", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TimingFunctionHandler(value string) bool {
|
||||
values := []string{"linear", "ease", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`cubic-bezier\(([ ]*(0(.[0-9]+)?|1(.0)?),){3}[ ]*(0(.[0-9]+)?|1)\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`steps\([ ]*[0-9]+([ ]*,[ ]*(start|end)?)\)`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func BackfaceVisibilityHandler(value string) bool {
|
||||
values := []string{"visible", "hidden", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BackgroundHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
newSplitVals := []string{}
|
||||
for _, i := range splitVals {
|
||||
if len(strings.Split(i, "/")) == 2 {
|
||||
newSplitVals = append(newSplitVals, strings.Split(i, "/")...)
|
||||
} else {
|
||||
newSplitVals = append(newSplitVals, i)
|
||||
}
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
ColorHandler,
|
||||
ImageHandler,
|
||||
BackgroundPositionHandler,
|
||||
BackgroundSizeHandler,
|
||||
BackgroundRepeatHandler,
|
||||
BackgroundOriginHandler,
|
||||
BackgroundClipHandler,
|
||||
BackgroundAttachmentHandler,
|
||||
}
|
||||
return recursiveCheck(newSplitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BackgroundAttachmentHandler(value string) bool {
|
||||
values := []string{"scroll", "fixed", "local", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BackgroundClipHandler(value string) bool {
|
||||
values := []string{"border-box", "padding-box", "content-box", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BackgroundBlendModeHandler(value string) bool {
|
||||
values := []string{"normal", "multiply", "screen", "overlay", "darken",
|
||||
"lighten", "color-dodge", "saturation", "color", "luminosity"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ImageHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`url\([\"\']?((https|http)[a-z0-9\.\\/_:]+[\"\']?)\)`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func BackgroundOriginHandler(value string) bool {
|
||||
values := []string{"padding-box", "border-box", "content-box", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BackgroundPositionHandler(value string) bool {
|
||||
splitVals := strings.Split(value, ";")
|
||||
values := []string{"left", "left top", "left bottom", "right", "right top", "right bottom", "right center", "center top", "center center", "center bottom", "center", "top", "bottom", "initial", "inherit"}
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`[\-]*[0-9]+[cm|mm|in|px|pt|pc\%]* [[\-]*[0-9]+[cm|mm|in|px|pt|pc\%]*]*`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func BackgroundRepeatHandler(value string) bool {
|
||||
values := []string{"repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BackgroundSizeHandler(value string) bool {
|
||||
splitVals := strings.Split(value, " ")
|
||||
values := []string{"auto", "cover", "contain", "initial", "inherit"}
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
if len(splitVals) > 0 && LengthHandler(splitVals[0]) {
|
||||
if len(splitVals) < 2 || (len(splitVals) == 2 && LengthHandler(splitVals[1])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func BorderHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := multiSplit(value, " ", "/")
|
||||
usedFunctions := []func(string) bool{
|
||||
BorderWidthHandler,
|
||||
BorderStyleHandler,
|
||||
ColorHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderSideHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
BorderSideWidthHandler,
|
||||
BorderSideStyleHandler,
|
||||
ColorHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderSideRadiusHandler(value string) bool {
|
||||
splitVals := strings.Split(value, " ")
|
||||
valid := true
|
||||
for _, i := range splitVals {
|
||||
if !LengthHandler(i) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if valid {
|
||||
return true
|
||||
}
|
||||
splitVals = splitValues(value)
|
||||
values := []string{"initial", "inherit"}
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderSideStyleHandler(value string) bool {
|
||||
values := []string{"none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderSideWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, ";")
|
||||
values := []string{"medium", "thin", "thick", "initial", "inherit"}
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderCollapseHandler(value string) bool {
|
||||
values := []string{"separate", "collapse", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderImageHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := multiSplit(value, " ", " / ")
|
||||
usedFunctions := []func(string) bool{
|
||||
ImageHandler,
|
||||
BorderImageSliceHandler,
|
||||
BorderImageWidthHandler,
|
||||
BorderImageOutsetHandler,
|
||||
BorderImageRepeatHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderImageOutsetHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderImageRepeatHandler(value string) bool {
|
||||
values := []string{"stretch", "repeat", "round", "space", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderImageSliceHandler(value string) bool {
|
||||
values := []string{"fill", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 4 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
LengthHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderImageWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BorderRadiusHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 4 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
LengthHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderSpacingHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 2 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
LengthHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderStyleHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 4 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
BorderSideStyleHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func BorderWidthHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 4 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
BorderSideWidthHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func SideHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "inherit", "unset"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BoxDecorationBreakHandler(value string) bool {
|
||||
values := []string{"slice", "clone", "initial", "initial", "inherit", "unset"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BoxShadowHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
commaSplitVals := strings.Split(value, ",")
|
||||
for _, val := range commaSplitVals {
|
||||
splitVals := strings.Split(val, " ")
|
||||
if len(splitVals) > 6 || len(splitVals) < 2 {
|
||||
return false
|
||||
}
|
||||
if !LengthHandler(splitVals[0]) {
|
||||
return false
|
||||
}
|
||||
if !LengthHandler(splitVals[1]) {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
LengthHandler,
|
||||
ColorHandler,
|
||||
}
|
||||
if len(splitVals) > 2 && !recursiveCheck(splitVals[2:], usedFunctions) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func BoxSizingHandler(value string) bool {
|
||||
values := []string{"slicontent-box", "border-box", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BreakBeforeAfterHandler(value string) bool {
|
||||
values := []string{"auto", "avoid", "always", "all", "avoid-page", "page", "left", "right", "recto", "verso", "avoid-column", "column", "avoid-region", "region"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func BreakInsideHandler(value string) bool {
|
||||
values := []string{"auto", "avoid", "avoid-page", "avoid-column", "avoid-region"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func CaptionSideHandler(value string) bool {
|
||||
values := []string{"top", "bottom", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func CaretColorHandler(value string) bool {
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, colorValues) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`#[0-9abcdef]{6}`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`rgba\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){3}[ ]*(1(\.0)?|0|(0\.[0-9]+))\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)`)
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ClearHandler(value string) bool {
|
||||
values := []string{"none", "left", "right", "both", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ClipHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`rect\([0-9]+px,[ ]*[0-9]+px,[ ]*[0-9]+px,[ ]*[0-9]+px\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColorHandler(value string) bool {
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, colorValues) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`#[0-9abcdef]{6}`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`rgb\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){2}([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))))\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`rgba\(([ ]*((([0-9]{1,2}|100)\%)|(([01]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))),){3}[ ]*(1(\.0)?|0|(0\.[0-9]+))\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`hsl\([ ]*([012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`hsla\(([ ]*[012]?[0-9]{1,2}|3[0-5][0-9]|360),[ ]*([0-9]{0,2}|100)\%,[ ]*([0-9]{0,2}|100)\%,[ ]*(1|1\.0|0|(0\.[0-9]+))\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ColumnCountHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9]+`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColumnFillHandler(value string) bool {
|
||||
values := []string{"balance", "auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColumnGapHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"normal", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColumnRuleHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
ColumnRuleWidthHandler,
|
||||
BorderSideStyleHandler,
|
||||
ColorHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func ColumnRuleWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, ";")
|
||||
values := []string{"medium", "thin", "thick", "initial", "inherit"}
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColumnSpanHandler(value string) bool {
|
||||
values := []string{"none", "all", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColumnWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, ";")
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ColumnsHandler(value string) bool {
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
ColumnWidthHandler,
|
||||
ColumnCountHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func CursorHandler(value string) bool {
|
||||
values := []string{"alias", "all-scroll", "auto", "cell", "context-menu", "col-resize", "copy", "crosshair", "default", "e-resize", "ew-resize", "grab", "grabbing", "help", "move", "n-resize", "ne-resize", "nesw-resize", "ns-resize", "nw-resize", "nwse-resize", "no-drop", "none", "not-allowed", "pointer", "progress", "row-resize", "s-resize", "se-resize", "sw-resize", "text", "vertical-text", "w-resize", "wait", "zoom-in", "zoom-out", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func DirectionHandler(value string) bool {
|
||||
values := []string{"ltr", "rtl", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func DisplayHandler(value string) bool {
|
||||
values := []string{"inline", "block", "contents", "flex", "grid", "inline-block", "inline-flex", "inline-grid", "inline-table", "list-item", "run-in", "table", "table-caption", "table-column-group", "table-header-group", "table-footer-group", "table-row-group", "table-cell", "table-column", "table-row", "none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func EmptyCellsHandler(value string) bool {
|
||||
values := []string{"show", "hide", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FilterHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`blur\([0-9]+px\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`(brightness|contrast)\([0-9]+\%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`drop-shadow\(([-]?[0-9]+px) ([-]?[0-9]+px)( [-]?[0-9]+px)?( ([-]?[0-9]+px))?`)
|
||||
reg.Longest()
|
||||
colorValue := strings.TrimSuffix(string(reg.ReplaceAll([]byte(value), []byte{})), ")")
|
||||
if ColorHandler(colorValue) {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`grayscale\(([0-9]{1,2}|100)%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`hue-rotate\(([12]?[0-9]{1,2}|3[0-5][0-9]|360)?\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`invert\(([0-9]{1,2}|100)%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`opacity\(([0-9]{1,2}|100)%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`saturate\([0-9]+%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`sepia\(([0-9]{1,2}|100)%\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
//Not allowing URLs
|
||||
return false
|
||||
}
|
||||
|
||||
func FlexHandler(value string) bool {
|
||||
values := []string{"auto", "initial", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
FlexGrowHandler,
|
||||
FlexBasisHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func FlexBasisHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, ";")
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FlexDirectionHandler(value string) bool {
|
||||
values := []string{"row", "row-reverse", "column", "column-reverse", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FlexFlowHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
FlexDirectionHandler,
|
||||
FlexWrapHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func FlexGrowHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9\.]+`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, ";")
|
||||
values := []string{"initial", "inherit"}
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FlexWrapHandler(value string) bool {
|
||||
values := []string{"nowrap", "wrap", "wrap-reverse", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FloatHandler(value string) bool {
|
||||
values := []string{"none", "left", "right", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontHandler(value string) bool {
|
||||
values := []string{"caption", "icon", "menu", "message-box", "small-caption", "status-bar", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
newSplitVals := []string{}
|
||||
for _, i := range splitVals {
|
||||
if len(strings.Split(i, "/")) == 2 {
|
||||
newSplitVals = append(newSplitVals, strings.Split(i, "/")...)
|
||||
} else {
|
||||
newSplitVals = append(newSplitVals, i)
|
||||
}
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
FontStyleHandler,
|
||||
FontVariantHandler,
|
||||
FontWeightHandler,
|
||||
FontSizeHandler,
|
||||
FontFamilyHandler,
|
||||
}
|
||||
return recursiveCheck(newSplitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func FontFamilyHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`('[a-z \-]+'|[a-z \-]+)`)
|
||||
reg.Longest()
|
||||
for _, i := range splitVals {
|
||||
i = strings.TrimSpace(i)
|
||||
if reg.FindString(i) != i {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func FontKerningHandler(value string) bool {
|
||||
values := []string{"auto", "normal", "none"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontLanguageOverrideHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[a-z]+`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func FontSizeHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"medium", "xx-small", "x-small", "small", "large", "x-large", "xx-large", "smaller", "larger", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontSizeAdjustHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9]+[\.]?[0-9]*`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontStretchHandler(value string) bool {
|
||||
values := []string{"ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "normal", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontStyleHandler(value string) bool {
|
||||
values := []string{"normal", "italic", "oblique", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontSynthesisHandler(value string) bool {
|
||||
values := []string{"none", "style", "weight"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontVariantCapsHandler(value string) bool {
|
||||
values := []string{"normal", "small-caps", "all-small-caps", "petite-caps", "all-petite-caps", "unicase", "titling-caps"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontVariantHandler(value string) bool {
|
||||
values := []string{"normal", "small-caps", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontVariantPositionHandler(value string) bool {
|
||||
values := []string{"normal", "sub", "super"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func FontWeightHandler(value string) bool {
|
||||
values := []string{"normal", "bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func GridHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
newSplitVals := []string{}
|
||||
for _, i := range splitVals {
|
||||
if i != "/" {
|
||||
newSplitVals = append(newSplitVals, i)
|
||||
}
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
GridTemplateRowsHandler,
|
||||
GridTemplateColumnsHandler,
|
||||
GridTemplateAreasHandler,
|
||||
GridAutoColumnsHandler,
|
||||
GridAutoFlowHandler,
|
||||
}
|
||||
return recursiveCheck(newSplitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func GridAreaHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " / ")
|
||||
usedFunctions := []func(string) bool{
|
||||
GridAxisStartEndHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func GridAutoColumnsHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "max-content", "min-content", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func GridAutoFlowHandler(value string) bool {
|
||||
values := []string{"row", "column", "dense", "row dense", "column dense"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func GridColumnHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " / ")
|
||||
if len(splitVals) > 2 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
GridAxisStartEndHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func GridColumnGapHandler(value string) bool {
|
||||
return LengthHandler(value)
|
||||
}
|
||||
|
||||
func LengthHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[\-]?[0-9]+[\.]?[0-9]*(%|cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|deg|rad|turn)?`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func LineBreakHandler(value string) bool {
|
||||
values := []string{"auto", "loose", "normal", "strict"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func GridAxisStartEndHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9]+`)
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`span [0-9]+`)
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func GridGapHandler(value string) bool {
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 2 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
GridColumnGapHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func GridRowHandler(value string) bool {
|
||||
splitVals := strings.Split(value, " / ")
|
||||
if len(splitVals) > 2 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
GridAxisStartEndHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func GridTemplateHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " / ")
|
||||
if len(splitVals) > 2 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
GridTemplateColumnsHandler,
|
||||
GridTemplateRowsHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func GridTemplateAreasHandler(value string) bool {
|
||||
values := []string{"none"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`['"]?[a-z ]+['"]?`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func GridTemplateColumnsHandler(value string) bool {
|
||||
splitVals := strings.Split(value, " ")
|
||||
values := []string{"none", "auto", "max-content", "min-content", "initial", "inherit"}
|
||||
for _, val := range splitVals {
|
||||
if LengthHandler(val) {
|
||||
continue
|
||||
}
|
||||
valArr := []string{val}
|
||||
if !in(valArr, values) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GridTemplateRowsHandler(value string) bool {
|
||||
splitVals := strings.Split(value, " ")
|
||||
values := []string{"none", "auto", "max-content", "min-content"}
|
||||
for _, val := range splitVals {
|
||||
if LengthHandler(val) {
|
||||
continue
|
||||
}
|
||||
valArr := []string{val}
|
||||
if !in(valArr, values) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func HangingPunctuationHandler(value string) bool {
|
||||
values := []string{"none", "first", "last", "allow-end", "force-end", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func HeightHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func HyphensHandler(value string) bool {
|
||||
values := []string{"none", "manual", "auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ImageRenderingHandler(value string) bool {
|
||||
values := []string{"auto", "smooth", "high-quality", "crisp-edges", "pixelated"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func IsolationHandler(value string) bool {
|
||||
values := []string{"auto", "isolate", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func JustifyContentHandler(value string) bool {
|
||||
values := []string{"flex-start", "flex-end", "center", "space-between", "space-around", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func LetterSpacingHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"normal", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func LineHeightHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"normal", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ListStyleHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
ListStyleTypeHandler,
|
||||
ListStylePositionHandler,
|
||||
ImageHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func ListStylePositionHandler(value string) bool {
|
||||
values := []string{"inside", "outside", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ListStyleTypeHandler(value string) bool {
|
||||
values := []string{"disc", "armenian", "circle", "cjk-ideographic", "decimal", "decimal-leading-zero", "georgian", "hebrew", "hiragana", "hiragana-iroha", "katakana", "katakana-iroha", "lower-alpha", "lower-greek", "lower-latin", "lower-roman", "none", "square", "upper-alpha", "upper-greek", "upper-latin", "upper-roman", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func MarginHandler(value string) bool {
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
MarginSideHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func MarginSideHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func MaxHeightWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func MinHeightWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func MixBlendModeHandler(value string) bool {
|
||||
values := []string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "difference", "exclusion", "hue", "saturation", "color", "luminosity"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ObjectFitHandler(value string) bool {
|
||||
values := []string{"fill", "contain", "cover", "none", "scale-down", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ObjectPositionHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 2 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
LengthHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func OpacityHandler(value string) bool {
|
||||
reg := regexp.MustCompile("(0[.]?[0-9]*)|(1.0)")
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OrderHandler(value string) bool {
|
||||
reg := regexp.MustCompile("[0-9]+")
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OutlineHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
ColorHandler,
|
||||
OutlineWidthHandler,
|
||||
OutlineStyleHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func OutlineOffsetHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OutlineStyleHandler(value string) bool {
|
||||
values := []string{"none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OutlineWidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"medium", "thin", "thick", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OverflowHandler(value string) bool {
|
||||
values := []string{"visible", "hidden", "scroll", "auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OverflowXYHandler(value string) bool {
|
||||
values := []string{"visible", "hidden", "scroll", "auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OverflowWrapHandler(value string) bool {
|
||||
values := []string{"normal", "break-word", "anywhere"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func OrphansHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[0-9]+`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func PaddingHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
if len(splitVals) > 4 {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
PaddingSideHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func PaddingSideHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func PageBreakBeforeAfterHandler(value string) bool {
|
||||
values := []string{"auto", "always", "avoid", "left", "right", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func PageBreakInsideHandler(value string) bool {
|
||||
values := []string{"auto", "avoid", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func PerspectiveHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func PerspectiveOriginHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
xValues := []string{"left", "center", "right"}
|
||||
yValues := []string{"top", "center", "bottom"}
|
||||
if len(splitVals) > 1 {
|
||||
if !in([]string{splitVals[0]}, xValues) && !LengthHandler(splitVals[0]) {
|
||||
return false
|
||||
}
|
||||
return in([]string{splitVals[1]}, yValues) || LengthHandler(splitVals[1])
|
||||
} else if len(splitVals) == 1 {
|
||||
return in(splitVals, xValues) || in(splitVals, yValues) || LengthHandler(splitVals[0])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func PointerEventsHandler(value string) bool {
|
||||
values := []string{"auto", "none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func PositionHandler(value string) bool {
|
||||
values := []string{"static", "absolute", "fixed", "relative", "sticky", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func QuotesHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`([ ]*["'][\x{0022}\x{0027}\x{2039}\x{2039}\x{203A}\x{00AB}\x{00BB}\x{2018}\x{2019}\x{201C}-\x{201E}]["'] ["'][\x{0022}\x{0027}\x{2039}\x{2039}\x{203A}\x{00AB}\x{00BB}\x{2018}\x{2019}\x{201C}-\x{201E}]["'])+`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func ResizeHandler(value string) bool {
|
||||
values := []string{"none", "both", "horizontal", "vertical", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ScrollBehaviorHandler(value string) bool {
|
||||
values := []string{"auto", "smooth", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TabSizeHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TableLayoutHandler(value string) bool {
|
||||
values := []string{"auto", "fixed", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextAlignHandler(value string) bool {
|
||||
values := []string{"left", "right", "center", "justify", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextAlignLastHandler(value string) bool {
|
||||
values := []string{"auto", "left", "right", "center", "justify", "start", "end", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextCombineUprightHandler(value string) bool {
|
||||
values := []string{"none", "all"}
|
||||
splitVals := splitValues(value)
|
||||
if in(splitVals, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`digits [2-4]`)
|
||||
reg.Longest()
|
||||
return reg.FindString(value) == value && value != ""
|
||||
}
|
||||
|
||||
func TextDecorationHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
TextDecorationStyleHandler,
|
||||
ColorHandler,
|
||||
TextDecorationLineHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func TextDecorationLineHandler(value string) bool {
|
||||
values := []string{"none", "underline", "overline", "line-through", "initial", "inherit"}
|
||||
splitVals := strings.Split(value, " ")
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextDecorationStyleHandler(value string) bool {
|
||||
values := []string{"solid", "double", "dotted", "dashed", "wavy", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextIndentHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextJustifyHandler(value string) bool {
|
||||
values := []string{"auto", "inter-word", "inter-character", "none", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextOverflowHandler(value string) bool {
|
||||
reg := regexp.MustCompile("[\"'][a-z]+[\"']")
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"clip", "ellipsis", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextOrientationHandler(value string) bool {
|
||||
values := []string{"mixed", "upright", "sideways", "sideways-right"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TextShadowHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
commaSplitVals := strings.Split(value, ",")
|
||||
for _, val := range commaSplitVals {
|
||||
splitVals := strings.Split(val, " ")
|
||||
if len(splitVals) > 6 || len(splitVals) < 2 {
|
||||
return false
|
||||
}
|
||||
if !LengthHandler(splitVals[0]) {
|
||||
return false
|
||||
}
|
||||
if !LengthHandler(splitVals[1]) {
|
||||
return false
|
||||
}
|
||||
usedFunctions := []func(string) bool{
|
||||
LengthHandler,
|
||||
ColorHandler,
|
||||
}
|
||||
if len(splitVals) > 2 && !recursiveCheck(splitVals[2:], usedFunctions) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TextTransformHandler(value string) bool {
|
||||
values := []string{"none", "capitalize", "uppercase", "lowercase", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TransformHandler(value string) bool {
|
||||
values := []string{"none", "initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
reg := regexp.MustCompile(`matrix\(([ ]*[0-9]+[\.]?[0-9]*,){5}([ ]*[0-9]+[\.]?[0-9]*)\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`matrix3d\(([ ]*[0-9]+[\.]?[0-9]*,){15}([ ]*[0-9]+[\.]?[0-9]*)\)`)
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`(translate|translate3d|translatex|translatey|translatez|scale|scale3d|scalex|scaley|scalez)\(`)
|
||||
reg.Longest()
|
||||
subValue := string(reg.ReplaceAll([]byte(value), []byte{}))
|
||||
trimValue := strings.Split(strings.TrimSuffix(subValue, ")"), ",")
|
||||
valid := true
|
||||
for _, i := range trimValue {
|
||||
if !LengthHandler(strings.TrimSpace(i)) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if valid && trimValue != nil {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`rotate(x|y|z)?\(([12]?|3[0-5][0-9]|360)\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`rotate3d\(([ ]?(1(\.0)?|0\.[0-9]+),){3}([12]?|3[0-5][0-9]|360)\)`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`skew(x|y)?\(`)
|
||||
reg.Longest()
|
||||
subValue = string(reg.ReplaceAll([]byte(value), []byte{}))
|
||||
subValue = strings.TrimSuffix(subValue, ")")
|
||||
trimValue = strings.Split(subValue, ",")
|
||||
valid = true
|
||||
for _, i := range trimValue {
|
||||
if !LengthHandler(strings.TrimSpace(i)) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if valid {
|
||||
return true
|
||||
}
|
||||
reg = regexp.MustCompile(`perspective\(`)
|
||||
reg.Longest()
|
||||
subValue = string(reg.ReplaceAll([]byte(value), []byte{}))
|
||||
subValue = strings.TrimSuffix(subValue, ")")
|
||||
return LengthHandler(subValue)
|
||||
}
|
||||
|
||||
func TransformOriginHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
xValues := []string{"left", "center", "right"}
|
||||
yValues := []string{"top", "center", "bottom"}
|
||||
if len(splitVals) > 2 {
|
||||
if !in([]string{splitVals[0]}, xValues) && !LengthHandler(splitVals[0]) {
|
||||
return false
|
||||
}
|
||||
if !in([]string{splitVals[1]}, yValues) && !LengthHandler(splitVals[1]) {
|
||||
return false
|
||||
}
|
||||
return LengthHandler(splitVals[2])
|
||||
} else if len(splitVals) > 1 {
|
||||
if !in([]string{splitVals[0]}, xValues) && !LengthHandler(splitVals[0]) {
|
||||
return false
|
||||
}
|
||||
return in([]string{splitVals[1]}, yValues) || LengthHandler(splitVals[1])
|
||||
} else if len(splitVals) == 1 {
|
||||
return in(splitVals, xValues) || in(splitVals, yValues) || LengthHandler(splitVals[0])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TransformStyleHandler(value string) bool {
|
||||
values := []string{"flat", "preserve-3d", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TransitionHandler(value string) bool {
|
||||
values := []string{"initial", "inherit"}
|
||||
if in([]string{value}, values) {
|
||||
return true
|
||||
}
|
||||
splitVals := strings.Split(value, " ")
|
||||
usedFunctions := []func(string) bool{
|
||||
TransitionPropertyHandler,
|
||||
TransitionDurationHandler,
|
||||
TimingFunctionHandler,
|
||||
TransitionDelayHandler,
|
||||
ColorHandler,
|
||||
}
|
||||
return recursiveCheck(splitVals, usedFunctions)
|
||||
}
|
||||
|
||||
func TransitionDelayHandler(value string) bool {
|
||||
reg := regexp.MustCompile("[0-9]+[.]?[0-9]*(s|ms)?")
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TransitionDurationHandler(value string) bool {
|
||||
reg := regexp.MustCompile("[0-9]+[.]?[0-9]*(s|ms)?")
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func TransitionPropertyHandler(value string) bool {
|
||||
reg := regexp.MustCompile("([a-zA-Z]+,[ ]?)*[a-zA-Z]+")
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"none", "all", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func UnicodeBidiHandler(value string) bool {
|
||||
values := []string{"normal", "embed", "bidi-override", "isolate", "isolate-override", "plaintext", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func UserSelectHandler(value string) bool {
|
||||
values := []string{"auto", "none", "text", "all"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func VerticalAlignHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"baseline", "sub", "super", "top", "text-top", "middle", "bottom", "text-bottom", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func VisiblityHandler(value string) bool {
|
||||
values := []string{"visible", "hidden", "collapse", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func WhiteSpaceHandler(value string) bool {
|
||||
values := []string{"normal", "nowrap", "pre", "pre-line", "pre-wrap", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func WidthHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func WordSpacingHandler(value string) bool {
|
||||
if LengthHandler(value) {
|
||||
return true
|
||||
}
|
||||
values := []string{"normal", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func WordBreakHandler(value string) bool {
|
||||
values := []string{"normal", "break-all", "keep-all", "break-word", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func WordWrapHandler(value string) bool {
|
||||
values := []string{"normal", "break-word", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func WritingModeHandler(value string) bool {
|
||||
values := []string{"horizontal-tb", "vertical-rl", "vertical-lr"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
||||
|
||||
func ZIndexHandler(value string) bool {
|
||||
reg := regexp.MustCompile(`[\-]?[0-9]+`)
|
||||
reg.Longest()
|
||||
if reg.FindString(value) == value && value != "" {
|
||||
return true
|
||||
}
|
||||
values := []string{"auto", "initial", "inherit"}
|
||||
splitVals := splitValues(value)
|
||||
return in(splitVals, values)
|
||||
}
|
|
@ -135,7 +135,7 @@ func (p *Policy) AllowStandardURLs() {
|
|||
// Most common URL schemes only
|
||||
p.AllowURLSchemes("mailto", "http", "https")
|
||||
|
||||
// For all anchors we will add rel="nofollow" if it does not already exist
|
||||
// For linking elements we will add rel="nofollow" if it does not already exist
|
||||
// This applies to "a" "area" "link"
|
||||
p.RequireNoFollowOnLinks(true)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
|
||||
package bluemonday
|
||||
|
||||
//TODO sgutzwiller create map of styles to default handlers
|
||||
//TODO sgutzwiller create handlers for various attributes
|
||||
import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
@ -51,14 +53,22 @@ type Policy struct {
|
|||
// tag is replaced by a space character.
|
||||
addSpaces bool
|
||||
|
||||
// When true, add rel="nofollow" to HTML anchors
|
||||
// When true, add rel="nofollow" to HTML a, area, and link tags
|
||||
requireNoFollow bool
|
||||
|
||||
// When true, add rel="nofollow" to HTML anchors
|
||||
// When true, add rel="nofollow" to HTML a, area, and link tags
|
||||
// Will add for href="http://foo"
|
||||
// Will skip for href="/foo" or href="foo"
|
||||
requireNoFollowFullyQualifiedLinks bool
|
||||
|
||||
// When true, add rel="noreferrer" to HTML a, area, and link tags
|
||||
requireNoReferrer bool
|
||||
|
||||
// When true, add rel="noreferrer" to HTML a, area, and link tags
|
||||
// Will add for href="http://foo"
|
||||
// Will skip for href="/foo" or href="foo"
|
||||
requireNoReferrerFullyQualifiedLinks bool
|
||||
|
||||
// When true add target="_blank" to fully qualified links
|
||||
// Will add for href="http://foo"
|
||||
// Will skip for href="/foo" or href="foo"
|
||||
|
@ -76,9 +86,21 @@ type Policy struct {
|
|||
// map[htmlElementName]map[htmlAttributeName]attrPolicy
|
||||
elsAndAttrs map[string]map[string]attrPolicy
|
||||
|
||||
// elsMatchingAndAttrs stores regex based element matches along with attributes
|
||||
elsMatchingAndAttrs map[*regexp.Regexp]map[string]attrPolicy
|
||||
|
||||
// map[htmlAttributeName]attrPolicy
|
||||
globalAttrs map[string]attrPolicy
|
||||
|
||||
// map[htmlElementName]map[cssPropertyName]stylePolicy
|
||||
elsAndStyles map[string]map[string]stylePolicy
|
||||
|
||||
// map[regex]map[cssPropertyName]stylePolicy
|
||||
elsMatchingAndStyles map[*regexp.Regexp]map[string]stylePolicy
|
||||
|
||||
// map[cssPropertyName]stylePolicy
|
||||
globalStyles map[string]stylePolicy
|
||||
|
||||
// If urlPolicy is nil, all URLs with matching schema are allowed.
|
||||
// Otherwise, only the URLs with matching schema and urlPolicy(url)
|
||||
// returning true are allowed.
|
||||
|
@ -93,6 +115,16 @@ type Policy struct {
|
|||
// be maintained in the output HTML.
|
||||
setOfElementsAllowedWithoutAttrs map[string]struct{}
|
||||
|
||||
// If an element has had all attributes removed as a result of a policy
|
||||
// being applied, then the element would be removed from the output.
|
||||
//
|
||||
// However some elements are valid and have strong layout meaning without
|
||||
// any attributes, i.e. <table>.
|
||||
//
|
||||
// In this case, any element matching a regular expression will be accepted without
|
||||
// attributes added.
|
||||
setOfElementsMatchingAllowedWithoutAttrs []*regexp.Regexp
|
||||
|
||||
setOfElementsToSkipContent map[string]struct{}
|
||||
}
|
||||
|
||||
|
@ -103,6 +135,20 @@ type attrPolicy struct {
|
|||
regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
type stylePolicy struct {
|
||||
// handler to validate
|
||||
handler func(string) bool
|
||||
|
||||
// optional pattern to match, when not nil the regexp needs to match
|
||||
// otherwise the property is removed
|
||||
regexp *regexp.Regexp
|
||||
|
||||
// optional list of allowed property values, for properties which
|
||||
// have a defined list of allowed values; property will be removed
|
||||
// if the value is not allowed
|
||||
enum []string
|
||||
}
|
||||
|
||||
type attrPolicyBuilder struct {
|
||||
p *Policy
|
||||
|
||||
|
@ -111,13 +157,26 @@ type attrPolicyBuilder struct {
|
|||
allowEmpty bool
|
||||
}
|
||||
|
||||
type stylePolicyBuilder struct {
|
||||
p *Policy
|
||||
|
||||
propertyNames []string
|
||||
regexp *regexp.Regexp
|
||||
enum []string
|
||||
handler func(string) bool
|
||||
}
|
||||
|
||||
type urlPolicy func(url *url.URL) (allowUrl bool)
|
||||
|
||||
// init initializes the maps if this has not been done already
|
||||
func (p *Policy) init() {
|
||||
if !p.initialized {
|
||||
p.elsAndAttrs = make(map[string]map[string]attrPolicy)
|
||||
p.elsMatchingAndAttrs = make(map[*regexp.Regexp]map[string]attrPolicy)
|
||||
p.globalAttrs = make(map[string]attrPolicy)
|
||||
p.elsAndStyles = make(map[string]map[string]stylePolicy)
|
||||
p.elsMatchingAndStyles = make(map[*regexp.Regexp]map[string]stylePolicy)
|
||||
p.globalStyles = make(map[string]stylePolicy)
|
||||
p.allowURLSchemes = make(map[string]urlPolicy)
|
||||
p.setOfElementsAllowedWithoutAttrs = make(map[string]struct{})
|
||||
p.setOfElementsToSkipContent = make(map[string]struct{})
|
||||
|
@ -245,6 +304,30 @@ func (abp *attrPolicyBuilder) OnElements(elements ...string) *Policy {
|
|||
return abp.p
|
||||
}
|
||||
|
||||
// OnElementsMatching will bind an attribute policy to all elements matching a given regex
|
||||
// and return the updated policy
|
||||
func (abp *attrPolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
|
||||
for _, attr := range abp.attrNames {
|
||||
if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok {
|
||||
abp.p.elsMatchingAndAttrs[regex] = make(map[string]attrPolicy)
|
||||
}
|
||||
ap := attrPolicy{}
|
||||
if abp.regexp != nil {
|
||||
ap.regexp = abp.regexp
|
||||
}
|
||||
abp.p.elsMatchingAndAttrs[regex][attr] = ap
|
||||
}
|
||||
|
||||
if abp.allowEmpty {
|
||||
abp.p.setOfElementsMatchingAllowedWithoutAttrs = append(abp.p.setOfElementsMatchingAllowedWithoutAttrs, regex)
|
||||
if _, ok := abp.p.elsMatchingAndAttrs[regex]; !ok {
|
||||
abp.p.elsMatchingAndAttrs[regex] = make(map[string]attrPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
return abp.p
|
||||
}
|
||||
|
||||
// Globally will bind an attribute policy to all HTML elements and return the
|
||||
// updated policy
|
||||
func (abp *attrPolicyBuilder) Globally() *Policy {
|
||||
|
@ -265,6 +348,139 @@ func (abp *attrPolicyBuilder) Globally() *Policy {
|
|||
return abp.p
|
||||
}
|
||||
|
||||
// AllowStyles takes a range of CSS property names and returns a
|
||||
// style policy builder that allows you to specify the pattern and scope of
|
||||
// the whitelisted property.
|
||||
//
|
||||
// The style policy is only added to the core policy when either Globally()
|
||||
// or OnElements(...) are called.
|
||||
func (p *Policy) AllowStyles(propertyNames ...string) *stylePolicyBuilder {
|
||||
|
||||
p.init()
|
||||
|
||||
abp := stylePolicyBuilder{
|
||||
p: p,
|
||||
}
|
||||
|
||||
for _, propertyName := range propertyNames {
|
||||
abp.propertyNames = append(abp.propertyNames, strings.ToLower(propertyName))
|
||||
}
|
||||
|
||||
return &abp
|
||||
}
|
||||
|
||||
// Matching allows a regular expression to be applied to a nascent style
|
||||
// policy, and returns the style policy. Calling this more than once will
|
||||
// replace the existing regexp.
|
||||
func (spb *stylePolicyBuilder) Matching(regex *regexp.Regexp) *stylePolicyBuilder {
|
||||
|
||||
spb.regexp = regex
|
||||
|
||||
return spb
|
||||
}
|
||||
|
||||
// MatchingEnum allows a list of allowed values to be applied to a nascent style
|
||||
// policy, and returns the style policy. Calling this more than once will
|
||||
// replace the existing list of allowed values.
|
||||
func (spb *stylePolicyBuilder) MatchingEnum(enum ...string) *stylePolicyBuilder {
|
||||
|
||||
spb.enum = enum
|
||||
|
||||
return spb
|
||||
}
|
||||
|
||||
// MatchingHandler allows a handler to be applied to a nascent style
|
||||
// policy, and returns the style policy. Calling this more than once will
|
||||
// replace the existing handler.
|
||||
func (spb *stylePolicyBuilder) MatchingHandler(handler func(string) bool) *stylePolicyBuilder {
|
||||
|
||||
spb.handler = handler
|
||||
|
||||
return spb
|
||||
}
|
||||
|
||||
// OnElements will bind a style policy to a given range of HTML elements
|
||||
// and return the updated policy
|
||||
func (spb *stylePolicyBuilder) OnElements(elements ...string) *Policy {
|
||||
|
||||
for _, element := range elements {
|
||||
element = strings.ToLower(element)
|
||||
|
||||
for _, attr := range spb.propertyNames {
|
||||
|
||||
if _, ok := spb.p.elsAndStyles[element]; !ok {
|
||||
spb.p.elsAndStyles[element] = make(map[string]stylePolicy)
|
||||
}
|
||||
|
||||
sp := stylePolicy{}
|
||||
if spb.handler != nil {
|
||||
sp.handler = spb.handler
|
||||
} else if len(spb.enum) > 0 {
|
||||
sp.enum = spb.enum
|
||||
} else if spb.regexp != nil {
|
||||
sp.regexp = spb.regexp
|
||||
} else {
|
||||
sp.handler = getDefaultHandler(attr)
|
||||
}
|
||||
spb.p.elsAndStyles[element][attr] = sp
|
||||
}
|
||||
}
|
||||
|
||||
return spb.p
|
||||
}
|
||||
|
||||
// OnElementsMatching will bind a style policy to any HTML elements matching the pattern
|
||||
// and return the updated policy
|
||||
func (spb *stylePolicyBuilder) OnElementsMatching(regex *regexp.Regexp) *Policy {
|
||||
|
||||
for _, attr := range spb.propertyNames {
|
||||
|
||||
if _, ok := spb.p.elsMatchingAndStyles[regex]; !ok {
|
||||
spb.p.elsMatchingAndStyles[regex] = make(map[string]stylePolicy)
|
||||
}
|
||||
|
||||
sp := stylePolicy{}
|
||||
if spb.handler != nil {
|
||||
sp.handler = spb.handler
|
||||
} else if len(spb.enum) > 0 {
|
||||
sp.enum = spb.enum
|
||||
} else if spb.regexp != nil {
|
||||
sp.regexp = spb.regexp
|
||||
} else {
|
||||
sp.handler = getDefaultHandler(attr)
|
||||
}
|
||||
spb.p.elsMatchingAndStyles[regex][attr] = sp
|
||||
}
|
||||
|
||||
return spb.p
|
||||
}
|
||||
|
||||
// Globally will bind a style policy to all HTML elements and return the
|
||||
// updated policy
|
||||
func (spb *stylePolicyBuilder) Globally() *Policy {
|
||||
|
||||
for _, attr := range spb.propertyNames {
|
||||
if _, ok := spb.p.globalStyles[attr]; !ok {
|
||||
spb.p.globalStyles[attr] = stylePolicy{}
|
||||
}
|
||||
|
||||
// Use only one strategy for validating styles, fallback to default
|
||||
sp := stylePolicy{}
|
||||
if spb.handler != nil {
|
||||
sp.handler = spb.handler
|
||||
} else if len(spb.enum) > 0 {
|
||||
sp.enum = spb.enum
|
||||
} else if spb.regexp != nil {
|
||||
sp.regexp = spb.regexp
|
||||
} else {
|
||||
sp.handler = getDefaultHandler(attr)
|
||||
}
|
||||
spb.p.globalStyles[attr] = sp
|
||||
}
|
||||
|
||||
return spb.p
|
||||
}
|
||||
|
||||
// AllowElements will append HTML elements to the whitelist without applying an
|
||||
// attribute policy to those elements (the elements are permitted
|
||||
// sans-attributes)
|
||||
|
@ -282,8 +498,16 @@ func (p *Policy) AllowElements(names ...string) *Policy {
|
|||
return p
|
||||
}
|
||||
|
||||
// RequireNoFollowOnLinks will result in all <a> tags having a rel="nofollow"
|
||||
// added to them if one does not already exist
|
||||
func (p *Policy) AllowElementsMatching(regex *regexp.Regexp) *Policy {
|
||||
p.init()
|
||||
if _, ok := p.elsMatchingAndAttrs[regex]; !ok {
|
||||
p.elsMatchingAndAttrs[regex] = make(map[string]attrPolicy)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// RequireNoFollowOnLinks will result in all a, area, link tags having a
|
||||
// rel="nofollow"added to them if one does not already exist
|
||||
//
|
||||
// Note: This requires p.RequireParseableURLs(true) and will enable it.
|
||||
func (p *Policy) RequireNoFollowOnLinks(require bool) *Policy {
|
||||
|
@ -294,9 +518,10 @@ func (p *Policy) RequireNoFollowOnLinks(require bool) *Policy {
|
|||
return p
|
||||
}
|
||||
|
||||
// RequireNoFollowOnFullyQualifiedLinks will result in all <a> tags that point
|
||||
// to a non-local destination (i.e. starts with a protocol and has a host)
|
||||
// having a rel="nofollow" added to them if one does not already exist
|
||||
// RequireNoFollowOnFullyQualifiedLinks will result in all a, area, and link
|
||||
// tags that point to a non-local destination (i.e. starts with a protocol and
|
||||
// has a host) having a rel="nofollow" added to them if one does not already
|
||||
// exist
|
||||
//
|
||||
// Note: This requires p.RequireParseableURLs(true) and will enable it.
|
||||
func (p *Policy) RequireNoFollowOnFullyQualifiedLinks(require bool) *Policy {
|
||||
|
@ -307,9 +532,35 @@ func (p *Policy) RequireNoFollowOnFullyQualifiedLinks(require bool) *Policy {
|
|||
return p
|
||||
}
|
||||
|
||||
// AddTargetBlankToFullyQualifiedLinks will result in all <a> tags that point
|
||||
// to a non-local destination (i.e. starts with a protocol and has a host)
|
||||
// having a target="_blank" added to them if one does not already exist
|
||||
// RequireNoReferrerOnLinks will result in all a, area, and link tags having a
|
||||
// rel="noreferrrer" added to them if one does not already exist
|
||||
//
|
||||
// Note: This requires p.RequireParseableURLs(true) and will enable it.
|
||||
func (p *Policy) RequireNoReferrerOnLinks(require bool) *Policy {
|
||||
|
||||
p.requireNoReferrer = require
|
||||
p.requireParseableURLs = true
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// RequireNoReferrerOnFullyQualifiedLinks will result in all a, area, and link
|
||||
// tags that point to a non-local destination (i.e. starts with a protocol and
|
||||
// has a host) having a rel="noreferrer" added to them if one does not already
|
||||
// exist
|
||||
//
|
||||
// Note: This requires p.RequireParseableURLs(true) and will enable it.
|
||||
func (p *Policy) RequireNoReferrerOnFullyQualifiedLinks(require bool) *Policy {
|
||||
|
||||
p.requireNoReferrerFullyQualifiedLinks = require
|
||||
p.requireParseableURLs = true
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// AddTargetBlankToFullyQualifiedLinks will result in all a, area and link tags
|
||||
// that point to a non-local destination (i.e. starts with a protocol and has a
|
||||
// host) having a target="_blank" added to them if one does not already exist
|
||||
//
|
||||
// Note: This requires p.RequireParseableURLs(true) and will enable it.
|
||||
func (p *Policy) AddTargetBlankToFullyQualifiedLinks(require bool) *Policy {
|
||||
|
|
|
@ -34,15 +34,19 @@ import (
|
|||
"io"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
|
||||
cssparser "github.com/chris-ramon/douceur/parser"
|
||||
)
|
||||
|
||||
var (
|
||||
dataAttribute = regexp.MustCompile("^data-.+")
|
||||
dataAttributeXMLPrefix = regexp.MustCompile("^xml.+")
|
||||
dataAttributeInvalidChars = regexp.MustCompile("[A-Z;]+")
|
||||
cssUnicodeChar = regexp.MustCompile(`\\[0-9a-f]{1,6} ?`)
|
||||
)
|
||||
|
||||
// Sanitize takes a string that contains a HTML fragment or document and applies
|
||||
|
@ -82,6 +86,98 @@ func (p *Policy) SanitizeReader(r io.Reader) *bytes.Buffer {
|
|||
return p.sanitize(r)
|
||||
}
|
||||
|
||||
const escapedURLChars = "'<>\"\r"
|
||||
|
||||
func escapeUrlComponent(val string) string {
|
||||
w := bytes.NewBufferString("")
|
||||
i := strings.IndexAny(val, escapedURLChars)
|
||||
for i != -1 {
|
||||
if _, err := w.WriteString(val[:i]); err != nil {
|
||||
return w.String()
|
||||
}
|
||||
var esc string
|
||||
switch val[i] {
|
||||
case '\'':
|
||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
esc = "'"
|
||||
case '<':
|
||||
esc = "<"
|
||||
case '>':
|
||||
esc = ">"
|
||||
case '"':
|
||||
// """ is shorter than """.
|
||||
esc = """
|
||||
case '\r':
|
||||
esc = " "
|
||||
default:
|
||||
panic("unrecognized escape character")
|
||||
}
|
||||
val = val[i+1:]
|
||||
if _, err := w.WriteString(esc); err != nil {
|
||||
return w.String()
|
||||
}
|
||||
i = strings.IndexAny(val, escapedURLChars)
|
||||
}
|
||||
w.WriteString(val)
|
||||
return w.String()
|
||||
}
|
||||
|
||||
func sanitizedUrl(val string) (string, error) {
|
||||
u, err := url.Parse(val)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// sanitize the url query params
|
||||
sanitizedQueryValues := make(url.Values, 0)
|
||||
queryValues := u.Query()
|
||||
for k, vals := range queryValues {
|
||||
sk := html.EscapeString(k)
|
||||
for _, v := range vals {
|
||||
sv := v
|
||||
sanitizedQueryValues.Add(sk, sv)
|
||||
}
|
||||
}
|
||||
u.RawQuery = sanitizedQueryValues.Encode()
|
||||
// u.String() will also sanitize host/scheme/user/pass
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (p *Policy) writeLinkableBuf(buff *bytes.Buffer, token *html.Token) {
|
||||
// do not escape multiple query parameters
|
||||
tokenBuff := bytes.NewBufferString("")
|
||||
tokenBuff.WriteString("<")
|
||||
tokenBuff.WriteString(token.Data)
|
||||
for _, attr := range token.Attr {
|
||||
tokenBuff.WriteByte(' ')
|
||||
tokenBuff.WriteString(attr.Key)
|
||||
tokenBuff.WriteString(`="`)
|
||||
switch attr.Key {
|
||||
case "href", "src":
|
||||
u, ok := p.validURL(attr.Val)
|
||||
if !ok {
|
||||
tokenBuff.WriteString(html.EscapeString(attr.Val))
|
||||
continue
|
||||
}
|
||||
u, err := sanitizedUrl(u)
|
||||
if err == nil {
|
||||
tokenBuff.WriteString(u)
|
||||
} else {
|
||||
// fallthrough
|
||||
tokenBuff.WriteString(html.EscapeString(attr.Val))
|
||||
}
|
||||
default:
|
||||
// re-apply
|
||||
tokenBuff.WriteString(html.EscapeString(attr.Val))
|
||||
}
|
||||
tokenBuff.WriteByte('"')
|
||||
}
|
||||
if token.Type == html.SelfClosingTagToken {
|
||||
tokenBuff.WriteString("/")
|
||||
}
|
||||
tokenBuff.WriteString(">")
|
||||
buff.WriteString(tokenBuff.String())
|
||||
}
|
||||
|
||||
// Performs the actual sanitization process.
|
||||
func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
||||
|
||||
|
@ -133,20 +229,23 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
|||
|
||||
case html.StartTagToken:
|
||||
|
||||
mostRecentlyStartedToken = token.Data
|
||||
mostRecentlyStartedToken = strings.ToLower(token.Data)
|
||||
|
||||
aps, ok := p.elsAndAttrs[token.Data]
|
||||
if !ok {
|
||||
if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {
|
||||
skipElementContent = true
|
||||
skippingElementsCount++
|
||||
aa, matched := p.matchRegex(token.Data)
|
||||
if !matched {
|
||||
if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {
|
||||
skipElementContent = true
|
||||
skippingElementsCount++
|
||||
}
|
||||
if p.addSpaces {
|
||||
buff.WriteString(" ")
|
||||
}
|
||||
break
|
||||
}
|
||||
if p.addSpaces {
|
||||
buff.WriteString(" ")
|
||||
}
|
||||
break
|
||||
aps = aa
|
||||
}
|
||||
|
||||
if len(token.Attr) != 0 {
|
||||
token.Attr = p.sanitizeAttrs(token.Data, token.Attr, aps)
|
||||
}
|
||||
|
@ -163,12 +262,17 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
|||
}
|
||||
|
||||
if !skipElementContent {
|
||||
buff.WriteString(token.String())
|
||||
// do not escape multiple query parameters
|
||||
if linkable(token.Data) {
|
||||
p.writeLinkableBuf(&buff, &token)
|
||||
} else {
|
||||
buff.WriteString(token.String())
|
||||
}
|
||||
}
|
||||
|
||||
case html.EndTagToken:
|
||||
|
||||
if mostRecentlyStartedToken == token.Data {
|
||||
if mostRecentlyStartedToken == strings.ToLower(token.Data) {
|
||||
mostRecentlyStartedToken = ""
|
||||
}
|
||||
|
||||
|
@ -182,18 +286,27 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
|||
}
|
||||
break
|
||||
}
|
||||
|
||||
if _, ok := p.elsAndAttrs[token.Data]; !ok {
|
||||
if _, ok := p.setOfElementsToSkipContent[token.Data]; ok {
|
||||
match := false
|
||||
for regex := range p.elsMatchingAndAttrs {
|
||||
if regex.MatchString(token.Data) {
|
||||
skipElementContent = false
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if _, ok := p.setOfElementsToSkipContent[token.Data]; ok && !match {
|
||||
skippingElementsCount--
|
||||
if skippingElementsCount == 0 {
|
||||
skipElementContent = false
|
||||
}
|
||||
}
|
||||
if p.addSpaces {
|
||||
buff.WriteString(" ")
|
||||
if !match {
|
||||
if p.addSpaces {
|
||||
buff.WriteString(" ")
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if !skipElementContent {
|
||||
|
@ -204,10 +317,14 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
|||
|
||||
aps, ok := p.elsAndAttrs[token.Data]
|
||||
if !ok {
|
||||
if p.addSpaces {
|
||||
buff.WriteString(" ")
|
||||
aa, matched := p.matchRegex(token.Data)
|
||||
if !matched {
|
||||
if p.addSpaces && !matched {
|
||||
buff.WriteString(" ")
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
aps = aa
|
||||
}
|
||||
|
||||
if len(token.Attr) != 0 {
|
||||
|
@ -217,12 +334,16 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
|||
if len(token.Attr) == 0 && !p.allowNoAttrs(token.Data) {
|
||||
if p.addSpaces {
|
||||
buff.WriteString(" ")
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if !skipElementContent {
|
||||
buff.WriteString(token.String())
|
||||
// do not escape multiple query parameters
|
||||
if linkable(token.Data) {
|
||||
p.writeLinkableBuf(&buff, &token)
|
||||
} else {
|
||||
buff.WriteString(token.String())
|
||||
}
|
||||
}
|
||||
|
||||
case html.TextToken:
|
||||
|
@ -242,6 +363,7 @@ func (p *Policy) sanitize(r io.Reader) *bytes.Buffer {
|
|||
buff.WriteString(token.String())
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// A token that didn't exist in the html package when we wrote this
|
||||
return &bytes.Buffer{}
|
||||
|
@ -262,6 +384,23 @@ func (p *Policy) sanitizeAttrs(
|
|||
return attrs
|
||||
}
|
||||
|
||||
hasStylePolicies := false
|
||||
sps, elementHasStylePolicies := p.elsAndStyles[elementName]
|
||||
if len(p.globalStyles) > 0 || (elementHasStylePolicies && len(sps) > 0) {
|
||||
hasStylePolicies = true
|
||||
}
|
||||
// no specific element policy found, look for a pattern match
|
||||
if !hasStylePolicies {
|
||||
for k, v := range p.elsMatchingAndStyles {
|
||||
if k.MatchString(elementName) {
|
||||
if len(v) > 0 {
|
||||
hasStylePolicies = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a new attribute slice based on the whether the attribute has been
|
||||
// whitelisted explicitly or globally.
|
||||
cleanAttrs := []html.Attribute{}
|
||||
|
@ -273,6 +412,19 @@ func (p *Policy) sanitizeAttrs(
|
|||
continue
|
||||
}
|
||||
}
|
||||
// Is this a "style" attribute, and if so, do we need to sanitize it?
|
||||
if htmlAttr.Key == "style" && hasStylePolicies {
|
||||
htmlAttr = p.sanitizeStyles(htmlAttr, elementName)
|
||||
if htmlAttr.Val == "" {
|
||||
// We've sanitized away any and all styles; don't bother to
|
||||
// output the style attribute (even if it's allowed)
|
||||
continue
|
||||
} else {
|
||||
cleanAttrs = append(cleanAttrs, htmlAttr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Is there an element specific attribute policy that applies?
|
||||
if ap, ok := aps[htmlAttr.Key]; ok {
|
||||
if ap.regexp != nil {
|
||||
|
@ -354,6 +506,8 @@ func (p *Policy) sanitizeAttrs(
|
|||
|
||||
if (p.requireNoFollow ||
|
||||
p.requireNoFollowFullyQualifiedLinks ||
|
||||
p.requireNoReferrer ||
|
||||
p.requireNoReferrerFullyQualifiedLinks ||
|
||||
p.addTargetBlankToFullyQualifiedLinks) &&
|
||||
len(cleanAttrs) > 0 {
|
||||
|
||||
|
@ -381,12 +535,16 @@ func (p *Policy) sanitizeAttrs(
|
|||
if hrefFound {
|
||||
var (
|
||||
noFollowFound bool
|
||||
noReferrerFound bool
|
||||
targetBlankFound bool
|
||||
)
|
||||
|
||||
addNoFollow := (p.requireNoFollow ||
|
||||
externalLink && p.requireNoFollowFullyQualifiedLinks)
|
||||
|
||||
addNoReferrer := (p.requireNoReferrer ||
|
||||
externalLink && p.requireNoReferrerFullyQualifiedLinks)
|
||||
|
||||
addTargetBlank := (externalLink &&
|
||||
p.addTargetBlankToFullyQualifiedLinks)
|
||||
|
||||
|
@ -394,18 +552,18 @@ func (p *Policy) sanitizeAttrs(
|
|||
for _, htmlAttr := range cleanAttrs {
|
||||
|
||||
var appended bool
|
||||
if htmlAttr.Key == "rel" && addNoFollow {
|
||||
if htmlAttr.Key == "rel" && (addNoFollow || addNoReferrer) {
|
||||
|
||||
if strings.Contains(htmlAttr.Val, "nofollow") {
|
||||
noFollowFound = true
|
||||
tmpAttrs = append(tmpAttrs, htmlAttr)
|
||||
appended = true
|
||||
} else {
|
||||
if addNoFollow && !strings.Contains(htmlAttr.Val, "nofollow") {
|
||||
htmlAttr.Val += " nofollow"
|
||||
noFollowFound = true
|
||||
tmpAttrs = append(tmpAttrs, htmlAttr)
|
||||
appended = true
|
||||
}
|
||||
if addNoReferrer && !strings.Contains(htmlAttr.Val, "noreferrer") {
|
||||
htmlAttr.Val += " noreferrer"
|
||||
}
|
||||
noFollowFound = addNoFollow
|
||||
noReferrerFound = addNoReferrer
|
||||
tmpAttrs = append(tmpAttrs, htmlAttr)
|
||||
appended = true
|
||||
}
|
||||
|
||||
if elementName == "a" && htmlAttr.Key == "target" {
|
||||
|
@ -424,14 +582,22 @@ func (p *Policy) sanitizeAttrs(
|
|||
tmpAttrs = append(tmpAttrs, htmlAttr)
|
||||
}
|
||||
}
|
||||
if noFollowFound || targetBlankFound {
|
||||
if noFollowFound || noReferrerFound || targetBlankFound {
|
||||
cleanAttrs = tmpAttrs
|
||||
}
|
||||
|
||||
if addNoFollow && !noFollowFound {
|
||||
if (addNoFollow && !noFollowFound) || (addNoReferrer && !noReferrerFound) {
|
||||
rel := html.Attribute{}
|
||||
rel.Key = "rel"
|
||||
rel.Val = "nofollow"
|
||||
if addNoFollow {
|
||||
rel.Val = "nofollow"
|
||||
}
|
||||
if addNoReferrer {
|
||||
if rel.Val != "" {
|
||||
rel.Val += " "
|
||||
}
|
||||
rel.Val += "noreferrer"
|
||||
}
|
||||
cleanAttrs = append(cleanAttrs, rel)
|
||||
}
|
||||
|
||||
|
@ -501,8 +667,95 @@ func (p *Policy) sanitizeAttrs(
|
|||
return cleanAttrs
|
||||
}
|
||||
|
||||
func (p *Policy) sanitizeStyles(attr html.Attribute, elementName string) html.Attribute {
|
||||
sps := p.elsAndStyles[elementName]
|
||||
if len(sps) == 0 {
|
||||
sps = map[string]stylePolicy{}
|
||||
// check for any matching elements, if we don't already have a policy found
|
||||
// if multiple matches are found they will be overwritten, it's best
|
||||
// to not have overlapping matchers
|
||||
for regex, policies := range p.elsMatchingAndStyles {
|
||||
if regex.MatchString(elementName) {
|
||||
for k, v := range policies {
|
||||
sps[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Add semi-colon to end to fix parsing issue
|
||||
if len(attr.Val) > 0 && attr.Val[len(attr.Val)-1] != ';' {
|
||||
attr.Val = attr.Val + ";"
|
||||
}
|
||||
decs, err := cssparser.ParseDeclarations(attr.Val)
|
||||
if err != nil {
|
||||
attr.Val = ""
|
||||
return attr
|
||||
}
|
||||
clean := []string{}
|
||||
prefixes := []string{"-webkit-", "-moz-", "-ms-", "-o-", "mso-", "-xv-", "-atsc-", "-wap-", "-khtml-", "prince-", "-ah-", "-hp-", "-ro-", "-rim-", "-tc-"}
|
||||
|
||||
for _, dec := range decs {
|
||||
addedProperty := false
|
||||
tempProperty := strings.ToLower(dec.Property)
|
||||
tempValue := removeUnicode(strings.ToLower(dec.Value))
|
||||
for _, i := range prefixes {
|
||||
tempProperty = strings.TrimPrefix(tempProperty, i)
|
||||
}
|
||||
if sp, ok := sps[tempProperty]; ok {
|
||||
if sp.handler != nil {
|
||||
if sp.handler(tempValue) {
|
||||
clean = append(clean, dec.Property+": "+dec.Value)
|
||||
addedProperty = true
|
||||
}
|
||||
} else if len(sp.enum) > 0 {
|
||||
if stringInSlice(tempValue, sp.enum) {
|
||||
clean = append(clean, dec.Property+": "+dec.Value)
|
||||
addedProperty = true
|
||||
}
|
||||
} else if sp.regexp != nil {
|
||||
if sp.regexp.MatchString(tempValue) {
|
||||
clean = append(clean, dec.Property+": "+dec.Value)
|
||||
addedProperty = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if sp, ok := p.globalStyles[tempProperty]; ok && !addedProperty {
|
||||
if sp.handler != nil {
|
||||
if sp.handler(tempValue) {
|
||||
clean = append(clean, dec.Property+": "+dec.Value)
|
||||
}
|
||||
} else if len(sp.enum) > 0 {
|
||||
if stringInSlice(tempValue, sp.enum) {
|
||||
clean = append(clean, dec.Property+": "+dec.Value)
|
||||
}
|
||||
} else if sp.regexp != nil {
|
||||
if sp.regexp.MatchString(tempValue) {
|
||||
clean = append(clean, dec.Property+": "+dec.Value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(clean) > 0 {
|
||||
attr.Val = strings.Join(clean, "; ")
|
||||
} else {
|
||||
attr.Val = ""
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
func (p *Policy) allowNoAttrs(elementName string) bool {
|
||||
_, ok := p.setOfElementsAllowedWithoutAttrs[elementName]
|
||||
if !ok {
|
||||
for _, r := range p.setOfElementsMatchingAllowedWithoutAttrs {
|
||||
if r.MatchString(elementName) {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
|
@ -561,6 +814,16 @@ func linkable(elementName string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// stringInSlice returns true if needle exists in haystack
|
||||
func stringInSlice(needle string, haystack []string) bool {
|
||||
for _, straw := range haystack {
|
||||
if strings.ToLower(straw) == strings.ToLower(needle) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isDataAttribute(val string) bool {
|
||||
if !dataAttribute.MatchString(val) {
|
||||
return false
|
||||
|
@ -579,3 +842,48 @@ func isDataAttribute(val string) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func removeUnicode(value string) string {
|
||||
substitutedValue := value
|
||||
currentLoc := cssUnicodeChar.FindStringIndex(substitutedValue)
|
||||
for currentLoc != nil {
|
||||
|
||||
character := substitutedValue[currentLoc[0]+1 : currentLoc[1]]
|
||||
character = strings.TrimSpace(character)
|
||||
if len(character) < 4 {
|
||||
character = strings.Repeat("0", 4-len(character)) + character
|
||||
} else {
|
||||
for len(character) > 4 {
|
||||
if character[0] != '0' {
|
||||
character = ""
|
||||
break
|
||||
} else {
|
||||
character = character[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
character = "\\u" + character
|
||||
translatedChar, err := strconv.Unquote(`"` + character + `"`)
|
||||
translatedChar = strings.TrimSpace(translatedChar)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
substitutedValue = substitutedValue[0:currentLoc[0]] + translatedChar + substitutedValue[currentLoc[1]:]
|
||||
currentLoc = cssUnicodeChar.FindStringIndex(substitutedValue)
|
||||
}
|
||||
return substitutedValue
|
||||
}
|
||||
|
||||
func (p *Policy) matchRegex(elementName string) (map[string]attrPolicy, bool) {
|
||||
aps := make(map[string]attrPolicy, 0)
|
||||
matched := false
|
||||
for regex, attrs := range p.elsMatchingAndAttrs {
|
||||
if regex.MatchString(elementName) {
|
||||
matched = true
|
||||
for k, v := range attrs {
|
||||
aps[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return aps, matched
|
||||
}
|
||||
|
|
|
@ -11,11 +11,16 @@ type Buffer struct {
|
|||
bytes.Buffer
|
||||
}
|
||||
|
||||
// PrintableRuneCount returns the amount of printable runes in the buffer.
|
||||
func (w Buffer) PrintableRuneCount() int {
|
||||
// PrintableRuneWidth returns the width of all printable runes in the buffer.
|
||||
func (w Buffer) PrintableRuneWidth() int {
|
||||
return PrintableRuneWidth(w.String())
|
||||
}
|
||||
|
||||
func PrintableRuneWidth(s string) int {
|
||||
var n int
|
||||
var ansi bool
|
||||
for _, c := range w.String() {
|
||||
|
||||
for _, c := range s {
|
||||
if c == '\x1B' {
|
||||
// ANSI escape sequence
|
||||
ansi = true
|
||||
|
|
|
@ -66,7 +66,7 @@ func (w *WordWrap) addSpace() {
|
|||
func (w *WordWrap) addWord() {
|
||||
if w.word.Len() > 0 {
|
||||
w.addSpace()
|
||||
w.lineLen += w.word.PrintableRuneCount()
|
||||
w.lineLen += w.word.PrintableRuneWidth()
|
||||
w.buf.Write(w.word.Bytes())
|
||||
w.word.Reset()
|
||||
}
|
||||
|
@ -139,8 +139,8 @@ func (w *WordWrap) Write(b []byte) (int, error) {
|
|||
|
||||
// add a line break if the current word would exceed the line's
|
||||
// character limit
|
||||
if w.lineLen+w.space.Len()+w.word.PrintableRuneCount() > w.Limit &&
|
||||
w.word.PrintableRuneCount() < w.Limit {
|
||||
if w.lineLen+w.space.Len()+w.word.PrintableRuneWidth() > w.Limit &&
|
||||
w.word.PrintableRuneWidth() < w.Limit {
|
||||
w.addNewLine()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
*.pprof
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
.DS_Store
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Yusuke Inuzuka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,71 @@
|
|||
goldmark-emoji
|
||||
=========================
|
||||
|
||||
[![GoDev][godev-image]][godev-url]
|
||||
|
||||
[godev-image]: https://pkg.go.dev/badge/github.com/yuin/goldmark-emoji
|
||||
[godev-url]: https://pkg.go.dev/github.com/yuin/goldmark-emoji
|
||||
|
||||
goldmark-emoji is an extension for the [goldmark](http://github.com/yuin/goldmark)
|
||||
that parses `:joy:` style emojis.
|
||||
|
||||
Installation
|
||||
--------------------
|
||||
|
||||
```
|
||||
go get github.com/yuin/goldmark-emoji
|
||||
```
|
||||
|
||||
Usage
|
||||
--------------------
|
||||
|
||||
```go
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark-emoji"
|
||||
"github.com/yuin/goldmark-emoji/definition"
|
||||
)
|
||||
|
||||
func main() {
|
||||
markdown := goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
emoji.Emoji,
|
||||
),
|
||||
)
|
||||
source := `
|
||||
Joy :joy:
|
||||
`
|
||||
var buf bytes.Buffer
|
||||
if err := markdown.Convert([]byte(source), &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Print(buf.String())
|
||||
}
|
||||
```
|
||||
|
||||
See `emoji_test.go` for detailed usage.
|
||||
|
||||
### Options
|
||||
|
||||
Options for the extension
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| `WithEmojis` | Definition of emojis. This defaults to github emoji set |
|
||||
| `WithRenderingMethod` | `Entity` : renders as HTML entities, `Twemoji` : renders as an img tag that uses [twemoji](https://github.com/twitter/twemoji), `Func` : renders using a go function |
|
||||
| `WithTwemojiTemplate` | Twemoji img tag printf template |
|
||||
| `WithRendererFunc` | renders by a go function |
|
||||
|
||||
|
||||
|
||||
License
|
||||
--------------------
|
||||
MIT
|
||||
|
||||
Author
|
||||
--------------------
|
||||
Yusuke Inuzuka
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Package ast defines AST nodes that represetns emoji extension's elements.
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/yuin/goldmark-emoji/definition"
|
||||
gast "github.com/yuin/goldmark/ast"
|
||||
)
|
||||
|
||||
// Emoji represents an inline emoji.
|
||||
type Emoji struct {
|
||||
gast.BaseInline
|
||||
|
||||
ShortName []byte
|
||||
Value *definition.Emoji
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *Emoji) Dump(source []byte, level int) {
|
||||
m := map[string]string{
|
||||
"ShortName": string(n.ShortName),
|
||||
"Value": fmt.Sprintf("%#v", n.Value),
|
||||
}
|
||||
gast.DumpHelper(n, source, level, m, nil)
|
||||
}
|
||||
|
||||
// KindEmoji is a NodeKind of the emoji node.
|
||||
var KindEmoji = gast.NewNodeKind("Emoji")
|
||||
|
||||
// Kind implements Node.Kind.
|
||||
func (n *Emoji) Kind() gast.NodeKind {
|
||||
return KindEmoji
|
||||
}
|
||||
|
||||
// NewEmoji returns a new Emoji node.
|
||||
func NewEmoji(shortName []byte, value *definition.Emoji) *Emoji {
|
||||
return &Emoji{
|
||||
ShortName: shortName,
|
||||
Value: value,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package definition
|
||||
|
||||
// Emoji is a data structure that holds a single emoji.
|
||||
type Emoji struct {
|
||||
// Name is a name of this emoji.
|
||||
Name string
|
||||
|
||||
// ShortNames is a shorter representation of this emoji.
|
||||
ShortNames []string
|
||||
|
||||
// Unicode is an unicode representation of this emoji.
|
||||
Unicode []rune
|
||||
}
|
||||
|
||||
// NewEmoji returns a new Emoji.
|
||||
func NewEmoji(name string, unicode []rune, shortNames ...string) Emoji {
|
||||
if len(shortNames) == 0 {
|
||||
panic("Emoji must have at leat 1 short name.")
|
||||
}
|
||||
if unicode == nil || len(unicode) == 0 {
|
||||
unicode = []rune{0xFFFD}
|
||||
}
|
||||
return Emoji{
|
||||
Name: name,
|
||||
ShortNames: shortNames,
|
||||
Unicode: unicode,
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnicode returns true if this emoji is defined in unicode, otherwise false.
|
||||
func (em *Emoji) IsUnicode() bool {
|
||||
return !(len(em.Unicode) == 1 && em.Unicode[0] == 0xFFFD)
|
||||
}
|
||||
|
||||
// Emojis is a collection of emojis.
|
||||
type Emojis interface {
|
||||
// Get returns (*Emoji, true) if found mapping associated with given short name, otherwise (nil, false).
|
||||
Get(shortName string) (*Emoji, bool)
|
||||
|
||||
// Add adds new emojis to this collection.
|
||||
Add(Emojis)
|
||||
|
||||
// Clone clones this collection.
|
||||
Clone() Emojis
|
||||
}
|
||||
|
||||
type emojis struct {
|
||||
list []Emoji
|
||||
m map[string]*Emoji
|
||||
children []Emojis
|
||||
}
|
||||
|
||||
// NewEmojis returns a new Emojis.
|
||||
func NewEmojis(es ...Emoji) Emojis {
|
||||
m := &emojis{
|
||||
list: es,
|
||||
m: map[string]*Emoji{},
|
||||
children: []Emojis{},
|
||||
}
|
||||
for i, _ := range es {
|
||||
emoji := &m.list[i]
|
||||
for _, s := range emoji.ShortNames {
|
||||
m.m[s] = emoji
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *emojis) Add(emojis Emojis) {
|
||||
m.children = append(m.children, emojis)
|
||||
}
|
||||
|
||||
func (m *emojis) Clone() Emojis {
|
||||
es := &emojis{
|
||||
list: m.list,
|
||||
m: m.m,
|
||||
children: make([]Emojis, len(m.children)),
|
||||
}
|
||||
copy(es.children, m.children)
|
||||
return es
|
||||
}
|
||||
|
||||
func (m *emojis) Get(shortName string) (*Emoji, bool) {
|
||||
v, ok := m.m[shortName]
|
||||
if ok {
|
||||
return v, ok
|
||||
}
|
||||
|
||||
for _, es := range m.children {
|
||||
v, ok := es.Get(shortName)
|
||||
if ok {
|
||||
return v, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// EmojisOption sets options for Emojis.
|
||||
type EmojisOption func(Emojis)
|
||||
|
||||
// WithEmojis is an EmojisOption that adds emojis to the Emojis.
|
||||
func WithEmojis(emojis ...Emoji) EmojisOption {
|
||||
return func(m Emojis) {
|
||||
m.Add(NewEmojis(emojis...))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,1757 @@
|
|||
// This file was generated by _tools/gen-deifinition.go. DO NOT EDIT.
|
||||
|
||||
package definition
|
||||
import "sync"
|
||||
|
||||
var github Emojis
|
||||
var githubOnce sync.Once
|
||||
|
||||
func Github(opts ...EmojisOption) Emojis {
|
||||
githubOnce.Do(func() {
|
||||
github = NewEmojis(
|
||||
NewEmoji("grinning face", []int32{128512}, "grinning"),
|
||||
NewEmoji("grinning face with big eyes", []int32{128515}, "smiley"),
|
||||
NewEmoji("grinning face with smiling eyes", []int32{128516}, "smile"),
|
||||
NewEmoji("beaming face with smiling eyes", []int32{128513}, "grin"),
|
||||
NewEmoji("grinning squinting face", []int32{128518}, "laughing"),
|
||||
NewEmoji("grinning face with sweat", []int32{128517}, "sweat_smile"),
|
||||
NewEmoji("rolling on the floor laughing", []int32{129315}, "rofl"),
|
||||
NewEmoji("face with tears of joy", []int32{128514}, "joy"),
|
||||
NewEmoji("slightly smiling face", []int32{128578}, "slightly_smiling_face"),
|
||||
NewEmoji("upside-down face", []int32{128579}, "upside_down_face"),
|
||||
NewEmoji("winking face", []int32{128521}, "wink"),
|
||||
NewEmoji("smiling face with smiling eyes", []int32{128522}, "blush"),
|
||||
NewEmoji("smiling face with halo", []int32{128519}, "innocent"),
|
||||
NewEmoji("smiling face with hearts", []int32{129392}, "smiling_face_with_three_hearts"),
|
||||
NewEmoji("smiling face with heart-eyes", []int32{128525}, "heart_eyes"),
|
||||
NewEmoji("star-struck", []int32{129321}, "star_struck"),
|
||||
NewEmoji("face blowing a kiss", []int32{128536}, "kissing_heart"),
|
||||
NewEmoji("kissing face", []int32{128535}, "kissing"),
|
||||
NewEmoji("smiling face", []int32{9786, 65039}, "relaxed"),
|
||||
NewEmoji("kissing face with closed eyes", []int32{128538}, "kissing_closed_eyes"),
|
||||
NewEmoji("kissing face with smiling eyes", []int32{128537}, "kissing_smiling_eyes"),
|
||||
NewEmoji("face savoring food", []int32{128523}, "yum"),
|
||||
NewEmoji("face with tongue", []int32{128539}, "stuck_out_tongue"),
|
||||
NewEmoji("winking face with tongue", []int32{128540}, "stuck_out_tongue_winking_eye"),
|
||||
NewEmoji("zany face", []int32{129322}, "zany_face"),
|
||||
NewEmoji("squinting face with tongue", []int32{128541}, "stuck_out_tongue_closed_eyes"),
|
||||
NewEmoji("money-mouth face", []int32{129297}, "money_mouth_face"),
|
||||
NewEmoji("hugging face", []int32{129303}, "hugs"),
|
||||
NewEmoji("face with hand over mouth", []int32{129325}, "hand_over_mouth"),
|
||||
NewEmoji("shushing face", []int32{129323}, "shushing_face"),
|
||||
NewEmoji("thinking face", []int32{129300}, "thinking"),
|
||||
NewEmoji("zipper-mouth face", []int32{129296}, "zipper_mouth_face"),
|
||||
NewEmoji("face with raised eyebrow", []int32{129320}, "raised_eyebrow"),
|
||||
NewEmoji("neutral face", []int32{128528}, "neutral_face"),
|
||||
NewEmoji("expressionless face", []int32{128529}, "expressionless"),
|
||||
NewEmoji("face without mouth", []int32{128566}, "no_mouth"),
|
||||
NewEmoji("smirking face", []int32{128527}, "smirk"),
|
||||
NewEmoji("unamused face", []int32{128530}, "unamused"),
|
||||
NewEmoji("face with rolling eyes", []int32{128580}, "roll_eyes"),
|
||||
NewEmoji("grimacing face", []int32{128556}, "grimacing"),
|
||||
NewEmoji("lying face", []int32{129317}, "lying_face"),
|
||||
NewEmoji("relieved face", []int32{128524}, "relieved"),
|
||||
NewEmoji("pensive face", []int32{128532}, "pensive"),
|
||||
NewEmoji("sleepy face", []int32{128554}, "sleepy"),
|
||||
NewEmoji("drooling face", []int32{129316}, "drooling_face"),
|
||||
NewEmoji("sleeping face", []int32{128564}, "sleeping"),
|
||||
NewEmoji("face with medical mask", []int32{128567}, "mask"),
|
||||
NewEmoji("face with thermometer", []int32{129298}, "face_with_thermometer"),
|
||||
NewEmoji("face with head-bandage", []int32{129301}, "face_with_head_bandage"),
|
||||
NewEmoji("nauseated face", []int32{129314}, "nauseated_face"),
|
||||
NewEmoji("face vomiting", []int32{129326}, "vomiting_face"),
|
||||
NewEmoji("sneezing face", []int32{129319}, "sneezing_face"),
|
||||
NewEmoji("hot face", []int32{129397}, "hot_face"),
|
||||
NewEmoji("cold face", []int32{129398}, "cold_face"),
|
||||
NewEmoji("woozy face", []int32{129396}, "woozy_face"),
|
||||
NewEmoji("dizzy face", []int32{128565}, "dizzy_face"),
|
||||
NewEmoji("exploding head", []int32{129327}, "exploding_head"),
|
||||
NewEmoji("cowboy hat face", []int32{129312}, "cowboy_hat_face"),
|
||||
NewEmoji("partying face", []int32{129395}, "partying_face"),
|
||||
NewEmoji("smiling face with sunglasses", []int32{128526}, "sunglasses"),
|
||||
NewEmoji("nerd face", []int32{129299}, "nerd_face"),
|
||||
NewEmoji("face with monocle", []int32{129488}, "monocle_face"),
|
||||
NewEmoji("confused face", []int32{128533}, "confused"),
|
||||
NewEmoji("worried face", []int32{128543}, "worried"),
|
||||
NewEmoji("slightly frowning face", []int32{128577}, "slightly_frowning_face"),
|
||||
NewEmoji("frowning face", []int32{9785, 65039}, "frowning_face"),
|
||||
NewEmoji("face with open mouth", []int32{128558}, "open_mouth"),
|
||||
NewEmoji("hushed face", []int32{128559}, "hushed"),
|
||||
NewEmoji("astonished face", []int32{128562}, "astonished"),
|
||||
NewEmoji("flushed face", []int32{128563}, "flushed"),
|
||||
NewEmoji("pleading face", []int32{129402}, "pleading_face"),
|
||||
NewEmoji("frowning face with open mouth", []int32{128550}, "frowning"),
|
||||
NewEmoji("anguished face", []int32{128551}, "anguished"),
|
||||
NewEmoji("fearful face", []int32{128552}, "fearful"),
|
||||
NewEmoji("anxious face with sweat", []int32{128560}, "cold_sweat"),
|
||||
NewEmoji("sad but relieved face", []int32{128549}, "disappointed_relieved"),
|
||||
NewEmoji("crying face", []int32{128546}, "cry"),
|
||||
NewEmoji("loudly crying face", []int32{128557}, "sob"),
|
||||
NewEmoji("face screaming in fear", []int32{128561}, "scream"),
|
||||
NewEmoji("confounded face", []int32{128534}, "confounded"),
|
||||
NewEmoji("persevering face", []int32{128547}, "persevere"),
|
||||
NewEmoji("disappointed face", []int32{128542}, "disappointed"),
|
||||
NewEmoji("downcast face with sweat", []int32{128531}, "sweat"),
|
||||
NewEmoji("weary face", []int32{128553}, "weary"),
|
||||
NewEmoji("tired face", []int32{128555}, "tired_face"),
|
||||
NewEmoji("yawning face", []int32{129393}, "yawning_face"),
|
||||
NewEmoji("face with steam from nose", []int32{128548}, "triumph"),
|
||||
NewEmoji("pouting face", []int32{128545}, "rage"),
|
||||
NewEmoji("angry face", []int32{128544}, "angry"),
|
||||
NewEmoji("face with symbols on mouth", []int32{129324}, "cursing_face"),
|
||||
NewEmoji("smiling face with horns", []int32{128520}, "smiling_imp"),
|
||||
NewEmoji("angry face with horns", []int32{128127}, "imp"),
|
||||
NewEmoji("skull", []int32{128128}, "skull"),
|
||||
NewEmoji("skull and crossbones", []int32{9760, 65039}, "skull_and_crossbones"),
|
||||
NewEmoji("pile of poo", []int32{128169}, "hankey"),
|
||||
NewEmoji("clown face", []int32{129313}, "clown_face"),
|
||||
NewEmoji("ogre", []int32{128121}, "japanese_ogre"),
|
||||
NewEmoji("goblin", []int32{128122}, "japanese_goblin"),
|
||||
NewEmoji("ghost", []int32{128123}, "ghost"),
|
||||
NewEmoji("alien", []int32{128125}, "alien"),
|
||||
NewEmoji("alien monster", []int32{128126}, "space_invader"),
|
||||
NewEmoji("robot", []int32{129302}, "robot"),
|
||||
NewEmoji("grinning cat", []int32{128570}, "smiley_cat"),
|
||||
NewEmoji("grinning cat with smiling eyes", []int32{128568}, "smile_cat"),
|
||||
NewEmoji("cat with tears of joy", []int32{128569}, "joy_cat"),
|
||||
NewEmoji("smiling cat with heart-eyes", []int32{128571}, "heart_eyes_cat"),
|
||||
NewEmoji("cat with wry smile", []int32{128572}, "smirk_cat"),
|
||||
NewEmoji("kissing cat", []int32{128573}, "kissing_cat"),
|
||||
NewEmoji("weary cat", []int32{128576}, "scream_cat"),
|
||||
NewEmoji("crying cat", []int32{128575}, "crying_cat_face"),
|
||||
NewEmoji("pouting cat", []int32{128574}, "pouting_cat"),
|
||||
NewEmoji("see-no-evil monkey", []int32{128584}, "see_no_evil"),
|
||||
NewEmoji("hear-no-evil monkey", []int32{128585}, "hear_no_evil"),
|
||||
NewEmoji("speak-no-evil monkey", []int32{128586}, "speak_no_evil"),
|
||||
NewEmoji("kiss mark", []int32{128139}, "kiss"),
|
||||
NewEmoji("love letter", []int32{128140}, "love_letter"),
|
||||
NewEmoji("heart with arrow", []int32{128152}, "cupid"),
|
||||
NewEmoji("heart with ribbon", []int32{128157}, "gift_heart"),
|
||||
NewEmoji("sparkling heart", []int32{128150}, "sparkling_heart"),
|
||||
NewEmoji("growing heart", []int32{128151}, "heartpulse"),
|
||||
NewEmoji("beating heart", []int32{128147}, "heartbeat"),
|
||||
NewEmoji("revolving hearts", []int32{128158}, "revolving_hearts"),
|
||||
NewEmoji("two hearts", []int32{128149}, "two_hearts"),
|
||||
NewEmoji("heart decoration", []int32{128159}, "heart_decoration"),
|
||||
NewEmoji("heart exclamation", []int32{10083, 65039}, "heavy_heart_exclamation"),
|
||||
NewEmoji("broken heart", []int32{128148}, "broken_heart"),
|
||||
NewEmoji("red heart", []int32{10084, 65039}, "heart"),
|
||||
NewEmoji("orange heart", []int32{129505}, "orange_heart"),
|
||||
NewEmoji("yellow heart", []int32{128155}, "yellow_heart"),
|
||||
NewEmoji("green heart", []int32{128154}, "green_heart"),
|
||||
NewEmoji("blue heart", []int32{128153}, "blue_heart"),
|
||||
NewEmoji("purple heart", []int32{128156}, "purple_heart"),
|
||||
NewEmoji("brown heart", []int32{129294}, "brown_heart"),
|
||||
NewEmoji("black heart", []int32{128420}, "black_heart"),
|
||||
NewEmoji("white heart", []int32{129293}, "white_heart"),
|
||||
NewEmoji("hundred points", []int32{128175}, "100"),
|
||||
NewEmoji("anger symbol", []int32{128162}, "anger"),
|
||||
NewEmoji("collision", []int32{128165}, "boom"),
|
||||
NewEmoji("dizzy", []int32{128171}, "dizzy"),
|
||||
NewEmoji("sweat droplets", []int32{128166}, "sweat_drops"),
|
||||
NewEmoji("dashing away", []int32{128168}, "dash"),
|
||||
NewEmoji("hole", []int32{128371, 65039}, "hole"),
|
||||
NewEmoji("bomb", []int32{128163}, "bomb"),
|
||||
NewEmoji("speech balloon", []int32{128172}, "speech_balloon"),
|
||||
NewEmoji("eye in speech bubble", []int32{128065, 65039, 8205, 128488, 65039}, "eye_speech_bubble"),
|
||||
NewEmoji("left speech bubble", []int32{128488, 65039}, "left_speech_bubble"),
|
||||
NewEmoji("right anger bubble", []int32{128495, 65039}, "right_anger_bubble"),
|
||||
NewEmoji("thought balloon", []int32{128173}, "thought_balloon"),
|
||||
NewEmoji("zzz", []int32{128164}, "zzz"),
|
||||
NewEmoji("waving hand", []int32{128075}, "wave"),
|
||||
NewEmoji("raised back of hand", []int32{129306}, "raised_back_of_hand"),
|
||||
NewEmoji("hand with fingers splayed", []int32{128400, 65039}, "raised_hand_with_fingers_splayed"),
|
||||
NewEmoji("raised hand", []int32{9995}, "hand"),
|
||||
NewEmoji("vulcan salute", []int32{128406}, "vulcan_salute"),
|
||||
NewEmoji("OK hand", []int32{128076}, "ok_hand"),
|
||||
NewEmoji("pinching hand", []int32{129295}, "pinching_hand"),
|
||||
NewEmoji("victory hand", []int32{9996, 65039}, "v"),
|
||||
NewEmoji("crossed fingers", []int32{129310}, "crossed_fingers"),
|
||||
NewEmoji("love-you gesture", []int32{129311}, "love_you_gesture"),
|
||||
NewEmoji("sign of the horns", []int32{129304}, "metal"),
|
||||
NewEmoji("call me hand", []int32{129305}, "call_me_hand"),
|
||||
NewEmoji("backhand index pointing left", []int32{128072}, "point_left"),
|
||||
NewEmoji("backhand index pointing right", []int32{128073}, "point_right"),
|
||||
NewEmoji("backhand index pointing up", []int32{128070}, "point_up_2"),
|
||||
NewEmoji("middle finger", []int32{128405}, "middle_finger"),
|
||||
NewEmoji("backhand index pointing down", []int32{128071}, "point_down"),
|
||||
NewEmoji("index pointing up", []int32{9757, 65039}, "point_up"),
|
||||
NewEmoji("thumbs up", []int32{128077}, "+1"),
|
||||
NewEmoji("thumbs down", []int32{128078}, "-1"),
|
||||
NewEmoji("raised fist", []int32{9994}, "fist_raised"),
|
||||
NewEmoji("oncoming fist", []int32{128074}, "fist_oncoming"),
|
||||
NewEmoji("left-facing fist", []int32{129307}, "fist_left"),
|
||||
NewEmoji("right-facing fist", []int32{129308}, "fist_right"),
|
||||
NewEmoji("clapping hands", []int32{128079}, "clap"),
|
||||
NewEmoji("raising hands", []int32{128588}, "raised_hands"),
|
||||
NewEmoji("open hands", []int32{128080}, "open_hands"),
|
||||
NewEmoji("palms up together", []int32{129330}, "palms_up_together"),
|
||||
NewEmoji("handshake", []int32{129309}, "handshake"),
|
||||
NewEmoji("folded hands", []int32{128591}, "pray"),
|
||||
NewEmoji("writing hand", []int32{9997, 65039}, "writing_hand"),
|
||||
NewEmoji("nail polish", []int32{128133}, "nail_care"),
|
||||
NewEmoji("selfie", []int32{129331}, "selfie"),
|
||||
NewEmoji("flexed biceps", []int32{128170}, "muscle"),
|
||||
NewEmoji("mechanical arm", []int32{129470}, "mechanical_arm"),
|
||||
NewEmoji("mechanical leg", []int32{129471}, "mechanical_leg"),
|
||||
NewEmoji("leg", []int32{129461}, "leg"),
|
||||
NewEmoji("foot", []int32{129462}, "foot"),
|
||||
NewEmoji("ear", []int32{128066}, "ear"),
|
||||
NewEmoji("ear with hearing aid", []int32{129467}, "ear_with_hearing_aid"),
|
||||
NewEmoji("nose", []int32{128067}, "nose"),
|
||||
NewEmoji("brain", []int32{129504}, "brain"),
|
||||
NewEmoji("tooth", []int32{129463}, "tooth"),
|
||||
NewEmoji("bone", []int32{129460}, "bone"),
|
||||
NewEmoji("eyes", []int32{128064}, "eyes"),
|
||||
NewEmoji("eye", []int32{128065, 65039}, "eye"),
|
||||
NewEmoji("tongue", []int32{128069}, "tongue"),
|
||||
NewEmoji("mouth", []int32{128068}, "lips"),
|
||||
NewEmoji("baby", []int32{128118}, "baby"),
|
||||
NewEmoji("child", []int32{129490}, "child"),
|
||||
NewEmoji("boy", []int32{128102}, "boy"),
|
||||
NewEmoji("girl", []int32{128103}, "girl"),
|
||||
NewEmoji("person", []int32{129489}, "adult"),
|
||||
NewEmoji("person: blond hair", []int32{128113}, "blond_haired_person"),
|
||||
NewEmoji("man", []int32{128104}, "man"),
|
||||
NewEmoji("man: beard", []int32{129492}, "bearded_person"),
|
||||
NewEmoji("man: red hair", []int32{128104, 8205, 129456}, "red_haired_man"),
|
||||
NewEmoji("man: curly hair", []int32{128104, 8205, 129457}, "curly_haired_man"),
|
||||
NewEmoji("man: white hair", []int32{128104, 8205, 129459}, "white_haired_man"),
|
||||
NewEmoji("man: bald", []int32{128104, 8205, 129458}, "bald_man"),
|
||||
NewEmoji("woman", []int32{128105}, "woman"),
|
||||
NewEmoji("woman: red hair", []int32{128105, 8205, 129456}, "red_haired_woman"),
|
||||
NewEmoji("person: red hair", []int32{129489, 8205, 129456}, "person_red_hair"),
|
||||
NewEmoji("woman: curly hair", []int32{128105, 8205, 129457}, "curly_haired_woman"),
|
||||
NewEmoji("person: curly hair", []int32{129489, 8205, 129457}, "person_curly_hair"),
|
||||
NewEmoji("woman: white hair", []int32{128105, 8205, 129459}, "white_haired_woman"),
|
||||
NewEmoji("person: white hair", []int32{129489, 8205, 129459}, "person_white_hair"),
|
||||
NewEmoji("woman: bald", []int32{128105, 8205, 129458}, "bald_woman"),
|
||||
NewEmoji("person: bald", []int32{129489, 8205, 129458}, "person_bald"),
|
||||
NewEmoji("woman: blond hair", []int32{128113, 8205, 9792, 65039}, "blond_haired_woman"),
|
||||
NewEmoji("man: blond hair", []int32{128113, 8205, 9794, 65039}, "blond_haired_man"),
|
||||
NewEmoji("older person", []int32{129491}, "older_adult"),
|
||||
NewEmoji("old man", []int32{128116}, "older_man"),
|
||||
NewEmoji("old woman", []int32{128117}, "older_woman"),
|
||||
NewEmoji("person frowning", []int32{128589}, "frowning_person"),
|
||||
NewEmoji("man frowning", []int32{128589, 8205, 9794, 65039}, "frowning_man"),
|
||||
NewEmoji("woman frowning", []int32{128589, 8205, 9792, 65039}, "frowning_woman"),
|
||||
NewEmoji("person pouting", []int32{128590}, "pouting_face"),
|
||||
NewEmoji("man pouting", []int32{128590, 8205, 9794, 65039}, "pouting_man"),
|
||||
NewEmoji("woman pouting", []int32{128590, 8205, 9792, 65039}, "pouting_woman"),
|
||||
NewEmoji("person gesturing NO", []int32{128581}, "no_good"),
|
||||
NewEmoji("man gesturing NO", []int32{128581, 8205, 9794, 65039}, "no_good_man"),
|
||||
NewEmoji("woman gesturing NO", []int32{128581, 8205, 9792, 65039}, "no_good_woman"),
|
||||
NewEmoji("person gesturing OK", []int32{128582}, "ok_person"),
|
||||
NewEmoji("man gesturing OK", []int32{128582, 8205, 9794, 65039}, "ok_man"),
|
||||
NewEmoji("woman gesturing OK", []int32{128582, 8205, 9792, 65039}, "ok_woman"),
|
||||
NewEmoji("person tipping hand", []int32{128129}, "tipping_hand_person"),
|
||||
NewEmoji("man tipping hand", []int32{128129, 8205, 9794, 65039}, "tipping_hand_man"),
|
||||
NewEmoji("woman tipping hand", []int32{128129, 8205, 9792, 65039}, "tipping_hand_woman"),
|
||||
NewEmoji("person raising hand", []int32{128587}, "raising_hand"),
|
||||
NewEmoji("man raising hand", []int32{128587, 8205, 9794, 65039}, "raising_hand_man"),
|
||||
NewEmoji("woman raising hand", []int32{128587, 8205, 9792, 65039}, "raising_hand_woman"),
|
||||
NewEmoji("deaf person", []int32{129487}, "deaf_person"),
|
||||
NewEmoji("deaf man", []int32{129487, 8205, 9794, 65039}, "deaf_man"),
|
||||
NewEmoji("deaf woman", []int32{129487, 8205, 9792, 65039}, "deaf_woman"),
|
||||
NewEmoji("person bowing", []int32{128583}, "bow"),
|
||||
NewEmoji("man bowing", []int32{128583, 8205, 9794, 65039}, "bowing_man"),
|
||||
NewEmoji("woman bowing", []int32{128583, 8205, 9792, 65039}, "bowing_woman"),
|
||||
NewEmoji("person facepalming", []int32{129318}, "facepalm"),
|
||||
NewEmoji("man facepalming", []int32{129318, 8205, 9794, 65039}, "man_facepalming"),
|
||||
NewEmoji("woman facepalming", []int32{129318, 8205, 9792, 65039}, "woman_facepalming"),
|
||||
NewEmoji("person shrugging", []int32{129335}, "shrug"),
|
||||
NewEmoji("man shrugging", []int32{129335, 8205, 9794, 65039}, "man_shrugging"),
|
||||
NewEmoji("woman shrugging", []int32{129335, 8205, 9792, 65039}, "woman_shrugging"),
|
||||
NewEmoji("health worker", []int32{129489, 8205, 9877, 65039}, "health_worker"),
|
||||
NewEmoji("man health worker", []int32{128104, 8205, 9877, 65039}, "man_health_worker"),
|
||||
NewEmoji("woman health worker", []int32{128105, 8205, 9877, 65039}, "woman_health_worker"),
|
||||
NewEmoji("student", []int32{129489, 8205, 127891}, "student"),
|
||||
NewEmoji("man student", []int32{128104, 8205, 127891}, "man_student"),
|
||||
NewEmoji("woman student", []int32{128105, 8205, 127891}, "woman_student"),
|
||||
NewEmoji("teacher", []int32{129489, 8205, 127979}, "teacher"),
|
||||
NewEmoji("man teacher", []int32{128104, 8205, 127979}, "man_teacher"),
|
||||
NewEmoji("woman teacher", []int32{128105, 8205, 127979}, "woman_teacher"),
|
||||
NewEmoji("judge", []int32{129489, 8205, 9878, 65039}, "judge"),
|
||||
NewEmoji("man judge", []int32{128104, 8205, 9878, 65039}, "man_judge"),
|
||||
NewEmoji("woman judge", []int32{128105, 8205, 9878, 65039}, "woman_judge"),
|
||||
NewEmoji("farmer", []int32{129489, 8205, 127806}, "farmer"),
|
||||
NewEmoji("man farmer", []int32{128104, 8205, 127806}, "man_farmer"),
|
||||
NewEmoji("woman farmer", []int32{128105, 8205, 127806}, "woman_farmer"),
|
||||
NewEmoji("cook", []int32{129489, 8205, 127859}, "cook"),
|
||||
NewEmoji("man cook", []int32{128104, 8205, 127859}, "man_cook"),
|
||||
NewEmoji("woman cook", []int32{128105, 8205, 127859}, "woman_cook"),
|
||||
NewEmoji("mechanic", []int32{129489, 8205, 128295}, "mechanic"),
|
||||
NewEmoji("man mechanic", []int32{128104, 8205, 128295}, "man_mechanic"),
|
||||
NewEmoji("woman mechanic", []int32{128105, 8205, 128295}, "woman_mechanic"),
|
||||
NewEmoji("factory worker", []int32{129489, 8205, 127981}, "factory_worker"),
|
||||
NewEmoji("man factory worker", []int32{128104, 8205, 127981}, "man_factory_worker"),
|
||||
NewEmoji("woman factory worker", []int32{128105, 8205, 127981}, "woman_factory_worker"),
|
||||
NewEmoji("office worker", []int32{129489, 8205, 128188}, "office_worker"),
|
||||
NewEmoji("man office worker", []int32{128104, 8205, 128188}, "man_office_worker"),
|
||||
NewEmoji("woman office worker", []int32{128105, 8205, 128188}, "woman_office_worker"),
|
||||
NewEmoji("scientist", []int32{129489, 8205, 128300}, "scientist"),
|
||||
NewEmoji("man scientist", []int32{128104, 8205, 128300}, "man_scientist"),
|
||||
NewEmoji("woman scientist", []int32{128105, 8205, 128300}, "woman_scientist"),
|
||||
NewEmoji("technologist", []int32{129489, 8205, 128187}, "technologist"),
|
||||
NewEmoji("man technologist", []int32{128104, 8205, 128187}, "man_technologist"),
|
||||
NewEmoji("woman technologist", []int32{128105, 8205, 128187}, "woman_technologist"),
|
||||
NewEmoji("singer", []int32{129489, 8205, 127908}, "singer"),
|
||||
NewEmoji("man singer", []int32{128104, 8205, 127908}, "man_singer"),
|
||||
NewEmoji("woman singer", []int32{128105, 8205, 127908}, "woman_singer"),
|
||||
NewEmoji("artist", []int32{129489, 8205, 127912}, "artist"),
|
||||
NewEmoji("man artist", []int32{128104, 8205, 127912}, "man_artist"),
|
||||
NewEmoji("woman artist", []int32{128105, 8205, 127912}, "woman_artist"),
|
||||
NewEmoji("pilot", []int32{129489, 8205, 9992, 65039}, "pilot"),
|
||||
NewEmoji("man pilot", []int32{128104, 8205, 9992, 65039}, "man_pilot"),
|
||||
NewEmoji("woman pilot", []int32{128105, 8205, 9992, 65039}, "woman_pilot"),
|
||||
NewEmoji("astronaut", []int32{129489, 8205, 128640}, "astronaut"),
|
||||
NewEmoji("man astronaut", []int32{128104, 8205, 128640}, "man_astronaut"),
|
||||
NewEmoji("woman astronaut", []int32{128105, 8205, 128640}, "woman_astronaut"),
|
||||
NewEmoji("firefighter", []int32{129489, 8205, 128658}, "firefighter"),
|
||||
NewEmoji("man firefighter", []int32{128104, 8205, 128658}, "man_firefighter"),
|
||||
NewEmoji("woman firefighter", []int32{128105, 8205, 128658}, "woman_firefighter"),
|
||||
NewEmoji("police officer", []int32{128110}, "police_officer"),
|
||||
NewEmoji("man police officer", []int32{128110, 8205, 9794, 65039}, "policeman"),
|
||||
NewEmoji("woman police officer", []int32{128110, 8205, 9792, 65039}, "policewoman"),
|
||||
NewEmoji("detective", []int32{128373, 65039}, "detective"),
|
||||
NewEmoji("man detective", []int32{128373, 65039, 8205, 9794, 65039}, "male_detective"),
|
||||
NewEmoji("woman detective", []int32{128373, 65039, 8205, 9792, 65039}, "female_detective"),
|
||||
NewEmoji("guard", []int32{128130}, "guard"),
|
||||
NewEmoji("man guard", []int32{128130, 8205, 9794, 65039}, "guardsman"),
|
||||
NewEmoji("woman guard", []int32{128130, 8205, 9792, 65039}, "guardswoman"),
|
||||
NewEmoji("construction worker", []int32{128119}, "construction_worker"),
|
||||
NewEmoji("man construction worker", []int32{128119, 8205, 9794, 65039}, "construction_worker_man"),
|
||||
NewEmoji("woman construction worker", []int32{128119, 8205, 9792, 65039}, "construction_worker_woman"),
|
||||
NewEmoji("prince", []int32{129332}, "prince"),
|
||||
NewEmoji("princess", []int32{128120}, "princess"),
|
||||
NewEmoji("person wearing turban", []int32{128115}, "person_with_turban"),
|
||||
NewEmoji("man wearing turban", []int32{128115, 8205, 9794, 65039}, "man_with_turban"),
|
||||
NewEmoji("woman wearing turban", []int32{128115, 8205, 9792, 65039}, "woman_with_turban"),
|
||||
NewEmoji("person with skullcap", []int32{128114}, "man_with_gua_pi_mao"),
|
||||
NewEmoji("woman with headscarf", []int32{129493}, "woman_with_headscarf"),
|
||||
NewEmoji("man in tuxedo", []int32{129333, 8205, 9794, 65039}, "man_in_tuxedo"),
|
||||
NewEmoji("woman with veil", []int32{128112, 8205, 9792, 65039}, "bride_with_veil"),
|
||||
NewEmoji("pregnant woman", []int32{129328}, "pregnant_woman"),
|
||||
NewEmoji("breast-feeding", []int32{129329}, "breast_feeding"),
|
||||
NewEmoji("baby angel", []int32{128124}, "angel"),
|
||||
NewEmoji("Santa Claus", []int32{127877}, "santa"),
|
||||
NewEmoji("Mrs. Claus", []int32{129334}, "mrs_claus"),
|
||||
NewEmoji("superhero", []int32{129464}, "superhero"),
|
||||
NewEmoji("man superhero", []int32{129464, 8205, 9794, 65039}, "superhero_man"),
|
||||
NewEmoji("woman superhero", []int32{129464, 8205, 9792, 65039}, "superhero_woman"),
|
||||
NewEmoji("supervillain", []int32{129465}, "supervillain"),
|
||||
NewEmoji("man supervillain", []int32{129465, 8205, 9794, 65039}, "supervillain_man"),
|
||||
NewEmoji("woman supervillain", []int32{129465, 8205, 9792, 65039}, "supervillain_woman"),
|
||||
NewEmoji("mage", []int32{129497}, "mage"),
|
||||
NewEmoji("man mage", []int32{129497, 8205, 9794, 65039}, "mage_man"),
|
||||
NewEmoji("woman mage", []int32{129497, 8205, 9792, 65039}, "mage_woman"),
|
||||
NewEmoji("fairy", []int32{129498}, "fairy"),
|
||||
NewEmoji("man fairy", []int32{129498, 8205, 9794, 65039}, "fairy_man"),
|
||||
NewEmoji("woman fairy", []int32{129498, 8205, 9792, 65039}, "fairy_woman"),
|
||||
NewEmoji("vampire", []int32{129499}, "vampire"),
|
||||
NewEmoji("man vampire", []int32{129499, 8205, 9794, 65039}, "vampire_man"),
|
||||
NewEmoji("woman vampire", []int32{129499, 8205, 9792, 65039}, "vampire_woman"),
|
||||
NewEmoji("merperson", []int32{129500}, "merperson"),
|
||||
NewEmoji("merman", []int32{129500, 8205, 9794, 65039}, "merman"),
|
||||
NewEmoji("mermaid", []int32{129500, 8205, 9792, 65039}, "mermaid"),
|
||||
NewEmoji("elf", []int32{129501}, "elf"),
|
||||
NewEmoji("man elf", []int32{129501, 8205, 9794, 65039}, "elf_man"),
|
||||
NewEmoji("woman elf", []int32{129501, 8205, 9792, 65039}, "elf_woman"),
|
||||
NewEmoji("genie", []int32{129502}, "genie"),
|
||||
NewEmoji("man genie", []int32{129502, 8205, 9794, 65039}, "genie_man"),
|
||||
NewEmoji("woman genie", []int32{129502, 8205, 9792, 65039}, "genie_woman"),
|
||||
NewEmoji("zombie", []int32{129503}, "zombie"),
|
||||
NewEmoji("man zombie", []int32{129503, 8205, 9794, 65039}, "zombie_man"),
|
||||
NewEmoji("woman zombie", []int32{129503, 8205, 9792, 65039}, "zombie_woman"),
|
||||
NewEmoji("person getting massage", []int32{128134}, "massage"),
|
||||
NewEmoji("man getting massage", []int32{128134, 8205, 9794, 65039}, "massage_man"),
|
||||
NewEmoji("woman getting massage", []int32{128134, 8205, 9792, 65039}, "massage_woman"),
|
||||
NewEmoji("person getting haircut", []int32{128135}, "haircut"),
|
||||
NewEmoji("man getting haircut", []int32{128135, 8205, 9794, 65039}, "haircut_man"),
|
||||
NewEmoji("woman getting haircut", []int32{128135, 8205, 9792, 65039}, "haircut_woman"),
|
||||
NewEmoji("person walking", []int32{128694}, "walking"),
|
||||
NewEmoji("man walking", []int32{128694, 8205, 9794, 65039}, "walking_man"),
|
||||
NewEmoji("woman walking", []int32{128694, 8205, 9792, 65039}, "walking_woman"),
|
||||
NewEmoji("person standing", []int32{129485}, "standing_person"),
|
||||
NewEmoji("man standing", []int32{129485, 8205, 9794, 65039}, "standing_man"),
|
||||
NewEmoji("woman standing", []int32{129485, 8205, 9792, 65039}, "standing_woman"),
|
||||
NewEmoji("person kneeling", []int32{129486}, "kneeling_person"),
|
||||
NewEmoji("man kneeling", []int32{129486, 8205, 9794, 65039}, "kneeling_man"),
|
||||
NewEmoji("woman kneeling", []int32{129486, 8205, 9792, 65039}, "kneeling_woman"),
|
||||
NewEmoji("person with white cane", []int32{129489, 8205, 129455}, "person_with_probing_cane"),
|
||||
NewEmoji("man with white cane", []int32{128104, 8205, 129455}, "man_with_probing_cane"),
|
||||
NewEmoji("woman with white cane", []int32{128105, 8205, 129455}, "woman_with_probing_cane"),
|
||||
NewEmoji("person in motorized wheelchair", []int32{129489, 8205, 129468}, "person_in_motorized_wheelchair"),
|
||||
NewEmoji("man in motorized wheelchair", []int32{128104, 8205, 129468}, "man_in_motorized_wheelchair"),
|
||||
NewEmoji("woman in motorized wheelchair", []int32{128105, 8205, 129468}, "woman_in_motorized_wheelchair"),
|
||||
NewEmoji("person in manual wheelchair", []int32{129489, 8205, 129469}, "person_in_manual_wheelchair"),
|
||||
NewEmoji("man in manual wheelchair", []int32{128104, 8205, 129469}, "man_in_manual_wheelchair"),
|
||||
NewEmoji("woman in manual wheelchair", []int32{128105, 8205, 129469}, "woman_in_manual_wheelchair"),
|
||||
NewEmoji("person running", []int32{127939}, "runner"),
|
||||
NewEmoji("man running", []int32{127939, 8205, 9794, 65039}, "running_man"),
|
||||
NewEmoji("woman running", []int32{127939, 8205, 9792, 65039}, "running_woman"),
|
||||
NewEmoji("woman dancing", []int32{128131}, "woman_dancing"),
|
||||
NewEmoji("man dancing", []int32{128378}, "man_dancing"),
|
||||
NewEmoji("person in suit levitating", []int32{128372, 65039}, "business_suit_levitating"),
|
||||
NewEmoji("people with bunny ears", []int32{128111}, "dancers"),
|
||||
NewEmoji("men with bunny ears", []int32{128111, 8205, 9794, 65039}, "dancing_men"),
|
||||
NewEmoji("women with bunny ears", []int32{128111, 8205, 9792, 65039}, "dancing_women"),
|
||||
NewEmoji("person in steamy room", []int32{129494}, "sauna_person"),
|
||||
NewEmoji("man in steamy room", []int32{129494, 8205, 9794, 65039}, "sauna_man"),
|
||||
NewEmoji("woman in steamy room", []int32{129494, 8205, 9792, 65039}, "sauna_woman"),
|
||||
NewEmoji("person climbing", []int32{129495}, "climbing"),
|
||||
NewEmoji("man climbing", []int32{129495, 8205, 9794, 65039}, "climbing_man"),
|
||||
NewEmoji("woman climbing", []int32{129495, 8205, 9792, 65039}, "climbing_woman"),
|
||||
NewEmoji("person fencing", []int32{129338}, "person_fencing"),
|
||||
NewEmoji("horse racing", []int32{127943}, "horse_racing"),
|
||||
NewEmoji("skier", []int32{9975, 65039}, "skier"),
|
||||
NewEmoji("snowboarder", []int32{127938}, "snowboarder"),
|
||||
NewEmoji("person golfing", []int32{127948, 65039}, "golfing"),
|
||||
NewEmoji("man golfing", []int32{127948, 65039, 8205, 9794, 65039}, "golfing_man"),
|
||||
NewEmoji("woman golfing", []int32{127948, 65039, 8205, 9792, 65039}, "golfing_woman"),
|
||||
NewEmoji("person surfing", []int32{127940}, "surfer"),
|
||||
NewEmoji("man surfing", []int32{127940, 8205, 9794, 65039}, "surfing_man"),
|
||||
NewEmoji("woman surfing", []int32{127940, 8205, 9792, 65039}, "surfing_woman"),
|
||||
NewEmoji("person rowing boat", []int32{128675}, "rowboat"),
|
||||
NewEmoji("man rowing boat", []int32{128675, 8205, 9794, 65039}, "rowing_man"),
|
||||
NewEmoji("woman rowing boat", []int32{128675, 8205, 9792, 65039}, "rowing_woman"),
|
||||
NewEmoji("person swimming", []int32{127946}, "swimmer"),
|
||||
NewEmoji("man swimming", []int32{127946, 8205, 9794, 65039}, "swimming_man"),
|
||||
NewEmoji("woman swimming", []int32{127946, 8205, 9792, 65039}, "swimming_woman"),
|
||||
NewEmoji("person bouncing ball", []int32{9977, 65039}, "bouncing_ball_person"),
|
||||
NewEmoji("man bouncing ball", []int32{9977, 65039, 8205, 9794, 65039}, "bouncing_ball_man"),
|
||||
NewEmoji("woman bouncing ball", []int32{9977, 65039, 8205, 9792, 65039}, "bouncing_ball_woman"),
|
||||
NewEmoji("person lifting weights", []int32{127947, 65039}, "weight_lifting"),
|
||||
NewEmoji("man lifting weights", []int32{127947, 65039, 8205, 9794, 65039}, "weight_lifting_man"),
|
||||
NewEmoji("woman lifting weights", []int32{127947, 65039, 8205, 9792, 65039}, "weight_lifting_woman"),
|
||||
NewEmoji("person biking", []int32{128692}, "bicyclist"),
|
||||
NewEmoji("man biking", []int32{128692, 8205, 9794, 65039}, "biking_man"),
|
||||
NewEmoji("woman biking", []int32{128692, 8205, 9792, 65039}, "biking_woman"),
|
||||
NewEmoji("person mountain biking", []int32{128693}, "mountain_bicyclist"),
|
||||
NewEmoji("man mountain biking", []int32{128693, 8205, 9794, 65039}, "mountain_biking_man"),
|
||||
NewEmoji("woman mountain biking", []int32{128693, 8205, 9792, 65039}, "mountain_biking_woman"),
|
||||
NewEmoji("person cartwheeling", []int32{129336}, "cartwheeling"),
|
||||
NewEmoji("man cartwheeling", []int32{129336, 8205, 9794, 65039}, "man_cartwheeling"),
|
||||
NewEmoji("woman cartwheeling", []int32{129336, 8205, 9792, 65039}, "woman_cartwheeling"),
|
||||
NewEmoji("people wrestling", []int32{129340}, "wrestling"),
|
||||
NewEmoji("men wrestling", []int32{129340, 8205, 9794, 65039}, "men_wrestling"),
|
||||
NewEmoji("women wrestling", []int32{129340, 8205, 9792, 65039}, "women_wrestling"),
|
||||
NewEmoji("person playing water polo", []int32{129341}, "water_polo"),
|
||||
NewEmoji("man playing water polo", []int32{129341, 8205, 9794, 65039}, "man_playing_water_polo"),
|
||||
NewEmoji("woman playing water polo", []int32{129341, 8205, 9792, 65039}, "woman_playing_water_polo"),
|
||||
NewEmoji("person playing handball", []int32{129342}, "handball_person"),
|
||||
NewEmoji("man playing handball", []int32{129342, 8205, 9794, 65039}, "man_playing_handball"),
|
||||
NewEmoji("woman playing handball", []int32{129342, 8205, 9792, 65039}, "woman_playing_handball"),
|
||||
NewEmoji("person juggling", []int32{129337}, "juggling_person"),
|
||||
NewEmoji("man juggling", []int32{129337, 8205, 9794, 65039}, "man_juggling"),
|
||||
NewEmoji("woman juggling", []int32{129337, 8205, 9792, 65039}, "woman_juggling"),
|
||||
NewEmoji("person in lotus position", []int32{129496}, "lotus_position"),
|
||||
NewEmoji("man in lotus position", []int32{129496, 8205, 9794, 65039}, "lotus_position_man"),
|
||||
NewEmoji("woman in lotus position", []int32{129496, 8205, 9792, 65039}, "lotus_position_woman"),
|
||||
NewEmoji("person taking bath", []int32{128704}, "bath"),
|
||||
NewEmoji("person in bed", []int32{128716}, "sleeping_bed"),
|
||||
NewEmoji("people holding hands", []int32{129489, 8205, 129309, 8205, 129489}, "people_holding_hands"),
|
||||
NewEmoji("women holding hands", []int32{128109}, "two_women_holding_hands"),
|
||||
NewEmoji("woman and man holding hands", []int32{128107}, "couple"),
|
||||
NewEmoji("men holding hands", []int32{128108}, "two_men_holding_hands"),
|
||||
NewEmoji("kiss", []int32{128143}, "couplekiss"),
|
||||
NewEmoji("kiss: woman, man", []int32{128105, 8205, 10084, 65039, 8205, 128139, 8205, 128104}, "couplekiss_man_woman"),
|
||||
NewEmoji("kiss: man, man", []int32{128104, 8205, 10084, 65039, 8205, 128139, 8205, 128104}, "couplekiss_man_man"),
|
||||
NewEmoji("kiss: woman, woman", []int32{128105, 8205, 10084, 65039, 8205, 128139, 8205, 128105}, "couplekiss_woman_woman"),
|
||||
NewEmoji("couple with heart", []int32{128145}, "couple_with_heart"),
|
||||
NewEmoji("couple with heart: woman, man", []int32{128105, 8205, 10084, 65039, 8205, 128104}, "couple_with_heart_woman_man"),
|
||||
NewEmoji("couple with heart: man, man", []int32{128104, 8205, 10084, 65039, 8205, 128104}, "couple_with_heart_man_man"),
|
||||
NewEmoji("couple with heart: woman, woman", []int32{128105, 8205, 10084, 65039, 8205, 128105}, "couple_with_heart_woman_woman"),
|
||||
NewEmoji("family", []int32{128106}, "family"),
|
||||
NewEmoji("family: man, woman, boy", []int32{128104, 8205, 128105, 8205, 128102}, "family_man_woman_boy"),
|
||||
NewEmoji("family: man, woman, girl", []int32{128104, 8205, 128105, 8205, 128103}, "family_man_woman_girl"),
|
||||
NewEmoji("family: man, woman, girl, boy", []int32{128104, 8205, 128105, 8205, 128103, 8205, 128102}, "family_man_woman_girl_boy"),
|
||||
NewEmoji("family: man, woman, boy, boy", []int32{128104, 8205, 128105, 8205, 128102, 8205, 128102}, "family_man_woman_boy_boy"),
|
||||
NewEmoji("family: man, woman, girl, girl", []int32{128104, 8205, 128105, 8205, 128103, 8205, 128103}, "family_man_woman_girl_girl"),
|
||||
NewEmoji("family: man, man, boy", []int32{128104, 8205, 128104, 8205, 128102}, "family_man_man_boy"),
|
||||
NewEmoji("family: man, man, girl", []int32{128104, 8205, 128104, 8205, 128103}, "family_man_man_girl"),
|
||||
NewEmoji("family: man, man, girl, boy", []int32{128104, 8205, 128104, 8205, 128103, 8205, 128102}, "family_man_man_girl_boy"),
|
||||
NewEmoji("family: man, man, boy, boy", []int32{128104, 8205, 128104, 8205, 128102, 8205, 128102}, "family_man_man_boy_boy"),
|
||||
NewEmoji("family: man, man, girl, girl", []int32{128104, 8205, 128104, 8205, 128103, 8205, 128103}, "family_man_man_girl_girl"),
|
||||
NewEmoji("family: woman, woman, boy", []int32{128105, 8205, 128105, 8205, 128102}, "family_woman_woman_boy"),
|
||||
NewEmoji("family: woman, woman, girl", []int32{128105, 8205, 128105, 8205, 128103}, "family_woman_woman_girl"),
|
||||
NewEmoji("family: woman, woman, girl, boy", []int32{128105, 8205, 128105, 8205, 128103, 8205, 128102}, "family_woman_woman_girl_boy"),
|
||||
NewEmoji("family: woman, woman, boy, boy", []int32{128105, 8205, 128105, 8205, 128102, 8205, 128102}, "family_woman_woman_boy_boy"),
|
||||
NewEmoji("family: woman, woman, girl, girl", []int32{128105, 8205, 128105, 8205, 128103, 8205, 128103}, "family_woman_woman_girl_girl"),
|
||||
NewEmoji("family: man, boy", []int32{128104, 8205, 128102}, "family_man_boy"),
|
||||
NewEmoji("family: man, boy, boy", []int32{128104, 8205, 128102, 8205, 128102}, "family_man_boy_boy"),
|
||||
NewEmoji("family: man, girl", []int32{128104, 8205, 128103}, "family_man_girl"),
|
||||
NewEmoji("family: man, girl, boy", []int32{128104, 8205, 128103, 8205, 128102}, "family_man_girl_boy"),
|
||||
NewEmoji("family: man, girl, girl", []int32{128104, 8205, 128103, 8205, 128103}, "family_man_girl_girl"),
|
||||
NewEmoji("family: woman, boy", []int32{128105, 8205, 128102}, "family_woman_boy"),
|
||||
NewEmoji("family: woman, boy, boy", []int32{128105, 8205, 128102, 8205, 128102}, "family_woman_boy_boy"),
|
||||
NewEmoji("family: woman, girl", []int32{128105, 8205, 128103}, "family_woman_girl"),
|
||||
NewEmoji("family: woman, girl, boy", []int32{128105, 8205, 128103, 8205, 128102}, "family_woman_girl_boy"),
|
||||
NewEmoji("family: woman, girl, girl", []int32{128105, 8205, 128103, 8205, 128103}, "family_woman_girl_girl"),
|
||||
NewEmoji("speaking head", []int32{128483, 65039}, "speaking_head"),
|
||||
NewEmoji("bust in silhouette", []int32{128100}, "bust_in_silhouette"),
|
||||
NewEmoji("busts in silhouette", []int32{128101}, "busts_in_silhouette"),
|
||||
NewEmoji("footprints", []int32{128099}, "footprints"),
|
||||
NewEmoji("monkey face", []int32{128053}, "monkey_face"),
|
||||
NewEmoji("monkey", []int32{128018}, "monkey"),
|
||||
NewEmoji("gorilla", []int32{129421}, "gorilla"),
|
||||
NewEmoji("orangutan", []int32{129447}, "orangutan"),
|
||||
NewEmoji("dog face", []int32{128054}, "dog"),
|
||||
NewEmoji("dog", []int32{128021}, "dog2"),
|
||||
NewEmoji("guide dog", []int32{129454}, "guide_dog"),
|
||||
NewEmoji("service dog", []int32{128021, 8205, 129466}, "service_dog"),
|
||||
NewEmoji("poodle", []int32{128041}, "poodle"),
|
||||
NewEmoji("wolf", []int32{128058}, "wolf"),
|
||||
NewEmoji("fox", []int32{129418}, "fox_face"),
|
||||
NewEmoji("raccoon", []int32{129437}, "raccoon"),
|
||||
NewEmoji("cat face", []int32{128049}, "cat"),
|
||||
NewEmoji("cat", []int32{128008}, "cat2"),
|
||||
NewEmoji("lion", []int32{129409}, "lion"),
|
||||
NewEmoji("tiger face", []int32{128047}, "tiger"),
|
||||
NewEmoji("tiger", []int32{128005}, "tiger2"),
|
||||
NewEmoji("leopard", []int32{128006}, "leopard"),
|
||||
NewEmoji("horse face", []int32{128052}, "horse"),
|
||||
NewEmoji("horse", []int32{128014}, "racehorse"),
|
||||
NewEmoji("unicorn", []int32{129412}, "unicorn"),
|
||||
NewEmoji("zebra", []int32{129427}, "zebra"),
|
||||
NewEmoji("deer", []int32{129420}, "deer"),
|
||||
NewEmoji("cow face", []int32{128046}, "cow"),
|
||||
NewEmoji("ox", []int32{128002}, "ox"),
|
||||
NewEmoji("water buffalo", []int32{128003}, "water_buffalo"),
|
||||
NewEmoji("cow", []int32{128004}, "cow2"),
|
||||
NewEmoji("pig face", []int32{128055}, "pig"),
|
||||
NewEmoji("pig", []int32{128022}, "pig2"),
|
||||
NewEmoji("boar", []int32{128023}, "boar"),
|
||||
NewEmoji("pig nose", []int32{128061}, "pig_nose"),
|
||||
NewEmoji("ram", []int32{128015}, "ram"),
|
||||
NewEmoji("ewe", []int32{128017}, "sheep"),
|
||||
NewEmoji("goat", []int32{128016}, "goat"),
|
||||
NewEmoji("camel", []int32{128042}, "dromedary_camel"),
|
||||
NewEmoji("two-hump camel", []int32{128043}, "camel"),
|
||||
NewEmoji("llama", []int32{129433}, "llama"),
|
||||
NewEmoji("giraffe", []int32{129426}, "giraffe"),
|
||||
NewEmoji("elephant", []int32{128024}, "elephant"),
|
||||
NewEmoji("rhinoceros", []int32{129423}, "rhinoceros"),
|
||||
NewEmoji("hippopotamus", []int32{129435}, "hippopotamus"),
|
||||
NewEmoji("mouse face", []int32{128045}, "mouse"),
|
||||
NewEmoji("mouse", []int32{128001}, "mouse2"),
|
||||
NewEmoji("rat", []int32{128000}, "rat"),
|
||||
NewEmoji("hamster", []int32{128057}, "hamster"),
|
||||
NewEmoji("rabbit face", []int32{128048}, "rabbit"),
|
||||
NewEmoji("rabbit", []int32{128007}, "rabbit2"),
|
||||
NewEmoji("chipmunk", []int32{128063, 65039}, "chipmunk"),
|
||||
NewEmoji("hedgehog", []int32{129428}, "hedgehog"),
|
||||
NewEmoji("bat", []int32{129415}, "bat"),
|
||||
NewEmoji("bear", []int32{128059}, "bear"),
|
||||
NewEmoji("koala", []int32{128040}, "koala"),
|
||||
NewEmoji("panda", []int32{128060}, "panda_face"),
|
||||
NewEmoji("sloth", []int32{129445}, "sloth"),
|
||||
NewEmoji("otter", []int32{129446}, "otter"),
|
||||
NewEmoji("skunk", []int32{129448}, "skunk"),
|
||||
NewEmoji("kangaroo", []int32{129432}, "kangaroo"),
|
||||
NewEmoji("badger", []int32{129441}, "badger"),
|
||||
NewEmoji("paw prints", []int32{128062}, "feet"),
|
||||
NewEmoji("turkey", []int32{129411}, "turkey"),
|
||||
NewEmoji("chicken", []int32{128020}, "chicken"),
|
||||
NewEmoji("rooster", []int32{128019}, "rooster"),
|
||||
NewEmoji("hatching chick", []int32{128035}, "hatching_chick"),
|
||||
NewEmoji("baby chick", []int32{128036}, "baby_chick"),
|
||||
NewEmoji("front-facing baby chick", []int32{128037}, "hatched_chick"),
|
||||
NewEmoji("bird", []int32{128038}, "bird"),
|
||||
NewEmoji("penguin", []int32{128039}, "penguin"),
|
||||
NewEmoji("dove", []int32{128330, 65039}, "dove"),
|
||||
NewEmoji("eagle", []int32{129413}, "eagle"),
|
||||
NewEmoji("duck", []int32{129414}, "duck"),
|
||||
NewEmoji("swan", []int32{129442}, "swan"),
|
||||
NewEmoji("owl", []int32{129417}, "owl"),
|
||||
NewEmoji("flamingo", []int32{129449}, "flamingo"),
|
||||
NewEmoji("peacock", []int32{129434}, "peacock"),
|
||||
NewEmoji("parrot", []int32{129436}, "parrot"),
|
||||
NewEmoji("frog", []int32{128056}, "frog"),
|
||||
NewEmoji("crocodile", []int32{128010}, "crocodile"),
|
||||
NewEmoji("turtle", []int32{128034}, "turtle"),
|
||||
NewEmoji("lizard", []int32{129422}, "lizard"),
|
||||
NewEmoji("snake", []int32{128013}, "snake"),
|
||||
NewEmoji("dragon face", []int32{128050}, "dragon_face"),
|
||||
NewEmoji("dragon", []int32{128009}, "dragon"),
|
||||
NewEmoji("sauropod", []int32{129429}, "sauropod"),
|
||||
NewEmoji("T-Rex", []int32{129430}, "t-rex"),
|
||||
NewEmoji("spouting whale", []int32{128051}, "whale"),
|
||||
NewEmoji("whale", []int32{128011}, "whale2"),
|
||||
NewEmoji("dolphin", []int32{128044}, "dolphin"),
|
||||
NewEmoji("fish", []int32{128031}, "fish"),
|
||||
NewEmoji("tropical fish", []int32{128032}, "tropical_fish"),
|
||||
NewEmoji("blowfish", []int32{128033}, "blowfish"),
|
||||
NewEmoji("shark", []int32{129416}, "shark"),
|
||||
NewEmoji("octopus", []int32{128025}, "octopus"),
|
||||
NewEmoji("spiral shell", []int32{128026}, "shell"),
|
||||
NewEmoji("snail", []int32{128012}, "snail"),
|
||||
NewEmoji("butterfly", []int32{129419}, "butterfly"),
|
||||
NewEmoji("bug", []int32{128027}, "bug"),
|
||||
NewEmoji("ant", []int32{128028}, "ant"),
|
||||
NewEmoji("honeybee", []int32{128029}, "bee"),
|
||||
NewEmoji("beetle", []int32{129714}, "beetle"),
|
||||
NewEmoji("cricket", []int32{129431}, "cricket"),
|
||||
NewEmoji("spider", []int32{128375, 65039}, "spider"),
|
||||
NewEmoji("spider web", []int32{128376, 65039}, "spider_web"),
|
||||
NewEmoji("scorpion", []int32{129410}, "scorpion"),
|
||||
NewEmoji("mosquito", []int32{129439}, "mosquito"),
|
||||
NewEmoji("microbe", []int32{129440}, "microbe"),
|
||||
NewEmoji("bouquet", []int32{128144}, "bouquet"),
|
||||
NewEmoji("cherry blossom", []int32{127800}, "cherry_blossom"),
|
||||
NewEmoji("white flower", []int32{128174}, "white_flower"),
|
||||
NewEmoji("rosette", []int32{127989, 65039}, "rosette"),
|
||||
NewEmoji("rose", []int32{127801}, "rose"),
|
||||
NewEmoji("wilted flower", []int32{129344}, "wilted_flower"),
|
||||
NewEmoji("hibiscus", []int32{127802}, "hibiscus"),
|
||||
NewEmoji("sunflower", []int32{127803}, "sunflower"),
|
||||
NewEmoji("blossom", []int32{127804}, "blossom"),
|
||||
NewEmoji("tulip", []int32{127799}, "tulip"),
|
||||
NewEmoji("seedling", []int32{127793}, "seedling"),
|
||||
NewEmoji("evergreen tree", []int32{127794}, "evergreen_tree"),
|
||||
NewEmoji("deciduous tree", []int32{127795}, "deciduous_tree"),
|
||||
NewEmoji("palm tree", []int32{127796}, "palm_tree"),
|
||||
NewEmoji("cactus", []int32{127797}, "cactus"),
|
||||
NewEmoji("sheaf of rice", []int32{127806}, "ear_of_rice"),
|
||||
NewEmoji("herb", []int32{127807}, "herb"),
|
||||
NewEmoji("shamrock", []int32{9752, 65039}, "shamrock"),
|
||||
NewEmoji("four leaf clover", []int32{127808}, "four_leaf_clover"),
|
||||
NewEmoji("maple leaf", []int32{127809}, "maple_leaf"),
|
||||
NewEmoji("fallen leaf", []int32{127810}, "fallen_leaf"),
|
||||
NewEmoji("leaf fluttering in wind", []int32{127811}, "leaves"),
|
||||
NewEmoji("grapes", []int32{127815}, "grapes"),
|
||||
NewEmoji("melon", []int32{127816}, "melon"),
|
||||
NewEmoji("watermelon", []int32{127817}, "watermelon"),
|
||||
NewEmoji("tangerine", []int32{127818}, "tangerine"),
|
||||
NewEmoji("lemon", []int32{127819}, "lemon"),
|
||||
NewEmoji("banana", []int32{127820}, "banana"),
|
||||
NewEmoji("pineapple", []int32{127821}, "pineapple"),
|
||||
NewEmoji("mango", []int32{129389}, "mango"),
|
||||
NewEmoji("red apple", []int32{127822}, "apple"),
|
||||
NewEmoji("green apple", []int32{127823}, "green_apple"),
|
||||
NewEmoji("pear", []int32{127824}, "pear"),
|
||||
NewEmoji("peach", []int32{127825}, "peach"),
|
||||
NewEmoji("cherries", []int32{127826}, "cherries"),
|
||||
NewEmoji("strawberry", []int32{127827}, "strawberry"),
|
||||
NewEmoji("kiwi fruit", []int32{129373}, "kiwi_fruit"),
|
||||
NewEmoji("tomato", []int32{127813}, "tomato"),
|
||||
NewEmoji("coconut", []int32{129381}, "coconut"),
|
||||
NewEmoji("avocado", []int32{129361}, "avocado"),
|
||||
NewEmoji("eggplant", []int32{127814}, "eggplant"),
|
||||
NewEmoji("potato", []int32{129364}, "potato"),
|
||||
NewEmoji("carrot", []int32{129365}, "carrot"),
|
||||
NewEmoji("ear of corn", []int32{127805}, "corn"),
|
||||
NewEmoji("hot pepper", []int32{127798, 65039}, "hot_pepper"),
|
||||
NewEmoji("cucumber", []int32{129362}, "cucumber"),
|
||||
NewEmoji("leafy green", []int32{129388}, "leafy_green"),
|
||||
NewEmoji("broccoli", []int32{129382}, "broccoli"),
|
||||
NewEmoji("garlic", []int32{129476}, "garlic"),
|
||||
NewEmoji("onion", []int32{129477}, "onion"),
|
||||
NewEmoji("mushroom", []int32{127812}, "mushroom"),
|
||||
NewEmoji("peanuts", []int32{129372}, "peanuts"),
|
||||
NewEmoji("chestnut", []int32{127792}, "chestnut"),
|
||||
NewEmoji("bread", []int32{127838}, "bread"),
|
||||
NewEmoji("croissant", []int32{129360}, "croissant"),
|
||||
NewEmoji("baguette bread", []int32{129366}, "baguette_bread"),
|
||||
NewEmoji("pretzel", []int32{129384}, "pretzel"),
|
||||
NewEmoji("bagel", []int32{129391}, "bagel"),
|
||||
NewEmoji("pancakes", []int32{129374}, "pancakes"),
|
||||
NewEmoji("waffle", []int32{129479}, "waffle"),
|
||||
NewEmoji("cheese wedge", []int32{129472}, "cheese"),
|
||||
NewEmoji("meat on bone", []int32{127830}, "meat_on_bone"),
|
||||
NewEmoji("poultry leg", []int32{127831}, "poultry_leg"),
|
||||
NewEmoji("cut of meat", []int32{129385}, "cut_of_meat"),
|
||||
NewEmoji("bacon", []int32{129363}, "bacon"),
|
||||
NewEmoji("hamburger", []int32{127828}, "hamburger"),
|
||||
NewEmoji("french fries", []int32{127839}, "fries"),
|
||||
NewEmoji("pizza", []int32{127829}, "pizza"),
|
||||
NewEmoji("hot dog", []int32{127789}, "hotdog"),
|
||||
NewEmoji("sandwich", []int32{129386}, "sandwich"),
|
||||
NewEmoji("taco", []int32{127790}, "taco"),
|
||||
NewEmoji("burrito", []int32{127791}, "burrito"),
|
||||
NewEmoji("stuffed flatbread", []int32{129369}, "stuffed_flatbread"),
|
||||
NewEmoji("falafel", []int32{129478}, "falafel"),
|
||||
NewEmoji("egg", []int32{129370}, "egg"),
|
||||
NewEmoji("cooking", []int32{127859}, "fried_egg"),
|
||||
NewEmoji("shallow pan of food", []int32{129368}, "shallow_pan_of_food"),
|
||||
NewEmoji("pot of food", []int32{127858}, "stew"),
|
||||
NewEmoji("bowl with spoon", []int32{129379}, "bowl_with_spoon"),
|
||||
NewEmoji("green salad", []int32{129367}, "green_salad"),
|
||||
NewEmoji("popcorn", []int32{127871}, "popcorn"),
|
||||
NewEmoji("butter", []int32{129480}, "butter"),
|
||||
NewEmoji("salt", []int32{129474}, "salt"),
|
||||
NewEmoji("canned food", []int32{129387}, "canned_food"),
|
||||
NewEmoji("bento box", []int32{127857}, "bento"),
|
||||
NewEmoji("rice cracker", []int32{127832}, "rice_cracker"),
|
||||
NewEmoji("rice ball", []int32{127833}, "rice_ball"),
|
||||
NewEmoji("cooked rice", []int32{127834}, "rice"),
|
||||
NewEmoji("curry rice", []int32{127835}, "curry"),
|
||||
NewEmoji("steaming bowl", []int32{127836}, "ramen"),
|
||||
NewEmoji("spaghetti", []int32{127837}, "spaghetti"),
|
||||
NewEmoji("roasted sweet potato", []int32{127840}, "sweet_potato"),
|
||||
NewEmoji("oden", []int32{127842}, "oden"),
|
||||
NewEmoji("sushi", []int32{127843}, "sushi"),
|
||||
NewEmoji("fried shrimp", []int32{127844}, "fried_shrimp"),
|
||||
NewEmoji("fish cake with swirl", []int32{127845}, "fish_cake"),
|
||||
NewEmoji("moon cake", []int32{129390}, "moon_cake"),
|
||||
NewEmoji("dango", []int32{127841}, "dango"),
|
||||
NewEmoji("dumpling", []int32{129375}, "dumpling"),
|
||||
NewEmoji("fortune cookie", []int32{129376}, "fortune_cookie"),
|
||||
NewEmoji("takeout box", []int32{129377}, "takeout_box"),
|
||||
NewEmoji("crab", []int32{129408}, "crab"),
|
||||
NewEmoji("lobster", []int32{129438}, "lobster"),
|
||||
NewEmoji("shrimp", []int32{129424}, "shrimp"),
|
||||
NewEmoji("squid", []int32{129425}, "squid"),
|
||||
NewEmoji("oyster", []int32{129450}, "oyster"),
|
||||
NewEmoji("soft ice cream", []int32{127846}, "icecream"),
|
||||
NewEmoji("shaved ice", []int32{127847}, "shaved_ice"),
|
||||
NewEmoji("ice cream", []int32{127848}, "ice_cream"),
|
||||
NewEmoji("doughnut", []int32{127849}, "doughnut"),
|
||||
NewEmoji("cookie", []int32{127850}, "cookie"),
|
||||
NewEmoji("birthday cake", []int32{127874}, "birthday"),
|
||||
NewEmoji("shortcake", []int32{127856}, "cake"),
|
||||
NewEmoji("cupcake", []int32{129473}, "cupcake"),
|
||||
NewEmoji("pie", []int32{129383}, "pie"),
|
||||
NewEmoji("chocolate bar", []int32{127851}, "chocolate_bar"),
|
||||
NewEmoji("candy", []int32{127852}, "candy"),
|
||||
NewEmoji("lollipop", []int32{127853}, "lollipop"),
|
||||
NewEmoji("custard", []int32{127854}, "custard"),
|
||||
NewEmoji("honey pot", []int32{127855}, "honey_pot"),
|
||||
NewEmoji("baby bottle", []int32{127868}, "baby_bottle"),
|
||||
NewEmoji("glass of milk", []int32{129371}, "milk_glass"),
|
||||
NewEmoji("hot beverage", []int32{9749}, "coffee"),
|
||||
NewEmoji("teacup without handle", []int32{127861}, "tea"),
|
||||
NewEmoji("sake", []int32{127862}, "sake"),
|
||||
NewEmoji("bottle with popping cork", []int32{127870}, "champagne"),
|
||||
NewEmoji("wine glass", []int32{127863}, "wine_glass"),
|
||||
NewEmoji("cocktail glass", []int32{127864}, "cocktail"),
|
||||
NewEmoji("tropical drink", []int32{127865}, "tropical_drink"),
|
||||
NewEmoji("beer mug", []int32{127866}, "beer"),
|
||||
NewEmoji("clinking beer mugs", []int32{127867}, "beers"),
|
||||
NewEmoji("clinking glasses", []int32{129346}, "clinking_glasses"),
|
||||
NewEmoji("tumbler glass", []int32{129347}, "tumbler_glass"),
|
||||
NewEmoji("cup with straw", []int32{129380}, "cup_with_straw"),
|
||||
NewEmoji("beverage box", []int32{129475}, "beverage_box"),
|
||||
NewEmoji("mate", []int32{129481}, "mate"),
|
||||
NewEmoji("ice", []int32{129482}, "ice_cube"),
|
||||
NewEmoji("chopsticks", []int32{129378}, "chopsticks"),
|
||||
NewEmoji("fork and knife with plate", []int32{127869, 65039}, "plate_with_cutlery"),
|
||||
NewEmoji("fork and knife", []int32{127860}, "fork_and_knife"),
|
||||
NewEmoji("spoon", []int32{129348}, "spoon"),
|
||||
NewEmoji("kitchen knife", []int32{128298}, "hocho"),
|
||||
NewEmoji("amphora", []int32{127994}, "amphora"),
|
||||
NewEmoji("globe showing Europe-Africa", []int32{127757}, "earth_africa"),
|
||||
NewEmoji("globe showing Americas", []int32{127758}, "earth_americas"),
|
||||
NewEmoji("globe showing Asia-Australia", []int32{127759}, "earth_asia"),
|
||||
NewEmoji("globe with meridians", []int32{127760}, "globe_with_meridians"),
|
||||
NewEmoji("world map", []int32{128506, 65039}, "world_map"),
|
||||
NewEmoji("map of Japan", []int32{128510}, "japan"),
|
||||
NewEmoji("compass", []int32{129517}, "compass"),
|
||||
NewEmoji("snow-capped mountain", []int32{127956, 65039}, "mountain_snow"),
|
||||
NewEmoji("mountain", []int32{9968, 65039}, "mountain"),
|
||||
NewEmoji("volcano", []int32{127755}, "volcano"),
|
||||
NewEmoji("mount fuji", []int32{128507}, "mount_fuji"),
|
||||
NewEmoji("camping", []int32{127957, 65039}, "camping"),
|
||||
NewEmoji("beach with umbrella", []int32{127958, 65039}, "beach_umbrella"),
|
||||
NewEmoji("desert", []int32{127964, 65039}, "desert"),
|
||||
NewEmoji("desert island", []int32{127965, 65039}, "desert_island"),
|
||||
NewEmoji("national park", []int32{127966, 65039}, "national_park"),
|
||||
NewEmoji("stadium", []int32{127967, 65039}, "stadium"),
|
||||
NewEmoji("classical building", []int32{127963, 65039}, "classical_building"),
|
||||
NewEmoji("building construction", []int32{127959, 65039}, "building_construction"),
|
||||
NewEmoji("brick", []int32{129521}, "bricks"),
|
||||
NewEmoji("houses", []int32{127960, 65039}, "houses"),
|
||||
NewEmoji("derelict house", []int32{127962, 65039}, "derelict_house"),
|
||||
NewEmoji("house", []int32{127968}, "house"),
|
||||
NewEmoji("house with garden", []int32{127969}, "house_with_garden"),
|
||||
NewEmoji("office building", []int32{127970}, "office"),
|
||||
NewEmoji("Japanese post office", []int32{127971}, "post_office"),
|
||||
NewEmoji("post office", []int32{127972}, "european_post_office"),
|
||||
NewEmoji("hospital", []int32{127973}, "hospital"),
|
||||
NewEmoji("bank", []int32{127974}, "bank"),
|
||||
NewEmoji("hotel", []int32{127976}, "hotel"),
|
||||
NewEmoji("love hotel", []int32{127977}, "love_hotel"),
|
||||
NewEmoji("convenience store", []int32{127978}, "convenience_store"),
|
||||
NewEmoji("school", []int32{127979}, "school"),
|
||||
NewEmoji("department store", []int32{127980}, "department_store"),
|
||||
NewEmoji("factory", []int32{127981}, "factory"),
|
||||
NewEmoji("Japanese castle", []int32{127983}, "japanese_castle"),
|
||||
NewEmoji("castle", []int32{127984}, "european_castle"),
|
||||
NewEmoji("wedding", []int32{128146}, "wedding"),
|
||||
NewEmoji("Tokyo tower", []int32{128508}, "tokyo_tower"),
|
||||
NewEmoji("Statue of Liberty", []int32{128509}, "statue_of_liberty"),
|
||||
NewEmoji("church", []int32{9962}, "church"),
|
||||
NewEmoji("mosque", []int32{128332}, "mosque"),
|
||||
NewEmoji("hindu temple", []int32{128725}, "hindu_temple"),
|
||||
NewEmoji("synagogue", []int32{128333}, "synagogue"),
|
||||
NewEmoji("shinto shrine", []int32{9961, 65039}, "shinto_shrine"),
|
||||
NewEmoji("kaaba", []int32{128331}, "kaaba"),
|
||||
NewEmoji("fountain", []int32{9970}, "fountain"),
|
||||
NewEmoji("tent", []int32{9978}, "tent"),
|
||||
NewEmoji("foggy", []int32{127745}, "foggy"),
|
||||
NewEmoji("night with stars", []int32{127747}, "night_with_stars"),
|
||||
NewEmoji("cityscape", []int32{127961, 65039}, "cityscape"),
|
||||
NewEmoji("sunrise over mountains", []int32{127748}, "sunrise_over_mountains"),
|
||||
NewEmoji("sunrise", []int32{127749}, "sunrise"),
|
||||
NewEmoji("cityscape at dusk", []int32{127750}, "city_sunset"),
|
||||
NewEmoji("sunset", []int32{127751}, "city_sunrise"),
|
||||
NewEmoji("bridge at night", []int32{127753}, "bridge_at_night"),
|
||||
NewEmoji("hot springs", []int32{9832, 65039}, "hotsprings"),
|
||||
NewEmoji("carousel horse", []int32{127904}, "carousel_horse"),
|
||||
NewEmoji("ferris wheel", []int32{127905}, "ferris_wheel"),
|
||||
NewEmoji("roller coaster", []int32{127906}, "roller_coaster"),
|
||||
NewEmoji("barber pole", []int32{128136}, "barber"),
|
||||
NewEmoji("circus tent", []int32{127914}, "circus_tent"),
|
||||
NewEmoji("locomotive", []int32{128642}, "steam_locomotive"),
|
||||
NewEmoji("railway car", []int32{128643}, "railway_car"),
|
||||
NewEmoji("high-speed train", []int32{128644}, "bullettrain_side"),
|
||||
NewEmoji("bullet train", []int32{128645}, "bullettrain_front"),
|
||||
NewEmoji("train", []int32{128646}, "train2"),
|
||||
NewEmoji("metro", []int32{128647}, "metro"),
|
||||
NewEmoji("light rail", []int32{128648}, "light_rail"),
|
||||
NewEmoji("station", []int32{128649}, "station"),
|
||||
NewEmoji("tram", []int32{128650}, "tram"),
|
||||
NewEmoji("monorail", []int32{128669}, "monorail"),
|
||||
NewEmoji("mountain railway", []int32{128670}, "mountain_railway"),
|
||||
NewEmoji("tram car", []int32{128651}, "train"),
|
||||
NewEmoji("bus", []int32{128652}, "bus"),
|
||||
NewEmoji("oncoming bus", []int32{128653}, "oncoming_bus"),
|
||||
NewEmoji("trolleybus", []int32{128654}, "trolleybus"),
|
||||
NewEmoji("minibus", []int32{128656}, "minibus"),
|
||||
NewEmoji("ambulance", []int32{128657}, "ambulance"),
|
||||
NewEmoji("fire engine", []int32{128658}, "fire_engine"),
|
||||
NewEmoji("police car", []int32{128659}, "police_car"),
|
||||
NewEmoji("oncoming police car", []int32{128660}, "oncoming_police_car"),
|
||||
NewEmoji("taxi", []int32{128661}, "taxi"),
|
||||
NewEmoji("oncoming taxi", []int32{128662}, "oncoming_taxi"),
|
||||
NewEmoji("automobile", []int32{128663}, "car"),
|
||||
NewEmoji("oncoming automobile", []int32{128664}, "oncoming_automobile"),
|
||||
NewEmoji("sport utility vehicle", []int32{128665}, "blue_car"),
|
||||
NewEmoji("delivery truck", []int32{128666}, "truck"),
|
||||
NewEmoji("articulated lorry", []int32{128667}, "articulated_lorry"),
|
||||
NewEmoji("tractor", []int32{128668}, "tractor"),
|
||||
NewEmoji("racing car", []int32{127950, 65039}, "racing_car"),
|
||||
NewEmoji("motorcycle", []int32{127949, 65039}, "motorcycle"),
|
||||
NewEmoji("motor scooter", []int32{128757}, "motor_scooter"),
|
||||
NewEmoji("manual wheelchair", []int32{129469}, "manual_wheelchair"),
|
||||
NewEmoji("motorized wheelchair", []int32{129468}, "motorized_wheelchair"),
|
||||
NewEmoji("auto rickshaw", []int32{128762}, "auto_rickshaw"),
|
||||
NewEmoji("bicycle", []int32{128690}, "bike"),
|
||||
NewEmoji("kick scooter", []int32{128756}, "kick_scooter"),
|
||||
NewEmoji("skateboard", []int32{128761}, "skateboard"),
|
||||
NewEmoji("bus stop", []int32{128655}, "busstop"),
|
||||
NewEmoji("motorway", []int32{128739, 65039}, "motorway"),
|
||||
NewEmoji("railway track", []int32{128740, 65039}, "railway_track"),
|
||||
NewEmoji("oil drum", []int32{128738, 65039}, "oil_drum"),
|
||||
NewEmoji("fuel pump", []int32{9981}, "fuelpump"),
|
||||
NewEmoji("police car light", []int32{128680}, "rotating_light"),
|
||||
NewEmoji("horizontal traffic light", []int32{128677}, "traffic_light"),
|
||||
NewEmoji("vertical traffic light", []int32{128678}, "vertical_traffic_light"),
|
||||
NewEmoji("stop sign", []int32{128721}, "stop_sign"),
|
||||
NewEmoji("construction", []int32{128679}, "construction"),
|
||||
NewEmoji("anchor", []int32{9875}, "anchor"),
|
||||
NewEmoji("sailboat", []int32{9973}, "boat"),
|
||||
NewEmoji("canoe", []int32{128758}, "canoe"),
|
||||
NewEmoji("speedboat", []int32{128676}, "speedboat"),
|
||||
NewEmoji("passenger ship", []int32{128755, 65039}, "passenger_ship"),
|
||||
NewEmoji("ferry", []int32{9972, 65039}, "ferry"),
|
||||
NewEmoji("motor boat", []int32{128741, 65039}, "motor_boat"),
|
||||
NewEmoji("ship", []int32{128674}, "ship"),
|
||||
NewEmoji("airplane", []int32{9992, 65039}, "airplane"),
|
||||
NewEmoji("small airplane", []int32{128745, 65039}, "small_airplane"),
|
||||
NewEmoji("airplane departure", []int32{128747}, "flight_departure"),
|
||||
NewEmoji("airplane arrival", []int32{128748}, "flight_arrival"),
|
||||
NewEmoji("parachute", []int32{129666}, "parachute"),
|
||||
NewEmoji("seat", []int32{128186}, "seat"),
|
||||
NewEmoji("helicopter", []int32{128641}, "helicopter"),
|
||||
NewEmoji("suspension railway", []int32{128671}, "suspension_railway"),
|
||||
NewEmoji("mountain cableway", []int32{128672}, "mountain_cableway"),
|
||||
NewEmoji("aerial tramway", []int32{128673}, "aerial_tramway"),
|
||||
NewEmoji("satellite", []int32{128752, 65039}, "artificial_satellite"),
|
||||
NewEmoji("rocket", []int32{128640}, "rocket"),
|
||||
NewEmoji("flying saucer", []int32{128760}, "flying_saucer"),
|
||||
NewEmoji("bellhop bell", []int32{128718, 65039}, "bellhop_bell"),
|
||||
NewEmoji("luggage", []int32{129523}, "luggage"),
|
||||
NewEmoji("hourglass done", []int32{8987}, "hourglass"),
|
||||
NewEmoji("hourglass not done", []int32{9203}, "hourglass_flowing_sand"),
|
||||
NewEmoji("watch", []int32{8986}, "watch"),
|
||||
NewEmoji("alarm clock", []int32{9200}, "alarm_clock"),
|
||||
NewEmoji("stopwatch", []int32{9201, 65039}, "stopwatch"),
|
||||
NewEmoji("timer clock", []int32{9202, 65039}, "timer_clock"),
|
||||
NewEmoji("mantelpiece clock", []int32{128368, 65039}, "mantelpiece_clock"),
|
||||
NewEmoji("twelve o’clock", []int32{128347}, "clock12"),
|
||||
NewEmoji("twelve-thirty", []int32{128359}, "clock1230"),
|
||||
NewEmoji("one o’clock", []int32{128336}, "clock1"),
|
||||
NewEmoji("one-thirty", []int32{128348}, "clock130"),
|
||||
NewEmoji("two o’clock", []int32{128337}, "clock2"),
|
||||
NewEmoji("two-thirty", []int32{128349}, "clock230"),
|
||||
NewEmoji("three o’clock", []int32{128338}, "clock3"),
|
||||
NewEmoji("three-thirty", []int32{128350}, "clock330"),
|
||||
NewEmoji("four o’clock", []int32{128339}, "clock4"),
|
||||
NewEmoji("four-thirty", []int32{128351}, "clock430"),
|
||||
NewEmoji("five o’clock", []int32{128340}, "clock5"),
|
||||
NewEmoji("five-thirty", []int32{128352}, "clock530"),
|
||||
NewEmoji("six o’clock", []int32{128341}, "clock6"),
|
||||
NewEmoji("six-thirty", []int32{128353}, "clock630"),
|
||||
NewEmoji("seven o’clock", []int32{128342}, "clock7"),
|
||||
NewEmoji("seven-thirty", []int32{128354}, "clock730"),
|
||||
NewEmoji("eight o’clock", []int32{128343}, "clock8"),
|
||||
NewEmoji("eight-thirty", []int32{128355}, "clock830"),
|
||||
NewEmoji("nine o’clock", []int32{128344}, "clock9"),
|
||||
NewEmoji("nine-thirty", []int32{128356}, "clock930"),
|
||||
NewEmoji("ten o’clock", []int32{128345}, "clock10"),
|
||||
NewEmoji("ten-thirty", []int32{128357}, "clock1030"),
|
||||
NewEmoji("eleven o’clock", []int32{128346}, "clock11"),
|
||||
NewEmoji("eleven-thirty", []int32{128358}, "clock1130"),
|
||||
NewEmoji("new moon", []int32{127761}, "new_moon"),
|
||||
NewEmoji("waxing crescent moon", []int32{127762}, "waxing_crescent_moon"),
|
||||
NewEmoji("first quarter moon", []int32{127763}, "first_quarter_moon"),
|
||||
NewEmoji("waxing gibbous moon", []int32{127764}, "moon"),
|
||||
NewEmoji("full moon", []int32{127765}, "full_moon"),
|
||||
NewEmoji("waning gibbous moon", []int32{127766}, "waning_gibbous_moon"),
|
||||
NewEmoji("last quarter moon", []int32{127767}, "last_quarter_moon"),
|
||||
NewEmoji("waning crescent moon", []int32{127768}, "waning_crescent_moon"),
|
||||
NewEmoji("crescent moon", []int32{127769}, "crescent_moon"),
|
||||
NewEmoji("new moon face", []int32{127770}, "new_moon_with_face"),
|
||||
NewEmoji("first quarter moon face", []int32{127771}, "first_quarter_moon_with_face"),
|
||||
NewEmoji("last quarter moon face", []int32{127772}, "last_quarter_moon_with_face"),
|
||||
NewEmoji("thermometer", []int32{127777, 65039}, "thermometer"),
|
||||
NewEmoji("sun", []int32{9728, 65039}, "sunny"),
|
||||
NewEmoji("full moon face", []int32{127773}, "full_moon_with_face"),
|
||||
NewEmoji("sun with face", []int32{127774}, "sun_with_face"),
|
||||
NewEmoji("ringed planet", []int32{129680}, "ringed_planet"),
|
||||
NewEmoji("star", []int32{11088}, "star"),
|
||||
NewEmoji("glowing star", []int32{127775}, "star2"),
|
||||
NewEmoji("shooting star", []int32{127776}, "stars"),
|
||||
NewEmoji("milky way", []int32{127756}, "milky_way"),
|
||||
NewEmoji("cloud", []int32{9729, 65039}, "cloud"),
|
||||
NewEmoji("sun behind cloud", []int32{9925}, "partly_sunny"),
|
||||
NewEmoji("cloud with lightning and rain", []int32{9928, 65039}, "cloud_with_lightning_and_rain"),
|
||||
NewEmoji("sun behind small cloud", []int32{127780, 65039}, "sun_behind_small_cloud"),
|
||||
NewEmoji("sun behind large cloud", []int32{127781, 65039}, "sun_behind_large_cloud"),
|
||||
NewEmoji("sun behind rain cloud", []int32{127782, 65039}, "sun_behind_rain_cloud"),
|
||||
NewEmoji("cloud with rain", []int32{127783, 65039}, "cloud_with_rain"),
|
||||
NewEmoji("cloud with snow", []int32{127784, 65039}, "cloud_with_snow"),
|
||||
NewEmoji("cloud with lightning", []int32{127785, 65039}, "cloud_with_lightning"),
|
||||
NewEmoji("tornado", []int32{127786, 65039}, "tornado"),
|
||||
NewEmoji("fog", []int32{127787, 65039}, "fog"),
|
||||
NewEmoji("wind face", []int32{127788, 65039}, "wind_face"),
|
||||
NewEmoji("cyclone", []int32{127744}, "cyclone"),
|
||||
NewEmoji("rainbow", []int32{127752}, "rainbow"),
|
||||
NewEmoji("closed umbrella", []int32{127746}, "closed_umbrella"),
|
||||
NewEmoji("umbrella", []int32{9730, 65039}, "open_umbrella"),
|
||||
NewEmoji("umbrella with rain drops", []int32{9748}, "umbrella"),
|
||||
NewEmoji("umbrella on ground", []int32{9969, 65039}, "parasol_on_ground"),
|
||||
NewEmoji("high voltage", []int32{9889}, "zap"),
|
||||
NewEmoji("snowflake", []int32{10052, 65039}, "snowflake"),
|
||||
NewEmoji("snowman", []int32{9731, 65039}, "snowman_with_snow"),
|
||||
NewEmoji("snowman without snow", []int32{9924}, "snowman"),
|
||||
NewEmoji("comet", []int32{9732, 65039}, "comet"),
|
||||
NewEmoji("fire", []int32{128293}, "fire"),
|
||||
NewEmoji("droplet", []int32{128167}, "droplet"),
|
||||
NewEmoji("water wave", []int32{127754}, "ocean"),
|
||||
NewEmoji("jack-o-lantern", []int32{127875}, "jack_o_lantern"),
|
||||
NewEmoji("Christmas tree", []int32{127876}, "christmas_tree"),
|
||||
NewEmoji("fireworks", []int32{127878}, "fireworks"),
|
||||
NewEmoji("sparkler", []int32{127879}, "sparkler"),
|
||||
NewEmoji("firecracker", []int32{129512}, "firecracker"),
|
||||
NewEmoji("sparkles", []int32{10024}, "sparkles"),
|
||||
NewEmoji("balloon", []int32{127880}, "balloon"),
|
||||
NewEmoji("party popper", []int32{127881}, "tada"),
|
||||
NewEmoji("confetti ball", []int32{127882}, "confetti_ball"),
|
||||
NewEmoji("tanabata tree", []int32{127883}, "tanabata_tree"),
|
||||
NewEmoji("pine decoration", []int32{127885}, "bamboo"),
|
||||
NewEmoji("Japanese dolls", []int32{127886}, "dolls"),
|
||||
NewEmoji("carp streamer", []int32{127887}, "flags"),
|
||||
NewEmoji("wind chime", []int32{127888}, "wind_chime"),
|
||||
NewEmoji("moon viewing ceremony", []int32{127889}, "rice_scene"),
|
||||
NewEmoji("red envelope", []int32{129511}, "red_envelope"),
|
||||
NewEmoji("ribbon", []int32{127872}, "ribbon"),
|
||||
NewEmoji("wrapped gift", []int32{127873}, "gift"),
|
||||
NewEmoji("reminder ribbon", []int32{127895, 65039}, "reminder_ribbon"),
|
||||
NewEmoji("admission tickets", []int32{127903, 65039}, "tickets"),
|
||||
NewEmoji("ticket", []int32{127915}, "ticket"),
|
||||
NewEmoji("military medal", []int32{127894, 65039}, "medal_military"),
|
||||
NewEmoji("trophy", []int32{127942}, "trophy"),
|
||||
NewEmoji("sports medal", []int32{127941}, "medal_sports"),
|
||||
NewEmoji("1st place medal", []int32{129351}, "1st_place_medal"),
|
||||
NewEmoji("2nd place medal", []int32{129352}, "2nd_place_medal"),
|
||||
NewEmoji("3rd place medal", []int32{129353}, "3rd_place_medal"),
|
||||
NewEmoji("soccer ball", []int32{9917}, "soccer"),
|
||||
NewEmoji("baseball", []int32{9918}, "baseball"),
|
||||
NewEmoji("softball", []int32{129358}, "softball"),
|
||||
NewEmoji("basketball", []int32{127936}, "basketball"),
|
||||
NewEmoji("volleyball", []int32{127952}, "volleyball"),
|
||||
NewEmoji("american football", []int32{127944}, "football"),
|
||||
NewEmoji("rugby football", []int32{127945}, "rugby_football"),
|
||||
NewEmoji("tennis", []int32{127934}, "tennis"),
|
||||
NewEmoji("flying disc", []int32{129359}, "flying_disc"),
|
||||
NewEmoji("bowling", []int32{127923}, "bowling"),
|
||||
NewEmoji("cricket game", []int32{127951}, "cricket_game"),
|
||||
NewEmoji("field hockey", []int32{127953}, "field_hockey"),
|
||||
NewEmoji("ice hockey", []int32{127954}, "ice_hockey"),
|
||||
NewEmoji("lacrosse", []int32{129357}, "lacrosse"),
|
||||
NewEmoji("ping pong", []int32{127955}, "ping_pong"),
|
||||
NewEmoji("badminton", []int32{127992}, "badminton"),
|
||||
NewEmoji("boxing glove", []int32{129354}, "boxing_glove"),
|
||||
NewEmoji("martial arts uniform", []int32{129355}, "martial_arts_uniform"),
|
||||
NewEmoji("goal net", []int32{129349}, "goal_net"),
|
||||
NewEmoji("flag in hole", []int32{9971}, "golf"),
|
||||
NewEmoji("ice skate", []int32{9976, 65039}, "ice_skate"),
|
||||
NewEmoji("fishing pole", []int32{127907}, "fishing_pole_and_fish"),
|
||||
NewEmoji("diving mask", []int32{129343}, "diving_mask"),
|
||||
NewEmoji("running shirt", []int32{127933}, "running_shirt_with_sash"),
|
||||
NewEmoji("skis", []int32{127935}, "ski"),
|
||||
NewEmoji("sled", []int32{128759}, "sled"),
|
||||
NewEmoji("curling stone", []int32{129356}, "curling_stone"),
|
||||
NewEmoji("direct hit", []int32{127919}, "dart"),
|
||||
NewEmoji("yo-yo", []int32{129664}, "yo_yo"),
|
||||
NewEmoji("kite", []int32{129665}, "kite"),
|
||||
NewEmoji("pool 8 ball", []int32{127921}, "8ball"),
|
||||
NewEmoji("crystal ball", []int32{128302}, "crystal_ball"),
|
||||
NewEmoji("nazar amulet", []int32{129535}, "nazar_amulet"),
|
||||
NewEmoji("video game", []int32{127918}, "video_game"),
|
||||
NewEmoji("joystick", []int32{128377, 65039}, "joystick"),
|
||||
NewEmoji("slot machine", []int32{127920}, "slot_machine"),
|
||||
NewEmoji("game die", []int32{127922}, "game_die"),
|
||||
NewEmoji("puzzle piece", []int32{129513}, "jigsaw"),
|
||||
NewEmoji("teddy bear", []int32{129528}, "teddy_bear"),
|
||||
NewEmoji("spade suit", []int32{9824, 65039}, "spades"),
|
||||
NewEmoji("heart suit", []int32{9829, 65039}, "hearts"),
|
||||
NewEmoji("diamond suit", []int32{9830, 65039}, "diamonds"),
|
||||
NewEmoji("club suit", []int32{9827, 65039}, "clubs"),
|
||||
NewEmoji("chess pawn", []int32{9823, 65039}, "chess_pawn"),
|
||||
NewEmoji("joker", []int32{127183}, "black_joker"),
|
||||
NewEmoji("mahjong red dragon", []int32{126980}, "mahjong"),
|
||||
NewEmoji("flower playing cards", []int32{127924}, "flower_playing_cards"),
|
||||
NewEmoji("performing arts", []int32{127917}, "performing_arts"),
|
||||
NewEmoji("framed picture", []int32{128444, 65039}, "framed_picture"),
|
||||
NewEmoji("artist palette", []int32{127912}, "art"),
|
||||
NewEmoji("thread", []int32{129525}, "thread"),
|
||||
NewEmoji("yarn", []int32{129526}, "yarn"),
|
||||
NewEmoji("glasses", []int32{128083}, "eyeglasses"),
|
||||
NewEmoji("sunglasses", []int32{128374, 65039}, "dark_sunglasses"),
|
||||
NewEmoji("goggles", []int32{129405}, "goggles"),
|
||||
NewEmoji("lab coat", []int32{129404}, "lab_coat"),
|
||||
NewEmoji("safety vest", []int32{129466}, "safety_vest"),
|
||||
NewEmoji("necktie", []int32{128084}, "necktie"),
|
||||
NewEmoji("t-shirt", []int32{128085}, "shirt"),
|
||||
NewEmoji("jeans", []int32{128086}, "jeans"),
|
||||
NewEmoji("scarf", []int32{129507}, "scarf"),
|
||||
NewEmoji("gloves", []int32{129508}, "gloves"),
|
||||
NewEmoji("coat", []int32{129509}, "coat"),
|
||||
NewEmoji("socks", []int32{129510}, "socks"),
|
||||
NewEmoji("dress", []int32{128087}, "dress"),
|
||||
NewEmoji("kimono", []int32{128088}, "kimono"),
|
||||
NewEmoji("sari", []int32{129403}, "sari"),
|
||||
NewEmoji("one-piece swimsuit", []int32{129649}, "one_piece_swimsuit"),
|
||||
NewEmoji("briefs", []int32{129650}, "swim_brief"),
|
||||
NewEmoji("shorts", []int32{129651}, "shorts"),
|
||||
NewEmoji("bikini", []int32{128089}, "bikini"),
|
||||
NewEmoji("woman’s clothes", []int32{128090}, "womans_clothes"),
|
||||
NewEmoji("purse", []int32{128091}, "purse"),
|
||||
NewEmoji("handbag", []int32{128092}, "handbag"),
|
||||
NewEmoji("clutch bag", []int32{128093}, "pouch"),
|
||||
NewEmoji("shopping bags", []int32{128717, 65039}, "shopping"),
|
||||
NewEmoji("backpack", []int32{127890}, "school_satchel"),
|
||||
NewEmoji("man’s shoe", []int32{128094}, "mans_shoe"),
|
||||
NewEmoji("running shoe", []int32{128095}, "athletic_shoe"),
|
||||
NewEmoji("hiking boot", []int32{129406}, "hiking_boot"),
|
||||
NewEmoji("flat shoe", []int32{129407}, "flat_shoe"),
|
||||
NewEmoji("high-heeled shoe", []int32{128096}, "high_heel"),
|
||||
NewEmoji("woman’s sandal", []int32{128097}, "sandal"),
|
||||
NewEmoji("ballet shoes", []int32{129648}, "ballet_shoes"),
|
||||
NewEmoji("woman’s boot", []int32{128098}, "boot"),
|
||||
NewEmoji("crown", []int32{128081}, "crown"),
|
||||
NewEmoji("woman’s hat", []int32{128082}, "womans_hat"),
|
||||
NewEmoji("top hat", []int32{127913}, "tophat"),
|
||||
NewEmoji("graduation cap", []int32{127891}, "mortar_board"),
|
||||
NewEmoji("billed cap", []int32{129506}, "billed_cap"),
|
||||
NewEmoji("rescue worker’s helmet", []int32{9937, 65039}, "rescue_worker_helmet"),
|
||||
NewEmoji("prayer beads", []int32{128255}, "prayer_beads"),
|
||||
NewEmoji("lipstick", []int32{128132}, "lipstick"),
|
||||
NewEmoji("ring", []int32{128141}, "ring"),
|
||||
NewEmoji("gem stone", []int32{128142}, "gem"),
|
||||
NewEmoji("muted speaker", []int32{128263}, "mute"),
|
||||
NewEmoji("speaker low volume", []int32{128264}, "speaker"),
|
||||
NewEmoji("speaker medium volume", []int32{128265}, "sound"),
|
||||
NewEmoji("speaker high volume", []int32{128266}, "loud_sound"),
|
||||
NewEmoji("loudspeaker", []int32{128226}, "loudspeaker"),
|
||||
NewEmoji("megaphone", []int32{128227}, "mega"),
|
||||
NewEmoji("postal horn", []int32{128239}, "postal_horn"),
|
||||
NewEmoji("bell", []int32{128276}, "bell"),
|
||||
NewEmoji("bell with slash", []int32{128277}, "no_bell"),
|
||||
NewEmoji("musical score", []int32{127932}, "musical_score"),
|
||||
NewEmoji("musical note", []int32{127925}, "musical_note"),
|
||||
NewEmoji("musical notes", []int32{127926}, "notes"),
|
||||
NewEmoji("studio microphone", []int32{127897, 65039}, "studio_microphone"),
|
||||
NewEmoji("level slider", []int32{127898, 65039}, "level_slider"),
|
||||
NewEmoji("control knobs", []int32{127899, 65039}, "control_knobs"),
|
||||
NewEmoji("microphone", []int32{127908}, "microphone"),
|
||||
NewEmoji("headphone", []int32{127911}, "headphones"),
|
||||
NewEmoji("radio", []int32{128251}, "radio"),
|
||||
NewEmoji("saxophone", []int32{127927}, "saxophone"),
|
||||
NewEmoji("guitar", []int32{127928}, "guitar"),
|
||||
NewEmoji("musical keyboard", []int32{127929}, "musical_keyboard"),
|
||||
NewEmoji("trumpet", []int32{127930}, "trumpet"),
|
||||
NewEmoji("violin", []int32{127931}, "violin"),
|
||||
NewEmoji("banjo", []int32{129685}, "banjo"),
|
||||
NewEmoji("drum", []int32{129345}, "drum"),
|
||||
NewEmoji("mobile phone", []int32{128241}, "iphone"),
|
||||
NewEmoji("mobile phone with arrow", []int32{128242}, "calling"),
|
||||
NewEmoji("telephone", []int32{9742, 65039}, "phone"),
|
||||
NewEmoji("telephone receiver", []int32{128222}, "telephone_receiver"),
|
||||
NewEmoji("pager", []int32{128223}, "pager"),
|
||||
NewEmoji("fax machine", []int32{128224}, "fax"),
|
||||
NewEmoji("battery", []int32{128267}, "battery"),
|
||||
NewEmoji("electric plug", []int32{128268}, "electric_plug"),
|
||||
NewEmoji("laptop", []int32{128187}, "computer"),
|
||||
NewEmoji("desktop computer", []int32{128421, 65039}, "desktop_computer"),
|
||||
NewEmoji("printer", []int32{128424, 65039}, "printer"),
|
||||
NewEmoji("keyboard", []int32{9000, 65039}, "keyboard"),
|
||||
NewEmoji("computer mouse", []int32{128433, 65039}, "computer_mouse"),
|
||||
NewEmoji("trackball", []int32{128434, 65039}, "trackball"),
|
||||
NewEmoji("computer disk", []int32{128189}, "minidisc"),
|
||||
NewEmoji("floppy disk", []int32{128190}, "floppy_disk"),
|
||||
NewEmoji("optical disk", []int32{128191}, "cd"),
|
||||
NewEmoji("dvd", []int32{128192}, "dvd"),
|
||||
NewEmoji("abacus", []int32{129518}, "abacus"),
|
||||
NewEmoji("movie camera", []int32{127909}, "movie_camera"),
|
||||
NewEmoji("film frames", []int32{127902, 65039}, "film_strip"),
|
||||
NewEmoji("film projector", []int32{128253, 65039}, "film_projector"),
|
||||
NewEmoji("clapper board", []int32{127916}, "clapper"),
|
||||
NewEmoji("television", []int32{128250}, "tv"),
|
||||
NewEmoji("camera", []int32{128247}, "camera"),
|
||||
NewEmoji("camera with flash", []int32{128248}, "camera_flash"),
|
||||
NewEmoji("video camera", []int32{128249}, "video_camera"),
|
||||
NewEmoji("videocassette", []int32{128252}, "vhs"),
|
||||
NewEmoji("magnifying glass tilted left", []int32{128269}, "mag"),
|
||||
NewEmoji("magnifying glass tilted right", []int32{128270}, "mag_right"),
|
||||
NewEmoji("candle", []int32{128367, 65039}, "candle"),
|
||||
NewEmoji("light bulb", []int32{128161}, "bulb"),
|
||||
NewEmoji("flashlight", []int32{128294}, "flashlight"),
|
||||
NewEmoji("red paper lantern", []int32{127982}, "izakaya_lantern"),
|
||||
NewEmoji("diya lamp", []int32{129684}, "diya_lamp"),
|
||||
NewEmoji("notebook with decorative cover", []int32{128212}, "notebook_with_decorative_cover"),
|
||||
NewEmoji("closed book", []int32{128213}, "closed_book"),
|
||||
NewEmoji("open book", []int32{128214}, "book"),
|
||||
NewEmoji("green book", []int32{128215}, "green_book"),
|
||||
NewEmoji("blue book", []int32{128216}, "blue_book"),
|
||||
NewEmoji("orange book", []int32{128217}, "orange_book"),
|
||||
NewEmoji("books", []int32{128218}, "books"),
|
||||
NewEmoji("notebook", []int32{128211}, "notebook"),
|
||||
NewEmoji("ledger", []int32{128210}, "ledger"),
|
||||
NewEmoji("page with curl", []int32{128195}, "page_with_curl"),
|
||||
NewEmoji("scroll", []int32{128220}, "scroll"),
|
||||
NewEmoji("page facing up", []int32{128196}, "page_facing_up"),
|
||||
NewEmoji("newspaper", []int32{128240}, "newspaper"),
|
||||
NewEmoji("rolled-up newspaper", []int32{128478, 65039}, "newspaper_roll"),
|
||||
NewEmoji("bookmark tabs", []int32{128209}, "bookmark_tabs"),
|
||||
NewEmoji("bookmark", []int32{128278}, "bookmark"),
|
||||
NewEmoji("label", []int32{127991, 65039}, "label"),
|
||||
NewEmoji("money bag", []int32{128176}, "moneybag"),
|
||||
NewEmoji("yen banknote", []int32{128180}, "yen"),
|
||||
NewEmoji("dollar banknote", []int32{128181}, "dollar"),
|
||||
NewEmoji("euro banknote", []int32{128182}, "euro"),
|
||||
NewEmoji("pound banknote", []int32{128183}, "pound"),
|
||||
NewEmoji("money with wings", []int32{128184}, "money_with_wings"),
|
||||
NewEmoji("credit card", []int32{128179}, "credit_card"),
|
||||
NewEmoji("receipt", []int32{129534}, "receipt"),
|
||||
NewEmoji("chart increasing with yen", []int32{128185}, "chart"),
|
||||
NewEmoji("envelope", []int32{9993, 65039}, "email"),
|
||||
NewEmoji("e-mail", []int32{128231}, "e-mail"),
|
||||
NewEmoji("incoming envelope", []int32{128232}, "incoming_envelope"),
|
||||
NewEmoji("envelope with arrow", []int32{128233}, "envelope_with_arrow"),
|
||||
NewEmoji("outbox tray", []int32{128228}, "outbox_tray"),
|
||||
NewEmoji("inbox tray", []int32{128229}, "inbox_tray"),
|
||||
NewEmoji("package", []int32{128230}, "package"),
|
||||
NewEmoji("closed mailbox with raised flag", []int32{128235}, "mailbox"),
|
||||
NewEmoji("closed mailbox with lowered flag", []int32{128234}, "mailbox_closed"),
|
||||
NewEmoji("open mailbox with raised flag", []int32{128236}, "mailbox_with_mail"),
|
||||
NewEmoji("open mailbox with lowered flag", []int32{128237}, "mailbox_with_no_mail"),
|
||||
NewEmoji("postbox", []int32{128238}, "postbox"),
|
||||
NewEmoji("ballot box with ballot", []int32{128499, 65039}, "ballot_box"),
|
||||
NewEmoji("pencil", []int32{9999, 65039}, "pencil2"),
|
||||
NewEmoji("black nib", []int32{10002, 65039}, "black_nib"),
|
||||
NewEmoji("fountain pen", []int32{128395, 65039}, "fountain_pen"),
|
||||
NewEmoji("pen", []int32{128394, 65039}, "pen"),
|
||||
NewEmoji("paintbrush", []int32{128396, 65039}, "paintbrush"),
|
||||
NewEmoji("crayon", []int32{128397, 65039}, "crayon"),
|
||||
NewEmoji("memo", []int32{128221}, "memo"),
|
||||
NewEmoji("briefcase", []int32{128188}, "briefcase"),
|
||||
NewEmoji("file folder", []int32{128193}, "file_folder"),
|
||||
NewEmoji("open file folder", []int32{128194}, "open_file_folder"),
|
||||
NewEmoji("card index dividers", []int32{128450, 65039}, "card_index_dividers"),
|
||||
NewEmoji("calendar", []int32{128197}, "date"),
|
||||
NewEmoji("tear-off calendar", []int32{128198}, "calendar"),
|
||||
NewEmoji("spiral notepad", []int32{128466, 65039}, "spiral_notepad"),
|
||||
NewEmoji("spiral calendar", []int32{128467, 65039}, "spiral_calendar"),
|
||||
NewEmoji("card index", []int32{128199}, "card_index"),
|
||||
NewEmoji("chart increasing", []int32{128200}, "chart_with_upwards_trend"),
|
||||
NewEmoji("chart decreasing", []int32{128201}, "chart_with_downwards_trend"),
|
||||
NewEmoji("bar chart", []int32{128202}, "bar_chart"),
|
||||
NewEmoji("clipboard", []int32{128203}, "clipboard"),
|
||||
NewEmoji("pushpin", []int32{128204}, "pushpin"),
|
||||
NewEmoji("round pushpin", []int32{128205}, "round_pushpin"),
|
||||
NewEmoji("paperclip", []int32{128206}, "paperclip"),
|
||||
NewEmoji("linked paperclips", []int32{128391, 65039}, "paperclips"),
|
||||
NewEmoji("straight ruler", []int32{128207}, "straight_ruler"),
|
||||
NewEmoji("triangular ruler", []int32{128208}, "triangular_ruler"),
|
||||
NewEmoji("scissors", []int32{9986, 65039}, "scissors"),
|
||||
NewEmoji("card file box", []int32{128451, 65039}, "card_file_box"),
|
||||
NewEmoji("file cabinet", []int32{128452, 65039}, "file_cabinet"),
|
||||
NewEmoji("wastebasket", []int32{128465, 65039}, "wastebasket"),
|
||||
NewEmoji("locked", []int32{128274}, "lock"),
|
||||
NewEmoji("unlocked", []int32{128275}, "unlock"),
|
||||
NewEmoji("locked with pen", []int32{128271}, "lock_with_ink_pen"),
|
||||
NewEmoji("locked with key", []int32{128272}, "closed_lock_with_key"),
|
||||
NewEmoji("key", []int32{128273}, "key"),
|
||||
NewEmoji("old key", []int32{128477, 65039}, "old_key"),
|
||||
NewEmoji("hammer", []int32{128296}, "hammer"),
|
||||
NewEmoji("axe", []int32{129683}, "axe"),
|
||||
NewEmoji("pick", []int32{9935, 65039}, "pick"),
|
||||
NewEmoji("hammer and pick", []int32{9874, 65039}, "hammer_and_pick"),
|
||||
NewEmoji("hammer and wrench", []int32{128736, 65039}, "hammer_and_wrench"),
|
||||
NewEmoji("dagger", []int32{128481, 65039}, "dagger"),
|
||||
NewEmoji("crossed swords", []int32{9876, 65039}, "crossed_swords"),
|
||||
NewEmoji("pistol", []int32{128299}, "gun"),
|
||||
NewEmoji("bow and arrow", []int32{127993}, "bow_and_arrow"),
|
||||
NewEmoji("shield", []int32{128737, 65039}, "shield"),
|
||||
NewEmoji("wrench", []int32{128295}, "wrench"),
|
||||
NewEmoji("nut and bolt", []int32{128297}, "nut_and_bolt"),
|
||||
NewEmoji("gear", []int32{9881, 65039}, "gear"),
|
||||
NewEmoji("clamp", []int32{128476, 65039}, "clamp"),
|
||||
NewEmoji("balance scale", []int32{9878, 65039}, "balance_scale"),
|
||||
NewEmoji("white cane", []int32{129455}, "probing_cane"),
|
||||
NewEmoji("link", []int32{128279}, "link"),
|
||||
NewEmoji("chains", []int32{9939, 65039}, "chains"),
|
||||
NewEmoji("toolbox", []int32{129520}, "toolbox"),
|
||||
NewEmoji("magnet", []int32{129522}, "magnet"),
|
||||
NewEmoji("alembic", []int32{9879, 65039}, "alembic"),
|
||||
NewEmoji("test tube", []int32{129514}, "test_tube"),
|
||||
NewEmoji("petri dish", []int32{129515}, "petri_dish"),
|
||||
NewEmoji("dna", []int32{129516}, "dna"),
|
||||
NewEmoji("microscope", []int32{128300}, "microscope"),
|
||||
NewEmoji("telescope", []int32{128301}, "telescope"),
|
||||
NewEmoji("satellite antenna", []int32{128225}, "satellite"),
|
||||
NewEmoji("syringe", []int32{128137}, "syringe"),
|
||||
NewEmoji("drop of blood", []int32{129656}, "drop_of_blood"),
|
||||
NewEmoji("pill", []int32{128138}, "pill"),
|
||||
NewEmoji("adhesive bandage", []int32{129657}, "adhesive_bandage"),
|
||||
NewEmoji("stethoscope", []int32{129658}, "stethoscope"),
|
||||
NewEmoji("door", []int32{128682}, "door"),
|
||||
NewEmoji("bed", []int32{128719, 65039}, "bed"),
|
||||
NewEmoji("couch and lamp", []int32{128715, 65039}, "couch_and_lamp"),
|
||||
NewEmoji("chair", []int32{129681}, "chair"),
|
||||
NewEmoji("toilet", []int32{128701}, "toilet"),
|
||||
NewEmoji("shower", []int32{128703}, "shower"),
|
||||
NewEmoji("bathtub", []int32{128705}, "bathtub"),
|
||||
NewEmoji("razor", []int32{129682}, "razor"),
|
||||
NewEmoji("lotion bottle", []int32{129524}, "lotion_bottle"),
|
||||
NewEmoji("safety pin", []int32{129527}, "safety_pin"),
|
||||
NewEmoji("broom", []int32{129529}, "broom"),
|
||||
NewEmoji("basket", []int32{129530}, "basket"),
|
||||
NewEmoji("roll of paper", []int32{129531}, "roll_of_paper"),
|
||||
NewEmoji("soap", []int32{129532}, "soap"),
|
||||
NewEmoji("sponge", []int32{129533}, "sponge"),
|
||||
NewEmoji("fire extinguisher", []int32{129519}, "fire_extinguisher"),
|
||||
NewEmoji("shopping cart", []int32{128722}, "shopping_cart"),
|
||||
NewEmoji("cigarette", []int32{128684}, "smoking"),
|
||||
NewEmoji("coffin", []int32{9904, 65039}, "coffin"),
|
||||
NewEmoji("funeral urn", []int32{9905, 65039}, "funeral_urn"),
|
||||
NewEmoji("moai", []int32{128511}, "moyai"),
|
||||
NewEmoji("ATM sign", []int32{127975}, "atm"),
|
||||
NewEmoji("litter in bin sign", []int32{128686}, "put_litter_in_its_place"),
|
||||
NewEmoji("potable water", []int32{128688}, "potable_water"),
|
||||
NewEmoji("wheelchair symbol", []int32{9855}, "wheelchair"),
|
||||
NewEmoji("men’s room", []int32{128697}, "mens"),
|
||||
NewEmoji("women’s room", []int32{128698}, "womens"),
|
||||
NewEmoji("restroom", []int32{128699}, "restroom"),
|
||||
NewEmoji("baby symbol", []int32{128700}, "baby_symbol"),
|
||||
NewEmoji("water closet", []int32{128702}, "wc"),
|
||||
NewEmoji("passport control", []int32{128706}, "passport_control"),
|
||||
NewEmoji("customs", []int32{128707}, "customs"),
|
||||
NewEmoji("baggage claim", []int32{128708}, "baggage_claim"),
|
||||
NewEmoji("left luggage", []int32{128709}, "left_luggage"),
|
||||
NewEmoji("warning", []int32{9888, 65039}, "warning"),
|
||||
NewEmoji("children crossing", []int32{128696}, "children_crossing"),
|
||||
NewEmoji("no entry", []int32{9940}, "no_entry"),
|
||||
NewEmoji("prohibited", []int32{128683}, "no_entry_sign"),
|
||||
NewEmoji("no bicycles", []int32{128691}, "no_bicycles"),
|
||||
NewEmoji("no smoking", []int32{128685}, "no_smoking"),
|
||||
NewEmoji("no littering", []int32{128687}, "do_not_litter"),
|
||||
NewEmoji("non-potable water", []int32{128689}, "non-potable_water"),
|
||||
NewEmoji("no pedestrians", []int32{128695}, "no_pedestrians"),
|
||||
NewEmoji("no mobile phones", []int32{128245}, "no_mobile_phones"),
|
||||
NewEmoji("no one under eighteen", []int32{128286}, "underage"),
|
||||
NewEmoji("radioactive", []int32{9762, 65039}, "radioactive"),
|
||||
NewEmoji("biohazard", []int32{9763, 65039}, "biohazard"),
|
||||
NewEmoji("up arrow", []int32{11014, 65039}, "arrow_up"),
|
||||
NewEmoji("up-right arrow", []int32{8599, 65039}, "arrow_upper_right"),
|
||||
NewEmoji("right arrow", []int32{10145, 65039}, "arrow_right"),
|
||||
NewEmoji("down-right arrow", []int32{8600, 65039}, "arrow_lower_right"),
|
||||
NewEmoji("down arrow", []int32{11015, 65039}, "arrow_down"),
|
||||
NewEmoji("down-left arrow", []int32{8601, 65039}, "arrow_lower_left"),
|
||||
NewEmoji("left arrow", []int32{11013, 65039}, "arrow_left"),
|
||||
NewEmoji("up-left arrow", []int32{8598, 65039}, "arrow_upper_left"),
|
||||
NewEmoji("up-down arrow", []int32{8597, 65039}, "arrow_up_down"),
|
||||
NewEmoji("left-right arrow", []int32{8596, 65039}, "left_right_arrow"),
|
||||
NewEmoji("right arrow curving left", []int32{8617, 65039}, "leftwards_arrow_with_hook"),
|
||||
NewEmoji("left arrow curving right", []int32{8618, 65039}, "arrow_right_hook"),
|
||||
NewEmoji("right arrow curving up", []int32{10548, 65039}, "arrow_heading_up"),
|
||||
NewEmoji("right arrow curving down", []int32{10549, 65039}, "arrow_heading_down"),
|
||||
NewEmoji("clockwise vertical arrows", []int32{128259}, "arrows_clockwise"),
|
||||
NewEmoji("counterclockwise arrows button", []int32{128260}, "arrows_counterclockwise"),
|
||||
NewEmoji("BACK arrow", []int32{128281}, "back"),
|
||||
NewEmoji("END arrow", []int32{128282}, "end"),
|
||||
NewEmoji("ON! arrow", []int32{128283}, "on"),
|
||||
NewEmoji("SOON arrow", []int32{128284}, "soon"),
|
||||
NewEmoji("TOP arrow", []int32{128285}, "top"),
|
||||
NewEmoji("place of worship", []int32{128720}, "place_of_worship"),
|
||||
NewEmoji("atom symbol", []int32{9883, 65039}, "atom_symbol"),
|
||||
NewEmoji("om", []int32{128329, 65039}, "om"),
|
||||
NewEmoji("star of David", []int32{10017, 65039}, "star_of_david"),
|
||||
NewEmoji("wheel of dharma", []int32{9784, 65039}, "wheel_of_dharma"),
|
||||
NewEmoji("yin yang", []int32{9775, 65039}, "yin_yang"),
|
||||
NewEmoji("latin cross", []int32{10013, 65039}, "latin_cross"),
|
||||
NewEmoji("orthodox cross", []int32{9766, 65039}, "orthodox_cross"),
|
||||
NewEmoji("star and crescent", []int32{9770, 65039}, "star_and_crescent"),
|
||||
NewEmoji("peace symbol", []int32{9774, 65039}, "peace_symbol"),
|
||||
NewEmoji("menorah", []int32{128334}, "menorah"),
|
||||
NewEmoji("dotted six-pointed star", []int32{128303}, "six_pointed_star"),
|
||||
NewEmoji("Aries", []int32{9800}, "aries"),
|
||||
NewEmoji("Taurus", []int32{9801}, "taurus"),
|
||||
NewEmoji("Gemini", []int32{9802}, "gemini"),
|
||||
NewEmoji("Cancer", []int32{9803}, "cancer"),
|
||||
NewEmoji("Leo", []int32{9804}, "leo"),
|
||||
NewEmoji("Virgo", []int32{9805}, "virgo"),
|
||||
NewEmoji("Libra", []int32{9806}, "libra"),
|
||||
NewEmoji("Scorpio", []int32{9807}, "scorpius"),
|
||||
NewEmoji("Sagittarius", []int32{9808}, "sagittarius"),
|
||||
NewEmoji("Capricorn", []int32{9809}, "capricorn"),
|
||||
NewEmoji("Aquarius", []int32{9810}, "aquarius"),
|
||||
NewEmoji("Pisces", []int32{9811}, "pisces"),
|
||||
NewEmoji("Ophiuchus", []int32{9934}, "ophiuchus"),
|
||||
NewEmoji("shuffle tracks button", []int32{128256}, "twisted_rightwards_arrows"),
|
||||
NewEmoji("repeat button", []int32{128257}, "repeat"),
|
||||
NewEmoji("repeat single button", []int32{128258}, "repeat_one"),
|
||||
NewEmoji("play button", []int32{9654, 65039}, "arrow_forward"),
|
||||
NewEmoji("fast-forward button", []int32{9193}, "fast_forward"),
|
||||
NewEmoji("next track button", []int32{9197, 65039}, "next_track_button"),
|
||||
NewEmoji("play or pause button", []int32{9199, 65039}, "play_or_pause_button"),
|
||||
NewEmoji("reverse button", []int32{9664, 65039}, "arrow_backward"),
|
||||
NewEmoji("fast reverse button", []int32{9194}, "rewind"),
|
||||
NewEmoji("last track button", []int32{9198, 65039}, "previous_track_button"),
|
||||
NewEmoji("upwards button", []int32{128316}, "arrow_up_small"),
|
||||
NewEmoji("fast up button", []int32{9195}, "arrow_double_up"),
|
||||
NewEmoji("downwards button", []int32{128317}, "arrow_down_small"),
|
||||
NewEmoji("fast down button", []int32{9196}, "arrow_double_down"),
|
||||
NewEmoji("pause button", []int32{9208, 65039}, "pause_button"),
|
||||
NewEmoji("stop button", []int32{9209, 65039}, "stop_button"),
|
||||
NewEmoji("record button", []int32{9210, 65039}, "record_button"),
|
||||
NewEmoji("eject button", []int32{9167, 65039}, "eject_button"),
|
||||
NewEmoji("cinema", []int32{127910}, "cinema"),
|
||||
NewEmoji("dim button", []int32{128261}, "low_brightness"),
|
||||
NewEmoji("bright button", []int32{128262}, "high_brightness"),
|
||||
NewEmoji("antenna bars", []int32{128246}, "signal_strength"),
|
||||
NewEmoji("vibration mode", []int32{128243}, "vibration_mode"),
|
||||
NewEmoji("mobile phone off", []int32{128244}, "mobile_phone_off"),
|
||||
NewEmoji("female sign", []int32{9792, 65039}, "female_sign"),
|
||||
NewEmoji("male sign", []int32{9794, 65039}, "male_sign"),
|
||||
NewEmoji("multiply", []int32{10006, 65039}, "heavy_multiplication_x"),
|
||||
NewEmoji("plus", []int32{10133}, "heavy_plus_sign"),
|
||||
NewEmoji("minus", []int32{10134}, "heavy_minus_sign"),
|
||||
NewEmoji("divide", []int32{10135}, "heavy_division_sign"),
|
||||
NewEmoji("infinity", []int32{9854, 65039}, "infinity"),
|
||||
NewEmoji("double exclamation mark", []int32{8252, 65039}, "bangbang"),
|
||||
NewEmoji("exclamation question mark", []int32{8265, 65039}, "interrobang"),
|
||||
NewEmoji("question mark", []int32{10067}, "question"),
|
||||
NewEmoji("white question mark", []int32{10068}, "grey_question"),
|
||||
NewEmoji("white exclamation mark", []int32{10069}, "grey_exclamation"),
|
||||
NewEmoji("exclamation mark", []int32{10071}, "exclamation"),
|
||||
NewEmoji("wavy dash", []int32{12336, 65039}, "wavy_dash"),
|
||||
NewEmoji("currency exchange", []int32{128177}, "currency_exchange"),
|
||||
NewEmoji("heavy dollar sign", []int32{128178}, "heavy_dollar_sign"),
|
||||
NewEmoji("medical symbol", []int32{9877, 65039}, "medical_symbol"),
|
||||
NewEmoji("recycling symbol", []int32{9851, 65039}, "recycle"),
|
||||
NewEmoji("fleur-de-lis", []int32{9884, 65039}, "fleur_de_lis"),
|
||||
NewEmoji("trident emblem", []int32{128305}, "trident"),
|
||||
NewEmoji("name badge", []int32{128219}, "name_badge"),
|
||||
NewEmoji("Japanese symbol for beginner", []int32{128304}, "beginner"),
|
||||
NewEmoji("hollow red circle", []int32{11093}, "o"),
|
||||
NewEmoji("check mark button", []int32{9989}, "white_check_mark"),
|
||||
NewEmoji("check box with check", []int32{9745, 65039}, "ballot_box_with_check"),
|
||||
NewEmoji("check mark", []int32{10004, 65039}, "heavy_check_mark"),
|
||||
NewEmoji("cross mark", []int32{10060}, "x"),
|
||||
NewEmoji("cross mark button", []int32{10062}, "negative_squared_cross_mark"),
|
||||
NewEmoji("curly loop", []int32{10160}, "curly_loop"),
|
||||
NewEmoji("double curly loop", []int32{10175}, "loop"),
|
||||
NewEmoji("part alternation mark", []int32{12349, 65039}, "part_alternation_mark"),
|
||||
NewEmoji("eight-spoked asterisk", []int32{10035, 65039}, "eight_spoked_asterisk"),
|
||||
NewEmoji("eight-pointed star", []int32{10036, 65039}, "eight_pointed_black_star"),
|
||||
NewEmoji("sparkle", []int32{10055, 65039}, "sparkle"),
|
||||
NewEmoji("copyright", []int32{169, 65039}, "copyright"),
|
||||
NewEmoji("registered", []int32{174, 65039}, "registered"),
|
||||
NewEmoji("trade mark", []int32{8482, 65039}, "tm"),
|
||||
NewEmoji("keycap: #", []int32{35, 65039, 8419}, "hash"),
|
||||
NewEmoji("keycap: *", []int32{42, 65039, 8419}, "asterisk"),
|
||||
NewEmoji("keycap: 0", []int32{48, 65039, 8419}, "zero"),
|
||||
NewEmoji("keycap: 1", []int32{49, 65039, 8419}, "one"),
|
||||
NewEmoji("keycap: 2", []int32{50, 65039, 8419}, "two"),
|
||||
NewEmoji("keycap: 3", []int32{51, 65039, 8419}, "three"),
|
||||
NewEmoji("keycap: 4", []int32{52, 65039, 8419}, "four"),
|
||||
NewEmoji("keycap: 5", []int32{53, 65039, 8419}, "five"),
|
||||
NewEmoji("keycap: 6", []int32{54, 65039, 8419}, "six"),
|
||||
NewEmoji("keycap: 7", []int32{55, 65039, 8419}, "seven"),
|
||||
NewEmoji("keycap: 8", []int32{56, 65039, 8419}, "eight"),
|
||||
NewEmoji("keycap: 9", []int32{57, 65039, 8419}, "nine"),
|
||||
NewEmoji("keycap: 10", []int32{128287}, "keycap_ten"),
|
||||
NewEmoji("input latin uppercase", []int32{128288}, "capital_abcd"),
|
||||
NewEmoji("input latin lowercase", []int32{128289}, "abcd"),
|
||||
NewEmoji("input numbers", []int32{128290}, "1234"),
|
||||
NewEmoji("input symbols", []int32{128291}, "symbols"),
|
||||
NewEmoji("input latin letters", []int32{128292}, "abc"),
|
||||
NewEmoji("A button (blood type)", []int32{127344, 65039}, "a"),
|
||||
NewEmoji("AB button (blood type)", []int32{127374}, "ab"),
|
||||
NewEmoji("B button (blood type)", []int32{127345, 65039}, "b"),
|
||||
NewEmoji("CL button", []int32{127377}, "cl"),
|
||||
NewEmoji("COOL button", []int32{127378}, "cool"),
|
||||
NewEmoji("FREE button", []int32{127379}, "free"),
|
||||
NewEmoji("information", []int32{8505, 65039}, "information_source"),
|
||||
NewEmoji("ID button", []int32{127380}, "id"),
|
||||
NewEmoji("circled M", []int32{9410, 65039}, "m"),
|
||||
NewEmoji("NEW button", []int32{127381}, "new"),
|
||||
NewEmoji("NG button", []int32{127382}, "ng"),
|
||||
NewEmoji("O button (blood type)", []int32{127358, 65039}, "o2"),
|
||||
NewEmoji("OK button", []int32{127383}, "ok"),
|
||||
NewEmoji("P button", []int32{127359, 65039}, "parking"),
|
||||
NewEmoji("SOS button", []int32{127384}, "sos"),
|
||||
NewEmoji("UP! button", []int32{127385}, "up"),
|
||||
NewEmoji("VS button", []int32{127386}, "vs"),
|
||||
NewEmoji("Japanese “here” button", []int32{127489}, "koko"),
|
||||
NewEmoji("Japanese “service charge” button", []int32{127490, 65039}, "sa"),
|
||||
NewEmoji("Japanese “monthly amount” button", []int32{127543, 65039}, "u6708"),
|
||||
NewEmoji("Japanese “not free of charge” button", []int32{127542}, "u6709"),
|
||||
NewEmoji("Japanese “reserved” button", []int32{127535}, "u6307"),
|
||||
NewEmoji("Japanese “bargain” button", []int32{127568}, "ideograph_advantage"),
|
||||
NewEmoji("Japanese “discount” button", []int32{127545}, "u5272"),
|
||||
NewEmoji("Japanese “free of charge” button", []int32{127514}, "u7121"),
|
||||
NewEmoji("Japanese “prohibited” button", []int32{127538}, "u7981"),
|
||||
NewEmoji("Japanese “acceptable” button", []int32{127569}, "accept"),
|
||||
NewEmoji("Japanese “application” button", []int32{127544}, "u7533"),
|
||||
NewEmoji("Japanese “passing grade” button", []int32{127540}, "u5408"),
|
||||
NewEmoji("Japanese “vacancy” button", []int32{127539}, "u7a7a"),
|
||||
NewEmoji("Japanese “congratulations” button", []int32{12951, 65039}, "congratulations"),
|
||||
NewEmoji("Japanese “secret” button", []int32{12953, 65039}, "secret"),
|
||||
NewEmoji("Japanese “open for business” button", []int32{127546}, "u55b6"),
|
||||
NewEmoji("Japanese “no vacancy” button", []int32{127541}, "u6e80"),
|
||||
NewEmoji("red circle", []int32{128308}, "red_circle"),
|
||||
NewEmoji("orange circle", []int32{128992}, "orange_circle"),
|
||||
NewEmoji("yellow circle", []int32{128993}, "yellow_circle"),
|
||||
NewEmoji("green circle", []int32{128994}, "green_circle"),
|
||||
NewEmoji("blue circle", []int32{128309}, "large_blue_circle"),
|
||||
NewEmoji("purple circle", []int32{128995}, "purple_circle"),
|
||||
NewEmoji("brown circle", []int32{128996}, "brown_circle"),
|
||||
NewEmoji("black circle", []int32{9899}, "black_circle"),
|
||||
NewEmoji("white circle", []int32{9898}, "white_circle"),
|
||||
NewEmoji("red square", []int32{128997}, "red_square"),
|
||||
NewEmoji("orange square", []int32{128999}, "orange_square"),
|
||||
NewEmoji("yellow square", []int32{129000}, "yellow_square"),
|
||||
NewEmoji("green square", []int32{129001}, "green_square"),
|
||||
NewEmoji("blue square", []int32{128998}, "blue_square"),
|
||||
NewEmoji("purple square", []int32{129002}, "purple_square"),
|
||||
NewEmoji("brown square", []int32{129003}, "brown_square"),
|
||||
NewEmoji("black large square", []int32{11035}, "black_large_square"),
|
||||
NewEmoji("white large square", []int32{11036}, "white_large_square"),
|
||||
NewEmoji("black medium square", []int32{9724, 65039}, "black_medium_square"),
|
||||
NewEmoji("white medium square", []int32{9723, 65039}, "white_medium_square"),
|
||||
NewEmoji("black medium-small square", []int32{9726}, "black_medium_small_square"),
|
||||
NewEmoji("white medium-small square", []int32{9725}, "white_medium_small_square"),
|
||||
NewEmoji("black small square", []int32{9642, 65039}, "black_small_square"),
|
||||
NewEmoji("white small square", []int32{9643, 65039}, "white_small_square"),
|
||||
NewEmoji("large orange diamond", []int32{128310}, "large_orange_diamond"),
|
||||
NewEmoji("large blue diamond", []int32{128311}, "large_blue_diamond"),
|
||||
NewEmoji("small orange diamond", []int32{128312}, "small_orange_diamond"),
|
||||
NewEmoji("small blue diamond", []int32{128313}, "small_blue_diamond"),
|
||||
NewEmoji("red triangle pointed up", []int32{128314}, "small_red_triangle"),
|
||||
NewEmoji("red triangle pointed down", []int32{128315}, "small_red_triangle_down"),
|
||||
NewEmoji("diamond with a dot", []int32{128160}, "diamond_shape_with_a_dot_inside"),
|
||||
NewEmoji("radio button", []int32{128280}, "radio_button"),
|
||||
NewEmoji("white square button", []int32{128307}, "white_square_button"),
|
||||
NewEmoji("black square button", []int32{128306}, "black_square_button"),
|
||||
NewEmoji("chequered flag", []int32{127937}, "checkered_flag"),
|
||||
NewEmoji("triangular flag", []int32{128681}, "triangular_flag_on_post"),
|
||||
NewEmoji("crossed flags", []int32{127884}, "crossed_flags"),
|
||||
NewEmoji("black flag", []int32{127988}, "black_flag"),
|
||||
NewEmoji("white flag", []int32{127987, 65039}, "white_flag"),
|
||||
NewEmoji("rainbow flag", []int32{127987, 65039, 8205, 127752}, "rainbow_flag"),
|
||||
NewEmoji("pirate flag", []int32{127988, 8205, 9760, 65039}, "pirate_flag"),
|
||||
NewEmoji("flag: Ascension Island", []int32{127462, 127464}, "ascension_island"),
|
||||
NewEmoji("flag: Andorra", []int32{127462, 127465}, "andorra"),
|
||||
NewEmoji("flag: United Arab Emirates", []int32{127462, 127466}, "united_arab_emirates"),
|
||||
NewEmoji("flag: Afghanistan", []int32{127462, 127467}, "afghanistan"),
|
||||
NewEmoji("flag: Antigua & Barbuda", []int32{127462, 127468}, "antigua_barbuda"),
|
||||
NewEmoji("flag: Anguilla", []int32{127462, 127470}, "anguilla"),
|
||||
NewEmoji("flag: Albania", []int32{127462, 127473}, "albania"),
|
||||
NewEmoji("flag: Armenia", []int32{127462, 127474}, "armenia"),
|
||||
NewEmoji("flag: Angola", []int32{127462, 127476}, "angola"),
|
||||
NewEmoji("flag: Antarctica", []int32{127462, 127478}, "antarctica"),
|
||||
NewEmoji("flag: Argentina", []int32{127462, 127479}, "argentina"),
|
||||
NewEmoji("flag: American Samoa", []int32{127462, 127480}, "american_samoa"),
|
||||
NewEmoji("flag: Austria", []int32{127462, 127481}, "austria"),
|
||||
NewEmoji("flag: Australia", []int32{127462, 127482}, "australia"),
|
||||
NewEmoji("flag: Aruba", []int32{127462, 127484}, "aruba"),
|
||||
NewEmoji("flag: Åland Islands", []int32{127462, 127485}, "aland_islands"),
|
||||
NewEmoji("flag: Azerbaijan", []int32{127462, 127487}, "azerbaijan"),
|
||||
NewEmoji("flag: Bosnia & Herzegovina", []int32{127463, 127462}, "bosnia_herzegovina"),
|
||||
NewEmoji("flag: Barbados", []int32{127463, 127463}, "barbados"),
|
||||
NewEmoji("flag: Bangladesh", []int32{127463, 127465}, "bangladesh"),
|
||||
NewEmoji("flag: Belgium", []int32{127463, 127466}, "belgium"),
|
||||
NewEmoji("flag: Burkina Faso", []int32{127463, 127467}, "burkina_faso"),
|
||||
NewEmoji("flag: Bulgaria", []int32{127463, 127468}, "bulgaria"),
|
||||
NewEmoji("flag: Bahrain", []int32{127463, 127469}, "bahrain"),
|
||||
NewEmoji("flag: Burundi", []int32{127463, 127470}, "burundi"),
|
||||
NewEmoji("flag: Benin", []int32{127463, 127471}, "benin"),
|
||||
NewEmoji("flag: St. Barthélemy", []int32{127463, 127473}, "st_barthelemy"),
|
||||
NewEmoji("flag: Bermuda", []int32{127463, 127474}, "bermuda"),
|
||||
NewEmoji("flag: Brunei", []int32{127463, 127475}, "brunei"),
|
||||
NewEmoji("flag: Bolivia", []int32{127463, 127476}, "bolivia"),
|
||||
NewEmoji("flag: Caribbean Netherlands", []int32{127463, 127478}, "caribbean_netherlands"),
|
||||
NewEmoji("flag: Brazil", []int32{127463, 127479}, "brazil"),
|
||||
NewEmoji("flag: Bahamas", []int32{127463, 127480}, "bahamas"),
|
||||
NewEmoji("flag: Bhutan", []int32{127463, 127481}, "bhutan"),
|
||||
NewEmoji("flag: Bouvet Island", []int32{127463, 127483}, "bouvet_island"),
|
||||
NewEmoji("flag: Botswana", []int32{127463, 127484}, "botswana"),
|
||||
NewEmoji("flag: Belarus", []int32{127463, 127486}, "belarus"),
|
||||
NewEmoji("flag: Belize", []int32{127463, 127487}, "belize"),
|
||||
NewEmoji("flag: Canada", []int32{127464, 127462}, "canada"),
|
||||
NewEmoji("flag: Cocos (Keeling) Islands", []int32{127464, 127464}, "cocos_islands"),
|
||||
NewEmoji("flag: Congo - Kinshasa", []int32{127464, 127465}, "congo_kinshasa"),
|
||||
NewEmoji("flag: Central African Republic", []int32{127464, 127467}, "central_african_republic"),
|
||||
NewEmoji("flag: Congo - Brazzaville", []int32{127464, 127468}, "congo_brazzaville"),
|
||||
NewEmoji("flag: Switzerland", []int32{127464, 127469}, "switzerland"),
|
||||
NewEmoji("flag: Côte d’Ivoire", []int32{127464, 127470}, "cote_divoire"),
|
||||
NewEmoji("flag: Cook Islands", []int32{127464, 127472}, "cook_islands"),
|
||||
NewEmoji("flag: Chile", []int32{127464, 127473}, "chile"),
|
||||
NewEmoji("flag: Cameroon", []int32{127464, 127474}, "cameroon"),
|
||||
NewEmoji("flag: China", []int32{127464, 127475}, "cn"),
|
||||
NewEmoji("flag: Colombia", []int32{127464, 127476}, "colombia"),
|
||||
NewEmoji("flag: Clipperton Island", []int32{127464, 127477}, "clipperton_island"),
|
||||
NewEmoji("flag: Costa Rica", []int32{127464, 127479}, "costa_rica"),
|
||||
NewEmoji("flag: Cuba", []int32{127464, 127482}, "cuba"),
|
||||
NewEmoji("flag: Cape Verde", []int32{127464, 127483}, "cape_verde"),
|
||||
NewEmoji("flag: Curaçao", []int32{127464, 127484}, "curacao"),
|
||||
NewEmoji("flag: Christmas Island", []int32{127464, 127485}, "christmas_island"),
|
||||
NewEmoji("flag: Cyprus", []int32{127464, 127486}, "cyprus"),
|
||||
NewEmoji("flag: Czechia", []int32{127464, 127487}, "czech_republic"),
|
||||
NewEmoji("flag: Germany", []int32{127465, 127466}, "de"),
|
||||
NewEmoji("flag: Diego Garcia", []int32{127465, 127468}, "diego_garcia"),
|
||||
NewEmoji("flag: Djibouti", []int32{127465, 127471}, "djibouti"),
|
||||
NewEmoji("flag: Denmark", []int32{127465, 127472}, "denmark"),
|
||||
NewEmoji("flag: Dominica", []int32{127465, 127474}, "dominica"),
|
||||
NewEmoji("flag: Dominican Republic", []int32{127465, 127476}, "dominican_republic"),
|
||||
NewEmoji("flag: Algeria", []int32{127465, 127487}, "algeria"),
|
||||
NewEmoji("flag: Ceuta & Melilla", []int32{127466, 127462}, "ceuta_melilla"),
|
||||
NewEmoji("flag: Ecuador", []int32{127466, 127464}, "ecuador"),
|
||||
NewEmoji("flag: Estonia", []int32{127466, 127466}, "estonia"),
|
||||
NewEmoji("flag: Egypt", []int32{127466, 127468}, "egypt"),
|
||||
NewEmoji("flag: Western Sahara", []int32{127466, 127469}, "western_sahara"),
|
||||
NewEmoji("flag: Eritrea", []int32{127466, 127479}, "eritrea"),
|
||||
NewEmoji("flag: Spain", []int32{127466, 127480}, "es"),
|
||||
NewEmoji("flag: Ethiopia", []int32{127466, 127481}, "ethiopia"),
|
||||
NewEmoji("flag: European Union", []int32{127466, 127482}, "eu"),
|
||||
NewEmoji("flag: Finland", []int32{127467, 127470}, "finland"),
|
||||
NewEmoji("flag: Fiji", []int32{127467, 127471}, "fiji"),
|
||||
NewEmoji("flag: Falkland Islands", []int32{127467, 127472}, "falkland_islands"),
|
||||
NewEmoji("flag: Micronesia", []int32{127467, 127474}, "micronesia"),
|
||||
NewEmoji("flag: Faroe Islands", []int32{127467, 127476}, "faroe_islands"),
|
||||
NewEmoji("flag: France", []int32{127467, 127479}, "fr"),
|
||||
NewEmoji("flag: Gabon", []int32{127468, 127462}, "gabon"),
|
||||
NewEmoji("flag: United Kingdom", []int32{127468, 127463}, "gb"),
|
||||
NewEmoji("flag: Grenada", []int32{127468, 127465}, "grenada"),
|
||||
NewEmoji("flag: Georgia", []int32{127468, 127466}, "georgia"),
|
||||
NewEmoji("flag: French Guiana", []int32{127468, 127467}, "french_guiana"),
|
||||
NewEmoji("flag: Guernsey", []int32{127468, 127468}, "guernsey"),
|
||||
NewEmoji("flag: Ghana", []int32{127468, 127469}, "ghana"),
|
||||
NewEmoji("flag: Gibraltar", []int32{127468, 127470}, "gibraltar"),
|
||||
NewEmoji("flag: Greenland", []int32{127468, 127473}, "greenland"),
|
||||
NewEmoji("flag: Gambia", []int32{127468, 127474}, "gambia"),
|
||||
NewEmoji("flag: Guinea", []int32{127468, 127475}, "guinea"),
|
||||
NewEmoji("flag: Guadeloupe", []int32{127468, 127477}, "guadeloupe"),
|
||||
NewEmoji("flag: Equatorial Guinea", []int32{127468, 127478}, "equatorial_guinea"),
|
||||
NewEmoji("flag: Greece", []int32{127468, 127479}, "greece"),
|
||||
NewEmoji("flag: South Georgia & South Sandwich Islands", []int32{127468, 127480}, "south_georgia_south_sandwich_islands"),
|
||||
NewEmoji("flag: Guatemala", []int32{127468, 127481}, "guatemala"),
|
||||
NewEmoji("flag: Guam", []int32{127468, 127482}, "guam"),
|
||||
NewEmoji("flag: Guinea-Bissau", []int32{127468, 127484}, "guinea_bissau"),
|
||||
NewEmoji("flag: Guyana", []int32{127468, 127486}, "guyana"),
|
||||
NewEmoji("flag: Hong Kong SAR China", []int32{127469, 127472}, "hong_kong"),
|
||||
NewEmoji("flag: Heard & McDonald Islands", []int32{127469, 127474}, "heard_mcdonald_islands"),
|
||||
NewEmoji("flag: Honduras", []int32{127469, 127475}, "honduras"),
|
||||
NewEmoji("flag: Croatia", []int32{127469, 127479}, "croatia"),
|
||||
NewEmoji("flag: Haiti", []int32{127469, 127481}, "haiti"),
|
||||
NewEmoji("flag: Hungary", []int32{127469, 127482}, "hungary"),
|
||||
NewEmoji("flag: Canary Islands", []int32{127470, 127464}, "canary_islands"),
|
||||
NewEmoji("flag: Indonesia", []int32{127470, 127465}, "indonesia"),
|
||||
NewEmoji("flag: Ireland", []int32{127470, 127466}, "ireland"),
|
||||
NewEmoji("flag: Israel", []int32{127470, 127473}, "israel"),
|
||||
NewEmoji("flag: Isle of Man", []int32{127470, 127474}, "isle_of_man"),
|
||||
NewEmoji("flag: India", []int32{127470, 127475}, "india"),
|
||||
NewEmoji("flag: British Indian Ocean Territory", []int32{127470, 127476}, "british_indian_ocean_territory"),
|
||||
NewEmoji("flag: Iraq", []int32{127470, 127478}, "iraq"),
|
||||
NewEmoji("flag: Iran", []int32{127470, 127479}, "iran"),
|
||||
NewEmoji("flag: Iceland", []int32{127470, 127480}, "iceland"),
|
||||
NewEmoji("flag: Italy", []int32{127470, 127481}, "it"),
|
||||
NewEmoji("flag: Jersey", []int32{127471, 127466}, "jersey"),
|
||||
NewEmoji("flag: Jamaica", []int32{127471, 127474}, "jamaica"),
|
||||
NewEmoji("flag: Jordan", []int32{127471, 127476}, "jordan"),
|
||||
NewEmoji("flag: Japan", []int32{127471, 127477}, "jp"),
|
||||
NewEmoji("flag: Kenya", []int32{127472, 127466}, "kenya"),
|
||||
NewEmoji("flag: Kyrgyzstan", []int32{127472, 127468}, "kyrgyzstan"),
|
||||
NewEmoji("flag: Cambodia", []int32{127472, 127469}, "cambodia"),
|
||||
NewEmoji("flag: Kiribati", []int32{127472, 127470}, "kiribati"),
|
||||
NewEmoji("flag: Comoros", []int32{127472, 127474}, "comoros"),
|
||||
NewEmoji("flag: St. Kitts & Nevis", []int32{127472, 127475}, "st_kitts_nevis"),
|
||||
NewEmoji("flag: North Korea", []int32{127472, 127477}, "north_korea"),
|
||||
NewEmoji("flag: South Korea", []int32{127472, 127479}, "kr"),
|
||||
NewEmoji("flag: Kuwait", []int32{127472, 127484}, "kuwait"),
|
||||
NewEmoji("flag: Cayman Islands", []int32{127472, 127486}, "cayman_islands"),
|
||||
NewEmoji("flag: Kazakhstan", []int32{127472, 127487}, "kazakhstan"),
|
||||
NewEmoji("flag: Laos", []int32{127473, 127462}, "laos"),
|
||||
NewEmoji("flag: Lebanon", []int32{127473, 127463}, "lebanon"),
|
||||
NewEmoji("flag: St. Lucia", []int32{127473, 127464}, "st_lucia"),
|
||||
NewEmoji("flag: Liechtenstein", []int32{127473, 127470}, "liechtenstein"),
|
||||
NewEmoji("flag: Sri Lanka", []int32{127473, 127472}, "sri_lanka"),
|
||||
NewEmoji("flag: Liberia", []int32{127473, 127479}, "liberia"),
|
||||
NewEmoji("flag: Lesotho", []int32{127473, 127480}, "lesotho"),
|
||||
NewEmoji("flag: Lithuania", []int32{127473, 127481}, "lithuania"),
|
||||
NewEmoji("flag: Luxembourg", []int32{127473, 127482}, "luxembourg"),
|
||||
NewEmoji("flag: Latvia", []int32{127473, 127483}, "latvia"),
|
||||
NewEmoji("flag: Libya", []int32{127473, 127486}, "libya"),
|
||||
NewEmoji("flag: Morocco", []int32{127474, 127462}, "morocco"),
|
||||
NewEmoji("flag: Monaco", []int32{127474, 127464}, "monaco"),
|
||||
NewEmoji("flag: Moldova", []int32{127474, 127465}, "moldova"),
|
||||
NewEmoji("flag: Montenegro", []int32{127474, 127466}, "montenegro"),
|
||||
NewEmoji("flag: St. Martin", []int32{127474, 127467}, "st_martin"),
|
||||
NewEmoji("flag: Madagascar", []int32{127474, 127468}, "madagascar"),
|
||||
NewEmoji("flag: Marshall Islands", []int32{127474, 127469}, "marshall_islands"),
|
||||
NewEmoji("flag: North Macedonia", []int32{127474, 127472}, "macedonia"),
|
||||
NewEmoji("flag: Mali", []int32{127474, 127473}, "mali"),
|
||||
NewEmoji("flag: Myanmar (Burma)", []int32{127474, 127474}, "myanmar"),
|
||||
NewEmoji("flag: Mongolia", []int32{127474, 127475}, "mongolia"),
|
||||
NewEmoji("flag: Macao SAR China", []int32{127474, 127476}, "macau"),
|
||||
NewEmoji("flag: Northern Mariana Islands", []int32{127474, 127477}, "northern_mariana_islands"),
|
||||
NewEmoji("flag: Martinique", []int32{127474, 127478}, "martinique"),
|
||||
NewEmoji("flag: Mauritania", []int32{127474, 127479}, "mauritania"),
|
||||
NewEmoji("flag: Montserrat", []int32{127474, 127480}, "montserrat"),
|
||||
NewEmoji("flag: Malta", []int32{127474, 127481}, "malta"),
|
||||
NewEmoji("flag: Mauritius", []int32{127474, 127482}, "mauritius"),
|
||||
NewEmoji("flag: Maldives", []int32{127474, 127483}, "maldives"),
|
||||
NewEmoji("flag: Malawi", []int32{127474, 127484}, "malawi"),
|
||||
NewEmoji("flag: Mexico", []int32{127474, 127485}, "mexico"),
|
||||
NewEmoji("flag: Malaysia", []int32{127474, 127486}, "malaysia"),
|
||||
NewEmoji("flag: Mozambique", []int32{127474, 127487}, "mozambique"),
|
||||
NewEmoji("flag: Namibia", []int32{127475, 127462}, "namibia"),
|
||||
NewEmoji("flag: New Caledonia", []int32{127475, 127464}, "new_caledonia"),
|
||||
NewEmoji("flag: Niger", []int32{127475, 127466}, "niger"),
|
||||
NewEmoji("flag: Norfolk Island", []int32{127475, 127467}, "norfolk_island"),
|
||||
NewEmoji("flag: Nigeria", []int32{127475, 127468}, "nigeria"),
|
||||
NewEmoji("flag: Nicaragua", []int32{127475, 127470}, "nicaragua"),
|
||||
NewEmoji("flag: Netherlands", []int32{127475, 127473}, "netherlands"),
|
||||
NewEmoji("flag: Norway", []int32{127475, 127476}, "norway"),
|
||||
NewEmoji("flag: Nepal", []int32{127475, 127477}, "nepal"),
|
||||
NewEmoji("flag: Nauru", []int32{127475, 127479}, "nauru"),
|
||||
NewEmoji("flag: Niue", []int32{127475, 127482}, "niue"),
|
||||
NewEmoji("flag: New Zealand", []int32{127475, 127487}, "new_zealand"),
|
||||
NewEmoji("flag: Oman", []int32{127476, 127474}, "oman"),
|
||||
NewEmoji("flag: Panama", []int32{127477, 127462}, "panama"),
|
||||
NewEmoji("flag: Peru", []int32{127477, 127466}, "peru"),
|
||||
NewEmoji("flag: French Polynesia", []int32{127477, 127467}, "french_polynesia"),
|
||||
NewEmoji("flag: Papua New Guinea", []int32{127477, 127468}, "papua_new_guinea"),
|
||||
NewEmoji("flag: Philippines", []int32{127477, 127469}, "philippines"),
|
||||
NewEmoji("flag: Pakistan", []int32{127477, 127472}, "pakistan"),
|
||||
NewEmoji("flag: Poland", []int32{127477, 127473}, "poland"),
|
||||
NewEmoji("flag: St. Pierre & Miquelon", []int32{127477, 127474}, "st_pierre_miquelon"),
|
||||
NewEmoji("flag: Pitcairn Islands", []int32{127477, 127475}, "pitcairn_islands"),
|
||||
NewEmoji("flag: Puerto Rico", []int32{127477, 127479}, "puerto_rico"),
|
||||
NewEmoji("flag: Palestinian Territories", []int32{127477, 127480}, "palestinian_territories"),
|
||||
NewEmoji("flag: Portugal", []int32{127477, 127481}, "portugal"),
|
||||
NewEmoji("flag: Palau", []int32{127477, 127484}, "palau"),
|
||||
NewEmoji("flag: Paraguay", []int32{127477, 127486}, "paraguay"),
|
||||
NewEmoji("flag: Qatar", []int32{127478, 127462}, "qatar"),
|
||||
NewEmoji("flag: Réunion", []int32{127479, 127466}, "reunion"),
|
||||
NewEmoji("flag: Romania", []int32{127479, 127476}, "romania"),
|
||||
NewEmoji("flag: Serbia", []int32{127479, 127480}, "serbia"),
|
||||
NewEmoji("flag: Russia", []int32{127479, 127482}, "ru"),
|
||||
NewEmoji("flag: Rwanda", []int32{127479, 127484}, "rwanda"),
|
||||
NewEmoji("flag: Saudi Arabia", []int32{127480, 127462}, "saudi_arabia"),
|
||||
NewEmoji("flag: Solomon Islands", []int32{127480, 127463}, "solomon_islands"),
|
||||
NewEmoji("flag: Seychelles", []int32{127480, 127464}, "seychelles"),
|
||||
NewEmoji("flag: Sudan", []int32{127480, 127465}, "sudan"),
|
||||
NewEmoji("flag: Sweden", []int32{127480, 127466}, "sweden"),
|
||||
NewEmoji("flag: Singapore", []int32{127480, 127468}, "singapore"),
|
||||
NewEmoji("flag: St. Helena", []int32{127480, 127469}, "st_helena"),
|
||||
NewEmoji("flag: Slovenia", []int32{127480, 127470}, "slovenia"),
|
||||
NewEmoji("flag: Svalbard & Jan Mayen", []int32{127480, 127471}, "svalbard_jan_mayen"),
|
||||
NewEmoji("flag: Slovakia", []int32{127480, 127472}, "slovakia"),
|
||||
NewEmoji("flag: Sierra Leone", []int32{127480, 127473}, "sierra_leone"),
|
||||
NewEmoji("flag: San Marino", []int32{127480, 127474}, "san_marino"),
|
||||
NewEmoji("flag: Senegal", []int32{127480, 127475}, "senegal"),
|
||||
NewEmoji("flag: Somalia", []int32{127480, 127476}, "somalia"),
|
||||
NewEmoji("flag: Suriname", []int32{127480, 127479}, "suriname"),
|
||||
NewEmoji("flag: South Sudan", []int32{127480, 127480}, "south_sudan"),
|
||||
NewEmoji("flag: São Tomé & Príncipe", []int32{127480, 127481}, "sao_tome_principe"),
|
||||
NewEmoji("flag: El Salvador", []int32{127480, 127483}, "el_salvador"),
|
||||
NewEmoji("flag: Sint Maarten", []int32{127480, 127485}, "sint_maarten"),
|
||||
NewEmoji("flag: Syria", []int32{127480, 127486}, "syria"),
|
||||
NewEmoji("flag: Eswatini", []int32{127480, 127487}, "swaziland"),
|
||||
NewEmoji("flag: Tristan da Cunha", []int32{127481, 127462}, "tristan_da_cunha"),
|
||||
NewEmoji("flag: Turks & Caicos Islands", []int32{127481, 127464}, "turks_caicos_islands"),
|
||||
NewEmoji("flag: Chad", []int32{127481, 127465}, "chad"),
|
||||
NewEmoji("flag: French Southern Territories", []int32{127481, 127467}, "french_southern_territories"),
|
||||
NewEmoji("flag: Togo", []int32{127481, 127468}, "togo"),
|
||||
NewEmoji("flag: Thailand", []int32{127481, 127469}, "thailand"),
|
||||
NewEmoji("flag: Tajikistan", []int32{127481, 127471}, "tajikistan"),
|
||||
NewEmoji("flag: Tokelau", []int32{127481, 127472}, "tokelau"),
|
||||
NewEmoji("flag: Timor-Leste", []int32{127481, 127473}, "timor_leste"),
|
||||
NewEmoji("flag: Turkmenistan", []int32{127481, 127474}, "turkmenistan"),
|
||||
NewEmoji("flag: Tunisia", []int32{127481, 127475}, "tunisia"),
|
||||
NewEmoji("flag: Tonga", []int32{127481, 127476}, "tonga"),
|
||||
NewEmoji("flag: Turkey", []int32{127481, 127479}, "tr"),
|
||||
NewEmoji("flag: Trinidad & Tobago", []int32{127481, 127481}, "trinidad_tobago"),
|
||||
NewEmoji("flag: Tuvalu", []int32{127481, 127483}, "tuvalu"),
|
||||
NewEmoji("flag: Taiwan", []int32{127481, 127484}, "taiwan"),
|
||||
NewEmoji("flag: Tanzania", []int32{127481, 127487}, "tanzania"),
|
||||
NewEmoji("flag: Ukraine", []int32{127482, 127462}, "ukraine"),
|
||||
NewEmoji("flag: Uganda", []int32{127482, 127468}, "uganda"),
|
||||
NewEmoji("flag: U.S. Outlying Islands", []int32{127482, 127474}, "us_outlying_islands"),
|
||||
NewEmoji("flag: United Nations", []int32{127482, 127475}, "united_nations"),
|
||||
NewEmoji("flag: United States", []int32{127482, 127480}, "us"),
|
||||
NewEmoji("flag: Uruguay", []int32{127482, 127486}, "uruguay"),
|
||||
NewEmoji("flag: Uzbekistan", []int32{127482, 127487}, "uzbekistan"),
|
||||
NewEmoji("flag: Vatican City", []int32{127483, 127462}, "vatican_city"),
|
||||
NewEmoji("flag: St. Vincent & Grenadines", []int32{127483, 127464}, "st_vincent_grenadines"),
|
||||
NewEmoji("flag: Venezuela", []int32{127483, 127466}, "venezuela"),
|
||||
NewEmoji("flag: British Virgin Islands", []int32{127483, 127468}, "british_virgin_islands"),
|
||||
NewEmoji("flag: U.S. Virgin Islands", []int32{127483, 127470}, "us_virgin_islands"),
|
||||
NewEmoji("flag: Vietnam", []int32{127483, 127475}, "vietnam"),
|
||||
NewEmoji("flag: Vanuatu", []int32{127483, 127482}, "vanuatu"),
|
||||
NewEmoji("flag: Wallis & Futuna", []int32{127484, 127467}, "wallis_futuna"),
|
||||
NewEmoji("flag: Samoa", []int32{127484, 127480}, "samoa"),
|
||||
NewEmoji("flag: Kosovo", []int32{127485, 127472}, "kosovo"),
|
||||
NewEmoji("flag: Yemen", []int32{127486, 127466}, "yemen"),
|
||||
NewEmoji("flag: Mayotte", []int32{127486, 127481}, "mayotte"),
|
||||
NewEmoji("flag: South Africa", []int32{127487, 127462}, "south_africa"),
|
||||
NewEmoji("flag: Zambia", []int32{127487, 127474}, "zambia"),
|
||||
NewEmoji("flag: Zimbabwe", []int32{127487, 127484}, "zimbabwe"),
|
||||
NewEmoji("flag: England", []int32{127988, 917607, 917602, 917605, 917614, 917607, 917631}, "england"),
|
||||
NewEmoji("flag: Scotland", []int32{127988, 917607, 917602, 917619, 917603, 917620, 917631}, "scotland"),
|
||||
NewEmoji("flag: Wales", []int32{127988, 917607, 917602, 917623, 917612, 917619, 917631}, "wales"),
|
||||
)
|
||||
})
|
||||
m := github.Clone()
|
||||
for _, opt := range opts {
|
||||
opt(m)
|
||||
}
|
||||
return m
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
// package emoji is a extension for the goldmark(http://github.com/yuin/goldmark).
|
||||
package emoji
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
east "github.com/yuin/goldmark-emoji/ast"
|
||||
"github.com/yuin/goldmark-emoji/definition"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Option interface sets options for this extension.
|
||||
type Option interface {
|
||||
emojiOption()
|
||||
}
|
||||
|
||||
// ParserConfig struct is a data structure that holds configuration of
|
||||
// the Emoji extension.
|
||||
type ParserConfig struct {
|
||||
Emojis definition.Emojis
|
||||
}
|
||||
|
||||
const optEmojis parser.OptionName = "EmojiEmojis"
|
||||
|
||||
// SetOption implements parser.SetOptioner
|
||||
func (c *ParserConfig) SetOption(name parser.OptionName, value interface{}) {
|
||||
switch name {
|
||||
case optEmojis:
|
||||
c.Emojis = value.(definition.Emojis)
|
||||
}
|
||||
}
|
||||
|
||||
// A ParserOption interface sets options for the emoji parser.
|
||||
type ParserOption interface {
|
||||
Option
|
||||
parser.Option
|
||||
|
||||
SetEmojiOption(*ParserConfig)
|
||||
}
|
||||
|
||||
var _ ParserOption = &withEmojis{}
|
||||
|
||||
type withEmojis struct {
|
||||
value definition.Emojis
|
||||
}
|
||||
|
||||
func (o *withEmojis) emojiOption() {}
|
||||
|
||||
func (o *withEmojis) SetParserOption(c *parser.Config) {
|
||||
c.Options[optEmojis] = o.value
|
||||
}
|
||||
|
||||
func (o *withEmojis) SetEmojiOption(c *ParserConfig) {
|
||||
c.Emojis = o.value
|
||||
}
|
||||
|
||||
// WithMaping is a functional option that defines links names to unicode emojis.
|
||||
func WithEmojis(value definition.Emojis) Option {
|
||||
return &withEmojis{
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// RenderingMethod indicates how emojis are rendered.
|
||||
type RenderingMethod int
|
||||
|
||||
// RendererFunc will be used for rendering emojis.
|
||||
type RendererFunc func(w util.BufWriter, source []byte, n *east.Emoji, config *RendererConfig)
|
||||
|
||||
const (
|
||||
// Entity renders an emoji as an html entity.
|
||||
Entity RenderingMethod = iota
|
||||
|
||||
// Unicode renders an emoji as unicode character.
|
||||
Unicode
|
||||
|
||||
// Twemoji renders an emoji as an img tag with [twemoji](https://github.com/twitter/twemoji).
|
||||
Twemoji
|
||||
|
||||
// Func renders an emoji using RendererFunc.
|
||||
Func
|
||||
)
|
||||
|
||||
// RendererConfig struct holds options for the emoji renderer.
|
||||
type RendererConfig struct {
|
||||
html.Config
|
||||
|
||||
// Method indicates how emojis are rendered.
|
||||
Method RenderingMethod
|
||||
|
||||
// TwemojiTemplate is a printf template for twemoji. This value is valid only when Method is set to Twemoji.
|
||||
// `printf` arguments are:
|
||||
//
|
||||
// 1: name (e.g. "face with tears of joy")
|
||||
// 2: file name without an extension (e.g. 1f646-2642)
|
||||
// 3: '/' if XHTML, otherwise ''
|
||||
//
|
||||
TwemojiTemplate string
|
||||
|
||||
// RendererFunc is a RendererFunc that renders emojis. This value is valid only when Method is set to Func.
|
||||
RendererFunc RendererFunc
|
||||
}
|
||||
|
||||
// DefaultTwemojiTemplate is a default value for RendererConfig.TwemojiTemplate.
|
||||
const DefaultTwemojiTemplate = `<img class="emoji" draggable="false" alt="%[1]s" src="https://twemoji.maxcdn.com/v/latest/72x72/%[2]s.png"%[3]s>`
|
||||
|
||||
// SetOption implements renderer.SetOptioner.
|
||||
func (c *RendererConfig) SetOption(name renderer.OptionName, value interface{}) {
|
||||
switch name {
|
||||
case optRenderingMethod:
|
||||
c.Method = value.(RenderingMethod)
|
||||
case optTwemojiTemplate:
|
||||
c.TwemojiTemplate = value.(string)
|
||||
case optRendererFunc:
|
||||
c.RendererFunc = value.(RendererFunc)
|
||||
default:
|
||||
c.Config.SetOption(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
// A RendererOption interface sets options for the emoji renderer.
|
||||
type RendererOption interface {
|
||||
Option
|
||||
renderer.Option
|
||||
|
||||
SetEmojiOption(*RendererConfig)
|
||||
}
|
||||
|
||||
var _ RendererOption = &withRenderingMethod{}
|
||||
|
||||
type withRenderingMethod struct {
|
||||
value RenderingMethod
|
||||
}
|
||||
|
||||
func (o *withRenderingMethod) emojiOption() {
|
||||
}
|
||||
|
||||
// SetConfig implements renderer.Option#SetConfig.
|
||||
func (o *withRenderingMethod) SetConfig(c *renderer.Config) {
|
||||
c.Options[optRenderingMethod] = o.value
|
||||
}
|
||||
|
||||
// SetEmojiOption implements RendererOption#SetEmojiOption
|
||||
func (o *withRenderingMethod) SetEmojiOption(c *RendererConfig) {
|
||||
c.Method = o.value
|
||||
}
|
||||
|
||||
const optRenderingMethod renderer.OptionName = "EmojiRenderingMethod"
|
||||
|
||||
// WithRenderingMethod is a functional option that indicates how emojis are rendered.
|
||||
func WithRenderingMethod(a RenderingMethod) Option {
|
||||
return &withRenderingMethod{a}
|
||||
}
|
||||
|
||||
type withTwemojiTemplate struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func (o *withTwemojiTemplate) emojiOption() {
|
||||
}
|
||||
|
||||
// SetConfig implements renderer.Option#SetConfig.
|
||||
func (o *withTwemojiTemplate) SetConfig(c *renderer.Config) {
|
||||
c.Options[optTwemojiTemplate] = o.value
|
||||
}
|
||||
|
||||
// SetEmojiOption implements RendererOption#SetEmojiOption
|
||||
func (o *withTwemojiTemplate) SetEmojiOption(c *RendererConfig) {
|
||||
c.TwemojiTemplate = o.value
|
||||
}
|
||||
|
||||
const optTwemojiTemplate renderer.OptionName = "EmojiTwemojiTemplate"
|
||||
|
||||
// WithTwemojiTemplate is a functional option that changes a twemoji img tag.
|
||||
func WithTwemojiTemplate(s string) Option {
|
||||
return &withTwemojiTemplate{s}
|
||||
}
|
||||
|
||||
var _ RendererOption = &withRendererFunc{}
|
||||
|
||||
type withRendererFunc struct {
|
||||
value RendererFunc
|
||||
}
|
||||
|
||||
func (o *withRendererFunc) emojiOption() {
|
||||
}
|
||||
|
||||
// SetConfig implements renderer.Option#SetConfig.
|
||||
func (o *withRendererFunc) SetConfig(c *renderer.Config) {
|
||||
c.Options[optRendererFunc] = o.value
|
||||
}
|
||||
|
||||
// SetEmojiOption implements RendererOption#SetEmojiOption
|
||||
func (o *withRendererFunc) SetEmojiOption(c *RendererConfig) {
|
||||
c.RendererFunc = o.value
|
||||
}
|
||||
|
||||
const optRendererFunc renderer.OptionName = "EmojiRendererFunc"
|
||||
|
||||
// WithRendererFunc is a functional option that changes a renderer func.
|
||||
func WithRendererFunc(f RendererFunc) Option {
|
||||
return &withRendererFunc{f}
|
||||
}
|
||||
|
||||
type emojiParser struct {
|
||||
ParserConfig
|
||||
}
|
||||
|
||||
// NewParser returns a new parser.InlineParser that can parse emoji expressions.
|
||||
func NewParser(opts ...ParserOption) parser.InlineParser {
|
||||
p := &emojiParser{
|
||||
ParserConfig: ParserConfig{
|
||||
Emojis: definition.Github(),
|
||||
},
|
||||
}
|
||||
for _, o := range opts {
|
||||
o.SetEmojiOption(&p.ParserConfig)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (s *emojiParser) Trigger() []byte {
|
||||
return []byte{':'}
|
||||
}
|
||||
|
||||
func (s *emojiParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
if len(line) < 1 {
|
||||
return nil
|
||||
}
|
||||
i := 1
|
||||
for ; i < len(line); i++ {
|
||||
c := line[i]
|
||||
if !(util.IsAlphaNumeric(c) || c == '_' || c == '-' || c == '+') {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i >= len(line) || line[i] != ':' {
|
||||
return nil
|
||||
}
|
||||
block.Advance(i + 1)
|
||||
shortName := line[1:i]
|
||||
emoji, ok := s.Emojis.Get(util.BytesToReadOnlyString(shortName))
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return east.NewEmoji(shortName, emoji)
|
||||
}
|
||||
|
||||
type emojiHTMLRenderer struct {
|
||||
RendererConfig
|
||||
}
|
||||
|
||||
// NewHTMLRenderer returns a new HTMLRenderer.
|
||||
func NewHTMLRenderer(opts ...RendererOption) renderer.NodeRenderer {
|
||||
r := &emojiHTMLRenderer{
|
||||
RendererConfig: RendererConfig{
|
||||
Config: html.NewConfig(),
|
||||
Method: Entity,
|
||||
TwemojiTemplate: DefaultTwemojiTemplate,
|
||||
RendererFunc: nil,
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.SetEmojiOption(&r.RendererConfig)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *emojiHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(east.KindEmoji, r.renderEmoji)
|
||||
}
|
||||
|
||||
const slash = " /"
|
||||
const empty = ""
|
||||
|
||||
func (r *emojiHTMLRenderer) renderEmoji(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
node := n.(*east.Emoji)
|
||||
if !node.Value.IsUnicode() && r.Method != Func {
|
||||
fmt.Fprintf(w, `<span title="%s">:%s:</span>`, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), node.ShortName)
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case Entity:
|
||||
for _, r := range node.Value.Unicode {
|
||||
if r == 0x200D {
|
||||
_, _ = w.WriteString("‍")
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "&#x%x;", r)
|
||||
}
|
||||
case Unicode:
|
||||
fmt.Fprintf(w, "%s", string(node.Value.Unicode))
|
||||
case Twemoji:
|
||||
s := slash
|
||||
if !r.XHTML {
|
||||
s = empty
|
||||
}
|
||||
values := []string{}
|
||||
for _, r := range node.Value.Unicode {
|
||||
values = append(values, fmt.Sprintf("%x", r))
|
||||
}
|
||||
fmt.Fprintf(w, r.TwemojiTemplate, util.EscapeHTML(util.StringToReadOnlyBytes(node.Value.Name)), strings.Join(values, "-"), s)
|
||||
case Func:
|
||||
r.RendererFunc(w, source, node, &r.RendererConfig)
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type emoji struct {
|
||||
options []Option
|
||||
}
|
||||
|
||||
// Emoji is a goldmark.Extender implementation.
|
||||
var Emoji = &emoji{
|
||||
options: []Option{},
|
||||
}
|
||||
|
||||
// New returns a new extension with given options.
|
||||
func New(opts ...Option) goldmark.Extender {
|
||||
return &emoji{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// Extend implements goldmark.Extender.
|
||||
func (e *emoji) Extend(m goldmark.Markdown) {
|
||||
pOpts := []ParserOption{}
|
||||
rOpts := []RendererOption{}
|
||||
for _, o := range e.options {
|
||||
if po, ok := o.(ParserOption); ok {
|
||||
pOpts = append(pOpts, po)
|
||||
continue
|
||||
}
|
||||
if ro, ok := o.(RendererOption); ok {
|
||||
rOpts = append(rOpts, ro)
|
||||
}
|
||||
}
|
||||
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewHTMLRenderer(rOpts...), 200),
|
||||
))
|
||||
|
||||
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||
util.Prioritized(NewParser(pOpts...), 999),
|
||||
))
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module github.com/yuin/goldmark-emoji
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/yuin/goldmark v1.2.1
|
|
@ -0,0 +1,2 @@
|
|||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
@ -1,7 +1,7 @@
|
|||
goldmark
|
||||
==========================================
|
||||
|
||||
[![http://godoc.org/github.com/yuin/goldmark](https://godoc.org/github.com/yuin/goldmark?status.svg)](http://godoc.org/github.com/yuin/goldmark)
|
||||
[![https://pkg.go.dev/github.com/yuin/goldmark](https://pkg.go.dev/badge/github.com/yuin/goldmark.svg)](https://pkg.go.dev/github.com/yuin/goldmark)
|
||||
[![https://github.com/yuin/goldmark/actions?query=workflow:test](https://github.com/yuin/goldmark/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
||||
[![https://coveralls.io/github/yuin/goldmark](https://coveralls.io/repos/github/yuin/goldmark/badge.svg?branch=master)](https://coveralls.io/github/yuin/goldmark)
|
||||
[![https://goreportcard.com/report/github.com/yuin/goldmark](https://goreportcard.com/badge/github.com/yuin/goldmark)](https://goreportcard.com/report/github.com/yuin/goldmark)
|
||||
|
@ -173,6 +173,7 @@ Parser and Renderer options
|
|||
- This extension enables Table, Strikethrough, Linkify and TaskList.
|
||||
- This extension does not filter tags defined in [6.11: Disallowed Raw HTML (extension)](https://github.github.com/gfm/#disallowed-raw-html-extension-).
|
||||
If you need to filter HTML tags, see [Security](#security).
|
||||
- If you need to parse github emojis, you can use [goldmark-emoji](https://github.com/yuin/goldmark-emoji) extension.
|
||||
- `extension.DefinitionList`
|
||||
- [PHP Markdown Extra: Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list)
|
||||
- `extension.Footnote`
|
||||
|
@ -286,6 +287,89 @@ markdown := goldmark.New(
|
|||
)
|
||||
```
|
||||
|
||||
### Footnotes extension
|
||||
|
||||
The Footnote extension implements [PHP Markdown Extra: Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes).
|
||||
|
||||
This extension has some options:
|
||||
|
||||
| Functional option | Type | Description |
|
||||
| ----------------- | ---- | ----------- |
|
||||
| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.|
|
||||
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
||||
| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.|
|
||||
| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. |
|
||||
| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. |
|
||||
| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
||||
| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `↩︎`. |
|
||||
|
||||
Some options can have special substitutions. Occurances of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurances of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
||||
|
||||
`extension.WithFootnoteIDPrefix` and `extension.WithFootnoteIDPrefixFunction` are useful if you have multiple Markdown documents displayed inside one HTML document to avoid footnote ids to clash each other.
|
||||
|
||||
`extension.WithFootnoteIDPrefix` sets fixed id prefix, so you may write codes like the following:
|
||||
|
||||
```go
|
||||
for _, path := range files {
|
||||
source := readAll(path)
|
||||
prefix := getPrefix(path)
|
||||
|
||||
markdown := goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
NewFootnote(
|
||||
WithFootnoteIDPrefix([]byte(path)),
|
||||
),
|
||||
),
|
||||
)
|
||||
var b bytes.Buffer
|
||||
err := markdown.Convert(source, &b)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`extension.WithFootnoteIDPrefixFunction` determines an id prefix by calling given function, so you may write codes like the following:
|
||||
|
||||
```go
|
||||
markdown := goldmark.New(
|
||||
goldmark.WithExtensions(
|
||||
NewFootnote(
|
||||
WithFootnoteIDPrefixFunction(func(n gast.Node) []byte {
|
||||
v, ok := n.OwnerDocument().Meta()["footnote-prefix"]
|
||||
if ok {
|
||||
return util.StringToReadOnlyBytes(v.(string))
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
for _, path := range files {
|
||||
source := readAll(path)
|
||||
var b bytes.Buffer
|
||||
|
||||
doc := markdown.Parser().Parse(text.NewReader(source))
|
||||
doc.Meta()["footnote-prefix"] = getPrefix(path)
|
||||
err := markdown.Renderer().Render(&b, source, doc)
|
||||
}
|
||||
```
|
||||
|
||||
You can use [goldmark-meta](https://github.com/yuin/goldmark-meta) to define a id prefix in the markdown document:
|
||||
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: document title
|
||||
slug: article1
|
||||
footnote-prefix: article1
|
||||
---
|
||||
|
||||
# My article
|
||||
|
||||
```
|
||||
|
||||
Security
|
||||
--------------------
|
||||
By default, goldmark does not render raw HTML or potentially-dangerous URLs.
|
||||
|
@ -336,6 +420,8 @@ Extensions
|
|||
extension for the goldmark Markdown parser.
|
||||
- [goldmark-highlighting](https://github.com/yuin/goldmark-highlighting): A syntax-highlighting extension
|
||||
for the goldmark markdown parser.
|
||||
- [goldmark-emoji](https://github.com/yuin/goldmark-emoji): An emoji
|
||||
extension for the goldmark Markdown parser.
|
||||
- [goldmark-mathjax](https://github.com/litao91/goldmark-mathjax): Mathjax support for the goldmark markdown parser
|
||||
|
||||
goldmark internal(for extension developers)
|
||||
|
|
|
@ -45,11 +45,6 @@ type Attribute struct {
|
|||
Value interface{}
|
||||
}
|
||||
|
||||
var attrNameIDS = []byte("#")
|
||||
var attrNameID = []byte("id")
|
||||
var attrNameClassS = []byte(".")
|
||||
var attrNameClass = []byte("class")
|
||||
|
||||
// A Node interface defines basic AST node functionalities.
|
||||
type Node interface {
|
||||
// Type returns a type of this node.
|
||||
|
@ -116,6 +111,11 @@ type Node interface {
|
|||
// tail of the children.
|
||||
InsertAfter(self, v1, insertee Node)
|
||||
|
||||
// OwnerDocument returns this node's owner document.
|
||||
// If this node is not a child of the Document node, OwnerDocument
|
||||
// returns nil.
|
||||
OwnerDocument() *Document
|
||||
|
||||
// Dump dumps an AST tree structure to stdout.
|
||||
// This function completely aimed for debugging.
|
||||
// level is a indent level. Implementer should indent informations with
|
||||
|
@ -169,7 +169,7 @@ type Node interface {
|
|||
RemoveAttributes()
|
||||
}
|
||||
|
||||
// A BaseNode struct implements the Node interface.
|
||||
// A BaseNode struct implements the Node interface partialliy.
|
||||
type BaseNode struct {
|
||||
firstChild Node
|
||||
lastChild Node
|
||||
|
@ -358,6 +358,22 @@ func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
|
|||
}
|
||||
}
|
||||
|
||||
// OwnerDocument implements Node.OwnerDocument
|
||||
func (n *BaseNode) OwnerDocument() *Document {
|
||||
d := n.Parent()
|
||||
for {
|
||||
p := d.Parent()
|
||||
if p == nil {
|
||||
if v, ok := d.(*Document); ok {
|
||||
return v
|
||||
}
|
||||
break
|
||||
}
|
||||
d = p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Text implements Node.Text .
|
||||
func (n *BaseNode) Text(source []byte) []byte {
|
||||
var buf bytes.Buffer
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
textm "github.com/yuin/goldmark/text"
|
||||
)
|
||||
|
||||
// A BaseBlock struct implements the Node interface.
|
||||
// A BaseBlock struct implements the Node interface partialliy.
|
||||
type BaseBlock struct {
|
||||
BaseNode
|
||||
blankPreviousLines bool
|
||||
|
@ -50,6 +50,8 @@ func (b *BaseBlock) SetLines(v *textm.Segments) {
|
|||
// A Document struct is a root node of Markdown text.
|
||||
type Document struct {
|
||||
BaseBlock
|
||||
|
||||
meta map[string]interface{}
|
||||
}
|
||||
|
||||
// KindDocument is a NodeKind of the Document node.
|
||||
|
@ -70,10 +72,29 @@ func (n *Document) Kind() NodeKind {
|
|||
return KindDocument
|
||||
}
|
||||
|
||||
// OwnerDocument implements Node.OwnerDocument
|
||||
func (n *Document) OwnerDocument() *Document {
|
||||
return n
|
||||
}
|
||||
|
||||
// Meta returns metadata of this document.
|
||||
func (n *Document) Meta() map[string]interface{} {
|
||||
if n.meta == nil {
|
||||
n.meta = map[string]interface{}{}
|
||||
}
|
||||
return n.meta
|
||||
}
|
||||
|
||||
// SetMeta sets given metadata to this document.
|
||||
func (n *Document) SetMeta(meta map[string]interface{}) {
|
||||
n.meta = meta
|
||||
}
|
||||
|
||||
// NewDocument returns a new Document node.
|
||||
func NewDocument() *Document {
|
||||
return &Document{
|
||||
BaseBlock: BaseBlock{},
|
||||
meta: nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// A BaseInline struct implements the Node interface.
|
||||
// A BaseInline struct implements the Node interface partialliy.
|
||||
type BaseInline struct {
|
||||
BaseNode
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package ast
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
gast "github.com/yuin/goldmark/ast"
|
||||
)
|
||||
|
||||
|
@ -9,13 +10,15 @@ import (
|
|||
// (PHP Markdown Extra) text.
|
||||
type FootnoteLink struct {
|
||||
gast.BaseInline
|
||||
Index int
|
||||
Index int
|
||||
RefCount int
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *FootnoteLink) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||
m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
|
||||
gast.DumpHelper(n, source, level, m, nil)
|
||||
}
|
||||
|
||||
|
@ -30,36 +33,40 @@ func (n *FootnoteLink) Kind() gast.NodeKind {
|
|||
// NewFootnoteLink returns a new FootnoteLink node.
|
||||
func NewFootnoteLink(index int) *FootnoteLink {
|
||||
return &FootnoteLink{
|
||||
Index: index,
|
||||
Index: index,
|
||||
RefCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// A FootnoteBackLink struct represents a link to a footnote of Markdown
|
||||
// A FootnoteBacklink struct represents a link to a footnote of Markdown
|
||||
// (PHP Markdown Extra) text.
|
||||
type FootnoteBackLink struct {
|
||||
type FootnoteBacklink struct {
|
||||
gast.BaseInline
|
||||
Index int
|
||||
Index int
|
||||
RefCount int
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *FootnoteBackLink) Dump(source []byte, level int) {
|
||||
func (n *FootnoteBacklink) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||
m["RefCount"] = fmt.Sprintf("%v", n.RefCount)
|
||||
gast.DumpHelper(n, source, level, m, nil)
|
||||
}
|
||||
|
||||
// KindFootnoteBackLink is a NodeKind of the FootnoteBackLink node.
|
||||
var KindFootnoteBackLink = gast.NewNodeKind("FootnoteBackLink")
|
||||
// KindFootnoteBacklink is a NodeKind of the FootnoteBacklink node.
|
||||
var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink")
|
||||
|
||||
// Kind implements Node.Kind.
|
||||
func (n *FootnoteBackLink) Kind() gast.NodeKind {
|
||||
return KindFootnoteBackLink
|
||||
func (n *FootnoteBacklink) Kind() gast.NodeKind {
|
||||
return KindFootnoteBacklink
|
||||
}
|
||||
|
||||
// NewFootnoteBackLink returns a new FootnoteBackLink node.
|
||||
func NewFootnoteBackLink(index int) *FootnoteBackLink {
|
||||
return &FootnoteBackLink{
|
||||
Index: index,
|
||||
// NewFootnoteBacklink returns a new FootnoteBacklink node.
|
||||
func NewFootnoteBacklink(index int) *FootnoteBacklink {
|
||||
return &FootnoteBacklink{
|
||||
Index: index,
|
||||
RefCount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package extension
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
gast "github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/extension/ast"
|
||||
|
@ -10,10 +12,10 @@ import (
|
|||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var footnoteListKey = parser.NewContextKey()
|
||||
var footnoteLinkListKey = parser.NewContextKey()
|
||||
|
||||
type footnoteBlockParser struct {
|
||||
}
|
||||
|
@ -164,7 +166,20 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
|||
return nil
|
||||
}
|
||||
|
||||
return ast.NewFootnoteLink(index)
|
||||
fnlink := ast.NewFootnoteLink(index)
|
||||
var fnlist []*ast.FootnoteLink
|
||||
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
|
||||
fnlist = tmp.([]*ast.FootnoteLink)
|
||||
} else {
|
||||
fnlist = []*ast.FootnoteLink{}
|
||||
pc.Set(footnoteLinkListKey, fnlist)
|
||||
}
|
||||
pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
|
||||
if line[0] == '!' {
|
||||
parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
|
||||
}
|
||||
|
||||
return fnlink
|
||||
}
|
||||
|
||||
type footnoteASTTransformer struct {
|
||||
|
@ -180,23 +195,46 @@ func NewFootnoteASTTransformer() parser.ASTTransformer {
|
|||
|
||||
func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
|
||||
var list *ast.FootnoteList
|
||||
if tlist := pc.Get(footnoteListKey); tlist != nil {
|
||||
list = tlist.(*ast.FootnoteList)
|
||||
} else {
|
||||
var fnlist []*ast.FootnoteLink
|
||||
if tmp := pc.Get(footnoteListKey); tmp != nil {
|
||||
list = tmp.(*ast.FootnoteList)
|
||||
}
|
||||
if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
|
||||
fnlist = tmp.([]*ast.FootnoteLink)
|
||||
}
|
||||
|
||||
pc.Set(footnoteListKey, nil)
|
||||
pc.Set(footnoteLinkListKey, nil)
|
||||
|
||||
if list == nil {
|
||||
return
|
||||
}
|
||||
pc.Set(footnoteListKey, nil)
|
||||
|
||||
counter := map[int]int{}
|
||||
if fnlist != nil {
|
||||
for _, fnlink := range fnlist {
|
||||
if fnlink.Index >= 0 {
|
||||
counter[fnlink.Index]++
|
||||
}
|
||||
}
|
||||
for _, fnlink := range fnlist {
|
||||
fnlink.RefCount = counter[fnlink.Index]
|
||||
}
|
||||
}
|
||||
for footnote := list.FirstChild(); footnote != nil; {
|
||||
var container gast.Node = footnote
|
||||
next := footnote.NextSibling()
|
||||
if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
|
||||
container = fc
|
||||
}
|
||||
index := footnote.(*ast.Footnote).Index
|
||||
fn := footnote.(*ast.Footnote)
|
||||
index := fn.Index
|
||||
if index < 0 {
|
||||
list.RemoveChild(list, footnote)
|
||||
} else {
|
||||
container.AppendChild(container, ast.NewFootnoteBackLink(index))
|
||||
backLink := ast.NewFootnoteBacklink(index)
|
||||
backLink.RefCount = counter[index]
|
||||
container.AppendChild(container, backLink)
|
||||
}
|
||||
footnote = next
|
||||
}
|
||||
|
@ -214,19 +252,250 @@ func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Read
|
|||
node.AppendChild(node, list)
|
||||
}
|
||||
|
||||
// FootnoteConfig holds configuration values for the footnote extension.
|
||||
//
|
||||
// Link* and Backlink* configurations have some variables:
|
||||
// Occurrances of “^^” in the string will be replaced by the
|
||||
// corresponding footnote number in the HTML output.
|
||||
// Occurrances of “%%” will be replaced by a number for the
|
||||
// reference (footnotes can have multiple references).
|
||||
type FootnoteConfig struct {
|
||||
html.Config
|
||||
|
||||
// IDPrefix is a prefix for the id attributes generated by footnotes.
|
||||
IDPrefix []byte
|
||||
|
||||
// IDPrefix is a function that determines the id attribute for given Node.
|
||||
IDPrefixFunction func(gast.Node) []byte
|
||||
|
||||
// LinkTitle is an optional title attribute for footnote links.
|
||||
LinkTitle []byte
|
||||
|
||||
// BacklinkTitle is an optional title attribute for footnote backlinks.
|
||||
BacklinkTitle []byte
|
||||
|
||||
// LinkClass is a class for footnote links.
|
||||
LinkClass []byte
|
||||
|
||||
// BacklinkClass is a class for footnote backlinks.
|
||||
BacklinkClass []byte
|
||||
|
||||
// BacklinkHTML is an HTML content for footnote backlinks.
|
||||
BacklinkHTML []byte
|
||||
}
|
||||
|
||||
// FootnoteOption interface is a functional option interface for the extension.
|
||||
type FootnoteOption interface {
|
||||
renderer.Option
|
||||
// SetFootnoteOption sets given option to the extension.
|
||||
SetFootnoteOption(*FootnoteConfig)
|
||||
}
|
||||
|
||||
// NewFootnoteConfig returns a new Config with defaults.
|
||||
func NewFootnoteConfig() FootnoteConfig {
|
||||
return FootnoteConfig{
|
||||
Config: html.NewConfig(),
|
||||
LinkTitle: []byte(""),
|
||||
BacklinkTitle: []byte(""),
|
||||
LinkClass: []byte("footnote-ref"),
|
||||
BacklinkClass: []byte("footnote-backref"),
|
||||
BacklinkHTML: []byte("↩︎"),
|
||||
}
|
||||
}
|
||||
|
||||
// SetOption implements renderer.SetOptioner.
|
||||
func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
|
||||
switch name {
|
||||
case optFootnoteIDPrefixFunction:
|
||||
c.IDPrefixFunction = value.(func(gast.Node) []byte)
|
||||
case optFootnoteIDPrefix:
|
||||
c.IDPrefix = value.([]byte)
|
||||
case optFootnoteLinkTitle:
|
||||
c.LinkTitle = value.([]byte)
|
||||
case optFootnoteBacklinkTitle:
|
||||
c.BacklinkTitle = value.([]byte)
|
||||
case optFootnoteLinkClass:
|
||||
c.LinkClass = value.([]byte)
|
||||
case optFootnoteBacklinkClass:
|
||||
c.BacklinkClass = value.([]byte)
|
||||
case optFootnoteBacklinkHTML:
|
||||
c.BacklinkHTML = value.([]byte)
|
||||
default:
|
||||
c.Config.SetOption(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
type withFootnoteHTMLOptions struct {
|
||||
value []html.Option
|
||||
}
|
||||
|
||||
func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
|
||||
if o.value != nil {
|
||||
for _, v := range o.value {
|
||||
v.(renderer.Option).SetConfig(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
|
||||
if o.value != nil {
|
||||
for _, v := range o.value {
|
||||
v.SetHTMLOption(&c.Config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
|
||||
func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
|
||||
return &withFootnoteHTMLOptions{opts}
|
||||
}
|
||||
|
||||
const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
|
||||
|
||||
type withFootnoteIDPrefix struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteIDPrefix] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.IDPrefix = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||
func WithFootnoteIDPrefix(a []byte) FootnoteOption {
|
||||
return &withFootnoteIDPrefix{a}
|
||||
}
|
||||
|
||||
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
||||
|
||||
type withFootnoteIDPrefixFunction struct {
|
||||
value func(gast.Node) []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteIDPrefixFunction] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.IDPrefixFunction = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||
func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
|
||||
return &withFootnoteIDPrefixFunction{a}
|
||||
}
|
||||
|
||||
const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
|
||||
|
||||
type withFootnoteLinkTitle struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteLinkTitle] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.LinkTitle = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
||||
func WithFootnoteLinkTitle(a []byte) FootnoteOption {
|
||||
return &withFootnoteLinkTitle{a}
|
||||
}
|
||||
|
||||
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
||||
|
||||
type withFootnoteBacklinkTitle struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteBacklinkTitle] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.BacklinkTitle = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
||||
func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
|
||||
return &withFootnoteBacklinkTitle{a}
|
||||
}
|
||||
|
||||
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
||||
|
||||
type withFootnoteLinkClass struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteLinkClass] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.LinkClass = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
||||
func WithFootnoteLinkClass(a []byte) FootnoteOption {
|
||||
return &withFootnoteLinkClass{a}
|
||||
}
|
||||
|
||||
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
||||
|
||||
type withFootnoteBacklinkClass struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteBacklinkClass] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.BacklinkClass = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
||||
func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
|
||||
return &withFootnoteBacklinkClass{a}
|
||||
}
|
||||
|
||||
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
||||
|
||||
type withFootnoteBacklinkHTML struct {
|
||||
value []byte
|
||||
}
|
||||
|
||||
func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
|
||||
c.Options[optFootnoteBacklinkHTML] = o.value
|
||||
}
|
||||
|
||||
func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
|
||||
c.BacklinkHTML = o.value
|
||||
}
|
||||
|
||||
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
||||
func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
|
||||
return &withFootnoteBacklinkHTML{a}
|
||||
}
|
||||
|
||||
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||
// renders FootnoteLink nodes.
|
||||
type FootnoteHTMLRenderer struct {
|
||||
html.Config
|
||||
FootnoteConfig
|
||||
}
|
||||
|
||||
// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
|
||||
func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||
func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
|
||||
r := &FootnoteHTMLRenderer{
|
||||
Config: html.NewConfig(),
|
||||
FootnoteConfig: NewFootnoteConfig(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.SetHTMLOption(&r.Config)
|
||||
opt.SetFootnoteOption(&r.FootnoteConfig)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -234,7 +503,7 @@ func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
|||
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||
func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
|
||||
reg.Register(ast.KindFootnoteBackLink, r.renderFootnoteBackLink)
|
||||
reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
|
||||
reg.Register(ast.KindFootnote, r.renderFootnote)
|
||||
reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
|
||||
}
|
||||
|
@ -243,25 +512,45 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
|
|||
if entering {
|
||||
n := node.(*ast.FootnoteLink)
|
||||
is := strconv.Itoa(n.Index)
|
||||
_, _ = w.WriteString(`<sup id="fnref:`)
|
||||
_, _ = w.WriteString(`<sup id="`)
|
||||
_, _ = w.Write(r.idPrefix(node))
|
||||
_, _ = w.WriteString(`fnref:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`"><a href="#fn:`)
|
||||
_, _ = w.WriteString(`"><a href="#`)
|
||||
_, _ = w.Write(r.idPrefix(node))
|
||||
_, _ = w.WriteString(`fn:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
|
||||
_, _ = w.WriteString(`" class="`)
|
||||
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
|
||||
n.Index, n.RefCount))
|
||||
if len(r.FootnoteConfig.LinkTitle) > 0 {
|
||||
_, _ = w.WriteString(`" title="`)
|
||||
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
|
||||
}
|
||||
_, _ = w.WriteString(`" role="doc-noteref">`)
|
||||
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`</a></sup>`)
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
if entering {
|
||||
n := node.(*ast.FootnoteBackLink)
|
||||
n := node.(*ast.FootnoteBacklink)
|
||||
is := strconv.Itoa(n.Index)
|
||||
_, _ = w.WriteString(` <a href="#fnref:`)
|
||||
_, _ = w.WriteString(` <a href="#`)
|
||||
_, _ = w.Write(r.idPrefix(node))
|
||||
_, _ = w.WriteString(`fnref:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`" class="footnote-backref" role="doc-backlink">`)
|
||||
_, _ = w.WriteString("↩︎")
|
||||
_, _ = w.WriteString(`" class="`)
|
||||
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
|
||||
if len(r.FootnoteConfig.BacklinkTitle) > 0 {
|
||||
_, _ = w.WriteString(`" title="`)
|
||||
_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
|
||||
}
|
||||
_, _ = w.WriteString(`" role="doc-backlink">`)
|
||||
_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
|
||||
_, _ = w.WriteString(`</a>`)
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
|
@ -271,7 +560,9 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
|
|||
n := node.(*ast.Footnote)
|
||||
is := strconv.Itoa(n.Index)
|
||||
if entering {
|
||||
_, _ = w.WriteString(`<li id="fn:`)
|
||||
_, _ = w.WriteString(`<li id="`)
|
||||
_, _ = w.Write(r.idPrefix(node))
|
||||
_, _ = w.WriteString(`fn:`)
|
||||
_, _ = w.WriteString(is)
|
||||
_, _ = w.WriteString(`" role="doc-endnote"`)
|
||||
if node.Attributes() != nil {
|
||||
|
@ -312,11 +603,54 @@ func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byt
|
|||
return gast.WalkContinue, nil
|
||||
}
|
||||
|
||||
func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
|
||||
if r.FootnoteConfig.IDPrefix != nil {
|
||||
return r.FootnoteConfig.IDPrefix
|
||||
}
|
||||
if r.FootnoteConfig.IDPrefixFunction != nil {
|
||||
return r.FootnoteConfig.IDPrefixFunction(node)
|
||||
}
|
||||
return []byte("")
|
||||
}
|
||||
|
||||
func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
|
||||
fast := true
|
||||
for i, c := range b {
|
||||
if i != 0 {
|
||||
if b[i-1] == '^' && c == '^' {
|
||||
fast = false
|
||||
break
|
||||
}
|
||||
if b[i-1] == '%' && c == '%' {
|
||||
fast = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if fast {
|
||||
return b
|
||||
}
|
||||
is := []byte(strconv.Itoa(index))
|
||||
rs := []byte(strconv.Itoa(refCount))
|
||||
ret := bytes.Replace(b, []byte("^^"), is, -1)
|
||||
return bytes.Replace(ret, []byte("%%"), rs, -1)
|
||||
}
|
||||
|
||||
type footnote struct {
|
||||
options []FootnoteOption
|
||||
}
|
||||
|
||||
// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
|
||||
var Footnote = &footnote{}
|
||||
var Footnote = &footnote{
|
||||
options: []FootnoteOption{},
|
||||
}
|
||||
|
||||
// NewFootnote returns a new extension with given options.
|
||||
func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
|
||||
return &footnote{
|
||||
options: opts,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *footnote) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(
|
||||
|
@ -331,6 +665,6 @@ func (e *footnote) Extend(m goldmark.Markdown) {
|
|||
),
|
||||
)
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewFootnoteHTMLRenderer(), 500),
|
||||
util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||
|
||||
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp):\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_+.~#$!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
||||
|
||||
// An LinkifyConfig struct is a data structure that holds configuration of the
|
||||
// Linkify extension.
|
||||
|
|
|
@ -15,6 +15,13 @@ import (
|
|||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
var escapedPipeCellListKey = parser.NewContextKey()
|
||||
|
||||
type escapedPipeCell struct {
|
||||
Cell *ast.TableCell
|
||||
Pos []int
|
||||
}
|
||||
|
||||
// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
|
||||
type TableCellAlignMethod int
|
||||
|
||||
|
@ -148,7 +155,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
|||
if alignments == nil {
|
||||
continue
|
||||
}
|
||||
header := b.parseRow(lines.At(i-1), alignments, true, reader)
|
||||
header := b.parseRow(lines.At(i-1), alignments, true, reader, pc)
|
||||
if header == nil || len(alignments) != header.ChildCount() {
|
||||
return
|
||||
}
|
||||
|
@ -156,7 +163,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
|||
table.Alignments = alignments
|
||||
table.AppendChild(table, ast.NewTableHeader(header))
|
||||
for j := i + 1; j < lines.Len(); j++ {
|
||||
table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader))
|
||||
table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc))
|
||||
}
|
||||
node.Lines().SetSliced(0, i-1)
|
||||
node.Parent().InsertAfter(node.Parent(), node, table)
|
||||
|
@ -170,7 +177,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
|||
}
|
||||
}
|
||||
|
||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *ast.TableRow {
|
||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
||||
source := reader.Source()
|
||||
line := segment.Value(source)
|
||||
pos := 0
|
||||
|
@ -194,18 +201,39 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []
|
|||
} else {
|
||||
alignment = alignments[i]
|
||||
}
|
||||
closure := util.FindClosure(line[pos:], byte(0), '|', true, false)
|
||||
if closure < 0 {
|
||||
closure = len(line[pos:])
|
||||
}
|
||||
|
||||
var escapedCell *escapedPipeCell
|
||||
node := ast.NewTableCell()
|
||||
seg := text.NewSegment(segment.Start+pos, segment.Start+pos+closure)
|
||||
node.Alignment = alignment
|
||||
hasBacktick := false
|
||||
closure := pos
|
||||
for ; closure < limit; closure++ {
|
||||
if line[closure] == '`' {
|
||||
hasBacktick = true
|
||||
}
|
||||
if line[closure] == '|' {
|
||||
if closure == 0 || line[closure-1] != '\\' {
|
||||
break
|
||||
} else if hasBacktick {
|
||||
if escapedCell == nil {
|
||||
escapedCell = &escapedPipeCell{node, []int{}}
|
||||
escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey,
|
||||
func() interface{} {
|
||||
return []*escapedPipeCell{}
|
||||
}).([]*escapedPipeCell)
|
||||
escapedList = append(escapedList, escapedCell)
|
||||
pc.Set(escapedPipeCellListKey, escapedList)
|
||||
}
|
||||
escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
seg := text.NewSegment(segment.Start+pos, segment.Start+closure)
|
||||
seg = seg.TrimLeftSpace(source)
|
||||
seg = seg.TrimRightSpace(source)
|
||||
node.Lines().Append(seg)
|
||||
node.Alignment = alignment
|
||||
row.AppendChild(row, node)
|
||||
pos += closure + 1
|
||||
pos = closure + 1
|
||||
}
|
||||
for ; i < len(alignments); i++ {
|
||||
row.AppendChild(row, ast.NewTableCell())
|
||||
|
@ -243,6 +271,49 @@ func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader
|
|||
return alignments
|
||||
}
|
||||
|
||||
type tableASTTransformer struct {
|
||||
}
|
||||
|
||||
var defaultTableASTTransformer = &tableASTTransformer{}
|
||||
|
||||
// NewTableASTTransformer returns a parser.ASTTransformer for tables.
|
||||
func NewTableASTTransformer() parser.ASTTransformer {
|
||||
return defaultTableASTTransformer
|
||||
}
|
||||
|
||||
func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
|
||||
lst := pc.Get(escapedPipeCellListKey)
|
||||
if lst == nil {
|
||||
return
|
||||
}
|
||||
pc.Set(escapedPipeCellListKey, nil)
|
||||
for _, v := range lst.([]*escapedPipeCell) {
|
||||
_ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||
if n.Kind() != gast.KindCodeSpan {
|
||||
return gast.WalkContinue, nil
|
||||
}
|
||||
c := n.FirstChild()
|
||||
for c != nil {
|
||||
next := c.NextSibling()
|
||||
if c.Kind() == gast.KindText {
|
||||
t := c.(*gast.Text)
|
||||
for _, pos := range v.Pos {
|
||||
if t.Segment.Start <= pos && t.Segment.Stop > pos {
|
||||
n1 := gast.NewRawTextSegment(t.Segment.WithStop(pos))
|
||||
n2 := gast.NewRawTextSegment(t.Segment.WithStart(pos + 1))
|
||||
n.InsertAfter(n, c, n1)
|
||||
n.InsertAfter(n, n1, n2)
|
||||
n.RemoveChild(n, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
c = next
|
||||
}
|
||||
return gast.WalkContinue, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TableHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||
// renders Table nodes.
|
||||
type TableHTMLRenderer struct {
|
||||
|
@ -419,7 +490,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
|
|||
cob.AppendByte(';')
|
||||
}
|
||||
style := fmt.Sprintf("text-align:%s", n.Alignment.String())
|
||||
cob.Append(util.StringToReadOnlyBytes(style))
|
||||
cob.AppendString(style)
|
||||
n.SetAttributeString("style", cob.Bytes())
|
||||
}
|
||||
}
|
||||
|
@ -454,9 +525,14 @@ func NewTable(opts ...TableOption) goldmark.Extender {
|
|||
}
|
||||
|
||||
func (e *table) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(parser.WithParagraphTransformers(
|
||||
util.Prioritized(NewTableParagraphTransformer(), 200),
|
||||
))
|
||||
m.Parser().AddOptions(
|
||||
parser.WithParagraphTransformers(
|
||||
util.Prioritized(NewTableParagraphTransformer(), 200),
|
||||
),
|
||||
parser.WithASTTransformers(
|
||||
util.Prioritized(defaultTableASTTransformer, 0),
|
||||
),
|
||||
)
|
||||
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||
util.Prioritized(NewTableHTMLRenderer(e.options...), 500),
|
||||
))
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module github.com/yuin/goldmark
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
|
|
@ -2,7 +2,6 @@ package parser
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
|
@ -113,8 +112,6 @@ func (s *linkParser) Trigger() []byte {
|
|||
return []byte{'!', '[', ']'}
|
||||
}
|
||||
|
||||
var linkDestinationRegexp = regexp.MustCompile(`\s*([^\s].+)`)
|
||||
var linkTitleRegexp = regexp.MustCompile(`\s+(\)|["'\(].+)`)
|
||||
var linkBottom = NewContextKey()
|
||||
|
||||
func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node {
|
||||
|
@ -293,20 +290,17 @@ func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text
|
|||
func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
||||
block.SkipSpaces()
|
||||
line, _ := block.PeekLine()
|
||||
buf := []byte{}
|
||||
if block.Peek() == '<' {
|
||||
i := 1
|
||||
for i < len(line) {
|
||||
c := line[i]
|
||||
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
|
||||
buf = append(buf, '\\', line[i+1])
|
||||
i += 2
|
||||
continue
|
||||
} else if c == '>' {
|
||||
block.Advance(i + 1)
|
||||
return line[1:i], true
|
||||
}
|
||||
buf = append(buf, c)
|
||||
i++
|
||||
}
|
||||
return nil, false
|
||||
|
@ -316,7 +310,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
|||
for i < len(line) {
|
||||
c := line[i]
|
||||
if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) {
|
||||
buf = append(buf, '\\', line[i+1])
|
||||
i += 2
|
||||
continue
|
||||
} else if c == '(' {
|
||||
|
@ -329,7 +322,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) {
|
|||
} else if util.IsSpace(c) {
|
||||
break
|
||||
}
|
||||
buf = append(buf, c)
|
||||
i++
|
||||
}
|
||||
block.Advance(i)
|
||||
|
|
|
@ -138,6 +138,9 @@ type Context interface {
|
|||
// Get returns a value associated with the given key.
|
||||
Get(ContextKey) interface{}
|
||||
|
||||
// ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value.
|
||||
ComputeIfAbsent(ContextKey, func() interface{}) interface{}
|
||||
|
||||
// Set sets the given value to the context.
|
||||
Set(ContextKey, interface{})
|
||||
|
||||
|
@ -252,6 +255,15 @@ func (p *parseContext) Get(key ContextKey) interface{} {
|
|||
return p.store[key]
|
||||
}
|
||||
|
||||
func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() interface{}) interface{} {
|
||||
v := p.store[key]
|
||||
if v == nil {
|
||||
v = f()
|
||||
p.store[key] = v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *parseContext) Set(key ContextKey, value interface{}) {
|
||||
p.store[key] = value
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ package parser
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type rawHTMLParser struct {
|
||||
|
@ -67,8 +68,6 @@ func (s *rawHTMLParser) parseSingleLineRegexp(reg *regexp.Regexp, block text.Rea
|
|||
return node
|
||||
}
|
||||
|
||||
var dummyMatch = [][]byte{}
|
||||
|
||||
func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node {
|
||||
sline, ssegment := block.Position()
|
||||
if block.Match(reg) {
|
||||
|
@ -102,7 +101,3 @@ func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Read
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *rawHTMLParser) CloseBlock(parent ast.Node, pc Context) {
|
||||
// nothing to do
|
||||
}
|
||||
|
|
|
@ -37,6 +37,12 @@ func (b *CopyOnWriteBuffer) Write(value []byte) {
|
|||
b.buffer = append(b.buffer, value...)
|
||||
}
|
||||
|
||||
// WriteString writes given string to the buffer.
|
||||
// WriteString allocate new buffer and clears it at the first time.
|
||||
func (b *CopyOnWriteBuffer) WriteString(value string) {
|
||||
b.Write(StringToReadOnlyBytes(value))
|
||||
}
|
||||
|
||||
// Append appends given bytes to the buffer.
|
||||
// Append copy buffer at the first time.
|
||||
func (b *CopyOnWriteBuffer) Append(value []byte) {
|
||||
|
@ -49,6 +55,12 @@ func (b *CopyOnWriteBuffer) Append(value []byte) {
|
|||
b.buffer = append(b.buffer, value...)
|
||||
}
|
||||
|
||||
// AppendString appends given string to the buffer.
|
||||
// AppendString copy buffer at the first time.
|
||||
func (b *CopyOnWriteBuffer) AppendString(value string) {
|
||||
b.Append(StringToReadOnlyBytes(value))
|
||||
}
|
||||
|
||||
// WriteByte writes the given byte to the buffer.
|
||||
// WriteByte allocate new buffer and clears it at the first time.
|
||||
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
|
||||
|
@ -804,7 +816,7 @@ func IsPunct(c byte) bool {
|
|||
return punctTable[c] == 1
|
||||
}
|
||||
|
||||
// IsPunct returns true if the given rune is a punctuation, otherwise false.
|
||||
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
||||
func IsPunctRune(r rune) bool {
|
||||
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
|
||||
}
|
||||
|
@ -814,7 +826,7 @@ func IsSpace(c byte) bool {
|
|||
return spaceTable[c] == 1
|
||||
}
|
||||
|
||||
// IsSpace returns true if the given rune is a space, otherwise false.
|
||||
// IsSpaceRune returns true if the given rune is a space, otherwise false.
|
||||
func IsSpaceRune(r rune) bool {
|
||||
return int32(r) <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ github.com/Microsoft/go-winio
|
|||
github.com/Microsoft/go-winio/pkg/guid
|
||||
# github.com/adrg/xdg v0.3.1
|
||||
github.com/adrg/xdg
|
||||
# github.com/alecthomas/chroma v0.7.3
|
||||
# github.com/alecthomas/chroma v0.8.1
|
||||
github.com/alecthomas/chroma
|
||||
github.com/alecthomas/chroma/formatters
|
||||
github.com/alecthomas/chroma/formatters/html
|
||||
|
@ -47,13 +47,18 @@ github.com/alecthomas/chroma/lexers/v
|
|||
github.com/alecthomas/chroma/lexers/w
|
||||
github.com/alecthomas/chroma/lexers/x
|
||||
github.com/alecthomas/chroma/lexers/y
|
||||
github.com/alecthomas/chroma/lexers/z
|
||||
github.com/alecthomas/chroma/quick
|
||||
github.com/alecthomas/chroma/styles
|
||||
# github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e
|
||||
github.com/araddon/dateparse
|
||||
# github.com/charmbracelet/glamour v0.2.0
|
||||
# github.com/aymerick/douceur v0.2.0
|
||||
github.com/aymerick/douceur/css
|
||||
# github.com/charmbracelet/glamour v0.2.0 => github.com/noerw/glamour v0.2.1-0.20210305125354-f0a29f1de0c2
|
||||
github.com/charmbracelet/glamour
|
||||
github.com/charmbracelet/glamour/ansi
|
||||
# github.com/chris-ramon/douceur v0.2.0
|
||||
github.com/chris-ramon/douceur/parser
|
||||
# github.com/cpuguy83/go-md2man/v2 v2.0.0
|
||||
github.com/cpuguy83/go-md2man/v2/md2man
|
||||
# github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
|
||||
|
@ -124,6 +129,8 @@ github.com/go-git/go-git/v5/utils/merkletrie/filesystem
|
|||
github.com/go-git/go-git/v5/utils/merkletrie/index
|
||||
github.com/go-git/go-git/v5/utils/merkletrie/internal/frame
|
||||
github.com/go-git/go-git/v5/utils/merkletrie/noder
|
||||
# github.com/gorilla/css v1.0.0
|
||||
github.com/gorilla/css/scanner
|
||||
# github.com/hashicorp/go-version v1.2.1
|
||||
github.com/hashicorp/go-version
|
||||
# github.com/imdario/mergo v0.3.11
|
||||
|
@ -144,11 +151,11 @@ github.com/mattn/go-isatty
|
|||
github.com/mattn/go-runewidth
|
||||
# github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/mgutz/ansi
|
||||
# github.com/microcosm-cc/bluemonday v1.0.2
|
||||
# github.com/microcosm-cc/bluemonday v1.0.4
|
||||
github.com/microcosm-cc/bluemonday
|
||||
# github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-homedir
|
||||
# github.com/muesli/reflow v0.1.0
|
||||
# github.com/muesli/reflow v0.2.0
|
||||
github.com/muesli/reflow/ansi
|
||||
github.com/muesli/reflow/indent
|
||||
github.com/muesli/reflow/padding
|
||||
|
@ -173,7 +180,7 @@ github.com/stretchr/testify/assert
|
|||
github.com/urfave/cli/v2
|
||||
# github.com/xanzy/ssh-agent v0.3.0
|
||||
github.com/xanzy/ssh-agent
|
||||
# github.com/yuin/goldmark v1.2.1
|
||||
# github.com/yuin/goldmark v1.3.1
|
||||
github.com/yuin/goldmark
|
||||
github.com/yuin/goldmark/ast
|
||||
github.com/yuin/goldmark/extension
|
||||
|
@ -183,6 +190,10 @@ github.com/yuin/goldmark/renderer
|
|||
github.com/yuin/goldmark/renderer/html
|
||||
github.com/yuin/goldmark/text
|
||||
github.com/yuin/goldmark/util
|
||||
# github.com/yuin/goldmark-emoji v1.0.1
|
||||
github.com/yuin/goldmark-emoji
|
||||
github.com/yuin/goldmark-emoji/ast
|
||||
github.com/yuin/goldmark-emoji/definition
|
||||
# golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||
golang.org/x/crypto/blowfish
|
||||
golang.org/x/crypto/cast5
|
||||
|
|
Loading…
Reference in New Issue