1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-14 11:23:02 +02:00

pull master

This commit is contained in:
Владимир Фетисов 2019-10-03 22:42:14 +03:00
commit 3bb0ab183b
134 changed files with 5000 additions and 2135 deletions

View File

@ -15,21 +15,20 @@ builds:
- amd64
- arm64
archive:
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
replacements:
darwin: darwin
linux: linux
windows: windows
386: i386
amd64: x86_64
# Can be used to change the archive formats for specific GOOSs.
# Most common use case is to archive as zip on Windows.
# Default is empty.
format_overrides:
- goos: windows
format: zip
archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
replacements:
darwin: darwin
linux: linux
windows: windows
386: i386
amd64: x86_64
# Can be used to change the archive formats for specific GOOSs.
# Most common use case is to archive as zip on Windows.
# Default is empty.
format_overrides:
- goos: windows
format: zip
checksum:
name_template: '{{ .ProjectName }}_checksums.txt'

View File

@ -10,23 +10,25 @@ go:
- "1.12.x"
- stable
services:
- docker
addons:
apt:
packages:
- openjdk-9-jre-headless
chrome: stable
install:
- go get -u github.com/mgechev/revive
- go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
- sudo curl -o /usr/local/lib/antlr-4.7.1-complete.jar https://www.antlr.org/download/antlr-4.7.1-complete.jar
- export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH"
- sudo curl -o /usr/local/lib/antlr-4.7.2-complete.jar https://www.antlr.org/download/antlr-4.7.2-complete.jar
- export CLASSPATH=".:/usr/local/lib/antlr-4.7.2-complete.jar:$CLASSPATH"
- mkdir $HOME/travis-bin
- echo -e '#!/bin/bash\njava -jar /usr/local/lib/antlr-4.7.1-complete.jar "$@"' > $HOME/travis-bin/antlr
- echo -e '#!/bin/bash\njava -jar /usr/local/lib/antlr-4.7.2-complete.jar "$@"' > $HOME/travis-bin/antlr
- echo -e '#!/bin/bash\njava org.antlr.v4.gui.TestRig "$@"' > $HOME/travis-bin/grun
- chmod +x $HOME/travis-bin/*
- export PATH=$PATH:$HOME/travis-bin
- export GO111MODULE=on
- git reset --hard
stages:
- lint
@ -56,11 +58,13 @@ jobs:
- stage: e2e
go: stable
before_script:
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 --disable-setuid-sandbox --no-sandbox about:blank &
- docker pull microbox/chromium-headless:75.0.3765.1
- docker run -d -p 9222:9222 microbox/chromium-headless:75.0.3765.1
- docker ps
script:
- make e2e
after_script:
- killall google-chrome-stable
- docker stop $(docker ps -q)
- stage: bench
go: stable
script:

View File

@ -1,5 +1,25 @@
## Changelog
### 0.9.0
#### Added
- ``INPUT_CLEAR`` function to clear input's value. [#366](https://github.com/MontFerret/ferret/pull/366)
- Support of tick for string literals. [#367](https://github.com/MontFerret/ferret/pull/367)
- Support of default headers and cookies. [#372](https://github.com/MontFerret/ferret/pull/372)
- Support of use of params in dot notation. [#378](https://github.com/MontFerret/ferret/pull/378)
- Optional count param to ``CLICK`` function. [#377](https://github.com/MontFerret/ferret/pull/377)
- ``BLUR`` function. [#379](https://github.com/MontFerret/ferret/pull/379)
#### Fixed
- Tabs don't get closed on page load error. [#359](https://github.com/MontFerret/ferret/pull/359)
- ``CLICK`` function does not allow to use element with a selector. [#355](https://github.com/MontFerret/ferret/pull/355)
- Unable to use member expression right after a function call. [#368](https://github.com/MontFerret/ferret/pull/368)
#### Changed
- Updated zerolog. [#352](https://github.com/MontFerret/ferret/pull/352)
- Runtime ``Object`` and ``Array`` values implement ``core.Getter`` interface. [#353](https://github.com/MontFerret/ferret/pull/353)
- Externalized default timeout values. [#371](https://github.com/MontFerret/ferret/pull/371)
- Refactored ``drivers.HTMLDocument`` and ``drivers.HTMLElement`` interfaces. [#376](https://github.com/MontFerret/ferret/pull/376), [#375](https://github.com/MontFerret/ferret/pull/375)
### 0.8.3
#### Fixed
- Unable to click by selector using an element.

View File

@ -49,8 +49,7 @@ fmt:
# https://github.com/mgechev/revive
# go get github.com/mgechev/revive
lint:
revive -config revive.toml -formatter friendly -exclude ./pkg/parser/fql/... -exclude ./vendor/... ./... && \
golangci-lint run ./pkg/...
revive -config revive.toml -formatter stylish -exclude ./pkg/parser/fql/... -exclude ./vendor/... ./...
# http://godoc.org/code.google.com/p/go.tools/cmd/vet
# go get code.google.com/p/go.tools/cmd/vet

View File

@ -60,12 +60,108 @@ func (r *Runner) Run(ctx context.Context) error {
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress)),
)
ctx = drivers.WithContext(
ctx,
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress),
cdp.WithCustomName("cdp_headers"),
cdp.WithHeader("Single_header", []string{"single_header_value"}),
cdp.WithHeaders(drivers.HTTPHeaders{
"Multi_set_header": []string{"multi_set_header_value"},
"Multi_set_header2": []string{"multi_set_header2_value"},
}),
),
)
ctx = drivers.WithContext(
ctx,
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress),
cdp.WithCustomName("cdp_cookies"),
cdp.WithCookie(drivers.HTTPCookie{
Name: "single_cookie",
Value: "single_cookie_value",
Path: "/",
MaxAge: 0,
Secure: false,
HTTPOnly: false,
SameSite: 0,
}),
cdp.WithCookies([]drivers.HTTPCookie{
{
Name: "multi_set_cookie",
Value: "multi_set_cookie_value",
Path: "/",
MaxAge: 0,
Secure: false,
HTTPOnly: false,
SameSite: 0,
},
{
Name: "multi_set_cookie2",
Value: "multi_set_cookie2_value",
Path: "/",
MaxAge: 0,
Secure: false,
HTTPOnly: false,
SameSite: 0,
},
}),
),
)
ctx = drivers.WithContext(
ctx,
http.NewDriver(),
drivers.AsDefault(),
)
ctx = drivers.WithContext(
ctx,
http.NewDriver(
http.WithCustomName("http_headers"),
http.WithHeader("Single_header", []string{"single_header_value"}),
http.WithHeaders(drivers.HTTPHeaders{
"Multi_set_header": []string{"multi_set_header_value"},
"Multi_set_header2": []string{"multi_set_header2_value"},
}),
),
)
ctx = drivers.WithContext(
ctx,
http.NewDriver(
http.WithCustomName("http_cookies"),
http.WithCookie(drivers.HTTPCookie{
Name: "single_cookie",
Value: "single_cookie_value",
Path: "/",
MaxAge: 0,
Secure: false,
HTTPOnly: false,
SameSite: 0,
}),
http.WithCookies([]drivers.HTTPCookie{
{
Name: "multi_set_cookie",
Value: "multi_set_cookie_value",
Path: "/",
MaxAge: 0,
Secure: false,
HTTPOnly: false,
SameSite: 0,
},
{
Name: "multi_set_cookie2",
Value: "multi_set_cookie2_value",
Path: "/",
MaxAge: 0,
Secure: false,
HTTPOnly: false,
SameSite: 0,
},
}),
),
)
results, err := r.runQueries(ctx, r.settings.Dir)
if err != nil {
@ -184,7 +280,7 @@ func (r *Runner) runQueries(ctx context.Context, dir string) ([]Result, error) {
return results, nil
}
func (r *Runner) runQuery(ctx context.Context, c *compiler.FqlCompiler, name, script string) Result {
func (r *Runner) runQuery(ctx context.Context, c *compiler.Compiler, name, script string) Result {
start := time.Now()
p, err := c.Compile(script)

View File

@ -8,7 +8,7 @@ import (
"path/filepath"
"time"
"github.com/labstack/echo"
"github.com/labstack/echo/v4"
)
type (
@ -54,6 +54,18 @@ func New(settings Settings) *Server {
headers = string(b)
}
var cookies string
if len(ctx.Request().Cookies()) > 0 {
b, err := json.Marshal(ctx.Request().Cookies())
if err != nil {
return err
}
cookies = string(b)
}
ts := time.Now().Format("2006-01-02 15:04:05")
return ctx.HTML(http.StatusOK, fmt.Sprintf(`
@ -65,9 +77,10 @@ func New(settings Settings) *Server {
<body>
<span id="timestamp">%s</span>
<span id="headers">%s</span>
<span id="cookies">%s</span>
</body>
</html>
`, ts, headers))
`, ts, headers, cookies))
})
api.GET("/ping", func(ctx echo.Context) error {
return ctx.JSON(http.StatusOK, echo.Map{

View File

@ -0,0 +1,21 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "cdp_cookies"
})
LET el = ELEMENT(page, "#cookies")
LET actual = (
FOR c IN JSON_PARSE(el.innerText)
SORT c.Name
RETURN c
)
LET expected = {
"Single_cookie": "single_cookie_value",
"Multi_set_cookie": "multi_set_cookie_value",
}
RETURN EXPECT(expected, {
"Single_cookie": actual[2].Value,
"Multi_set_cookie": actual[0].Value,
})

View File

@ -0,0 +1,31 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "cdp_cookies",
cookies: [
{
name: "Single_cookie",
value: "Foo"
},
{
name: "Multi_set_cookie",
value: "Bar"
}
]
})
LET el = ELEMENT(page, "#cookies")
LET actual = (
FOR c IN JSON_PARSE(el.innerText)
SORT c.Name
RETURN c
)
LET expected = {
"Single_cookie": "Foo",
"Multi_set_cookie": "Bar",
}
RETURN EXPECT(expected, {
"Single_cookie": actual[1].Value,
"Multi_set_cookie": actual[0].Value,
})

View File

@ -0,0 +1,17 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "cdp_headers"
})
LET el = ELEMENT(page, "#headers")
LET actual = JSON_PARSE(el.innerText)
LET expected = {
"Single_header": ["single_header_value"],
"Multi_set_header":["multi_set_header_value"],
}
RETURN EXPECT(expected, {
"Single_header": actual["Single_header"],
"Multi_set_header": actual["Multi_set_header"],
})

View File

@ -0,0 +1,20 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "cdp_headers",
headers: {
"single_header": "foo"
}
})
LET el = ELEMENT(page, "#headers")
LET actual = JSON_PARSE(el.innerText)
LET expected = {
"Single_header": ["foo"],
"Multi_set_header":["multi_set_header_value"],
}
RETURN EXPECT(expected, {
"Single_header": actual["Single_header"],
"Multi_set_header": actual["Multi_set_header"],
})

View File

@ -0,0 +1,14 @@
LET url = @dynamic + "/#/events"
LET page = DOCUMENT(url, true)
LET input = ELEMENT(page, "#focus-input")
FOCUS(input)
WAIT_CLASS(page, "#focus-content", "alert-success")
BLUR(input)
WAIT_NO_CLASS(page, "#focus-content", "alert-success")
RETURN ""

View File

@ -0,0 +1,12 @@
LET url = @dynamic + "/#/events"
LET page = DOCUMENT(url, true)
FOCUS(page, "#focus-input")
WAIT_CLASS(page, "#focus-content", "alert-success")
BLUR(page, "#focus-input")
WAIT_NO_CLASS(page, "#focus-content", "alert-success")
RETURN ""

View File

@ -0,0 +1,12 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET input = ELEMENT(doc, "#text_input")
INPUT(input, "Foo", 100)
INPUT_CLEAR(input)
RETURN EXPECT("", INNER_TEXT(doc, "#text_output"))

View File

@ -0,0 +1,14 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET form = ELEMENT(doc, "#page-form")
INPUT(form, "#text_input", "foo")
INPUT_CLEAR(form, "#text_input")
LET input = ELEMENT(doc, "#text_input")
LET output = ELEMENT(doc, "#text_output")
RETURN EXPECT("", output.innerText)

View File

@ -1,4 +1,4 @@
LET url = "http://192.168.1.170:8080/#/events"
LET url = @dynamic + "/#/events"
LET page = DOCUMENT(url, true)
LET div = ELEMENT(page, "#wait-class-random")
@ -7,4 +7,4 @@ CLICK(div, "button")
WAIT_CLASS(page, "#wait-class-random-content", "alert-success", 10000)
RETURN ""
RETURN ""

View File

@ -0,0 +1,16 @@
LET url = @dynamic + "/#/forms"
LET page = DOCUMENT(url, true)
WAIT_ELEMENT(page, "form")
LET input = ELEMENT(page, "#text_input")
INPUT(input, "Foo")
CLICK(page, "#text_input", 2)
INPUT(input, "Bar")
WAIT(100)
RETURN EXPECT("Bar", input.value)

View File

@ -0,0 +1,16 @@
LET url = @dynamic + "/#/forms"
LET page = DOCUMENT(url, true)
WAIT_ELEMENT(page, "form")
LET input = ELEMENT(page, "#text_input")
INPUT(input, "Foo")
CLICK(input, 2)
INPUT(input, "Bar")
WAIT(100)
RETURN EXPECT("Bar", input.value)

View File

@ -0,0 +1,8 @@
LET url = @dynamic + "/#/events"
LET page = DOCUMENT(url, true)
FOCUS(page, "#focus-input")
WAIT_CLASS(page, "#focus-content", "alert-success")
RETURN ""

View File

@ -0,0 +1,12 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET form = ELEMENT(doc, "#page-form")
INPUT(form, "#text_input", "foo")
LET output = ELEMENT(doc, "#text_output")
RETURN EXPECT(output.innerText, "foo")

View File

@ -0,0 +1,12 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET form = ELEMENT(doc, "#page-form")
INPUT(form, "#text_input", "foo", 100)
LET output = ELEMENT(doc, "#text_output")
RETURN EXPECT(output.innerText, "foo")

View File

@ -0,0 +1,21 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "http_cookies"
})
LET el = ELEMENT(page, "#cookies")
LET actual = (
FOR c IN JSON_PARSE(el.innerText)
SORT c.Name
RETURN c
)
LET expected = {
"Single_cookie": "single_cookie_value",
"Multi_set_cookie": "multi_set_cookie_value",
}
RETURN EXPECT(expected, {
"Single_cookie": actual[2].Value,
"Multi_set_cookie": actual[0].Value,
})

View File

@ -0,0 +1,10 @@
LET url = @static + "/api/ts"
LET doc = DOCUMENT(url, {
driver: "http"
})
LET cookiesPath = LENGTH(doc.cookies) > 0 ? "ok" : "false"
LET cookie = COOKIE_GET(doc, "x-ferret")
LET expected = "ok e2e"
RETURN EXPECT(expected, cookiesPath + " " + cookie.value)

View File

@ -0,0 +1,31 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "http_cookies",
cookies: [
{
name: "Single_cookie",
value: "Foo"
},
{
name: "Multi_set_cookie",
value: "Bar"
}
]
})
LET el = ELEMENT(page, "#cookies")
LET actual = (
FOR c IN JSON_PARSE(el.innerText)
SORT c.Name
RETURN c
)
LET expected = {
"Single_cookie": "Foo",
"Multi_set_cookie": "Bar",
}
RETURN EXPECT(expected, {
"Single_cookie": actual[1].Value,
"Multi_set_cookie": actual[0].Value,
})

View File

@ -0,0 +1,17 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "http_headers"
})
LET el = ELEMENT(page, "#headers")
LET actual = JSON_PARSE(el.innerText)
LET expected = {
"Single_header": ["single_header_value"],
"Multi_set_header":["multi_set_header_value"],
}
RETURN EXPECT(expected, {
"Single_header": actual["Single_header"],
"Multi_set_header": actual["Multi_set_header"],
})

View File

@ -0,0 +1,20 @@
LET url = @static + "/api/ts"
LET page = DOCUMENT(url, {
driver: "http_headers",
headers: {
"single_header": "foo"
}
})
LET el = ELEMENT(page, "#headers")
LET actual = JSON_PARSE(el.innerText)
LET expected = {
"Single_header": ["foo"],
"Multi_set_header":["multi_set_header_value"],
}
RETURN EXPECT(expected, {
"Single_header": actual["Single_header"],
"Multi_set_header": actual["Multi_set_header"],
})

37
go.mod
View File

@ -1,46 +1,25 @@
module github.com/MontFerret/ferret
go 1.12
go 1.13
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/PuerkitoBio/goquery v1.5.0
github.com/antchfx/htmlquery v1.0.0
github.com/antchfx/xpath v1.0.0
github.com/antlr/antlr4 v0.0.0-20190325153624-837aa60e2c47
github.com/chzyer/logex v1.1.10 // indirect
github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/corpix/uarand v0.0.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/derekparker/trie v0.0.0-20190322172448-1ce4922c7ad9
github.com/fatih/structs v1.1.0
github.com/corpix/uarand v0.1.1
github.com/derekparker/trie v0.0.0-20190812220523-e66023ee76eb
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.2.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f // indirect
github.com/gorilla/css v1.0.0
github.com/gorilla/websocket v1.4.0 // indirect
github.com/k0kubun/pp v3.0.1+incompatible
github.com/kr/pretty v0.1.0 // indirect
github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.2.8 // indirect
github.com/gorilla/websocket v1.4.1 // indirect
github.com/labstack/echo/v4 v4.1.10
github.com/mafredri/cdp v0.24.2
github.com/mattn/go-colorable v0.1.1 // indirect
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/pkg/errors v0.8.1
github.com/rs/zerolog v1.15.0
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac // indirect
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff
github.com/stretchr/testify v1.3.0 // indirect
github.com/valyala/fasttemplate v1.0.1 // indirect
github.com/yudai/pp v2.0.1+incompatible
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c // indirect
golang.org/x/net v0.0.0-20190328230028-74de082e2cca
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

92
go.sum
View File

@ -1,5 +1,6 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
@ -8,74 +9,57 @@ github.com/antchfx/htmlquery v1.0.0 h1:O5IXz8fZF3B3MW+B33MZWbTHBlYmcfw0BAxgErHua
github.com/antchfx/htmlquery v1.0.0/go.mod h1:MS9yksVSQXls00iXkiMqXr0J+umL/AmxXKuP28SUJM8=
github.com/antchfx/xpath v1.0.0 h1:Q5gFgh2O40VTSwMOVbFE7nFNRBu3tS21Tn0KAWeEjtk=
github.com/antchfx/xpath v1.0.0/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antlr/antlr4 v0.0.0-20190325153624-837aa60e2c47 h1:Lp5nUoQzppfVmfZadpzAytNyb5IMtxyOJLzoQS5dExg=
github.com/antlr/antlr4 v0.0.0-20190325153624-837aa60e2c47/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015 h1:StuiJFxQUsxSCzcby6NFZRdEhPkXD5vxN7TZ4MD6T84=
github.com/antlr/antlr4 v0.0.0-20190819145818-b43a4c3a8015/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/corpix/uarand v0.0.0 h1:mNbzro1GwUcZ1hmO2rWXytkR3JBxNxxctzjyuhO+Aig=
github.com/corpix/uarand v0.0.0/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M=
github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/derekparker/trie v0.0.0-20190322172448-1ce4922c7ad9 h1:aSaTVlEXc2QKl4fzXU1tMYCjlrSc2mA4DZtiVfckQHo=
github.com/derekparker/trie v0.0.0-20190322172448-1ce4922c7ad9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/derekparker/trie v0.0.0-20190812220523-e66023ee76eb h1:HGjKnH6D1WD8sc9SfCkWkeD4OteWPPXD+ayJY+P1Bgk=
github.com/derekparker/trie v0.0.0-20190812220523-e66023ee76eb/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f h1:4Gslotqbs16iAg+1KR/XdabIfq8TlAWHdwS5QJFksLc=
github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
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/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40=
github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
github.com/labstack/echo/v4 v4.1.10 h1:/yhIpO50CBInUbE/nHJtGIyhBv0dJe2cDAYxc3V3uMo=
github.com/labstack/echo/v4 v4.1.10/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/mafredri/cdp v0.24.2 h1:Rzhj/EQw9opbiwUpNML7P+4Hvf0/nSYPaDbiCEpILOM=
github.com/mafredri/cdp v0.24.2/go.mod h1:hgdiA0yp1uqhSaDOHJWPgXpMbh+LAfUdD9vbN2AM8gE=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0 h1:X9XMOYjxEfAYSy3xK1DzO5dMkkWhs9E9UCcS1IERx2k=
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg=
github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys=
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
@ -84,26 +68,24 @@ github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcm
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI=
golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,19 +1,20 @@
package compiler
import (
"github.com/pkg/errors"
"github.com/MontFerret/ferret/pkg/parser"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/stdlib"
"github.com/pkg/errors"
)
type FqlCompiler struct {
type Compiler struct {
*NamespaceContainer
}
func New(setters ...Option) *FqlCompiler {
c := &FqlCompiler{}
func New(setters ...Option) *Compiler {
c := &Compiler{}
c.NamespaceContainer = newRootNamespace()
c.funcs = make(map[string]core.Function)
@ -32,7 +33,7 @@ func New(setters ...Option) *FqlCompiler {
return c
}
func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error) {
func (c *Compiler) Compile(query string) (program *runtime.Program, err error) {
if query == "" {
return nil, ErrEmptyQuery
}
@ -69,7 +70,7 @@ func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error
return program, err
}
func (c *FqlCompiler) MustCompile(query string) *runtime.Program {
func (c *Compiler) MustCompile(query string) *runtime.Program {
program, err := c.Compile(query)
if err != nil {

View File

@ -119,14 +119,110 @@ func TestMember(t *testing.T) {
So(string(out), ShouldEqual, `"wsx"`)
})
Convey("Deep path", func() {
c := compiler.New()
p, err := c.Compile(`
LET obj = {
first: {
second: {
third: {
fourth: {
fifth: {
bottom: true
}
}
}
}
}
}
RETURN obj.first.second.third.fourth.fifth.bottom
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `true`)
})
Convey("Deep computed path", func() {
c := compiler.New()
p, err := c.Compile(`
LET obj = {
first: {
second: {
third: {
fourth: {
fifth: {
bottom: true
}
}
}
}
}
}
RETURN obj["first"]["second"]["third"]["fourth"]["fifth"].bottom
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `true`)
})
Convey("Prop after a func call", func() {
c := compiler.New()
p, err := c.Compile(`
LET arr = [{ name: "Bob" }]
RETURN FIRST(arr).name
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `"Bob"`)
})
Convey("Computed prop after a func call", func() {
c := compiler.New()
p, err := c.Compile(`
LET arr = [{ name: { first: "Bob" } }]
RETURN FIRST(arr)['name'].first
`)
So(err, ShouldBeNil)
out, err := p.Run(context.Background())
So(err, ShouldBeNil)
So(string(out), ShouldEqual, `"Bob"`)
})
})
}
func BenchmarkMemberArray(b *testing.B) {
p := compiler.New().MustCompile(`
LET arr = [1]
LET arr = [[[[1]]]]
RETURN arr[0]
RETURN arr[0][0][0][0]
`)
for n := 0; n < b.N; n++ {
@ -136,9 +232,21 @@ func BenchmarkMemberArray(b *testing.B) {
func BenchmarkMemberObject(b *testing.B) {
p := compiler.New().MustCompile(`
LET obj = { "foo": "bar"}
LET obj = {
first: {
second: {
third: {
fourth: {
fifth: {
bottom: true
}
}
}
}
}
}
RETURN obj.foo
RETURN obj.first.second.third.fourth.fifth.bottom
`)
for n := 0; n < b.N; n++ {

View File

@ -63,4 +63,54 @@ func TestParam(t *testing.T) {
So(string(out), ShouldEqual, `[1,2,3,4]`)
})
Convey("Should be possible to use in member expression", t, func() {
prog := compiler.New().
MustCompile(`
RETURN @param.value
`)
out := prog.MustRun(
context.Background(),
runtime.WithParam("param", map[string]interface{}{
"value": "foobar",
}),
)
So(string(out), ShouldEqual, `"foobar"`)
})
Convey("Should be possible to use in member expression as a computed property", t, func() {
prog := compiler.New().
MustCompile(`
LET obj = { foo: "bar" }
RETURN obj[@param]
`)
out := prog.MustRun(
context.Background(),
runtime.WithParam("param", "foo"),
)
So(string(out), ShouldEqual, `"bar"`)
})
Convey("Should be possible to use in member expression as segments", t, func() {
prog := compiler.New().
MustCompile(`
LET doc = { foo: { bar: "baz" } }
RETURN doc.@attr.@subattr
`)
out := prog.MustRun(
context.Background(),
runtime.WithParam("attr", "foo"),
runtime.WithParam("subattr", "bar"),
)
So(string(out), ShouldEqual, `"baz"`)
})
}

View File

@ -23,7 +23,7 @@ BAR
So(string(out), ShouldEqual, `"\nFOO\nBAR\n"`)
})
Convey("Should be possible to use multi line string with nested strings", t, func() {
Convey("Should be possible to use multi line string with nested strings using backtick", t, func() {
compiler.New().
MustCompile(fmt.Sprintf(`
RETURN %s<!DOCTYPE html>
@ -54,6 +54,38 @@ RETURN %s<!DOCTYPE html>
So(string(out), ShouldEqual, string(out))
})
Convey("Should be possible to use multi line string with nested strings using tick", t, func() {
compiler.New().
MustCompile(fmt.Sprintf(`
RETURN %s<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GetTitle</title>
</head>
<body>
Hello world
</body>
</html>%s
`, "´", "´")).
MustRun(context.Background())
out, err := json.Marshal(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GetTitle</title>
</head>
<body>
Hello world
</body>
</html>`)
So(err, ShouldBeNil)
So(string(out), ShouldEqual, string(out))
})
}
func BenchmarkStringLiteral(b *testing.B) {

View File

@ -736,15 +736,13 @@ func (v *visitor) doVisitForExpressionStatement(ctx *fql.ForExpressionStatementC
}
func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scope *scope) (core.Expression, error) {
varName := ctx.Identifier().GetText()
_, err := scope.GetVariable(varName)
member, err := v.doVisitMember(ctx.Member().(*fql.MemberContext), scope)
if err != nil {
return nil, err
}
children := ctx.GetChildren()
children := ctx.MemberPath().GetChildren()
path := make([]core.Expression, 0, len(children))
for _, child := range children {
@ -784,9 +782,9 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
path = append(path, exp)
}
member, err := expressions.NewMemberExpression(
exp, err := expressions.NewMemberExpression(
v.getSourceMap(ctx),
varName,
member,
path,
)
@ -794,7 +792,51 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
return nil, err
}
return member, nil
return exp, nil
}
func (v *visitor) doVisitMember(ctx *fql.MemberContext, scope *scope) (core.Expression, error) {
identifier := ctx.Identifier()
if identifier != nil {
varName := ctx.Identifier().GetText()
_, err := scope.GetVariable(varName)
if err != nil {
return nil, err
}
exp, err := expressions.NewVariableExpression(v.getSourceMap(ctx), varName)
if err != nil {
return nil, err
}
return exp, nil
}
fnCall := ctx.FunctionCallExpression()
if fnCall != nil {
exp, err := v.doVisitFunctionCallExpression(fnCall.(*fql.FunctionCallExpressionContext), scope)
if err != nil {
return nil, err
}
return exp, nil
}
param := ctx.Param()
exp, err := v.doVisitParamContext(param.(*fql.ParamContext), scope)
if err != nil {
return nil, err
}
return exp, nil
}
func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *scope) (core.Expression, error) {
@ -847,7 +889,7 @@ func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *sco
return literals.NewObjectLiteralWith(props...), nil
}
func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, _ *scope) (core.Expression, error) {
func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, scope *scope) (core.Expression, error) {
var name string
identifier := ctx.Identifier()
@ -860,6 +902,14 @@ func (v *visitor) doVisitPropertyNameContext(ctx *fql.PropertyNameContext, _ *sc
if stringLiteral != nil {
runes := []rune(stringLiteral.GetText())
name = string(runes[1 : len(runes)-1])
} else {
param, err := v.doVisitParamContext(ctx.Param().(*fql.ParamContext), scope)
if err != nil {
return nil, err
}
return param, nil
}
}
@ -937,8 +987,6 @@ func (v *visitor) doVisitStringLiteral(ctx *fql.StringLiteralContext) (core.Expr
if strLiteral != nil {
text = strLiteral.GetText()
} else {
text = ctx.TemplateStringLiteral().GetText()
}
// remove extra quotes

View File

@ -243,23 +243,23 @@ func (doc *HTMLDocument) GetNodeName() values.String {
return "#document"
}
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) core.Value {
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) (*values.Array, error) {
return doc.element.GetChildNodes(ctx)
}
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) core.Value {
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
return doc.element.GetChildNode(ctx, idx)
}
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
return doc.element.QuerySelector(ctx, selector)
}
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
return doc.element.QuerySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) values.Int {
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
return doc.element.CountBySelector(ctx, selector)
}
@ -268,7 +268,7 @@ func (doc *HTMLDocument) ExistsBySelector(ctx context.Context, selector values.S
}
func (doc *HTMLDocument) GetTitle() values.String {
value, err := doc.exec.ReadProperty(context.Background(), doc.element.id.objectID, "title")
value, err := doc.exec.ReadProperty(context.Background(), doc.element.id.ObjectID, "title")
if err != nil {
doc.logError(errors.Wrap(err, "failed to read document title"))
@ -317,32 +317,8 @@ func (doc *HTMLDocument) GetURL() values.String {
return values.NewString(doc.frames.Frame.URL)
}
func (doc *HTMLDocument) ClickBySelector(ctx context.Context, selector values.String) error {
return doc.element.ClickBySelector(ctx, selector)
}
func (doc *HTMLDocument) ClickBySelectorAll(ctx context.Context, selector values.String) error {
return doc.element.ClickBySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return doc.input.TypeBySelector(ctx, doc.element.id.nodeID, selector, value, delay)
}
func (doc *HTMLDocument) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) {
return doc.input.SelectBySelector(ctx, doc.element.id.nodeID, selector, value)
}
func (doc *HTMLDocument) FocusBySelector(ctx context.Context, selector values.String) error {
return doc.input.FocusBySelector(ctx, doc.element.id.nodeID, selector)
}
func (doc *HTMLDocument) MoveMouseBySelector(ctx context.Context, selector values.String) error {
return doc.input.MoveMouseBySelector(ctx, doc.element.id.nodeID, selector)
}
func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) error {
return doc.input.MoveMouseByXY(ctx, x, y)
return doc.input.MoveMouseByXY(ctx, float64(x), float64(y))
}
func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error {
@ -501,11 +477,11 @@ func (doc *HTMLDocument) ScrollBottom(ctx context.Context) error {
}
func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector values.String) error {
return doc.input.ScrollIntoViewBySelector(ctx, selector)
return doc.input.ScrollIntoViewBySelector(ctx, selector.String())
}
func (doc *HTMLDocument) ScrollByXY(ctx context.Context, x, y values.Float) error {
return doc.input.ScrollByXY(ctx, x, y)
return doc.input.ScrollByXY(ctx, float64(x), float64(y))
}
func (doc *HTMLDocument) loadChildren(ctx context.Context) (value core.Value, e error) {

View File

@ -2,15 +2,13 @@ package cdp
import (
"context"
"sync"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/devtool"
"github.com/mafredri/cdp/protocol/target"
"github.com/mafredri/cdp/rpcc"
"github.com/mafredri/cdp/session"
"github.com/pkg/errors"
"sync"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/logging"
@ -18,7 +16,6 @@ import (
const DriverName = "cdp"
const BlankPageURL = "about:blank"
const DefaultTimeout = 5000 * time.Millisecond
var defaultViewport = &drivers.Viewport{
Width: 1600,
@ -107,6 +104,34 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
params.Viewport = defaultViewport
}
if drv.options.Headers != nil && params.Headers == nil {
params.Headers = make(drivers.HTTPHeaders)
}
// set default headers
for k, v := range drv.options.Headers {
_, exists := params.Headers[k]
// do not override user's set values
if !exists {
params.Headers[k] = v
}
}
if drv.options.Cookies != nil && params.Cookies == nil {
params.Cookies = make(drivers.HTTPCookies)
}
// set default cookies
for k, v := range drv.options.Cookies {
_, exists := params.Cookies[k]
// do not override user's set values
if !exists {
params.Cookies[k] = v
}
}
return LoadHTMLPage(ctx, conn, params)
}

View File

@ -8,6 +8,14 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
@ -18,21 +26,14 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"golang.org/x/net/html"
)
var emptyNodeID = dom.NodeID(0)
type (
HTMLElementIdentity struct {
nodeID dom.NodeID
objectID runtime.RemoteObjectID
NodeID dom.NodeID
ObjectID runtime.RemoteObjectID
}
HTMLElement struct {
@ -48,7 +49,6 @@ type (
nodeName values.String
innerHTML *common.LazyValue
innerText *common.LazyValue
value core.Value
attributes *common.LazyValue
style *common.LazyValue
children []HTMLElementIdentity
@ -82,10 +82,6 @@ func LoadHTMLElement(
return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %d", nodeID))
}
id := HTMLElementIdentity{}
id.nodeID = nodeID
id.objectID = *obj.Object.ObjectID
return LoadHTMLElementWithID(
ctx,
logger,
@ -93,7 +89,10 @@ func LoadHTMLElement(
broker,
input,
exec,
id,
HTMLElementIdentity{
NodeID: nodeID,
ObjectID: *obj.Object.ObjectID,
},
)
}
@ -110,18 +109,12 @@ func LoadHTMLElementWithID(
ctx,
dom.
NewDescribeNodeArgs().
SetObjectID(id.objectID).
SetObjectID(id.ObjectID).
SetDepth(1),
)
if err != nil {
return nil, core.Error(err, strconv.Itoa(int(id.nodeID)))
}
var val string
if node.Node.Value != nil {
val = *node.Node.Value
return nil, core.Error(err, strconv.Itoa(int(id.NodeID)))
}
return NewHTMLElement(
@ -133,7 +126,6 @@ func LoadHTMLElementWithID(
id,
node.Node.NodeType,
node.Node.NodeName,
val,
createChildrenArray(node.Node.Children),
), nil
}
@ -147,7 +139,6 @@ func NewHTMLElement(
id HTMLElementIdentity,
nodeType int,
nodeName string,
value string,
children []HTMLElementIdentity,
) *HTMLElement {
el := new(HTMLElement)
@ -164,9 +155,7 @@ func NewHTMLElement(
el.innerText = common.NewLazyValue(el.loadInnerText)
el.attributes = common.NewLazyValue(el.loadAttrs)
el.style = common.NewLazyValue(el.parseStyle)
el.value = values.EmptyString
el.loadedChildren = common.NewLazyValue(el.loadChildren)
el.value = values.NewString(value)
el.children = children
broker.AddEventListener(events.EventReload, el.handlePageReload)
@ -208,7 +197,7 @@ func (el *HTMLElement) MarshalJSON() ([]byte, error) {
}
func (el *HTMLElement) String() string {
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(drivers.DefaultWaitTimeout)*time.Millisecond)
defer cancel()
res, err := el.GetInnerHTML(ctx)
@ -242,7 +231,7 @@ func (el *HTMLElement) Hash() uint64 {
h.Write([]byte(el.Type().String()))
h.Write([]byte(":"))
h.Write([]byte(strconv.Itoa(int(el.id.nodeID))))
h.Write([]byte(strconv.Itoa(int(el.id.NodeID))))
return h.Sum64()
}
@ -263,31 +252,20 @@ func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.
return common.SetInElement(ctx, el, path, value)
}
func (el *HTMLElement) GetValue(ctx context.Context) core.Value {
func (el *HTMLElement) GetValue(ctx context.Context) (core.Value, error) {
if el.IsDetached() {
return el.value
return values.None, drivers.ErrDetached
}
val, err := el.exec.ReadProperty(ctx, el.id.objectID, "value")
if err != nil {
el.logError(err).Msg("failed to get node value")
return el.value
}
el.value = val
return val
return el.exec.ReadProperty(ctx, el.id.ObjectID, "value")
}
func (el *HTMLElement) SetValue(ctx context.Context, value core.Value) error {
if el.IsDetached() {
// TODO: Return an error
return nil
return drivers.ErrDetached
}
return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.nodeID, value.String()))
return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.NodeID, value.String()))
}
func (el *HTMLElement) GetNodeType() values.Int {
@ -395,33 +373,33 @@ func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String)
return el.SetAttribute(ctx, "style", str)
}
func (el *HTMLElement) GetAttributes(ctx context.Context) *values.Object {
func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error) {
val, err := el.attributes.Read(ctx)
if err != nil {
return values.NewObject()
return values.NewObject(), err
}
attrs := val.(*values.Object)
// returning shallow copy
return attrs.Copy().(*values.Object)
return attrs.Copy().(*values.Object), nil
}
func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) core.Value {
func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) (core.Value, error) {
attrs, err := el.attributes.Read(ctx)
if err != nil {
return values.None
return values.None, err
}
val, found := attrs.(*values.Object).Get(name)
if !found {
return values.None
return values.None, nil
}
return val
return val, nil
}
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
@ -439,7 +417,7 @@ func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object)
func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error {
return el.client.DOM.SetAttributeValue(
ctx,
dom.NewSetAttributeValueArgs(el.id.nodeID, string(name), string(value)),
dom.NewSetAttributeValueArgs(el.id.NodeID, string(name), string(value)),
)
}
@ -447,7 +425,7 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri
for _, name := range names {
err := el.client.DOM.RemoveAttribute(
ctx,
dom.NewRemoveAttributeArgs(el.id.nodeID, name.String()),
dom.NewRemoveAttributeArgs(el.id.NodeID, name.String()),
)
if err != nil {
@ -458,79 +436,61 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri
return nil
}
func (el *HTMLElement) GetChildNodes(ctx context.Context) core.Value {
func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) {
val, err := el.loadedChildren.Read(ctx)
if err != nil {
return values.NewArray(0)
return values.NewArray(0), err
}
return val
return val.Copy().(*values.Array), nil
}
func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) core.Value {
func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
val, err := el.loadedChildren.Read(ctx)
if err != nil {
return values.None
return values.None, err
}
return val.(*values.Array).Get(idx)
return val.(*values.Array).Get(idx), nil
}
func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
if el.IsDetached() {
return values.None
return values.None, drivers.ErrDetached
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String())
found, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve a node by selector")
return values.None
return values.None, err
}
if found.NodeID == emptyNodeID {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to find a node by selector. returned 0 NodeID")
return values.None
return values.None, nil
}
res, err := LoadHTMLElement(ctx, el.logger, el.client, el.events, el.input, el.exec, found.NodeID)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to load a child node by selector")
return values.None
return values.None, nil
}
return res
return res, nil
}
func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
if el.IsDetached() {
return values.NewArray(0)
return values.NewArray(0), drivers.ErrDetached
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve nodes by selector")
return values.None
return values.NewArray(0), err
}
arr := values.NewArray(len(res.NodeIDs))
@ -547,10 +507,6 @@ func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.Str
childEl, err := LoadHTMLElement(ctx, el.logger, el.client, el.events, el.input, el.exec, id)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to load nodes by selector")
// close elements that are already loaded, but won't be used because of the error
if arr.Length() > 0 {
arr.ForEach(func(e core.Value, _ int) bool {
@ -560,13 +516,13 @@ func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.Str
})
}
return values.None
return values.NewArray(0), err
}
arr.Push(childEl)
}
return arr
return arr, nil
}
func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (result core.Value, err error) {
@ -578,7 +534,7 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res
out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.XPath(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: json.RawMessage(exp),
@ -657,8 +613,8 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res
el.input,
el.exec,
HTMLElementIdentity{
nodeID: repl.NodeID,
objectID: *descr.Value.ObjectID,
NodeID: repl.NodeID,
ObjectID: *descr.Value.ObjectID,
},
)
@ -689,8 +645,8 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res
el.input,
el.exec,
HTMLElementIdentity{
nodeID: repl.NodeID,
objectID: *out.ObjectID,
NodeID: repl.NodeID,
ObjectID: *out.ObjectID,
},
)
default:
@ -737,7 +693,7 @@ func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector valu
ctx,
templates.GetInnerTextBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@ -772,7 +728,7 @@ func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, inn
ctx,
templates.SetInnerTextBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@ -798,7 +754,7 @@ func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector v
ctx,
templates.GetInnerTextBySelectorAll(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@ -857,7 +813,7 @@ func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector valu
ctx,
templates.GetInnerHTMLBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@ -892,7 +848,7 @@ func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, inn
ctx,
templates.SetInnerHTMLBySelector(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@ -918,7 +874,7 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v
ctx,
templates.GetInnerHTMLBySelectorAll(),
runtime.CallArgument{
ObjectID: &el.id.objectID,
ObjectID: &el.id.ObjectID,
},
runtime.CallArgument{
Value: sel,
@ -938,24 +894,19 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v
return arr, nil
}
func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) values.Int {
func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
if el.IsDetached() {
return values.ZeroInt
return values.ZeroInt, drivers.ErrDetached
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs)
if err != nil {
el.logError(err).
Str("selector", selector.String()).
Msg("failed to retrieve nodes by selector")
return values.ZeroInt
return values.ZeroInt, err
}
return values.NewInt(len(res.NodeIDs))
return values.NewInt(len(res.NodeIDs)), nil
}
func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) {
@ -964,7 +915,7 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str
}
// TODO: Can we use RemoteObjectID or BackendID instead of NodeId?
selectorArgs := dom.NewQuerySelectorArgs(el.id.nodeID, selector.String())
selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String())
res, err := el.client.DOM.QuerySelector(ctx, selectorArgs)
if err != nil {
@ -981,7 +932,11 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str
func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, when drivers.WaitEvent) error {
task := events.NewWaitTask(
func(ctx2 context.Context) (core.Value, error) {
current := el.GetAttribute(ctx2, "class")
current, err := el.GetAttribute(ctx2, "class")
if err != nil {
return values.None, nil
}
if current.Type() != types.String {
return values.None, nil
@ -1033,7 +988,7 @@ func (el *HTMLElement) WaitForAttribute(
when drivers.WaitEvent,
) error {
task := events.NewValueWaitTask(when, value, func(ctx context.Context) (core.Value, error) {
return el.GetAttribute(ctx, name), nil
return el.GetAttribute(ctx, name)
}, events.DefaultPolling)
_, err := task.Run(ctx)
@ -1051,16 +1006,16 @@ func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, val
return err
}
func (el *HTMLElement) Click(ctx context.Context) error {
return el.input.Click(ctx, el.id.objectID)
func (el *HTMLElement) Click(ctx context.Context, count values.Int) error {
return el.input.Click(ctx, el.id.ObjectID, int(count))
}
func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String) error {
return el.input.ClickBySelector(ctx, el.id.nodeID, selector)
func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String, count values.Int) error {
return el.input.ClickBySelector(ctx, el.id.NodeID, selector.String(), int(count))
}
func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String) error {
return el.input.ClickBySelectorAll(ctx, el.id.nodeID, selector)
func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error {
return el.input.ClickBySelectorAll(ctx, el.id.NodeID, selector.String(), int(count))
}
func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values.Int) error {
@ -1068,23 +1023,63 @@ func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values
return core.Error(core.ErrInvalidOperation, "element is not an <input> element.")
}
return el.input.Type(ctx, el.id.objectID, value, delay)
return el.input.Type(ctx, el.id.ObjectID, input.TypeParams{
Text: value.String(),
Clear: false,
Delay: time.Duration(delay) * time.Millisecond,
})
}
func (el *HTMLElement) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return el.input.TypeBySelector(ctx, el.id.NodeID, selector.String(), input.TypeParams{
Text: value.String(),
Clear: false,
Delay: time.Duration(delay) * time.Millisecond,
})
}
func (el *HTMLElement) Clear(ctx context.Context) error {
return el.input.Clear(ctx, el.id.ObjectID)
}
func (el *HTMLElement) ClearBySelector(ctx context.Context, selector values.String) error {
return el.input.ClearBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values.Array, error) {
return el.input.Select(ctx, el.id.objectID, value)
return el.input.Select(ctx, el.id.ObjectID, value)
}
func (el *HTMLElement) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) {
return el.input.SelectBySelector(ctx, el.id.NodeID, selector.String(), value)
}
func (el *HTMLElement) ScrollIntoView(ctx context.Context) error {
return el.input.ScrollIntoView(ctx, el.id.objectID)
return el.input.ScrollIntoView(ctx, el.id.ObjectID)
}
func (el *HTMLElement) Focus(ctx context.Context) error {
return el.input.Focus(ctx, el.id.objectID)
return el.input.Focus(ctx, el.id.ObjectID)
}
func (el *HTMLElement) FocusBySelector(ctx context.Context, selector values.String) error {
return el.input.FocusBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) Blur(ctx context.Context) error {
return el.input.Blur(ctx, el.id.ObjectID)
}
func (el *HTMLElement) BlurBySelector(ctx context.Context, selector values.String) error {
return el.input.BlurBySelector(ctx, el.id.ObjectID, selector.String())
}
func (el *HTMLElement) Hover(ctx context.Context) error {
return el.input.MoveMouse(ctx, el.id.objectID)
return el.input.MoveMouse(ctx, el.id.ObjectID)
}
func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.String) error {
return el.input.MoveMouseBySelector(ctx, el.id.NodeID, selector.String())
}
func (el *HTMLElement) IsDetached() values.Boolean {
@ -1139,7 +1134,7 @@ func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
}
func (el *HTMLElement) loadAttrs(ctx context.Context) (core.Value, error) {
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.nodeID))
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID))
if err != nil {
return values.None, err
@ -1163,7 +1158,7 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
el.events,
el.input,
el.exec,
childID.nodeID,
childID.NodeID,
)
if err != nil {
@ -1179,7 +1174,11 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
}
func (el *HTMLElement) parseStyle(ctx context.Context) (core.Value, error) {
value := el.GetAttribute(ctx, "style")
value, err := el.GetAttribute(ctx, "style")
if err != nil {
return values.None, err
}
if value == values.None {
return values.NewObject(), nil
@ -1205,7 +1204,7 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
}
// it's not for this el
if reply.NodeID != el.id.nodeID {
if reply.NodeID != el.id.NodeID {
return
}
@ -1215,6 +1214,10 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return
}
if el.IsDetached() {
return
}
el.attributes.Mutate(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@ -1245,7 +1248,7 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
}
// it's not for this el
if reply.NodeID != el.id.nodeID {
if reply.NodeID != el.id.NodeID {
return
}
@ -1255,6 +1258,10 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
return
}
if el.IsDetached() {
return
}
el.attributes.Mutate(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@ -1283,13 +1290,20 @@ func (el *HTMLElement) handleChildrenCountChanged(ctx context.Context, message i
return
}
if reply.NodeID != el.id.nodeID {
if reply.NodeID != el.id.NodeID {
return
}
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
node, err := el.client.DOM.DescribeNode(
ctx,
dom.NewDescribeNodeArgs().SetObjectID(el.id.objectID),
dom.NewDescribeNodeArgs().SetObjectID(el.id.ObjectID),
)
if err != nil {
@ -1298,9 +1312,6 @@ func (el *HTMLElement) handleChildrenCountChanged(ctx context.Context, message i
return
}
el.mu.Lock()
defer el.mu.Unlock()
el.children = createChildrenArray(node.Node.Children)
}
@ -1311,7 +1322,7 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
return
}
if reply.ParentNodeID != el.id.nodeID {
if reply.ParentNodeID != el.id.NodeID {
return
}
@ -1319,11 +1330,15 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
prevID := reply.PreviousNodeID
nextID := reply.Node.NodeID
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
for idx, id := range el.children {
if id.nodeID == prevID {
if id.NodeID == prevID {
targetIDx = idx
break
}
@ -1334,7 +1349,7 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
}
nextIdentity := HTMLElementIdentity{
nodeID: reply.Node.NodeID,
NodeID: reply.Node.NodeID,
}
arr := el.children
@ -1368,18 +1383,22 @@ func (el *HTMLElement) handleChildRemoved(ctx context.Context, message interface
return
}
if reply.ParentNodeID != el.id.nodeID {
if reply.ParentNodeID != el.id.NodeID {
return
}
targetIDx := -1
targetID := reply.NodeID
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
for idx, id := range el.children {
if id.nodeID == targetID {
if id.NodeID == targetID {
targetIDx = idx
break
}
@ -1401,7 +1420,7 @@ func (el *HTMLElement) handleChildRemoved(ctx context.Context, message interface
el.logger.Error().
Timestamp().
Err(err).
Int("nodeID", int(el.id.nodeID)).
Int("nodeID", int(el.id.NodeID)).
Msg("failed to update element")
return
@ -1419,7 +1438,7 @@ func (el *HTMLElement) logError(err error) *zerolog.Event {
return el.logger.
Error().
Timestamp().
Int("nodeID", int(el.id.nodeID)).
Str("objectID", string(el.id.objectID)).
Int("nodeID", int(el.id.NodeID)).
Str("objectID", string(el.id.ObjectID)).
Err(err)
}

View File

@ -4,6 +4,7 @@ import (
"context"
"reflect"
"sync"
"time"
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/page"
@ -289,7 +290,7 @@ func (broker *EventBroker) emit(ctx context.Context, event Event, message interf
case <-ctx.Done():
return
default:
ctx2, fn := drivers.WithDefaultTimeout(ctx)
ctx2, fn := context.WithTimeout(ctx, time.Duration(drivers.DefaultTimeout)*time.Millisecond)
listener(ctx2, message)

View File

@ -71,10 +71,10 @@ func parseAttrs(attrs []string) *values.Object {
func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, innerHTML values.String) error {
var objID *runtime.RemoteObjectID
if id.objectID != "" {
objID = &id.objectID
if id.ObjectID != "" {
objID = &id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return err
@ -110,10 +110,10 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC
if nodeType != html.DocumentNode {
var objID runtime.RemoteObjectID
if id.objectID != "" {
objID = id.objectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return "", err
@ -147,10 +147,10 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC
func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, innerText values.String) error {
var objID *runtime.RemoteObjectID
if id.objectID != "" {
objID = &id.objectID
if id.ObjectID != "" {
objID = &id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return err
@ -186,10 +186,10 @@ func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC
if nodeType != html.DocumentNode {
var objID runtime.RemoteObjectID
if id.objectID != "" {
objID = id.objectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.nodeID))
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return "", err
@ -238,7 +238,7 @@ func createChildrenArray(nodes []dom.Node) []HTMLElementIdentity {
for idx, child := range nodes {
child := child
children[idx] = HTMLElementIdentity{
nodeID: child.NodeID,
NodeID: child.NodeID,
}
}

View File

@ -2,15 +2,45 @@ package input
import (
"context"
"github.com/pkg/errors"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/input"
)
type Keyboard struct {
client *cdp.Client
}
const DefaultDelay = 25
type (
KeyboardModifier int
KeyboardLocation int
KeyboardKey struct {
KeyCode int
Key string
Code string
Modifier KeyboardModifier
Location KeyboardLocation
}
Keyboard struct {
client *cdp.Client
}
)
const (
KeyboardModifierNone KeyboardModifier = 0
KeyboardModifierAlt KeyboardModifier = 1
KeyboardModifierCtrl KeyboardModifier = 2
KeyboardModifierCmd KeyboardModifier = 4
KeyboardModifierShift KeyboardModifier = 8
// 1=Left, 2=Right
KeyboardLocationNone KeyboardLocation = 0
KeyboardLocationLeft KeyboardLocation = 1
KeyboardLocationRight KeyboardLocation = 2
)
func NewKeyboard(client *cdp.Client) *Keyboard {
return &Keyboard{client}
@ -32,7 +62,7 @@ func (k *Keyboard) Up(ctx context.Context, char string) error {
)
}
func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
func (k *Keyboard) Type(ctx context.Context, text string, delay time.Duration) error {
for _, ch := range text {
ch := string(ch)
@ -40,7 +70,7 @@ func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
return err
}
releaseDelay := randomDuration(delay)
releaseDelay := randomDuration(int(delay))
time.Sleep(releaseDelay)
if err := k.Up(ctx, ch); err != nil {
@ -50,3 +80,34 @@ func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
return nil
}
func (k *Keyboard) Press(ctx context.Context, name string) error {
key, found := usKeyboardLayout[name]
if !found {
return errors.New("invalid key")
}
err := k.client.Input.DispatchKeyEvent(
ctx,
input.NewDispatchKeyEventArgs("keyDown").
SetCode(key.Code).
SetKey(key.Key).
SetWindowsVirtualKeyCode(key.KeyCode),
)
if err != nil {
return err
}
releaseDelay := randomDuration(DefaultDelay)
time.Sleep(releaseDelay)
return k.client.Input.DispatchKeyEvent(
ctx,
input.NewDispatchKeyEventArgs("keyUp").
SetCode(key.Code).
SetKey(key.Key).
SetWindowsVirtualKeyCode(key.KeyCode),
)
}

File diff suppressed because it is too large Load Diff

View File

@ -8,18 +8,27 @@ import (
"github.com/mafredri/cdp/protocol/dom"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
type Manager struct {
client *cdp.Client
exec *eval.ExecutionContext
keyboard *Keyboard
mouse *Mouse
}
type (
TypeParams struct {
Text string
Clear bool
Delay time.Duration
}
Manager struct {
client *cdp.Client
exec *eval.ExecutionContext
keyboard *Keyboard
mouse *Mouse
}
)
func NewManager(
client *cdp.Client,
@ -61,14 +70,14 @@ func (m *Manager) ScrollIntoView(ctx context.Context, objectID runtime.RemoteObj
)
}
func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector values.String) error {
return m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector.String()))
func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector string) error {
return m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector))
}
func (m *Manager) ScrollByXY(ctx context.Context, x, y values.Float) error {
func (m *Manager) ScrollByXY(ctx context.Context, x, y float64) error {
return m.exec.Eval(
ctx,
templates.Scroll(eval.ParamFloat(float64(x)), eval.ParamFloat(float64(y))),
templates.Scroll(eval.ParamFloat(x), eval.ParamFloat(y)),
)
}
@ -82,14 +91,14 @@ func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) er
return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID))
}
func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return nil
@ -98,6 +107,18 @@ func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID,
return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
}
func (m *Manager) Blur(ctx context.Context, objectID runtime.RemoteObjectID) error {
return m.exec.EvalWithArguments(ctx, templates.Blur(), runtime.CallArgument{
ObjectID: &objectID,
})
}
func (m *Manager) BlurBySelector(ctx context.Context, parentObjectID runtime.RemoteObjectID, selector string) error {
return m.exec.EvalWithArguments(ctx, templates.BlurBySelector(selector), runtime.CallArgument{
ObjectID: &parentObjectID,
})
}
func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID) error {
if err := m.ScrollIntoView(ctx, objectID); err != nil {
return err
@ -112,12 +133,12 @@ func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID
return m.mouse.Move(ctx, q.X, q.Y)
}
func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
@ -132,15 +153,15 @@ func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.Node
return m.mouse.Move(ctx, q.X, q.Y)
}
func (m *Manager) MoveMouseByXY(ctx context.Context, x, y values.Float) error {
func (m *Manager) MoveMouseByXY(ctx context.Context, x, y float64) error {
if err := m.ScrollByXY(ctx, x, y); err != nil {
return err
}
return m.mouse.Move(ctx, float64(x), float64(y))
return m.mouse.Move(ctx, x, y)
}
func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) error {
func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, count int) error {
if err := m.ScrollIntoView(ctx, objectID); err != nil {
return err
}
@ -151,19 +172,21 @@ func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) er
return err
}
if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
return nil
}
return nil
}
func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error {
if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
@ -175,29 +198,30 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID,
return err
}
if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
return nil
}
return nil
}
func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error {
if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil {
return err
}
found, err := m.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector))
if err != nil {
return err
}
for _, nodeID := range found.NodeIDs {
_, min := core.NumberBoundaries(100)
beforeTypeDelay := time.Duration(min)
beforeTypeDelay := time.Duration(core.NumberLowerBoundary(drivers.DefaultMouseDelay*10)) * time.Millisecond
time.Sleep(beforeTypeDelay * time.Millisecond)
time.Sleep(beforeTypeDelay)
points, err := GetClickablePointByNodeID(ctx, m.client, nodeID)
@ -205,7 +229,9 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI
return err
}
if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
return nil
}
}
@ -213,7 +239,7 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI
return nil
}
func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, text core.Value, delay values.Int) error {
func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, params TypeParams) error {
err := m.ScrollIntoView(ctx, objectID)
if err != nil {
@ -226,22 +252,34 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, tex
return err
}
_, min := core.NumberBoundaries(float64(delay))
beforeTypeDelay := time.Duration(min)
if params.Clear {
points, err := GetClickablePointByObjectID(ctx, m.client, objectID)
time.Sleep(beforeTypeDelay * time.Millisecond)
if err != nil {
return err
}
return m.keyboard.Type(ctx, text.String(), int(delay))
if err := m.ClearByXY(ctx, points); err != nil {
return err
}
}
d := core.NumberLowerBoundary(float64(params.Delay))
beforeTypeDelay := time.Duration(d)
time.Sleep(beforeTypeDelay)
return m.keyboard.Type(ctx, params.Text, params.Delay)
}
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, text core.Value, delay values.Int) error {
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, params TypeParams) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
@ -253,12 +291,85 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s
return err
}
_, min := core.NumberBoundaries(float64(delay))
beforeTypeDelay := time.Duration(min)
if params.Clear {
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
time.Sleep(beforeTypeDelay * time.Millisecond)
if err != nil {
return err
}
return m.keyboard.Type(ctx, text.String(), int(delay))
if err := m.ClearByXY(ctx, points); err != nil {
return err
}
}
d := core.NumberLowerBoundary(float64(params.Delay))
beforeTypeDelay := time.Duration(d)
time.Sleep(beforeTypeDelay)
return m.keyboard.Type(ctx, params.Text, params.Delay)
}
func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) error {
err := m.ScrollIntoView(ctx, objectID)
if err != nil {
return err
}
points, err := GetClickablePointByObjectID(ctx, m.client, objectID)
if err != nil {
return err
}
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID))
if err != nil {
return err
}
return m.ClearByXY(ctx, points)
}
func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
if err != nil {
return err
}
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
if err != nil {
return err
}
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
if err != nil {
return err
}
return m.ClearByXY(ctx, points)
}
func (m *Manager) ClearByXY(ctx context.Context, points Quad) error {
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, 2)
if err != nil {
return err
}
return m.keyboard.Press(ctx, "Backspace")
}
func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, value *values.Array) (*values.Array, error) {
@ -283,12 +394,12 @@ func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, v
return arr, nil
}
func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, value *values.Array) (*values.Array, error) {
func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, value *values.Array) (*values.Array, error) {
if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil {
return values.NewArray(0), err
}
res, err := m.exec.EvalWithReturnValue(ctx, templates.SelectBySelector(selector.String(), value.String()))
res, err := m.exec.EvalWithReturnValue(ctx, templates.SelectBySelector(selector, value.String()))
if err != nil {
return values.NewArray(0), err

View File

@ -18,37 +18,47 @@ func NewMouse(client *cdp.Client) *Mouse {
return &Mouse{client, 0, 0}
}
func (m *Mouse) Click(ctx context.Context, x, y float64, delay int) error {
func (m *Mouse) Click(ctx context.Context, x, y float64, delay time.Duration) error {
return m.ClickWithCount(ctx, x, y, delay, 1)
}
func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, delay time.Duration, count int) error {
if err := m.Move(ctx, x, y); err != nil {
return err
}
if err := m.Down(ctx, "left"); err != nil {
if err := m.DownWithCount(ctx, "left", count); err != nil {
return err
}
releaseDelay := randomDuration(delay)
time.Sleep(randomDuration(int(delay)))
time.Sleep(releaseDelay * time.Millisecond)
return m.Up(ctx, "left")
return m.UpWithCount(ctx, "left", count)
}
func (m *Mouse) Down(ctx context.Context, button string) error {
return m.DownWithCount(ctx, button, 1)
}
func (m *Mouse) DownWithCount(ctx context.Context, button string, count int) error {
return m.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mousePressed", m.x, m.y).
SetClickCount(1).
SetButton(button),
SetButton(button).
SetClickCount(count),
)
}
func (m *Mouse) Up(ctx context.Context, button string) error {
return m.UpWithCount(ctx, button, 1)
}
func (m *Mouse) UpWithCount(ctx context.Context, button string, count int) error {
return m.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mouseReleased", m.x, m.y).
SetClickCount(1).
SetButton(button),
SetButton(button).
SetClickCount(count),
)
}

View File

@ -1,5 +1,7 @@
package cdp
import "github.com/MontFerret/ferret/pkg/drivers"
type (
Options struct {
Name string
@ -7,6 +9,8 @@ type (
UserAgent string
Address string
KeepCookies bool
Headers drivers.HTTPHeaders
Cookies drivers.HTTPCookies
}
Option func(opts *Options)
@ -57,3 +61,47 @@ func WithCustomName(name string) Option {
opts.Name = name
}
}
func WithHeader(name string, value []string) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
opts.Headers[name] = value
}
}
func WithHeaders(headers drivers.HTTPHeaders) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
for k, v := range headers {
opts.Headers[k] = v
}
}
}
func WithCookie(cookie drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
opts.Cookies[cookie.Name] = cookie
}
}
func WithCookies(cookies []drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
for _, c := range cookies {
opts.Cookies[c.Name] = c
}
}
}

View File

@ -144,7 +144,7 @@ func LoadHTMLPage(
return nil, err
}
if params.Cookies != nil {
if len(params.Cookies) > 0 {
cookies := make([]network.CookieParam, 0, len(params.Cookies))
for _, c := range params.Cookies {
@ -167,7 +167,7 @@ func LoadHTMLPage(
}
}
if params.Headers != nil {
if len(params.Headers) > 0 {
j, err := json.Marshal(params.Headers)
if err != nil {

View File

@ -0,0 +1,28 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
)
func Blur() string {
return `
(el) => {
el.blur()
}
`
}
func BlurBySelector(selector string) string {
return fmt.Sprintf(`
(parent) => {
const el = parent.querySelector('%s');
if (el == null) {
throw new Error('%s')
}
el.blur();
}
`, selector, drivers.ErrNotFound)
}

View File

@ -121,7 +121,11 @@ func GetInDocument(ctx context.Context, doc drivers.HTMLDocument, path []core.Va
return GetInDocument(ctx, parent, path[1:])
case "body", "head":
out := doc.QuerySelector(ctx, segment)
out, err := doc.QuerySelector(ctx, segment)
if err != nil {
return values.None, err
}
if out == values.None {
return out, nil
@ -166,9 +170,13 @@ func GetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
case "innerHTML":
return el.GetInnerHTML(ctx)
case "value":
return el.GetValue(ctx), nil
return el.GetValue(ctx)
case "attributes":
attrs := el.GetAttributes(ctx)
attrs, err := el.GetAttributes(ctx)
if err != nil {
return values.None, err
}
if len(path) == 1 {
return attrs, nil
@ -209,7 +217,7 @@ func GetInNode(ctx context.Context, node drivers.HTMLNode, path []core.Value) (c
if nt == drivers.HTMLElementType || nt == drivers.HTMLDocumentType {
re := node.(drivers.HTMLNode)
return re.GetChildNode(ctx, segment.(values.Int)), nil
return re.GetChildNode(ctx, values.ToInt(segment))
}
return values.GetIn(ctx, node, path[1:])
@ -224,7 +232,11 @@ func GetInNode(ctx context.Context, node drivers.HTMLNode, path []core.Value) (c
case "nodeName":
return node.GetNodeName(), nil
case "children":
children := node.GetChildNodes(ctx)
children, err := node.GetChildNodes(ctx)
if err != nil {
return values.None, err
}
if len(path) == 1 {
return children, nil

View File

@ -26,7 +26,11 @@ func NewIterator(
func (iterator *Iterator) Next(ctx context.Context) (value core.Value, key core.Value, err error) {
if iterator.node.Length() > iterator.pos {
idx := iterator.pos
val := iterator.node.GetChildNode(ctx, idx)
val, err := iterator.node.GetChildNode(ctx, idx)
if err != nil {
return values.None, values.None, err
}
iterator.pos++

View File

@ -49,7 +49,11 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
return err
}
curr := el.GetAttributes(ctx)
curr, err := el.GetAttributes(ctx)
if err != nil {
return err
}
// remove all previous attributes
err = el.RemoveAttribute(ctx, curr.Keys()...)

9
pkg/drivers/consts.go Normal file
View File

@ -0,0 +1,9 @@
package drivers
const (
DefaultPageLoadTimeout = 60000
DefaultWaitTimeout = 5000
DefaultKeyboardDelay = 25
DefaultMouseDelay = 10
DefaultTimeout = 30000
)

View File

@ -30,6 +30,8 @@ type (
HTTPOnly bool
SameSite SameSite
}
HTTPCookies map[string]HTTPCookie
)
const (

View File

@ -2,14 +2,10 @@ package drivers
import (
"context"
"io"
"time"
"github.com/MontFerret/ferret/pkg/runtime/core"
"io"
)
const DefaultTimeout = time.Second * 30
type (
ctxKey struct{}

View File

@ -1,15 +1,9 @@
package drivers
import (
"context"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
func WithDefaultTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
return context.WithTimeout(ctx, DefaultTimeout)
}
func ToPage(value core.Value) (HTMLPage, error) {
err := core.ValidateType(value, HTMLPageType)

View File

@ -115,7 +115,7 @@ func (doc *HTMLDocument) Copy() core.Value {
return cp
}
func (doc *HTMLDocument) Clone() core.Value {
func (doc *HTMLDocument) Clone() core.Cloneable {
cloned, err := NewHTMLDocument(doc.doc, doc.url.String(), doc.parent)
if err != nil {
@ -149,23 +149,23 @@ func (doc *HTMLDocument) GetNodeName() values.String {
return "#document"
}
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) core.Value {
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) (*values.Array, error) {
return doc.element.GetChildNodes(ctx)
}
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) core.Value {
func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) {
return doc.element.GetChildNode(ctx, idx)
}
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) {
return doc.element.QuerySelector(ctx, selector)
}
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) core.Value {
func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) {
return doc.element.QuerySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) values.Int {
func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) {
return doc.element.CountBySelector(ctx, selector)
}
@ -207,30 +207,6 @@ func (doc *HTMLDocument) GetParentDocument() drivers.HTMLDocument {
return doc.parent
}
func (doc *HTMLDocument) ClickBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) ClickBySelectorAll(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) InputBySelector(_ context.Context, _ values.String, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) SelectBySelector(_ context.Context, _ values.String, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) PrintToPDF(_ context.Context, _ drivers.PDFParams) (values.Binary, error) {
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) CaptureScreenshot(_ context.Context, _ drivers.ScreenshotParams) (values.Binary, error) {
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) ScrollTop(_ context.Context) error {
return core.ErrNotSupported
}
@ -247,22 +223,10 @@ func (doc *HTMLDocument) ScrollByXY(_ context.Context, _, _ values.Float) error
return core.ErrNotSupported
}
func (doc *HTMLDocument) FocusBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) MoveMouseBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) MoveMouseByXY(_ context.Context, _, _ values.Float) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForNavigation(_ context.Context) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForElement(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}

View File

@ -26,7 +26,7 @@ func NewDriver(opts ...Option) *Driver {
drv := new(Driver)
drv.options = newOptions(opts)
if drv.options.proxy == "" {
if drv.options.Proxy == "" {
drv.client = pester.New()
} else {
client, err := newClientWithProxy(drv.options)
@ -38,15 +38,15 @@ func NewDriver(opts ...Option) *Driver {
}
}
drv.client.Concurrency = drv.options.concurrency
drv.client.MaxRetries = drv.options.maxRetries
drv.client.Backoff = drv.options.backoff
drv.client.Concurrency = drv.options.Concurrency
drv.client.MaxRetries = drv.options.MaxRetries
drv.client.Backoff = drv.options.Backoff
return drv
}
func newClientWithProxy(options *Options) (*http.Client, error) {
proxyURL, err := url.Parse(options.proxy)
proxyURL, err := url.Parse(options.Proxy)
if err != nil {
return nil, err
@ -59,7 +59,7 @@ func newClientWithProxy(options *Options) (*http.Client, error) {
}
func (drv *Driver) Name() string {
return DriverName
return drv.options.Name
}
func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTMLPage, error) {
@ -76,33 +76,54 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
if params.Headers != nil {
for k := range params.Headers {
req.Header.Add(k, params.Headers.Get(k))
if drv.options.Headers != nil && params.Headers == nil {
params.Headers = make(drivers.HTTPHeaders)
}
logger.
Debug().
Timestamp().
Str("header", k).
Msg("set header")
// Set default headers
for k, v := range drv.options.Headers {
_, exists := params.Headers[k]
// do not override user's set values
if !exists {
params.Headers[k] = v
}
}
if params.Cookies != nil {
for _, c := range params.Cookies {
req.AddCookie(&http.Cookie{
Name: c.Name,
Value: c.Value,
})
for k := range params.Headers {
req.Header.Add(k, params.Headers.Get(k))
logger.
Debug().
Timestamp().
Str("cookie", c.Name).
Msg("set cookie")
logger.
Debug().
Timestamp().
Str("header", k).
Msg("set header")
}
if drv.options.Cookies != nil && params.Cookies == nil {
params.Cookies = make(drivers.HTTPCookies)
}
// set default cookies
for k, v := range drv.options.Cookies {
_, exists := params.Cookies[k]
// do not override user's set values
if !exists {
params.Cookies[k] = v
}
}
for _, c := range params.Cookies {
req.AddCookie(fromDriverCookie(c))
logger.
Debug().
Timestamp().
Str("cookie", c.Name).
Msg("set cookie")
}
req = req.WithContext(ctx)
var ua string
@ -110,7 +131,7 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
if params.UserAgent != "" {
ua = common.GetUserAgent(params.UserAgent)
} else {
ua = common.GetUserAgent(drv.options.userAgent)
ua = common.GetUserAgent(drv.options.UserAgent)
}
logger.
@ -141,6 +162,12 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
return nil, errors.Wrapf(err, "failed to parse a document %s", params.URL)
}
cookies, err := toDriverCookies(resp.Cookies())
if err != nil {
return nil, err
}
// HTTPResponse is temporarily unavailable when the status code != OK
r := drivers.HTTPResponse{
StatusCode: resp.StatusCode,
@ -148,7 +175,7 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM
Headers: drivers.HTTPHeaders(resp.Header),
}
return NewHTMLPage(doc, params.URL, params.Cookies, &r)
return NewHTMLPage(doc, params.URL, &r, cookies)
}
func (drv *Driver) Parse(_ context.Context, str values.String) (drivers.HTMLPage, error) {

View File

@ -116,14 +116,14 @@ func (el *HTMLElement) Length() values.Int {
return el.children.Length()
}
func (el *HTMLElement) GetValue(_ context.Context) core.Value {
func (el *HTMLElement) GetValue(_ context.Context) (core.Value, error) {
val, ok := el.selection.Attr("value")
if ok {
return values.NewString(val)
return values.NewString(val), nil
}
return values.EmptyString
return values.EmptyString, nil
}
func (el *HTMLElement) SetValue(_ context.Context, value core.Value) error {
@ -242,16 +242,16 @@ func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object)
return err
}
func (el *HTMLElement) GetAttributes(_ context.Context) *values.Object {
func (el *HTMLElement) GetAttributes(_ context.Context) (*values.Object, error) {
el.ensureAttrs()
return el.attrs.Copy().(*values.Object)
return el.attrs.Copy().(*values.Object), nil
}
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) (core.Value, error) {
el.ensureAttrs()
return el.attrs.MustGet(name)
return el.attrs.MustGet(name), nil
}
func (el *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
@ -274,43 +274,43 @@ func (el *HTMLElement) RemoveAttribute(_ context.Context, name ...values.String)
return nil
}
func (el *HTMLElement) GetChildNodes(_ context.Context) core.Value {
func (el *HTMLElement) GetChildNodes(_ context.Context) (*values.Array, error) {
if el.children == nil {
el.children = el.parseChildren()
}
return el.children
return el.children.Copy().(*values.Array), nil
}
func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) (core.Value, error) {
if el.children == nil {
el.children = el.parseChildren()
}
return el.children.Get(idx)
return el.children.Get(idx), nil
}
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) (core.Value, error) {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.None
return values.None, nil
}
res, err := NewHTMLElement(selection)
if err != nil {
return values.None
return values.None, err
}
return res
return res, nil
}
func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) (*values.Array, error) {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.None
return values.NewArray(0), nil
}
arr := values.NewArray(selection.Length())
@ -323,7 +323,7 @@ func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.Strin
}
})
return arr
return arr, nil
}
func (el *HTMLElement) XPath(_ context.Context, expression values.String) (core.Value, error) {
@ -457,14 +457,14 @@ func (el *HTMLElement) GetInnerTextBySelectorAll(_ context.Context, selector val
return arr, nil
}
func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) (values.Int, error) {
selection := el.selection.Find(selector.String())
if selection == nil {
return values.ZeroInt
return values.ZeroInt, nil
}
return values.NewInt(selection.Size())
return values.NewInt(selection.Size()), nil
}
func (el *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) (values.Boolean, error) {
@ -489,15 +489,23 @@ func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
return common.NewIterator(el)
}
func (el *HTMLElement) Click(_ context.Context) error {
func (el *HTMLElement) Click(_ context.Context, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClickBySelector(_ context.Context, _ values.String) error {
func (el *HTMLElement) ClickBySelector(_ context.Context, _ values.String, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String) error {
func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Clear(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClearBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
@ -505,10 +513,18 @@ func (el *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) erro
return core.ErrNotSupported
}
func (el *HTMLElement) InputBySelector(_ context.Context, _ values.String, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (el *HTMLElement) SelectBySelector(_ context.Context, _ values.String, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}
func (el *HTMLElement) ScrollIntoView(_ context.Context) error {
return core.ErrNotSupported
}
@ -517,10 +533,26 @@ func (el *HTMLElement) Focus(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) FocusBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Blur(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) BlurBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Hover(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) HoverBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
@ -548,7 +580,11 @@ func (el *HTMLElement) ensureStyles(ctx context.Context) error {
}
func (el *HTMLElement) parseStyles(ctx context.Context) (*values.Object, error) {
str := el.GetAttribute(ctx, "style")
str, err := el.GetAttribute(ctx, "style")
if err != nil {
return values.NewObject(), err
}
if str == values.None {
return values.NewObject(), nil

View File

@ -322,8 +322,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
v := el.GetValue(context.Background())
v, err := el.GetValue(context.Background())
So(err, ShouldBeNil)
So(v, ShouldEqual, "find")
})
@ -393,8 +394,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
found := el.QuerySelector(context.Background(), values.NewString("body .card-img-top:nth-child(1)"))
found, err := el.QuerySelector(context.Background(), values.NewString("body .card-img-top:nth-child(1)"))
So(err, ShouldBeNil)
So(found, ShouldNotEqual, values.None)
v := found.(drivers.HTMLNode).GetNodeName()
@ -415,8 +417,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
v := el.CountBySelector(context.Background(), values.NewString("head meta"))
v, err := el.CountBySelector(context.Background(), values.NewString("head meta"))
So(err, ShouldBeNil)
So(v, ShouldEqual, 4)
})
}

View File

@ -2,14 +2,16 @@ package http
import (
"bytes"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
HTTP "net/http"
"github.com/PuerkitoBio/goquery"
"github.com/antchfx/htmlquery"
"github.com/antchfx/xpath"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func parseXPathNode(nav *htmlquery.NodeNavigator) (core.Value, error) {
@ -45,3 +47,71 @@ func outerHTML(s *goquery.Selection) (string, error) {
return buf.String(), nil
}
func toDriverCookies(cookies []*HTTP.Cookie) (drivers.HTTPCookies, error) {
res := make(drivers.HTTPCookies)
for _, c := range cookies {
dc, err := toDriverCookie(c)
if err != nil {
return nil, err
}
res[dc.Name] = dc
}
return res, nil
}
func toDriverCookie(cookie *HTTP.Cookie) (drivers.HTTPCookie, error) {
res := drivers.HTTPCookie{}
if cookie == nil {
return res, core.Error(core.ErrMissedArgument, "cookie")
}
res.Name = cookie.Name
res.Value = cookie.Value
res.Path = cookie.Path
res.Domain = cookie.Domain
res.Expires = cookie.Expires
res.MaxAge = cookie.MaxAge
res.Secure = cookie.Secure
res.HTTPOnly = cookie.HttpOnly
switch cookie.SameSite {
case HTTP.SameSiteLaxMode:
res.SameSite = drivers.SameSiteLaxMode
case HTTP.SameSiteStrictMode:
res.SameSite = drivers.SameSiteStrictMode
default:
res.SameSite = drivers.SameSiteDefaultMode
}
return res, nil
}
func fromDriverCookie(cookie drivers.HTTPCookie) *HTTP.Cookie {
res := &HTTP.Cookie{}
res.Name = cookie.Name
res.Value = cookie.Value
res.Path = cookie.Path
res.Domain = cookie.Domain
res.Expires = cookie.Expires
res.MaxAge = cookie.MaxAge
res.Secure = cookie.Secure
res.HttpOnly = cookie.HTTPOnly
switch cookie.SameSite {
case drivers.SameSiteLaxMode:
res.SameSite = HTTP.SameSiteLaxMode
case drivers.SameSiteStrictMode:
res.SameSite = HTTP.SameSiteStrictMode
default:
res.SameSite = HTTP.SameSiteDefaultMode
}
return res
}

View File

@ -1,6 +1,7 @@
package http
import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/sethgrid/pester"
)
@ -8,19 +9,23 @@ type (
Option func(opts *Options)
Options struct {
backoff pester.BackoffStrategy
maxRetries int
concurrency int
proxy string
userAgent string
Name string
Backoff pester.BackoffStrategy
MaxRetries int
Concurrency int
Proxy string
UserAgent string
Headers drivers.HTTPHeaders
Cookies drivers.HTTPCookies
}
)
func newOptions(setters []Option) *Options {
opts := new(Options)
opts.backoff = pester.ExponentialBackoff
opts.concurrency = 3
opts.maxRetries = 5
opts.Name = DriverName
opts.Backoff = pester.ExponentialBackoff
opts.Concurrency = 3
opts.MaxRetries = 5
for _, setter := range setters {
setter(opts)
@ -31,42 +36,92 @@ func newOptions(setters []Option) *Options {
func WithDefaultBackoff() Option {
return func(opts *Options) {
opts.backoff = pester.DefaultBackoff
opts.Backoff = pester.DefaultBackoff
}
}
func WithLinearBackoff() Option {
return func(opts *Options) {
opts.backoff = pester.LinearBackoff
opts.Backoff = pester.LinearBackoff
}
}
func WithExponentialBackoff() Option {
return func(opts *Options) {
opts.backoff = pester.ExponentialBackoff
opts.Backoff = pester.ExponentialBackoff
}
}
func WithMaxRetries(value int) Option {
return func(opts *Options) {
opts.maxRetries = value
opts.MaxRetries = value
}
}
func WithConcurrency(value int) Option {
return func(opts *Options) {
opts.concurrency = value
opts.Concurrency = value
}
}
func WithProxy(address string) Option {
return func(opts *Options) {
opts.proxy = address
opts.Proxy = address
}
}
func WithUserAgent(value string) Option {
return func(opts *Options) {
opts.userAgent = value
opts.UserAgent = value
}
}
func WithCustomName(name string) Option {
return func(opts *Options) {
opts.Name = name
}
}
func WithHeader(name string, value []string) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
opts.Headers[name] = value
}
}
func WithHeaders(headers drivers.HTTPHeaders) Option {
return func(opts *Options) {
if opts.Headers == nil {
opts.Headers = make(drivers.HTTPHeaders)
}
for k, v := range headers {
opts.Headers[k] = v
}
}
}
func WithCookie(cookie drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
opts.Cookies[cookie.Name] = cookie
}
}
func WithCookies(cookies []drivers.HTTPCookie) Option {
return func(opts *Options) {
if opts.Cookies == nil {
opts.Cookies = make(drivers.HTTPCookies)
}
for _, c := range cookies {
opts.Cookies[c.Name] = c
}
}
}

View File

@ -4,16 +4,17 @@ import (
"context"
"hash/fnv"
"github.com/PuerkitoBio/goquery"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/PuerkitoBio/goquery"
)
type HTMLPage struct {
document *HTMLDocument
cookies []drivers.HTTPCookie
cookies drivers.HTTPCookies
frames *values.Array
response *drivers.HTTPResponse
}
@ -21,8 +22,8 @@ type HTMLPage struct {
func NewHTMLPage(
qdoc *goquery.Document,
url string,
cookies []drivers.HTTPCookie,
response *drivers.HTTPResponse,
cookies drivers.HTTPCookies,
) (*HTMLPage, error) {
doc, err := NewRootHTMLDocument(qdoc, url)
@ -83,11 +84,17 @@ func (p *HTMLPage) Hash() uint64 {
}
func (p *HTMLPage) Copy() core.Value {
cookies := make(drivers.HTTPCookies)
for k, v := range p.cookies {
cookies[k] = v
}
page, err := NewHTMLPage(
p.document.doc,
p.document.GetURL().String(),
p.cookies,
p.response,
cookies,
)
if err != nil {

View File

@ -13,7 +13,7 @@ type (
URL string
UserAgent string
KeepCookies bool
Cookies []HTTPCookie
Cookies HTTPCookies
Headers HTTPHeaders
Viewport *Viewport
}

View File

@ -30,23 +30,19 @@ type (
GetNodeName() values.String
GetChildNodes(ctx context.Context) core.Value
GetChildNodes(ctx context.Context) (*values.Array, error)
GetChildNode(ctx context.Context, idx values.Int) core.Value
GetChildNode(ctx context.Context, idx values.Int) (core.Value, error)
QuerySelector(ctx context.Context, selector values.String) core.Value
QuerySelector(ctx context.Context, selector values.String) (core.Value, error)
QuerySelectorAll(ctx context.Context, selector values.String) core.Value
QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error)
CountBySelector(ctx context.Context, selector values.String) values.Int
CountBySelector(ctx context.Context, selector values.String) (values.Int, error)
ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error)
XPath(ctx context.Context, expression values.String) (core.Value, error)
ClickBySelector(ctx context.Context, selector values.String) error
ClickBySelectorAll(ctx context.Context, selector values.String) error
}
// HTMLElement is the most general base interface which most objects in a GetMainFrame implement.
@ -61,7 +57,7 @@ type (
SetInnerHTML(ctx context.Context, innerHTML values.String) error
GetValue(ctx context.Context) core.Value
GetValue(ctx context.Context) (core.Value, error)
SetValue(ctx context.Context, value core.Value) error
@ -75,9 +71,9 @@ type (
RemoveStyle(ctx context.Context, name ...values.String) error
GetAttributes(ctx context.Context) *values.Object
GetAttributes(ctx context.Context) (*values.Object, error)
GetAttribute(ctx context.Context, name values.String) core.Value
GetAttribute(ctx context.Context, name values.String) (core.Value, error)
SetAttributes(ctx context.Context, values *values.Object) error
@ -97,18 +93,38 @@ type (
GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error)
Click(ctx context.Context) error
Click(ctx context.Context, count values.Int) error
ClickBySelector(ctx context.Context, selector values.String, count values.Int) error
ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error
Clear(ctx context.Context) error
ClearBySelector(ctx context.Context, selector values.String) error
Input(ctx context.Context, value core.Value, delay values.Int) error
InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error
Select(ctx context.Context, value *values.Array) (*values.Array, error)
SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error)
ScrollIntoView(ctx context.Context) error
Focus(ctx context.Context) error
FocusBySelector(ctx context.Context, selector values.String) error
Blur(ctx context.Context) error
BlurBySelector(ctx context.Context, selector values.String) error
Hover(ctx context.Context) error
HoverBySelector(ctx context.Context, selector values.String) error
WaitForAttribute(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
WaitForStyle(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
@ -131,10 +147,6 @@ type (
GetChildDocuments(ctx context.Context) (*values.Array, error)
InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error
SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error)
ScrollTop(ctx context.Context) error
ScrollBottom(ctx context.Context) error
@ -143,12 +155,8 @@ type (
ScrollByXY(ctx context.Context, x, y values.Float) error
FocusBySelector(ctx context.Context, selector values.String) error
MoveMouseByXY(ctx context.Context, x, y values.Float) error
MoveMouseBySelector(ctx context.Context, selector values.String) error
WaitForElement(ctx context.Context, selector values.String, when WaitEvent) error
WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error

View File

@ -78,8 +78,7 @@ In: 'IN';
// Literals
Param: '@';
Identifier: Letter+ (Symbols (Identifier)*)* (Digit (Identifier)*)*;
StringLiteral: SQString | DQSring;
TemplateStringLiteral: '`' ('\\`' | ~'`')* '`';
StringLiteral: SQString | DQSring | BacktickString | TickString;
IntegerLiteral: [0-9]+;
FloatLiteral
: DecimalIntegerLiteral Dot [0-9]+ ExponentPart?
@ -108,4 +107,6 @@ fragment Digit
;
fragment DQSring: '"' ( '\\'. | '""' | ~('"'| '\\') )* '"';
fragment SQString: '\'' ('\\'. | '\'\'' | ~('\'' | '\\'))* '\'';
fragment BacktickString: '`' ('\\`' | ~'`')* '`';
fragment TickString: '´' ('\\´' | ~'´')* '´';
fragment NamespaceSeparator: '::';

View File

@ -57,10 +57,9 @@ In=56
Param=57
Identifier=58
StringLiteral=59
TemplateStringLiteral=60
IntegerLiteral=61
FloatLiteral=62
NamespaceSegment=63
IntegerLiteral=60
FloatLiteral=61
NamespaceSegment=62
':'=5
';'=6
'.'=7

View File

@ -159,7 +159,6 @@ booleanLiteral
stringLiteral
: StringLiteral
| TemplateStringLiteral
;
integerLiteral
@ -185,11 +184,6 @@ propertyAssignment
| shorthandPropertyName
;
memberExpression
: Identifier (Dot propertyName (computedPropertyName)*)+
| Identifier computedPropertyName (Dot propertyName (computedPropertyName)*)* (computedPropertyName (Dot propertyName)*)*
;
shorthandPropertyName
: variable
;
@ -201,6 +195,7 @@ computedPropertyName
propertyName
: Identifier
| stringLiteral
| param
;
expressionGroup
@ -215,6 +210,21 @@ functionCallExpression
: namespace Identifier arguments
;
member
: Identifier
| functionCallExpression
| param
;
memberPath
: (Dot propertyName (computedPropertyName)*)+
| computedPropertyName (Dot propertyName (computedPropertyName)*)* (computedPropertyName (Dot propertyName)*)*
;
memberExpression
: member memberPath
;
arguments
: OpenParen(expression (Comma expression)*)?CloseParen
;

File diff suppressed because one or more lines are too long

View File

@ -57,10 +57,9 @@ In=56
Param=57
Identifier=58
StringLiteral=59
TemplateStringLiteral=60
IntegerLiteral=61
FloatLiteral=62
NamespaceSegment=63
IntegerLiteral=60
FloatLiteral=61
NamespaceSegment=62
':'=5
';'=6
'.'=7

File diff suppressed because one or more lines are too long

View File

@ -57,10 +57,9 @@ In=56
Param=57
Identifier=58
StringLiteral=59
TemplateStringLiteral=60
IntegerLiteral=61
FloatLiteral=62
NamespaceSegment=63
IntegerLiteral=60
FloatLiteral=61
NamespaceSegment=62
':'=5
';'=6
'.'=7

View File

@ -14,7 +14,7 @@ var _ = fmt.Printf
var _ = unicode.IsLetter
var serializedLexerAtn = []uint16{
3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 65, 529,
3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 64, 544,
8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7,
9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12,
4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4,
@ -28,59 +28,61 @@ var serializedLexerAtn = []uint16{
4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4,
60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65,
9, 65, 4, 66, 9, 66, 4, 67, 9, 67, 4, 68, 9, 68, 4, 69, 9, 69, 4, 70, 9,
70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 3, 2, 3, 2, 3, 2, 3, 2, 7,
2, 152, 10, 2, 12, 2, 14, 2, 155, 11, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2,
3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 166, 10, 3, 12, 3, 14, 3, 169, 11, 3, 3,
3, 3, 3, 3, 4, 6, 4, 174, 10, 4, 13, 4, 14, 4, 175, 3, 4, 3, 4, 3, 5, 3,
5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 3, 9, 3, 10, 3,
10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3, 14, 3, 15, 3, 15,
3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3,
20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 23, 3, 23, 3, 24,
3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3,
28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 5, 29, 241, 10, 29, 3, 30, 3, 30,
3, 30, 3, 30, 5, 30, 247, 10, 30, 3, 31, 3, 31, 3, 31, 3, 32, 3, 32, 3,
33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3, 36, 3, 36, 3, 36,
3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3,
38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3, 39, 3, 39, 3, 39,
3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40, 3, 41, 3, 41, 3,
41, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 42, 3, 43, 3, 43, 3, 43,
3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44, 3, 44, 3, 44, 3,
44, 3, 44, 5, 44, 319, 10, 44, 3, 45, 3, 45, 3, 45, 3, 45, 3, 45, 3, 46,
3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3,
47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47,
3, 47, 5, 47, 349, 10, 47, 3, 48, 3, 48, 3, 48, 3, 48, 3, 48, 3, 49, 3,
49, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 50, 3, 50, 3, 51, 3, 51,
3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 52, 3, 53, 3, 53, 3,
53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54,
3, 54, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 3, 56, 5,
56, 399, 10, 56, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 3, 59, 6, 59, 407,
10, 59, 13, 59, 14, 59, 408, 3, 59, 3, 59, 7, 59, 413, 10, 59, 12, 59,
14, 59, 416, 11, 59, 7, 59, 418, 10, 59, 12, 59, 14, 59, 421, 11, 59, 3,
59, 3, 59, 7, 59, 425, 10, 59, 12, 59, 14, 59, 428, 11, 59, 7, 59, 430,
10, 59, 12, 59, 14, 59, 433, 11, 59, 3, 60, 3, 60, 5, 60, 437, 10, 60,
3, 61, 3, 61, 3, 61, 3, 61, 7, 61, 443, 10, 61, 12, 61, 14, 61, 446, 11,
61, 3, 61, 3, 61, 3, 62, 6, 62, 451, 10, 62, 13, 62, 14, 62, 452, 3, 63,
3, 63, 3, 63, 6, 63, 458, 10, 63, 13, 63, 14, 63, 459, 3, 63, 5, 63, 463,
10, 63, 3, 63, 3, 63, 5, 63, 467, 10, 63, 5, 63, 469, 10, 63, 3, 64, 3,
64, 3, 64, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 7, 66, 479, 10, 66, 12, 66,
14, 66, 482, 11, 66, 5, 66, 484, 10, 66, 3, 67, 3, 67, 5, 67, 488, 10,
67, 3, 67, 6, 67, 491, 10, 67, 13, 67, 14, 67, 492, 3, 68, 3, 68, 3, 69,
3, 69, 3, 70, 3, 70, 3, 71, 3, 71, 3, 71, 3, 71, 3, 71, 3, 71, 7, 71, 507,
10, 71, 12, 71, 14, 71, 510, 11, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72,
3, 72, 3, 72, 3, 72, 7, 72, 520, 10, 72, 12, 72, 14, 72, 523, 11, 72, 3,
72, 3, 72, 3, 73, 3, 73, 3, 73, 3, 153, 2, 74, 3, 3, 5, 4, 7, 5, 9, 6,
11, 7, 13, 8, 15, 9, 17, 10, 19, 11, 21, 12, 23, 13, 25, 14, 27, 15, 29,
16, 31, 17, 33, 18, 35, 19, 37, 20, 39, 21, 41, 22, 43, 23, 45, 24, 47,
25, 49, 26, 51, 27, 53, 28, 55, 29, 57, 30, 59, 31, 61, 32, 63, 33, 65,
34, 67, 35, 69, 36, 71, 37, 73, 38, 75, 39, 77, 40, 79, 41, 81, 42, 83,
43, 85, 44, 87, 45, 89, 46, 91, 47, 93, 48, 95, 49, 97, 50, 99, 51, 101,
52, 103, 53, 105, 54, 107, 55, 109, 56, 111, 57, 113, 58, 115, 59, 117,
60, 119, 61, 121, 62, 123, 63, 125, 64, 127, 65, 129, 2, 131, 2, 133, 2,
135, 2, 137, 2, 139, 2, 141, 2, 143, 2, 145, 2, 3, 2, 13, 5, 2, 12, 12,
15, 15, 8234, 8235, 6, 2, 11, 11, 13, 14, 34, 34, 162, 162, 3, 2, 98, 98,
3, 2, 50, 59, 5, 2, 50, 59, 67, 72, 99, 104, 3, 2, 51, 59, 4, 2, 71, 71,
103, 103, 4, 2, 45, 45, 47, 47, 4, 2, 67, 92, 99, 124, 4, 2, 36, 36, 94,
94, 4, 2, 41, 41, 94, 94, 2, 552, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2,
70, 4, 71, 9, 71, 4, 72, 9, 72, 4, 73, 9, 73, 4, 74, 9, 74, 3, 2, 3, 2,
3, 2, 3, 2, 7, 2, 154, 10, 2, 12, 2, 14, 2, 157, 11, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 168, 10, 3, 12, 3, 14, 3,
171, 11, 3, 3, 3, 3, 3, 3, 4, 6, 4, 176, 10, 4, 13, 4, 14, 4, 177, 3, 4,
3, 4, 3, 5, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9,
3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 12, 3, 12, 3, 13, 3, 13, 3, 14, 3,
14, 3, 15, 3, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 19,
3, 19, 3, 19, 3, 20, 3, 20, 3, 20, 3, 21, 3, 21, 3, 21, 3, 22, 3, 22, 3,
23, 3, 23, 3, 24, 3, 24, 3, 25, 3, 25, 3, 26, 3, 26, 3, 27, 3, 27, 3, 27,
3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 5, 29, 243, 10,
29, 3, 30, 3, 30, 3, 30, 3, 30, 5, 30, 249, 10, 30, 3, 31, 3, 31, 3, 31,
3, 32, 3, 32, 3, 33, 3, 33, 3, 34, 3, 34, 3, 34, 3, 35, 3, 35, 3, 35, 3,
36, 3, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37, 3, 37,
3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 39, 3,
39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 3, 40, 3, 40,
3, 41, 3, 41, 3, 41, 3, 41, 3, 41, 3, 41, 3, 42, 3, 42, 3, 42, 3, 42, 3,
43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 43, 3, 44, 3, 44, 3, 44,
3, 44, 3, 44, 3, 44, 3, 44, 5, 44, 321, 10, 44, 3, 45, 3, 45, 3, 45, 3,
45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47,
3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3,
47, 3, 47, 3, 47, 3, 47, 5, 47, 351, 10, 47, 3, 48, 3, 48, 3, 48, 3, 48,
3, 48, 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 3, 50, 3, 50, 3, 50, 3, 50, 3,
50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 52, 3, 52,
3, 53, 3, 53, 3, 53, 3, 53, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3, 54, 3,
54, 3, 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 55, 3, 55, 3, 55, 3, 56, 3, 56,
3, 56, 3, 56, 5, 56, 401, 10, 56, 3, 57, 3, 57, 3, 57, 3, 58, 3, 58, 3,
59, 6, 59, 409, 10, 59, 13, 59, 14, 59, 410, 3, 59, 3, 59, 7, 59, 415,
10, 59, 12, 59, 14, 59, 418, 11, 59, 7, 59, 420, 10, 59, 12, 59, 14, 59,
423, 11, 59, 3, 59, 3, 59, 7, 59, 427, 10, 59, 12, 59, 14, 59, 430, 11,
59, 7, 59, 432, 10, 59, 12, 59, 14, 59, 435, 11, 59, 3, 60, 3, 60, 3, 60,
3, 60, 5, 60, 441, 10, 60, 3, 61, 6, 61, 444, 10, 61, 13, 61, 14, 61, 445,
3, 62, 3, 62, 3, 62, 6, 62, 451, 10, 62, 13, 62, 14, 62, 452, 3, 62, 5,
62, 456, 10, 62, 3, 62, 3, 62, 5, 62, 460, 10, 62, 5, 62, 462, 10, 62,
3, 63, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 7, 65, 472, 10,
65, 12, 65, 14, 65, 475, 11, 65, 5, 65, 477, 10, 65, 3, 66, 3, 66, 5, 66,
481, 10, 66, 3, 66, 6, 66, 484, 10, 66, 13, 66, 14, 66, 485, 3, 67, 3,
67, 3, 68, 3, 68, 3, 69, 3, 69, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70, 3, 70,
7, 70, 500, 10, 70, 12, 70, 14, 70, 503, 11, 70, 3, 70, 3, 70, 3, 71, 3,
71, 3, 71, 3, 71, 3, 71, 3, 71, 7, 71, 513, 10, 71, 12, 71, 14, 71, 516,
11, 71, 3, 71, 3, 71, 3, 72, 3, 72, 3, 72, 3, 72, 7, 72, 524, 10, 72, 12,
72, 14, 72, 527, 11, 72, 3, 72, 3, 72, 3, 73, 3, 73, 3, 73, 3, 73, 7, 73,
535, 10, 73, 12, 73, 14, 73, 538, 11, 73, 3, 73, 3, 73, 3, 74, 3, 74, 3,
74, 3, 155, 2, 75, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8, 15, 9, 17, 10,
19, 11, 21, 12, 23, 13, 25, 14, 27, 15, 29, 16, 31, 17, 33, 18, 35, 19,
37, 20, 39, 21, 41, 22, 43, 23, 45, 24, 47, 25, 49, 26, 51, 27, 53, 28,
55, 29, 57, 30, 59, 31, 61, 32, 63, 33, 65, 34, 67, 35, 69, 36, 71, 37,
73, 38, 75, 39, 77, 40, 79, 41, 81, 42, 83, 43, 85, 44, 87, 45, 89, 46,
91, 47, 93, 48, 95, 49, 97, 50, 99, 51, 101, 52, 103, 53, 105, 54, 107,
55, 109, 56, 111, 57, 113, 58, 115, 59, 117, 60, 119, 61, 121, 62, 123,
63, 125, 64, 127, 2, 129, 2, 131, 2, 133, 2, 135, 2, 137, 2, 139, 2, 141,
2, 143, 2, 145, 2, 147, 2, 3, 2, 14, 5, 2, 12, 12, 15, 15, 8234, 8235,
6, 2, 11, 11, 13, 14, 34, 34, 162, 162, 3, 2, 50, 59, 5, 2, 50, 59, 67,
72, 99, 104, 3, 2, 51, 59, 4, 2, 71, 71, 103, 103, 4, 2, 45, 45, 47, 47,
4, 2, 67, 92, 99, 124, 4, 2, 36, 36, 94, 94, 4, 2, 41, 41, 94, 94, 3, 2,
98, 98, 3, 2, 182, 182, 2, 569, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2,
7, 3, 2, 2, 2, 2, 9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2,
2, 15, 3, 2, 2, 2, 2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2,
2, 2, 23, 3, 2, 2, 2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2,
@ -96,160 +98,166 @@ var serializedLexerAtn = []uint16{
99, 3, 2, 2, 2, 2, 101, 3, 2, 2, 2, 2, 103, 3, 2, 2, 2, 2, 105, 3, 2, 2,
2, 2, 107, 3, 2, 2, 2, 2, 109, 3, 2, 2, 2, 2, 111, 3, 2, 2, 2, 2, 113,
3, 2, 2, 2, 2, 115, 3, 2, 2, 2, 2, 117, 3, 2, 2, 2, 2, 119, 3, 2, 2, 2,
2, 121, 3, 2, 2, 2, 2, 123, 3, 2, 2, 2, 2, 125, 3, 2, 2, 2, 2, 127, 3,
2, 2, 2, 3, 147, 3, 2, 2, 2, 5, 161, 3, 2, 2, 2, 7, 173, 3, 2, 2, 2, 9,
179, 3, 2, 2, 2, 11, 183, 3, 2, 2, 2, 13, 185, 3, 2, 2, 2, 15, 187, 3,
2, 2, 2, 17, 189, 3, 2, 2, 2, 19, 191, 3, 2, 2, 2, 21, 193, 3, 2, 2, 2,
23, 195, 3, 2, 2, 2, 25, 197, 3, 2, 2, 2, 27, 199, 3, 2, 2, 2, 29, 201,
3, 2, 2, 2, 31, 203, 3, 2, 2, 2, 33, 205, 3, 2, 2, 2, 35, 207, 3, 2, 2,
2, 37, 210, 3, 2, 2, 2, 39, 213, 3, 2, 2, 2, 41, 216, 3, 2, 2, 2, 43, 219,
3, 2, 2, 2, 45, 221, 3, 2, 2, 2, 47, 223, 3, 2, 2, 2, 49, 225, 3, 2, 2,
2, 51, 227, 3, 2, 2, 2, 53, 229, 3, 2, 2, 2, 55, 232, 3, 2, 2, 2, 57, 240,
3, 2, 2, 2, 59, 246, 3, 2, 2, 2, 61, 248, 3, 2, 2, 2, 63, 251, 3, 2, 2,
2, 65, 253, 3, 2, 2, 2, 67, 255, 3, 2, 2, 2, 69, 258, 3, 2, 2, 2, 71, 261,
3, 2, 2, 2, 73, 265, 3, 2, 2, 2, 75, 272, 3, 2, 2, 2, 77, 281, 3, 2, 2,
2, 79, 288, 3, 2, 2, 2, 81, 293, 3, 2, 2, 2, 83, 299, 3, 2, 2, 2, 85, 303,
3, 2, 2, 2, 87, 318, 3, 2, 2, 2, 89, 320, 3, 2, 2, 2, 91, 325, 3, 2, 2,
2, 93, 348, 3, 2, 2, 2, 95, 350, 3, 2, 2, 2, 97, 355, 3, 2, 2, 2, 99, 360,
3, 2, 2, 2, 101, 365, 3, 2, 2, 2, 103, 371, 3, 2, 2, 2, 105, 375, 3, 2,
2, 2, 107, 379, 3, 2, 2, 2, 109, 389, 3, 2, 2, 2, 111, 398, 3, 2, 2, 2,
113, 400, 3, 2, 2, 2, 115, 403, 3, 2, 2, 2, 117, 406, 3, 2, 2, 2, 119,
436, 3, 2, 2, 2, 121, 438, 3, 2, 2, 2, 123, 450, 3, 2, 2, 2, 125, 468,
3, 2, 2, 2, 127, 470, 3, 2, 2, 2, 129, 473, 3, 2, 2, 2, 131, 483, 3, 2,
2, 2, 133, 485, 3, 2, 2, 2, 135, 494, 3, 2, 2, 2, 137, 496, 3, 2, 2, 2,
139, 498, 3, 2, 2, 2, 141, 500, 3, 2, 2, 2, 143, 513, 3, 2, 2, 2, 145,
526, 3, 2, 2, 2, 147, 148, 7, 49, 2, 2, 148, 149, 7, 44, 2, 2, 149, 153,
3, 2, 2, 2, 150, 152, 11, 2, 2, 2, 151, 150, 3, 2, 2, 2, 152, 155, 3, 2,
2, 2, 153, 154, 3, 2, 2, 2, 153, 151, 3, 2, 2, 2, 154, 156, 3, 2, 2, 2,
155, 153, 3, 2, 2, 2, 156, 157, 7, 44, 2, 2, 157, 158, 7, 49, 2, 2, 158,
159, 3, 2, 2, 2, 159, 160, 8, 2, 2, 2, 160, 4, 3, 2, 2, 2, 161, 162, 7,
49, 2, 2, 162, 163, 7, 49, 2, 2, 163, 167, 3, 2, 2, 2, 164, 166, 10, 2,
2, 2, 165, 164, 3, 2, 2, 2, 166, 169, 3, 2, 2, 2, 167, 165, 3, 2, 2, 2,
167, 168, 3, 2, 2, 2, 168, 170, 3, 2, 2, 2, 169, 167, 3, 2, 2, 2, 170,
171, 8, 3, 2, 2, 171, 6, 3, 2, 2, 2, 172, 174, 9, 3, 2, 2, 173, 172, 3,
2, 2, 2, 174, 175, 3, 2, 2, 2, 175, 173, 3, 2, 2, 2, 175, 176, 3, 2, 2,
2, 176, 177, 3, 2, 2, 2, 177, 178, 8, 4, 2, 2, 178, 8, 3, 2, 2, 2, 179,
180, 9, 2, 2, 2, 180, 181, 3, 2, 2, 2, 181, 182, 8, 5, 2, 2, 182, 10, 3,
2, 2, 2, 183, 184, 7, 60, 2, 2, 184, 12, 3, 2, 2, 2, 185, 186, 7, 61, 2,
2, 186, 14, 3, 2, 2, 2, 187, 188, 7, 48, 2, 2, 188, 16, 3, 2, 2, 2, 189,
190, 7, 46, 2, 2, 190, 18, 3, 2, 2, 2, 191, 192, 7, 93, 2, 2, 192, 20,
3, 2, 2, 2, 193, 194, 7, 95, 2, 2, 194, 22, 3, 2, 2, 2, 195, 196, 7, 42,
2, 2, 196, 24, 3, 2, 2, 2, 197, 198, 7, 43, 2, 2, 198, 26, 3, 2, 2, 2,
199, 200, 7, 125, 2, 2, 200, 28, 3, 2, 2, 2, 201, 202, 7, 127, 2, 2, 202,
30, 3, 2, 2, 2, 203, 204, 7, 64, 2, 2, 204, 32, 3, 2, 2, 2, 205, 206, 7,
62, 2, 2, 206, 34, 3, 2, 2, 2, 207, 208, 7, 63, 2, 2, 208, 209, 7, 63,
2, 2, 209, 36, 3, 2, 2, 2, 210, 211, 7, 64, 2, 2, 211, 212, 7, 63, 2, 2,
212, 38, 3, 2, 2, 2, 213, 214, 7, 62, 2, 2, 214, 215, 7, 63, 2, 2, 215,
40, 3, 2, 2, 2, 216, 217, 7, 35, 2, 2, 217, 218, 7, 63, 2, 2, 218, 42,
3, 2, 2, 2, 219, 220, 7, 44, 2, 2, 220, 44, 3, 2, 2, 2, 221, 222, 7, 49,
2, 2, 222, 46, 3, 2, 2, 2, 223, 224, 7, 39, 2, 2, 224, 48, 3, 2, 2, 2,
225, 226, 7, 45, 2, 2, 226, 50, 3, 2, 2, 2, 227, 228, 7, 47, 2, 2, 228,
52, 3, 2, 2, 2, 229, 230, 7, 47, 2, 2, 230, 231, 7, 47, 2, 2, 231, 54,
3, 2, 2, 2, 232, 233, 7, 45, 2, 2, 233, 234, 7, 45, 2, 2, 234, 56, 3, 2,
2, 2, 235, 236, 7, 67, 2, 2, 236, 237, 7, 80, 2, 2, 237, 241, 7, 70, 2,
2, 238, 239, 7, 40, 2, 2, 239, 241, 7, 40, 2, 2, 240, 235, 3, 2, 2, 2,
240, 238, 3, 2, 2, 2, 241, 58, 3, 2, 2, 2, 242, 243, 7, 81, 2, 2, 243,
247, 7, 84, 2, 2, 244, 245, 7, 126, 2, 2, 245, 247, 7, 126, 2, 2, 246,
242, 3, 2, 2, 2, 246, 244, 3, 2, 2, 2, 247, 60, 3, 2, 2, 2, 248, 249, 5,
15, 8, 2, 249, 250, 5, 15, 8, 2, 250, 62, 3, 2, 2, 2, 251, 252, 7, 63,
2, 2, 252, 64, 3, 2, 2, 2, 253, 254, 7, 65, 2, 2, 254, 66, 3, 2, 2, 2,
255, 256, 7, 35, 2, 2, 256, 257, 7, 128, 2, 2, 257, 68, 3, 2, 2, 2, 258,
259, 7, 63, 2, 2, 259, 260, 7, 128, 2, 2, 260, 70, 3, 2, 2, 2, 261, 262,
7, 72, 2, 2, 262, 263, 7, 81, 2, 2, 263, 264, 7, 84, 2, 2, 264, 72, 3,
2, 2, 2, 265, 266, 7, 84, 2, 2, 266, 267, 7, 71, 2, 2, 267, 268, 7, 86,
2, 2, 268, 269, 7, 87, 2, 2, 269, 270, 7, 84, 2, 2, 270, 271, 7, 80, 2,
2, 271, 74, 3, 2, 2, 2, 272, 273, 7, 70, 2, 2, 273, 274, 7, 75, 2, 2, 274,
275, 7, 85, 2, 2, 275, 276, 7, 86, 2, 2, 276, 277, 7, 75, 2, 2, 277, 278,
7, 80, 2, 2, 278, 279, 7, 69, 2, 2, 279, 280, 7, 86, 2, 2, 280, 76, 3,
2, 2, 2, 281, 282, 7, 72, 2, 2, 282, 283, 7, 75, 2, 2, 283, 284, 7, 78,
2, 2, 284, 285, 7, 86, 2, 2, 285, 286, 7, 71, 2, 2, 286, 287, 7, 84, 2,
2, 287, 78, 3, 2, 2, 2, 288, 289, 7, 85, 2, 2, 289, 290, 7, 81, 2, 2, 290,
291, 7, 84, 2, 2, 291, 292, 7, 86, 2, 2, 292, 80, 3, 2, 2, 2, 293, 294,
7, 78, 2, 2, 294, 295, 7, 75, 2, 2, 295, 296, 7, 79, 2, 2, 296, 297, 7,
75, 2, 2, 297, 298, 7, 86, 2, 2, 298, 82, 3, 2, 2, 2, 299, 300, 7, 78,
2, 2, 300, 301, 7, 71, 2, 2, 301, 302, 7, 86, 2, 2, 302, 84, 3, 2, 2, 2,
303, 304, 7, 69, 2, 2, 304, 305, 7, 81, 2, 2, 305, 306, 7, 78, 2, 2, 306,
307, 7, 78, 2, 2, 307, 308, 7, 71, 2, 2, 308, 309, 7, 69, 2, 2, 309, 310,
7, 86, 2, 2, 310, 86, 3, 2, 2, 2, 311, 312, 7, 67, 2, 2, 312, 313, 7, 85,
2, 2, 313, 319, 7, 69, 2, 2, 314, 315, 7, 70, 2, 2, 315, 316, 7, 71, 2,
2, 316, 317, 7, 85, 2, 2, 317, 319, 7, 69, 2, 2, 318, 311, 3, 2, 2, 2,
318, 314, 3, 2, 2, 2, 319, 88, 3, 2, 2, 2, 320, 321, 7, 80, 2, 2, 321,
322, 7, 81, 2, 2, 322, 323, 7, 80, 2, 2, 323, 324, 7, 71, 2, 2, 324, 90,
3, 2, 2, 2, 325, 326, 7, 80, 2, 2, 326, 327, 7, 87, 2, 2, 327, 328, 7,
78, 2, 2, 328, 329, 7, 78, 2, 2, 329, 92, 3, 2, 2, 2, 330, 331, 7, 86,
2, 2, 331, 332, 7, 84, 2, 2, 332, 333, 7, 87, 2, 2, 333, 349, 7, 71, 2,
2, 334, 335, 7, 118, 2, 2, 335, 336, 7, 116, 2, 2, 336, 337, 7, 119, 2,
2, 337, 349, 7, 103, 2, 2, 338, 339, 7, 72, 2, 2, 339, 340, 7, 67, 2, 2,
340, 341, 7, 78, 2, 2, 341, 342, 7, 85, 2, 2, 342, 349, 7, 71, 2, 2, 343,
344, 7, 104, 2, 2, 344, 345, 7, 99, 2, 2, 345, 346, 7, 110, 2, 2, 346,
347, 7, 117, 2, 2, 347, 349, 7, 103, 2, 2, 348, 330, 3, 2, 2, 2, 348, 334,
3, 2, 2, 2, 348, 338, 3, 2, 2, 2, 348, 343, 3, 2, 2, 2, 349, 94, 3, 2,
2, 2, 350, 351, 7, 75, 2, 2, 351, 352, 7, 80, 2, 2, 352, 353, 7, 86, 2,
2, 353, 354, 7, 81, 2, 2, 354, 96, 3, 2, 2, 2, 355, 356, 7, 77, 2, 2, 356,
357, 7, 71, 2, 2, 357, 358, 7, 71, 2, 2, 358, 359, 7, 82, 2, 2, 359, 98,
3, 2, 2, 2, 360, 361, 7, 89, 2, 2, 361, 362, 7, 75, 2, 2, 362, 363, 7,
86, 2, 2, 363, 364, 7, 74, 2, 2, 364, 100, 3, 2, 2, 2, 365, 366, 7, 69,
2, 2, 366, 367, 7, 81, 2, 2, 367, 368, 7, 87, 2, 2, 368, 369, 7, 80, 2,
2, 369, 370, 7, 86, 2, 2, 370, 102, 3, 2, 2, 2, 371, 372, 7, 67, 2, 2,
372, 373, 7, 78, 2, 2, 373, 374, 7, 78, 2, 2, 374, 104, 3, 2, 2, 2, 375,
376, 7, 67, 2, 2, 376, 377, 7, 80, 2, 2, 377, 378, 7, 91, 2, 2, 378, 106,
3, 2, 2, 2, 379, 380, 7, 67, 2, 2, 380, 381, 7, 73, 2, 2, 381, 382, 7,
73, 2, 2, 382, 383, 7, 84, 2, 2, 383, 384, 7, 71, 2, 2, 384, 385, 7, 73,
2, 2, 385, 386, 7, 67, 2, 2, 386, 387, 7, 86, 2, 2, 387, 388, 7, 71, 2,
2, 388, 108, 3, 2, 2, 2, 389, 390, 7, 78, 2, 2, 390, 391, 7, 75, 2, 2,
391, 392, 7, 77, 2, 2, 392, 393, 7, 71, 2, 2, 393, 110, 3, 2, 2, 2, 394,
395, 7, 80, 2, 2, 395, 396, 7, 81, 2, 2, 396, 399, 7, 86, 2, 2, 397, 399,
7, 35, 2, 2, 398, 394, 3, 2, 2, 2, 398, 397, 3, 2, 2, 2, 399, 112, 3, 2,
2, 2, 400, 401, 7, 75, 2, 2, 401, 402, 7, 80, 2, 2, 402, 114, 3, 2, 2,
2, 403, 404, 7, 66, 2, 2, 404, 116, 3, 2, 2, 2, 405, 407, 5, 135, 68, 2,
406, 405, 3, 2, 2, 2, 407, 408, 3, 2, 2, 2, 408, 406, 3, 2, 2, 2, 408,
409, 3, 2, 2, 2, 409, 419, 3, 2, 2, 2, 410, 414, 5, 137, 69, 2, 411, 413,
5, 117, 59, 2, 412, 411, 3, 2, 2, 2, 413, 416, 3, 2, 2, 2, 414, 412, 3,
2, 2, 2, 414, 415, 3, 2, 2, 2, 415, 418, 3, 2, 2, 2, 416, 414, 3, 2, 2,
2, 417, 410, 3, 2, 2, 2, 418, 421, 3, 2, 2, 2, 419, 417, 3, 2, 2, 2, 419,
420, 3, 2, 2, 2, 420, 431, 3, 2, 2, 2, 421, 419, 3, 2, 2, 2, 422, 426,
5, 139, 70, 2, 423, 425, 5, 117, 59, 2, 424, 423, 3, 2, 2, 2, 425, 428,
3, 2, 2, 2, 426, 424, 3, 2, 2, 2, 426, 427, 3, 2, 2, 2, 427, 430, 3, 2,
2, 2, 428, 426, 3, 2, 2, 2, 429, 422, 3, 2, 2, 2, 430, 433, 3, 2, 2, 2,
431, 429, 3, 2, 2, 2, 431, 432, 3, 2, 2, 2, 432, 118, 3, 2, 2, 2, 433,
431, 3, 2, 2, 2, 434, 437, 5, 143, 72, 2, 435, 437, 5, 141, 71, 2, 436,
434, 3, 2, 2, 2, 436, 435, 3, 2, 2, 2, 437, 120, 3, 2, 2, 2, 438, 444,
7, 98, 2, 2, 439, 440, 7, 94, 2, 2, 440, 443, 7, 98, 2, 2, 441, 443, 10,
4, 2, 2, 442, 439, 3, 2, 2, 2, 442, 441, 3, 2, 2, 2, 443, 446, 3, 2, 2,
2, 444, 442, 3, 2, 2, 2, 444, 445, 3, 2, 2, 2, 445, 447, 3, 2, 2, 2, 446,
444, 3, 2, 2, 2, 447, 448, 7, 98, 2, 2, 448, 122, 3, 2, 2, 2, 449, 451,
9, 5, 2, 2, 450, 449, 3, 2, 2, 2, 451, 452, 3, 2, 2, 2, 452, 450, 3, 2,
2, 2, 452, 453, 3, 2, 2, 2, 453, 124, 3, 2, 2, 2, 454, 455, 5, 131, 66,
2, 455, 457, 5, 15, 8, 2, 456, 458, 9, 5, 2, 2, 457, 456, 3, 2, 2, 2, 458,
459, 3, 2, 2, 2, 459, 457, 3, 2, 2, 2, 459, 460, 3, 2, 2, 2, 460, 462,
3, 2, 2, 2, 461, 463, 5, 133, 67, 2, 462, 461, 3, 2, 2, 2, 462, 463, 3,
2, 2, 2, 463, 469, 3, 2, 2, 2, 464, 466, 5, 131, 66, 2, 465, 467, 5, 133,
67, 2, 466, 465, 3, 2, 2, 2, 466, 467, 3, 2, 2, 2, 467, 469, 3, 2, 2, 2,
468, 454, 3, 2, 2, 2, 468, 464, 3, 2, 2, 2, 469, 126, 3, 2, 2, 2, 470,
471, 5, 117, 59, 2, 471, 472, 5, 145, 73, 2, 472, 128, 3, 2, 2, 2, 473,
474, 9, 6, 2, 2, 474, 130, 3, 2, 2, 2, 475, 484, 7, 50, 2, 2, 476, 480,
9, 7, 2, 2, 477, 479, 9, 5, 2, 2, 478, 477, 3, 2, 2, 2, 479, 482, 3, 2,
2, 2, 480, 478, 3, 2, 2, 2, 480, 481, 3, 2, 2, 2, 481, 484, 3, 2, 2, 2,
482, 480, 3, 2, 2, 2, 483, 475, 3, 2, 2, 2, 483, 476, 3, 2, 2, 2, 484,
132, 3, 2, 2, 2, 485, 487, 9, 8, 2, 2, 486, 488, 9, 9, 2, 2, 487, 486,
3, 2, 2, 2, 487, 488, 3, 2, 2, 2, 488, 490, 3, 2, 2, 2, 489, 491, 9, 5,
2, 2, 490, 489, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 490, 3, 2, 2, 2,
492, 493, 3, 2, 2, 2, 493, 134, 3, 2, 2, 2, 494, 495, 9, 10, 2, 2, 495,
136, 3, 2, 2, 2, 496, 497, 7, 97, 2, 2, 497, 138, 3, 2, 2, 2, 498, 499,
4, 50, 59, 2, 499, 140, 3, 2, 2, 2, 500, 508, 7, 36, 2, 2, 501, 502, 7,
94, 2, 2, 502, 507, 11, 2, 2, 2, 503, 504, 7, 36, 2, 2, 504, 507, 7, 36,
2, 2, 505, 507, 10, 11, 2, 2, 506, 501, 3, 2, 2, 2, 506, 503, 3, 2, 2,
2, 506, 505, 3, 2, 2, 2, 507, 510, 3, 2, 2, 2, 508, 506, 3, 2, 2, 2, 508,
509, 3, 2, 2, 2, 509, 511, 3, 2, 2, 2, 510, 508, 3, 2, 2, 2, 511, 512,
7, 36, 2, 2, 512, 142, 3, 2, 2, 2, 513, 521, 7, 41, 2, 2, 514, 515, 7,
94, 2, 2, 515, 520, 11, 2, 2, 2, 516, 517, 7, 41, 2, 2, 517, 520, 7, 41,
2, 2, 518, 520, 10, 12, 2, 2, 519, 514, 3, 2, 2, 2, 519, 516, 3, 2, 2,
2, 519, 518, 3, 2, 2, 2, 520, 523, 3, 2, 2, 2, 521, 519, 3, 2, 2, 2, 521,
522, 3, 2, 2, 2, 522, 524, 3, 2, 2, 2, 523, 521, 3, 2, 2, 2, 524, 525,
7, 41, 2, 2, 525, 144, 3, 2, 2, 2, 526, 527, 7, 60, 2, 2, 527, 528, 7,
60, 2, 2, 528, 146, 3, 2, 2, 2, 32, 2, 153, 167, 175, 240, 246, 318, 348,
398, 408, 414, 419, 426, 431, 436, 442, 444, 452, 459, 462, 466, 468, 480,
483, 487, 492, 506, 508, 519, 521, 3, 2, 3, 2,
2, 121, 3, 2, 2, 2, 2, 123, 3, 2, 2, 2, 2, 125, 3, 2, 2, 2, 3, 149, 3,
2, 2, 2, 5, 163, 3, 2, 2, 2, 7, 175, 3, 2, 2, 2, 9, 181, 3, 2, 2, 2, 11,
185, 3, 2, 2, 2, 13, 187, 3, 2, 2, 2, 15, 189, 3, 2, 2, 2, 17, 191, 3,
2, 2, 2, 19, 193, 3, 2, 2, 2, 21, 195, 3, 2, 2, 2, 23, 197, 3, 2, 2, 2,
25, 199, 3, 2, 2, 2, 27, 201, 3, 2, 2, 2, 29, 203, 3, 2, 2, 2, 31, 205,
3, 2, 2, 2, 33, 207, 3, 2, 2, 2, 35, 209, 3, 2, 2, 2, 37, 212, 3, 2, 2,
2, 39, 215, 3, 2, 2, 2, 41, 218, 3, 2, 2, 2, 43, 221, 3, 2, 2, 2, 45, 223,
3, 2, 2, 2, 47, 225, 3, 2, 2, 2, 49, 227, 3, 2, 2, 2, 51, 229, 3, 2, 2,
2, 53, 231, 3, 2, 2, 2, 55, 234, 3, 2, 2, 2, 57, 242, 3, 2, 2, 2, 59, 248,
3, 2, 2, 2, 61, 250, 3, 2, 2, 2, 63, 253, 3, 2, 2, 2, 65, 255, 3, 2, 2,
2, 67, 257, 3, 2, 2, 2, 69, 260, 3, 2, 2, 2, 71, 263, 3, 2, 2, 2, 73, 267,
3, 2, 2, 2, 75, 274, 3, 2, 2, 2, 77, 283, 3, 2, 2, 2, 79, 290, 3, 2, 2,
2, 81, 295, 3, 2, 2, 2, 83, 301, 3, 2, 2, 2, 85, 305, 3, 2, 2, 2, 87, 320,
3, 2, 2, 2, 89, 322, 3, 2, 2, 2, 91, 327, 3, 2, 2, 2, 93, 350, 3, 2, 2,
2, 95, 352, 3, 2, 2, 2, 97, 357, 3, 2, 2, 2, 99, 362, 3, 2, 2, 2, 101,
367, 3, 2, 2, 2, 103, 373, 3, 2, 2, 2, 105, 377, 3, 2, 2, 2, 107, 381,
3, 2, 2, 2, 109, 391, 3, 2, 2, 2, 111, 400, 3, 2, 2, 2, 113, 402, 3, 2,
2, 2, 115, 405, 3, 2, 2, 2, 117, 408, 3, 2, 2, 2, 119, 440, 3, 2, 2, 2,
121, 443, 3, 2, 2, 2, 123, 461, 3, 2, 2, 2, 125, 463, 3, 2, 2, 2, 127,
466, 3, 2, 2, 2, 129, 476, 3, 2, 2, 2, 131, 478, 3, 2, 2, 2, 133, 487,
3, 2, 2, 2, 135, 489, 3, 2, 2, 2, 137, 491, 3, 2, 2, 2, 139, 493, 3, 2,
2, 2, 141, 506, 3, 2, 2, 2, 143, 519, 3, 2, 2, 2, 145, 530, 3, 2, 2, 2,
147, 541, 3, 2, 2, 2, 149, 150, 7, 49, 2, 2, 150, 151, 7, 44, 2, 2, 151,
155, 3, 2, 2, 2, 152, 154, 11, 2, 2, 2, 153, 152, 3, 2, 2, 2, 154, 157,
3, 2, 2, 2, 155, 156, 3, 2, 2, 2, 155, 153, 3, 2, 2, 2, 156, 158, 3, 2,
2, 2, 157, 155, 3, 2, 2, 2, 158, 159, 7, 44, 2, 2, 159, 160, 7, 49, 2,
2, 160, 161, 3, 2, 2, 2, 161, 162, 8, 2, 2, 2, 162, 4, 3, 2, 2, 2, 163,
164, 7, 49, 2, 2, 164, 165, 7, 49, 2, 2, 165, 169, 3, 2, 2, 2, 166, 168,
10, 2, 2, 2, 167, 166, 3, 2, 2, 2, 168, 171, 3, 2, 2, 2, 169, 167, 3, 2,
2, 2, 169, 170, 3, 2, 2, 2, 170, 172, 3, 2, 2, 2, 171, 169, 3, 2, 2, 2,
172, 173, 8, 3, 2, 2, 173, 6, 3, 2, 2, 2, 174, 176, 9, 3, 2, 2, 175, 174,
3, 2, 2, 2, 176, 177, 3, 2, 2, 2, 177, 175, 3, 2, 2, 2, 177, 178, 3, 2,
2, 2, 178, 179, 3, 2, 2, 2, 179, 180, 8, 4, 2, 2, 180, 8, 3, 2, 2, 2, 181,
182, 9, 2, 2, 2, 182, 183, 3, 2, 2, 2, 183, 184, 8, 5, 2, 2, 184, 10, 3,
2, 2, 2, 185, 186, 7, 60, 2, 2, 186, 12, 3, 2, 2, 2, 187, 188, 7, 61, 2,
2, 188, 14, 3, 2, 2, 2, 189, 190, 7, 48, 2, 2, 190, 16, 3, 2, 2, 2, 191,
192, 7, 46, 2, 2, 192, 18, 3, 2, 2, 2, 193, 194, 7, 93, 2, 2, 194, 20,
3, 2, 2, 2, 195, 196, 7, 95, 2, 2, 196, 22, 3, 2, 2, 2, 197, 198, 7, 42,
2, 2, 198, 24, 3, 2, 2, 2, 199, 200, 7, 43, 2, 2, 200, 26, 3, 2, 2, 2,
201, 202, 7, 125, 2, 2, 202, 28, 3, 2, 2, 2, 203, 204, 7, 127, 2, 2, 204,
30, 3, 2, 2, 2, 205, 206, 7, 64, 2, 2, 206, 32, 3, 2, 2, 2, 207, 208, 7,
62, 2, 2, 208, 34, 3, 2, 2, 2, 209, 210, 7, 63, 2, 2, 210, 211, 7, 63,
2, 2, 211, 36, 3, 2, 2, 2, 212, 213, 7, 64, 2, 2, 213, 214, 7, 63, 2, 2,
214, 38, 3, 2, 2, 2, 215, 216, 7, 62, 2, 2, 216, 217, 7, 63, 2, 2, 217,
40, 3, 2, 2, 2, 218, 219, 7, 35, 2, 2, 219, 220, 7, 63, 2, 2, 220, 42,
3, 2, 2, 2, 221, 222, 7, 44, 2, 2, 222, 44, 3, 2, 2, 2, 223, 224, 7, 49,
2, 2, 224, 46, 3, 2, 2, 2, 225, 226, 7, 39, 2, 2, 226, 48, 3, 2, 2, 2,
227, 228, 7, 45, 2, 2, 228, 50, 3, 2, 2, 2, 229, 230, 7, 47, 2, 2, 230,
52, 3, 2, 2, 2, 231, 232, 7, 47, 2, 2, 232, 233, 7, 47, 2, 2, 233, 54,
3, 2, 2, 2, 234, 235, 7, 45, 2, 2, 235, 236, 7, 45, 2, 2, 236, 56, 3, 2,
2, 2, 237, 238, 7, 67, 2, 2, 238, 239, 7, 80, 2, 2, 239, 243, 7, 70, 2,
2, 240, 241, 7, 40, 2, 2, 241, 243, 7, 40, 2, 2, 242, 237, 3, 2, 2, 2,
242, 240, 3, 2, 2, 2, 243, 58, 3, 2, 2, 2, 244, 245, 7, 81, 2, 2, 245,
249, 7, 84, 2, 2, 246, 247, 7, 126, 2, 2, 247, 249, 7, 126, 2, 2, 248,
244, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 249, 60, 3, 2, 2, 2, 250, 251, 5,
15, 8, 2, 251, 252, 5, 15, 8, 2, 252, 62, 3, 2, 2, 2, 253, 254, 7, 63,
2, 2, 254, 64, 3, 2, 2, 2, 255, 256, 7, 65, 2, 2, 256, 66, 3, 2, 2, 2,
257, 258, 7, 35, 2, 2, 258, 259, 7, 128, 2, 2, 259, 68, 3, 2, 2, 2, 260,
261, 7, 63, 2, 2, 261, 262, 7, 128, 2, 2, 262, 70, 3, 2, 2, 2, 263, 264,
7, 72, 2, 2, 264, 265, 7, 81, 2, 2, 265, 266, 7, 84, 2, 2, 266, 72, 3,
2, 2, 2, 267, 268, 7, 84, 2, 2, 268, 269, 7, 71, 2, 2, 269, 270, 7, 86,
2, 2, 270, 271, 7, 87, 2, 2, 271, 272, 7, 84, 2, 2, 272, 273, 7, 80, 2,
2, 273, 74, 3, 2, 2, 2, 274, 275, 7, 70, 2, 2, 275, 276, 7, 75, 2, 2, 276,
277, 7, 85, 2, 2, 277, 278, 7, 86, 2, 2, 278, 279, 7, 75, 2, 2, 279, 280,
7, 80, 2, 2, 280, 281, 7, 69, 2, 2, 281, 282, 7, 86, 2, 2, 282, 76, 3,
2, 2, 2, 283, 284, 7, 72, 2, 2, 284, 285, 7, 75, 2, 2, 285, 286, 7, 78,
2, 2, 286, 287, 7, 86, 2, 2, 287, 288, 7, 71, 2, 2, 288, 289, 7, 84, 2,
2, 289, 78, 3, 2, 2, 2, 290, 291, 7, 85, 2, 2, 291, 292, 7, 81, 2, 2, 292,
293, 7, 84, 2, 2, 293, 294, 7, 86, 2, 2, 294, 80, 3, 2, 2, 2, 295, 296,
7, 78, 2, 2, 296, 297, 7, 75, 2, 2, 297, 298, 7, 79, 2, 2, 298, 299, 7,
75, 2, 2, 299, 300, 7, 86, 2, 2, 300, 82, 3, 2, 2, 2, 301, 302, 7, 78,
2, 2, 302, 303, 7, 71, 2, 2, 303, 304, 7, 86, 2, 2, 304, 84, 3, 2, 2, 2,
305, 306, 7, 69, 2, 2, 306, 307, 7, 81, 2, 2, 307, 308, 7, 78, 2, 2, 308,
309, 7, 78, 2, 2, 309, 310, 7, 71, 2, 2, 310, 311, 7, 69, 2, 2, 311, 312,
7, 86, 2, 2, 312, 86, 3, 2, 2, 2, 313, 314, 7, 67, 2, 2, 314, 315, 7, 85,
2, 2, 315, 321, 7, 69, 2, 2, 316, 317, 7, 70, 2, 2, 317, 318, 7, 71, 2,
2, 318, 319, 7, 85, 2, 2, 319, 321, 7, 69, 2, 2, 320, 313, 3, 2, 2, 2,
320, 316, 3, 2, 2, 2, 321, 88, 3, 2, 2, 2, 322, 323, 7, 80, 2, 2, 323,
324, 7, 81, 2, 2, 324, 325, 7, 80, 2, 2, 325, 326, 7, 71, 2, 2, 326, 90,
3, 2, 2, 2, 327, 328, 7, 80, 2, 2, 328, 329, 7, 87, 2, 2, 329, 330, 7,
78, 2, 2, 330, 331, 7, 78, 2, 2, 331, 92, 3, 2, 2, 2, 332, 333, 7, 86,
2, 2, 333, 334, 7, 84, 2, 2, 334, 335, 7, 87, 2, 2, 335, 351, 7, 71, 2,
2, 336, 337, 7, 118, 2, 2, 337, 338, 7, 116, 2, 2, 338, 339, 7, 119, 2,
2, 339, 351, 7, 103, 2, 2, 340, 341, 7, 72, 2, 2, 341, 342, 7, 67, 2, 2,
342, 343, 7, 78, 2, 2, 343, 344, 7, 85, 2, 2, 344, 351, 7, 71, 2, 2, 345,
346, 7, 104, 2, 2, 346, 347, 7, 99, 2, 2, 347, 348, 7, 110, 2, 2, 348,
349, 7, 117, 2, 2, 349, 351, 7, 103, 2, 2, 350, 332, 3, 2, 2, 2, 350, 336,
3, 2, 2, 2, 350, 340, 3, 2, 2, 2, 350, 345, 3, 2, 2, 2, 351, 94, 3, 2,
2, 2, 352, 353, 7, 75, 2, 2, 353, 354, 7, 80, 2, 2, 354, 355, 7, 86, 2,
2, 355, 356, 7, 81, 2, 2, 356, 96, 3, 2, 2, 2, 357, 358, 7, 77, 2, 2, 358,
359, 7, 71, 2, 2, 359, 360, 7, 71, 2, 2, 360, 361, 7, 82, 2, 2, 361, 98,
3, 2, 2, 2, 362, 363, 7, 89, 2, 2, 363, 364, 7, 75, 2, 2, 364, 365, 7,
86, 2, 2, 365, 366, 7, 74, 2, 2, 366, 100, 3, 2, 2, 2, 367, 368, 7, 69,
2, 2, 368, 369, 7, 81, 2, 2, 369, 370, 7, 87, 2, 2, 370, 371, 7, 80, 2,
2, 371, 372, 7, 86, 2, 2, 372, 102, 3, 2, 2, 2, 373, 374, 7, 67, 2, 2,
374, 375, 7, 78, 2, 2, 375, 376, 7, 78, 2, 2, 376, 104, 3, 2, 2, 2, 377,
378, 7, 67, 2, 2, 378, 379, 7, 80, 2, 2, 379, 380, 7, 91, 2, 2, 380, 106,
3, 2, 2, 2, 381, 382, 7, 67, 2, 2, 382, 383, 7, 73, 2, 2, 383, 384, 7,
73, 2, 2, 384, 385, 7, 84, 2, 2, 385, 386, 7, 71, 2, 2, 386, 387, 7, 73,
2, 2, 387, 388, 7, 67, 2, 2, 388, 389, 7, 86, 2, 2, 389, 390, 7, 71, 2,
2, 390, 108, 3, 2, 2, 2, 391, 392, 7, 78, 2, 2, 392, 393, 7, 75, 2, 2,
393, 394, 7, 77, 2, 2, 394, 395, 7, 71, 2, 2, 395, 110, 3, 2, 2, 2, 396,
397, 7, 80, 2, 2, 397, 398, 7, 81, 2, 2, 398, 401, 7, 86, 2, 2, 399, 401,
7, 35, 2, 2, 400, 396, 3, 2, 2, 2, 400, 399, 3, 2, 2, 2, 401, 112, 3, 2,
2, 2, 402, 403, 7, 75, 2, 2, 403, 404, 7, 80, 2, 2, 404, 114, 3, 2, 2,
2, 405, 406, 7, 66, 2, 2, 406, 116, 3, 2, 2, 2, 407, 409, 5, 133, 67, 2,
408, 407, 3, 2, 2, 2, 409, 410, 3, 2, 2, 2, 410, 408, 3, 2, 2, 2, 410,
411, 3, 2, 2, 2, 411, 421, 3, 2, 2, 2, 412, 416, 5, 135, 68, 2, 413, 415,
5, 117, 59, 2, 414, 413, 3, 2, 2, 2, 415, 418, 3, 2, 2, 2, 416, 414, 3,
2, 2, 2, 416, 417, 3, 2, 2, 2, 417, 420, 3, 2, 2, 2, 418, 416, 3, 2, 2,
2, 419, 412, 3, 2, 2, 2, 420, 423, 3, 2, 2, 2, 421, 419, 3, 2, 2, 2, 421,
422, 3, 2, 2, 2, 422, 433, 3, 2, 2, 2, 423, 421, 3, 2, 2, 2, 424, 428,
5, 137, 69, 2, 425, 427, 5, 117, 59, 2, 426, 425, 3, 2, 2, 2, 427, 430,
3, 2, 2, 2, 428, 426, 3, 2, 2, 2, 428, 429, 3, 2, 2, 2, 429, 432, 3, 2,
2, 2, 430, 428, 3, 2, 2, 2, 431, 424, 3, 2, 2, 2, 432, 435, 3, 2, 2, 2,
433, 431, 3, 2, 2, 2, 433, 434, 3, 2, 2, 2, 434, 118, 3, 2, 2, 2, 435,
433, 3, 2, 2, 2, 436, 441, 5, 141, 71, 2, 437, 441, 5, 139, 70, 2, 438,
441, 5, 143, 72, 2, 439, 441, 5, 145, 73, 2, 440, 436, 3, 2, 2, 2, 440,
437, 3, 2, 2, 2, 440, 438, 3, 2, 2, 2, 440, 439, 3, 2, 2, 2, 441, 120,
3, 2, 2, 2, 442, 444, 9, 4, 2, 2, 443, 442, 3, 2, 2, 2, 444, 445, 3, 2,
2, 2, 445, 443, 3, 2, 2, 2, 445, 446, 3, 2, 2, 2, 446, 122, 3, 2, 2, 2,
447, 448, 5, 129, 65, 2, 448, 450, 5, 15, 8, 2, 449, 451, 9, 4, 2, 2, 450,
449, 3, 2, 2, 2, 451, 452, 3, 2, 2, 2, 452, 450, 3, 2, 2, 2, 452, 453,
3, 2, 2, 2, 453, 455, 3, 2, 2, 2, 454, 456, 5, 131, 66, 2, 455, 454, 3,
2, 2, 2, 455, 456, 3, 2, 2, 2, 456, 462, 3, 2, 2, 2, 457, 459, 5, 129,
65, 2, 458, 460, 5, 131, 66, 2, 459, 458, 3, 2, 2, 2, 459, 460, 3, 2, 2,
2, 460, 462, 3, 2, 2, 2, 461, 447, 3, 2, 2, 2, 461, 457, 3, 2, 2, 2, 462,
124, 3, 2, 2, 2, 463, 464, 5, 117, 59, 2, 464, 465, 5, 147, 74, 2, 465,
126, 3, 2, 2, 2, 466, 467, 9, 5, 2, 2, 467, 128, 3, 2, 2, 2, 468, 477,
7, 50, 2, 2, 469, 473, 9, 6, 2, 2, 470, 472, 9, 4, 2, 2, 471, 470, 3, 2,
2, 2, 472, 475, 3, 2, 2, 2, 473, 471, 3, 2, 2, 2, 473, 474, 3, 2, 2, 2,
474, 477, 3, 2, 2, 2, 475, 473, 3, 2, 2, 2, 476, 468, 3, 2, 2, 2, 476,
469, 3, 2, 2, 2, 477, 130, 3, 2, 2, 2, 478, 480, 9, 7, 2, 2, 479, 481,
9, 8, 2, 2, 480, 479, 3, 2, 2, 2, 480, 481, 3, 2, 2, 2, 481, 483, 3, 2,
2, 2, 482, 484, 9, 4, 2, 2, 483, 482, 3, 2, 2, 2, 484, 485, 3, 2, 2, 2,
485, 483, 3, 2, 2, 2, 485, 486, 3, 2, 2, 2, 486, 132, 3, 2, 2, 2, 487,
488, 9, 9, 2, 2, 488, 134, 3, 2, 2, 2, 489, 490, 7, 97, 2, 2, 490, 136,
3, 2, 2, 2, 491, 492, 4, 50, 59, 2, 492, 138, 3, 2, 2, 2, 493, 501, 7,
36, 2, 2, 494, 495, 7, 94, 2, 2, 495, 500, 11, 2, 2, 2, 496, 497, 7, 36,
2, 2, 497, 500, 7, 36, 2, 2, 498, 500, 10, 10, 2, 2, 499, 494, 3, 2, 2,
2, 499, 496, 3, 2, 2, 2, 499, 498, 3, 2, 2, 2, 500, 503, 3, 2, 2, 2, 501,
499, 3, 2, 2, 2, 501, 502, 3, 2, 2, 2, 502, 504, 3, 2, 2, 2, 503, 501,
3, 2, 2, 2, 504, 505, 7, 36, 2, 2, 505, 140, 3, 2, 2, 2, 506, 514, 7, 41,
2, 2, 507, 508, 7, 94, 2, 2, 508, 513, 11, 2, 2, 2, 509, 510, 7, 41, 2,
2, 510, 513, 7, 41, 2, 2, 511, 513, 10, 11, 2, 2, 512, 507, 3, 2, 2, 2,
512, 509, 3, 2, 2, 2, 512, 511, 3, 2, 2, 2, 513, 516, 3, 2, 2, 2, 514,
512, 3, 2, 2, 2, 514, 515, 3, 2, 2, 2, 515, 517, 3, 2, 2, 2, 516, 514,
3, 2, 2, 2, 517, 518, 7, 41, 2, 2, 518, 142, 3, 2, 2, 2, 519, 525, 7, 98,
2, 2, 520, 521, 7, 94, 2, 2, 521, 524, 7, 98, 2, 2, 522, 524, 10, 12, 2,
2, 523, 520, 3, 2, 2, 2, 523, 522, 3, 2, 2, 2, 524, 527, 3, 2, 2, 2, 525,
523, 3, 2, 2, 2, 525, 526, 3, 2, 2, 2, 526, 528, 3, 2, 2, 2, 527, 525,
3, 2, 2, 2, 528, 529, 7, 98, 2, 2, 529, 144, 3, 2, 2, 2, 530, 536, 7, 182,
2, 2, 531, 532, 7, 94, 2, 2, 532, 535, 7, 182, 2, 2, 533, 535, 10, 13,
2, 2, 534, 531, 3, 2, 2, 2, 534, 533, 3, 2, 2, 2, 535, 538, 3, 2, 2, 2,
536, 534, 3, 2, 2, 2, 536, 537, 3, 2, 2, 2, 537, 539, 3, 2, 2, 2, 538,
536, 3, 2, 2, 2, 539, 540, 7, 182, 2, 2, 540, 146, 3, 2, 2, 2, 541, 542,
7, 60, 2, 2, 542, 543, 7, 60, 2, 2, 543, 148, 3, 2, 2, 2, 34, 2, 155, 169,
177, 242, 248, 320, 350, 400, 410, 416, 421, 428, 433, 440, 445, 452, 455,
459, 461, 473, 476, 480, 485, 499, 501, 512, 514, 523, 525, 534, 536, 3,
2, 3, 2,
}
var lexerDeserializer = antlr.NewATNDeserializer(nil)
@ -281,8 +289,7 @@ var lexerSymbolicNames = []string{
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
"Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Param", "Identifier",
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
"NamespaceSegment",
"StringLiteral", "IntegerLiteral", "FloatLiteral", "NamespaceSegment",
}
var lexerRuleNames = []string{
@ -294,9 +301,9 @@ var lexerRuleNames = []string{
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
"Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Param", "Identifier",
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
"NamespaceSegment", "HexDigit", "DecimalIntegerLiteral", "ExponentPart",
"Letter", "Symbols", "Digit", "DQSring", "SQString", "NamespaceSeparator",
"StringLiteral", "IntegerLiteral", "FloatLiteral", "NamespaceSegment",
"HexDigit", "DecimalIntegerLiteral", "ExponentPart", "Letter", "Symbols",
"Digit", "DQSring", "SQString", "BacktickString", "TickString", "NamespaceSeparator",
}
type FqlLexer struct {
@ -334,67 +341,66 @@ func NewFqlLexer(input antlr.CharStream) *FqlLexer {
// FqlLexer tokens.
const (
FqlLexerMultiLineComment = 1
FqlLexerSingleLineComment = 2
FqlLexerWhiteSpaces = 3
FqlLexerLineTerminator = 4
FqlLexerColon = 5
FqlLexerSemiColon = 6
FqlLexerDot = 7
FqlLexerComma = 8
FqlLexerOpenBracket = 9
FqlLexerCloseBracket = 10
FqlLexerOpenParen = 11
FqlLexerCloseParen = 12
FqlLexerOpenBrace = 13
FqlLexerCloseBrace = 14
FqlLexerGt = 15
FqlLexerLt = 16
FqlLexerEq = 17
FqlLexerGte = 18
FqlLexerLte = 19
FqlLexerNeq = 20
FqlLexerMulti = 21
FqlLexerDiv = 22
FqlLexerMod = 23
FqlLexerPlus = 24
FqlLexerMinus = 25
FqlLexerMinusMinus = 26
FqlLexerPlusPlus = 27
FqlLexerAnd = 28
FqlLexerOr = 29
FqlLexerRange = 30
FqlLexerAssign = 31
FqlLexerQuestionMark = 32
FqlLexerRegexNotMatch = 33
FqlLexerRegexMatch = 34
FqlLexerFor = 35
FqlLexerReturn = 36
FqlLexerDistinct = 37
FqlLexerFilter = 38
FqlLexerSort = 39
FqlLexerLimit = 40
FqlLexerLet = 41
FqlLexerCollect = 42
FqlLexerSortDirection = 43
FqlLexerNone = 44
FqlLexerNull = 45
FqlLexerBooleanLiteral = 46
FqlLexerInto = 47
FqlLexerKeep = 48
FqlLexerWith = 49
FqlLexerCount = 50
FqlLexerAll = 51
FqlLexerAny = 52
FqlLexerAggregate = 53
FqlLexerLike = 54
FqlLexerNot = 55
FqlLexerIn = 56
FqlLexerParam = 57
FqlLexerIdentifier = 58
FqlLexerStringLiteral = 59
FqlLexerTemplateStringLiteral = 60
FqlLexerIntegerLiteral = 61
FqlLexerFloatLiteral = 62
FqlLexerNamespaceSegment = 63
FqlLexerMultiLineComment = 1
FqlLexerSingleLineComment = 2
FqlLexerWhiteSpaces = 3
FqlLexerLineTerminator = 4
FqlLexerColon = 5
FqlLexerSemiColon = 6
FqlLexerDot = 7
FqlLexerComma = 8
FqlLexerOpenBracket = 9
FqlLexerCloseBracket = 10
FqlLexerOpenParen = 11
FqlLexerCloseParen = 12
FqlLexerOpenBrace = 13
FqlLexerCloseBrace = 14
FqlLexerGt = 15
FqlLexerLt = 16
FqlLexerEq = 17
FqlLexerGte = 18
FqlLexerLte = 19
FqlLexerNeq = 20
FqlLexerMulti = 21
FqlLexerDiv = 22
FqlLexerMod = 23
FqlLexerPlus = 24
FqlLexerMinus = 25
FqlLexerMinusMinus = 26
FqlLexerPlusPlus = 27
FqlLexerAnd = 28
FqlLexerOr = 29
FqlLexerRange = 30
FqlLexerAssign = 31
FqlLexerQuestionMark = 32
FqlLexerRegexNotMatch = 33
FqlLexerRegexMatch = 34
FqlLexerFor = 35
FqlLexerReturn = 36
FqlLexerDistinct = 37
FqlLexerFilter = 38
FqlLexerSort = 39
FqlLexerLimit = 40
FqlLexerLet = 41
FqlLexerCollect = 42
FqlLexerSortDirection = 43
FqlLexerNone = 44
FqlLexerNull = 45
FqlLexerBooleanLiteral = 46
FqlLexerInto = 47
FqlLexerKeep = 48
FqlLexerWith = 49
FqlLexerCount = 50
FqlLexerAll = 51
FqlLexerAny = 52
FqlLexerAggregate = 53
FqlLexerLike = 54
FqlLexerNot = 55
FqlLexerIn = 56
FqlLexerParam = 57
FqlLexerIdentifier = 58
FqlLexerStringLiteral = 59
FqlLexerIntegerLiteral = 60
FqlLexerFloatLiteral = 61
FqlLexerNamespaceSegment = 62
)

File diff suppressed because it is too large Load Diff

View File

@ -250,12 +250,6 @@ func (s *BaseFqlParserListener) EnterPropertyAssignment(ctx *PropertyAssignmentC
// ExitPropertyAssignment is called when production propertyAssignment is exited.
func (s *BaseFqlParserListener) ExitPropertyAssignment(ctx *PropertyAssignmentContext) {}
// EnterMemberExpression is called when production memberExpression is entered.
func (s *BaseFqlParserListener) EnterMemberExpression(ctx *MemberExpressionContext) {}
// ExitMemberExpression is called when production memberExpression is exited.
func (s *BaseFqlParserListener) ExitMemberExpression(ctx *MemberExpressionContext) {}
// EnterShorthandPropertyName is called when production shorthandPropertyName is entered.
func (s *BaseFqlParserListener) EnterShorthandPropertyName(ctx *ShorthandPropertyNameContext) {}
@ -292,6 +286,24 @@ func (s *BaseFqlParserListener) EnterFunctionCallExpression(ctx *FunctionCallExp
// ExitFunctionCallExpression is called when production functionCallExpression is exited.
func (s *BaseFqlParserListener) ExitFunctionCallExpression(ctx *FunctionCallExpressionContext) {}
// EnterMember is called when production member is entered.
func (s *BaseFqlParserListener) EnterMember(ctx *MemberContext) {}
// ExitMember is called when production member is exited.
func (s *BaseFqlParserListener) ExitMember(ctx *MemberContext) {}
// EnterMemberPath is called when production memberPath is entered.
func (s *BaseFqlParserListener) EnterMemberPath(ctx *MemberPathContext) {}
// ExitMemberPath is called when production memberPath is exited.
func (s *BaseFqlParserListener) ExitMemberPath(ctx *MemberPathContext) {}
// EnterMemberExpression is called when production memberExpression is entered.
func (s *BaseFqlParserListener) EnterMemberExpression(ctx *MemberExpressionContext) {}
// ExitMemberExpression is called when production memberExpression is exited.
func (s *BaseFqlParserListener) ExitMemberExpression(ctx *MemberExpressionContext) {}
// EnterArguments is called when production arguments is entered.
func (s *BaseFqlParserListener) EnterArguments(ctx *ArgumentsContext) {}

View File

@ -159,10 +159,6 @@ func (v *BaseFqlParserVisitor) VisitPropertyAssignment(ctx *PropertyAssignmentCo
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMemberExpression(ctx *MemberExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitShorthandPropertyName(ctx *ShorthandPropertyNameContext) interface{} {
return v.VisitChildren(ctx)
}
@ -187,6 +183,18 @@ func (v *BaseFqlParserVisitor) VisitFunctionCallExpression(ctx *FunctionCallExpr
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMember(ctx *MemberContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMemberPath(ctx *MemberPathContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitMemberExpression(ctx *MemberExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitArguments(ctx *ArgumentsContext) interface{} {
return v.VisitChildren(ctx)
}

View File

@ -121,9 +121,6 @@ type FqlParserListener interface {
// EnterPropertyAssignment is called when entering the propertyAssignment production.
EnterPropertyAssignment(c *PropertyAssignmentContext)
// EnterMemberExpression is called when entering the memberExpression production.
EnterMemberExpression(c *MemberExpressionContext)
// EnterShorthandPropertyName is called when entering the shorthandPropertyName production.
EnterShorthandPropertyName(c *ShorthandPropertyNameContext)
@ -142,6 +139,15 @@ type FqlParserListener interface {
// EnterFunctionCallExpression is called when entering the functionCallExpression production.
EnterFunctionCallExpression(c *FunctionCallExpressionContext)
// EnterMember is called when entering the member production.
EnterMember(c *MemberContext)
// EnterMemberPath is called when entering the memberPath production.
EnterMemberPath(c *MemberPathContext)
// EnterMemberExpression is called when entering the memberExpression production.
EnterMemberExpression(c *MemberExpressionContext)
// EnterArguments is called when entering the arguments production.
EnterArguments(c *ArgumentsContext)
@ -292,9 +298,6 @@ type FqlParserListener interface {
// ExitPropertyAssignment is called when exiting the propertyAssignment production.
ExitPropertyAssignment(c *PropertyAssignmentContext)
// ExitMemberExpression is called when exiting the memberExpression production.
ExitMemberExpression(c *MemberExpressionContext)
// ExitShorthandPropertyName is called when exiting the shorthandPropertyName production.
ExitShorthandPropertyName(c *ShorthandPropertyNameContext)
@ -313,6 +316,15 @@ type FqlParserListener interface {
// ExitFunctionCallExpression is called when exiting the functionCallExpression production.
ExitFunctionCallExpression(c *FunctionCallExpressionContext)
// ExitMember is called when exiting the member production.
ExitMember(c *MemberContext)
// ExitMemberPath is called when exiting the memberPath production.
ExitMemberPath(c *MemberPathContext)
// ExitMemberExpression is called when exiting the memberExpression production.
ExitMemberExpression(c *MemberExpressionContext)
// ExitArguments is called when exiting the arguments production.
ExitArguments(c *ArgumentsContext)

View File

@ -121,9 +121,6 @@ type FqlParserVisitor interface {
// Visit a parse tree produced by FqlParser#propertyAssignment.
VisitPropertyAssignment(ctx *PropertyAssignmentContext) interface{}
// Visit a parse tree produced by FqlParser#memberExpression.
VisitMemberExpression(ctx *MemberExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#shorthandPropertyName.
VisitShorthandPropertyName(ctx *ShorthandPropertyNameContext) interface{}
@ -142,6 +139,15 @@ type FqlParserVisitor interface {
// Visit a parse tree produced by FqlParser#functionCallExpression.
VisitFunctionCallExpression(ctx *FunctionCallExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#member.
VisitMember(ctx *MemberContext) interface{}
// Visit a parse tree produced by FqlParser#memberPath.
VisitMemberPath(ctx *MemberPathContext) interface{}
// Visit a parse tree produced by FqlParser#memberExpression.
VisitMemberExpression(ctx *MemberExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#arguments.
VisitArguments(ctx *ArgumentsContext) interface{}

View File

@ -36,6 +36,14 @@ func NumberBoundaries(input float64) (max float64, min float64) {
return
}
func NumberUpperBoundary(input float64) float64 {
return input * 2
}
func NumberLowerBoundary(input float64) float64 {
return input / 2
}
func Random(max float64, min float64) float64 {
r := rand.Float64()
i := r * (max - min + 1)

View File

@ -8,25 +8,25 @@ import (
)
type MemberExpression struct {
src core.SourceMap
variableName string
path []core.Expression
src core.SourceMap
source core.Expression
path []core.Expression
}
func NewMemberExpression(src core.SourceMap, variableName string, path []core.Expression) (*MemberExpression, error) {
if variableName == "" {
return nil, core.Error(core.ErrMissedArgument, "variable name")
func NewMemberExpression(src core.SourceMap, source core.Expression, path []core.Expression) (*MemberExpression, error) {
if source == nil {
return nil, core.Error(core.ErrMissedArgument, "source")
}
if len(path) == 0 {
return nil, core.Error(core.ErrMissedArgument, "path expressions")
}
return &MemberExpression{src, variableName, path}, nil
return &MemberExpression{src, source, path}, nil
}
func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
val, err := scope.GetVariable(e.variableName)
val, err := e.source.Exec(ctx, scope)
if err != nil {
return values.None, core.SourceError(
@ -35,22 +35,24 @@ func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Va
)
}
strPath := make([]core.Value, len(e.path))
out := val
path := make([]core.Value, 1)
for idx, exp := range e.path {
for _, exp := range e.path {
segment, err := exp.Exec(ctx, scope)
if err != nil {
return values.None, err
}
strPath[idx] = segment
}
path[0] = segment
c, err := values.GetIn(ctx, out, path)
out, err := values.GetIn(ctx, val, strPath)
if err != nil {
return values.None, core.SourceError(e.src, err)
}
if err != nil {
return values.None, core.SourceError(e.src, err)
out = c
}
return out, nil

View File

@ -329,7 +329,7 @@ func ToInt(input core.Value) Int {
}
}
func ToArray(ctx context.Context, input core.Value) core.Value {
func ToArray(ctx context.Context, input core.Value) *Array {
switch value := input.(type) {
case Boolean,
Int,
@ -339,7 +339,7 @@ func ToArray(ctx context.Context, input core.Value) core.Value {
return NewArrayWith(value)
case *Array:
return value.Copy()
return value.Copy().(*Array)
case *Object:
arr := NewArray(int(value.Length()))
@ -354,7 +354,7 @@ func ToArray(ctx context.Context, input core.Value) core.Value {
iterator, err := value.Iterate(ctx)
if err != nil {
return None
return NewArray(0)
}
arr := NewArray(10)
@ -363,7 +363,7 @@ func ToArray(ctx context.Context, input core.Value) core.Value {
val, _, err := iterator.Next(ctx)
if err != nil {
return None
return NewArray(0)
}
if val == None {

View File

@ -390,9 +390,7 @@ func TestHelpers(t *testing.T) {
}
input := values.NewArrayWith(vals...)
output := values.ToArray(context.Background(), input)
arr := output.(*values.Array)
arr := values.ToArray(context.Background(), input)
So(input == arr, ShouldBeFalse)
So(arr.Length() == input.Length(), ShouldBeTrue)
@ -414,9 +412,7 @@ func TestHelpers(t *testing.T) {
values.NewObjectProperty("qaz", values.NewObject()),
)
output := values.ToArray(context.Background(), input)
arr := output.(*values.Array).Sort()
arr := values.ToArray(context.Background(), input).Sort()
So(arr.String(), ShouldEqual, "[1,\"bar\",{}]")
So(arr.Get(values.NewInt(2)) == input.MustGet("qaz"), ShouldBeTrue)

View File

@ -40,3 +40,7 @@ func (t *none) Hash() uint64 {
func (t *none) Copy() core.Value {
return None
}
func (t *none) Clone() core.Cloneable {
return None
}

View File

@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// AttributeGet gets single or more attribute(s) of a given element.
// ATTR_GET gets single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Attribute name(s).
// @returns Object - Key-value pairs of attribute values.
@ -27,7 +27,11 @@ func AttributeGet(ctx context.Context, args ...core.Value) (core.Value, error) {
names := args[1:]
result := values.NewObject()
attrs := el.GetAttributes(ctx)
attrs, err := el.GetAttributes(ctx)
if err != nil {
return values.None, err
}
for _, n := range names {
name := values.NewString(n.String())

View File

@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeRemove removes single or more attribute(s) of a given element.
// ATTR_REMOVE removes single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param names (...String) - Attribute name(s).
func AttributeRemove(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// AttributeSet sets or updates a single or more attribute(s) of a given element.
// ATTR_SET sets or updates a single or more attribute(s) of a given element.
// @param el (HTMLElement) - Target element.
// @param nameOrObj (String | Object) - Attribute name or an object representing a key-value pair of attributes.
// @param value (String) - If a second parameter is a string value, this parameter represent an attribute value.

32
pkg/stdlib/html/blur.go Normal file
View File

@ -0,0 +1,32 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/core"
)
// BLUR Calls blur on the element.
// @param target (HTMLPage | HTMLDocument | HTMLElement) - Target node.
// @param selector (String, optional) - Optional CSS selector.
func Blur(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.None, err
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
if len(args) == 1 {
return values.None, el.Blur(ctx)
}
return values.None, el.BlurBySelector(ctx, values.ToString(args[1]))
}

33
pkg/stdlib/html/clear.go Normal file
View File

@ -0,0 +1,33 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// INPUT_CLEAR clears a value from an underlying input element.
// @param source (HTMLPage | HTMLDocument | HTMLElement) - Event target.
// @param selector (String, options) - Selector.
func InputClear(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.None, err
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
// CLEAR(el)
if len(args) == 1 {
return values.None, el.Clear(ctx)
}
return values.None, el.ClearBySelector(ctx, values.ToString(args[1]))
}

View File

@ -6,13 +6,15 @@ import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Click dispatches click event on a given element
// CLICK dispatches click event on a given element
// @param source (Open | GetElement) - Event source.
// @param selector (String, optional) - Optional selector.
// @param selectorOrCount (String | Int, optional) - Optional selector or count of clicks.
// @param count (Int, optional) - Optional count of clicks.
func Click(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
err := core.ValidateArgs(args, 1, 3)
if err != nil {
return values.False, err
@ -26,7 +28,44 @@ func Click(ctx context.Context, args ...core.Value) (core.Value, error) {
// CLICK(elOrDoc)
if len(args) == 1 {
return values.True, el.Click(ctx)
return values.True, el.Click(ctx, 1)
}
if len(args) == 2 {
err := core.ValidateType(args[1], types.String, types.Int)
if err != nil {
return values.False, err
}
if args[1].Type() == types.String {
selector := values.ToString(args[1])
exists, err := el.ExistsBySelector(ctx, selector)
if err != nil {
return values.False, err
}
if !exists {
return exists, nil
}
return exists, el.ClickBySelector(ctx, selector, 1)
}
return values.True, el.Click(ctx, values.ToInt(args[1]))
}
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.False, err
}
err = core.ValidateType(args[2], types.Int)
if err != nil {
return values.False, err
}
// CLICK(doc, selector)
@ -41,5 +80,7 @@ func Click(ctx context.Context, args ...core.Value) (core.Value, error) {
return exists, nil
}
return exists, el.ClickBySelector(ctx, selector)
count := values.ToInt(args[2])
return exists, el.ClickBySelector(ctx, selector, count)
}

View File

@ -6,14 +6,16 @@ import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ClickAll dispatches click event on all matched element
// CLICK_ALL dispatches click event on all matched element
// @param source (Open) - Open.
// @param selector (String) - Selector.
// @param count (Int, optional) - Optional count of clicks.
// @returns (Boolean) - Returns true if matched at least one element.
func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 2)
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.False, err
@ -22,7 +24,7 @@ func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) {
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
return values.False, err
}
selector := values.ToString(args[1])
@ -37,5 +39,17 @@ func ClickAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.False, nil
}
return values.True, el.ClickBySelectorAll(ctx, selector)
count := values.NewInt(1)
if len(args) == 3 {
err := core.ValidateType(args[2], types.Int)
if err != nil {
return values.False, err
}
count = values.ToInt(args[2])
}
return values.True, el.ClickBySelectorAll(ctx, selector, count)
}

View File

@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// CookieSet gets a cookie from a given page by name.
// COOKIE_DEL gets a cookie from a given page by name.
// @param page (HTMLPage) - Target page.
// @param cookie (...HTTPCookie|String) - Cookie or cookie name to delete.
func CookieDel(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// CookieSet gets a cookie from a given page by name.
// COOKIE_GET gets a cookie from a given page by name.
// @param page (HTMLPage) - Target page.
// @param name (String) - Cookie or cookie name to delete.
func CookieGet(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@ -8,7 +8,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// CookieSet sets cookies to a given page
// COOKIE_SET sets cookies to a given page
// @param page (HTMLPage) - Target page.
// @param cookie... (HTTPCookie) - Target cookies.
func CookieSet(ctx context.Context, args ...core.Value) (core.Value, error) {

View File

@ -18,7 +18,7 @@ type PageLoadParams struct {
Timeout time.Duration
}
// Open opens an HTML page by a given url.
// DOCUMENT opens an HTML page by a given url.
// By default, loads a page by http call - resulted page does not support any interactions.
// @param params (Object) - Optional, An object containing the following properties :
// driver (String) - Optional, driver name.
@ -26,7 +26,7 @@ type PageLoadParams struct {
// userAgent (String) - Optional, user agent.
// keepCookies (Boolean) - Optional, boolean value indicating whether to use cookies from previous sessions.
// i.e. not to open a page in the Incognito mode.
// cookies (HTTPCookie) - Optional, set of HTTP cookies.
// cookies (HTTPCookies) - Optional, set of HTTP cookies.
// headers (HTTPHeaders) - Optional, HTTP headers.
// viewport (Viewport) - Optional, viewport params.
// @returns (HTMLPage) - Returns loaded HTML page.
@ -76,7 +76,7 @@ func newDefaultDocLoadParams(url values.String) PageLoadParams {
Params: drivers.Params{
URL: url.String(),
},
Timeout: time.Second * 30,
Timeout: drivers.DefaultPageLoadTimeout * time.Millisecond,
}
}
@ -134,17 +134,30 @@ func newPageLoadParams(url values.String, arg core.Value) (PageLoadParams, error
cookies, exists := obj.Get(values.NewString("cookies"))
if exists {
if err := core.ValidateType(cookies, types.Array); err != nil {
if err := core.ValidateType(cookies, types.Array, types.Object); err != nil {
return res, err
}
cookies, err := parseCookies(cookies.(*values.Array))
switch c := cookies.(type) {
case *values.Array:
cookies, err := parseCookieArray(c)
if err != nil {
return res, err
if err != nil {
return res, err
}
res.Cookies = cookies
case *values.Object:
cookies, err := parseCookieObject(c)
if err != nil {
return res, err
}
res.Cookies = cookies
default:
res.Cookies = make(drivers.HTTPCookies)
}
res.Cookies = cookies
}
headers, exists := obj.Get(values.NewString("headers"))
@ -183,11 +196,11 @@ func newPageLoadParams(url values.String, arg core.Value) (PageLoadParams, error
return res, nil
}
func parseCookies(arr *values.Array) ([]drivers.HTTPCookie, error) {
func parseCookieObject(obj *values.Object) (drivers.HTTPCookies, error) {
var err error
res := make([]drivers.HTTPCookie, 0, arr.Length())
res := make(drivers.HTTPCookies)
arr.ForEach(func(value core.Value, idx int) bool {
obj.ForEach(func(value core.Value, _ string) bool {
cookie, e := parseCookie(value)
if e != nil {
@ -196,7 +209,28 @@ func parseCookies(arr *values.Array) ([]drivers.HTTPCookie, error) {
return false
}
res = append(res, cookie)
res[cookie.Name] = cookie
return true
})
return res, err
}
func parseCookieArray(arr *values.Array) (drivers.HTTPCookies, error) {
var err error
res := make(drivers.HTTPCookies)
arr.ForEach(func(value core.Value, _ int) bool {
cookie, e := parseCookie(value)
if e != nil {
err = e
return false
}
res[cookie.Name] = cookie
return true
})

View File

@ -10,7 +10,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// Download a resource from the given GetURL.
// DOWNLOAD downloads a resource from the given GetURL.
// @param GetURL (String) - GetURL to download.
// @returns data (Binary) - Returns a base64 encoded string in binary format.
func Download(_ context.Context, args ...core.Value) (core.Value, error) {

View File

@ -9,7 +9,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// GetElement finds an element by a given CSS selector.
// ELEMENT finds an element by a given CSS selector.
// Returns NONE if element not found.
// @param docOrEl (HTMLDocument|HTMLElement) - Parent document or element.
// @param selector (String) - CSS selector.
@ -21,7 +21,7 @@ func Element(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
return el.QuerySelector(ctx, selector), nil
return el.QuerySelector(ctx, selector)
}
func queryArgs(args []core.Value) (drivers.HTMLElement, values.String, error) {

View File

@ -7,7 +7,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// ElementExists returns a boolean value indicating whether there is an element matched by selector.
// ELEMENT_EXISTS returns a boolean value indicating whether there is an element matched by selector.
// @param docOrEl (HTMLDocument|HTMLNode) - Parent document or element.
// @param selector (String) - CSS selector.
// @returns (Boolean) - A boolean value indicating whether there is an element matched by selector.

View File

@ -7,7 +7,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// Elements finds HTML elements by a given CSS selector.
// ELEMENTS finds HTML elements by a given CSS selector.
// Returns an empty array if element not found.
// @param docOrEl (HTMLDocument|HTMLNode) - Parent document or element.
// @param selector (String) - CSS selector.
@ -19,5 +19,5 @@ func Elements(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
return el.QuerySelectorAll(ctx, selector), nil
return el.QuerySelectorAll(ctx, selector)
}

View File

@ -7,7 +7,7 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// ElementsCount returns a number of found HTML elements by a given CSS selector.
// ELEMENTS_COUNT returns a number of found HTML elements by a given CSS selector.
// Returns an empty array if element not found.
// @param docOrEl (HTMLDocument|HTMLNode) - Parent document or element.
// @param selector (String) - CSS selector.
@ -19,5 +19,5 @@ func ElementsCount(ctx context.Context, args ...core.Value) (core.Value, error)
return values.None, err
}
return el.CountBySelector(ctx, selector), nil
return el.CountBySelector(ctx, selector)
}

View File

@ -2,14 +2,15 @@ package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// FOCUS Calls focus on the element.
// FOCUS Sets focus on the element.
// @param target (HTMLPage | HTMLDocument | HTMLElement) - Target node.
// @param selector (String, optional) - Optional CSS selector. Required when target is HTMLPage or HTMLDocument.
// @param selector (String, optional) - Optional CSS selector.
func Focus(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
@ -17,22 +18,15 @@ func Focus(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
// Document with selector
if len(args) == 2 {
doc, err := drivers.ToDocument(args[0])
if err != nil {
return values.None, err
}
return values.None, doc.FocusBySelector(ctx, values.ToString(args[1]))
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
return values.None, el.Focus(ctx)
if len(args) == 1 {
return values.None, el.Focus(ctx)
}
return values.None, el.FocusBySelector(ctx, values.ToString(args[1]))
}

Some files were not shown because too many files have changed in this diff Show More