mirror of
https://github.com/MontFerret/ferret.git
synced 2025-03-17 21:18:37 +02:00
Feature/#250 wait style (#255)
* Added support for parsed styles * Added stdlib function. * Added e2e tests * Added e2e tests for STYLE_* functions
This commit is contained in:
parent
391836cbb2
commit
376ad77404
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@ -65,6 +65,14 @@
|
||||
pruneopts = "UT"
|
||||
revision = "d547d1d9531ed93dbdebcbff7f83e7c876a1e0ee"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fc51ecee8f31d03436c1a0167eb1e383ad0a241d02272541853f3995374a08f1"
|
||||
name = "github.com/gorilla/css"
|
||||
packages = ["scanner"]
|
||||
pruneopts = "UT"
|
||||
revision = "398b0b046082ecb3694c01bec6b336a06a4e530a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
|
||||
name = "github.com/gorilla/websocket"
|
||||
@ -297,6 +305,7 @@
|
||||
"github.com/corpix/uarand",
|
||||
"github.com/derekparker/trie",
|
||||
"github.com/gofrs/uuid",
|
||||
"github.com/gorilla/css/scanner",
|
||||
"github.com/labstack/echo",
|
||||
"github.com/mafredri/cdp",
|
||||
"github.com/mafredri/cdp/devtool",
|
||||
|
@ -45,6 +45,10 @@
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
version = "1.5.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/css"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gofrs/uuid"
|
||||
version = "3.1.2"
|
||||
|
@ -2,23 +2,52 @@ import random from "../../../utils/random.js";
|
||||
|
||||
const e = React.createElement;
|
||||
|
||||
function render(id) {
|
||||
return e("span", { id: `${id}-content` }, ["Hello world"]);
|
||||
function render(id, props = {}) {
|
||||
return e("span", { id: `${id}-content`, ...props }, ["Hello world"]);
|
||||
}
|
||||
|
||||
export default class AppearableComponent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let element = null;
|
||||
|
||||
if (props.appear) {
|
||||
if (props.useStyle) {
|
||||
element = render(props.id, { style: {display: "none"}})
|
||||
}
|
||||
} else {
|
||||
if (props.useStyle) {
|
||||
element = render(props.id, { style: {display: "block" }})
|
||||
} else {
|
||||
element = render(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
element: props.appear === true ? null : render(props.id)
|
||||
element
|
||||
};
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
setTimeout(() => {
|
||||
const props = this.props;
|
||||
let element = null;
|
||||
|
||||
if (props.appear) {
|
||||
if (props.useStyle) {
|
||||
element = render(props.id, { style: {display: "block" }})
|
||||
} else {
|
||||
element = render(props.id)
|
||||
}
|
||||
} else {
|
||||
if (props.useStyle) {
|
||||
element = render(props.id, { style: {display: "none"}})
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
element: this.props.appear === true ? render(this.props.id) : null
|
||||
element,
|
||||
})
|
||||
}, random())
|
||||
}
|
||||
|
@ -48,13 +48,31 @@ export default class EventsPage extends React.Component {
|
||||
title: "Appearable"
|
||||
})
|
||||
]),
|
||||
]),
|
||||
e("div", { className: "row" }, [
|
||||
e("div", { className: "col-lg-4"}, [
|
||||
e(Appearable, {
|
||||
id: "wait-no-element",
|
||||
appear: false,
|
||||
title: "Disappearable"
|
||||
})
|
||||
])
|
||||
]),
|
||||
e("div", { className: "col-lg-4"}, [
|
||||
e(Appearable, {
|
||||
id: "wait-style",
|
||||
appear: true,
|
||||
title: "Appearable with style",
|
||||
useStyle: true,
|
||||
})
|
||||
]),
|
||||
e("div", { className: "col-lg-4"}, [
|
||||
e(Appearable, {
|
||||
id: "wait-no-style",
|
||||
appear: false,
|
||||
title: "Disappearable",
|
||||
useStyle: true,
|
||||
})
|
||||
]),
|
||||
])
|
||||
])
|
||||
}
|
||||
|
11
e2e/tests/el_attrs_get.d.fql
Normal file
11
e2e/tests/el_attrs_get.d.fql
Normal file
@ -0,0 +1,11 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
LET attrs = ATTR_GET(el, "style")
|
||||
|
||||
RETURN EXPECT("display: block;", attrs.style)
|
17
e2e/tests/el_attrs_remove.d.fql
Normal file
17
e2e/tests/el_attrs_remove.d.fql
Normal file
@ -0,0 +1,17 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
|
||||
LET prev = el.attributes.style
|
||||
|
||||
ATTR_REMOVE(el, "style")
|
||||
|
||||
WAIT(1000)
|
||||
|
||||
LET curr = el.attributes.style
|
||||
|
||||
RETURN prev == "display: block;" && curr == NONE ? "" : "expected attribute to be removed"
|
17
e2e/tests/el_attrs_set.d.fql
Normal file
17
e2e/tests/el_attrs_set.d.fql
Normal file
@ -0,0 +1,17 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
LET prev = el.style
|
||||
|
||||
ATTR_SET(el, "style", "color: black;")
|
||||
|
||||
WAIT(1000)
|
||||
|
||||
LET curr = el.style
|
||||
|
||||
RETURN curr.color == "black" ? "" : "styles should be updated"
|
14
e2e/tests/el_attrs_set_bulk.d.fql
Normal file
14
e2e/tests/el_attrs_set_bulk.d.fql
Normal file
@ -0,0 +1,14 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
|
||||
ATTR_SET(el, { style: "color: black;", "data-ferret-x": "test" })
|
||||
|
||||
WAIT(1000)
|
||||
|
||||
RETURN el.style.color == "black" && el.attributes["data-ferret-x"] == "test" ? "" : "styles should be updated"
|
11
e2e/tests/el_style_get.d.fql
Normal file
11
e2e/tests/el_style_get.d.fql
Normal file
@ -0,0 +1,11 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
LET val = STYLE_GET(el, "display")
|
||||
|
||||
RETURN val.display == "block" ? "" : "could not get style values"
|
17
e2e/tests/el_style_remove.d.fql
Normal file
17
e2e/tests/el_style_remove.d.fql
Normal file
@ -0,0 +1,17 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
|
||||
LET prev = el.style
|
||||
|
||||
STYLE_REMOVE(el, "display")
|
||||
|
||||
WAIT(1000)
|
||||
|
||||
LET curr = el.style
|
||||
|
||||
RETURN prev.display == "block" && curr.display == NONE ? "" : "expected style to be removed"
|
17
e2e/tests/el_style_set.d.fql
Normal file
17
e2e/tests/el_style_set.d.fql
Normal file
@ -0,0 +1,17 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
LET prev = el.style
|
||||
|
||||
STYLE_SET(el, "color", "black")
|
||||
|
||||
WAIT(1000)
|
||||
|
||||
LET curr = el.style
|
||||
|
||||
RETURN curr.color == "black" ? "" : "styles should be updated"
|
17
e2e/tests/el_style_set_bulk.d.fql
Normal file
17
e2e/tests/el_style_set_bulk.d.fql
Normal file
@ -0,0 +1,17 @@
|
||||
LET url = @dynamic + "?redirect=/events"
|
||||
LET doc = DOCUMENT(url, true)
|
||||
LET pageSelector = "#page-events"
|
||||
LET elemSelector = "#wait-no-style-content"
|
||||
|
||||
WAIT_ELEMENT(doc, pageSelector)
|
||||
|
||||
LET el = ELEMENT(doc, elemSelector)
|
||||
LET prev = el.style
|
||||
|
||||
STYLE_SET(el, { color: "black", "min-width": "100px", "background-color": "#11111" })
|
||||
|
||||
WAIT(1000)
|
||||
|
||||
LET curr = el.style
|
||||
|
||||
RETURN curr.color == "black" && curr["min-width"] == "100px" && curr["background-color"] == "#11111" ? "" : "styles should be updated"
|
@ -47,8 +47,8 @@ type (
|
||||
innerHTML values.String
|
||||
innerText *common.LazyValue
|
||||
value core.Value
|
||||
rawAttrs []string
|
||||
attributes *common.LazyValue
|
||||
style *common.LazyValue
|
||||
children []*HTMLElementIdentity
|
||||
loadedChildren *common.LazyValue
|
||||
}
|
||||
@ -128,7 +128,6 @@ func LoadElement(
|
||||
id,
|
||||
node.Node.NodeType,
|
||||
node.Node.NodeName,
|
||||
node.Node.Attributes,
|
||||
val,
|
||||
innerHTML,
|
||||
createChildrenArray(node.Node.Children),
|
||||
@ -142,7 +141,6 @@ func NewHTMLElement(
|
||||
id *HTMLElementIdentity,
|
||||
nodeType int,
|
||||
nodeName string,
|
||||
attributes []string,
|
||||
value string,
|
||||
innerHTML values.String,
|
||||
children []*HTMLElementIdentity,
|
||||
@ -157,8 +155,8 @@ func NewHTMLElement(
|
||||
el.nodeName = values.NewString(nodeName)
|
||||
el.innerHTML = innerHTML
|
||||
el.innerText = common.NewLazyValue(el.loadInnerText)
|
||||
el.rawAttrs = attributes
|
||||
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)
|
||||
@ -297,6 +295,99 @@ func (el *HTMLElement) Length() values.Int {
|
||||
return values.NewInt(len(el.children))
|
||||
}
|
||||
|
||||
func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
|
||||
val, err := el.style.Read(ctx)
|
||||
|
||||
if err != nil {
|
||||
return values.NewObject(), err
|
||||
}
|
||||
|
||||
if val == values.None {
|
||||
return values.NewObject(), nil
|
||||
}
|
||||
|
||||
styles := val.(*values.Object)
|
||||
|
||||
return styles.Copy().(*values.Object), nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
|
||||
styles, err := el.style.Read(ctx)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
val, found := styles.(*values.Object).Get(name)
|
||||
|
||||
if !found {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error {
|
||||
if styles == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
val, err := el.style.Read(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentStyles := val.(*values.Object)
|
||||
|
||||
styles.ForEach(func(value core.Value, key string) bool {
|
||||
currentStyles.Set(values.NewString(key), value)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
str := common.SerializeStyles(ctx, currentStyles)
|
||||
|
||||
return el.SetAttribute(ctx, "style", str)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetStyle(ctx context.Context, name values.String, value core.Value) error {
|
||||
val, err := el.style.Read(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
styles := val.(*values.Object)
|
||||
styles.Set(name, value)
|
||||
|
||||
str := common.SerializeStyles(ctx, styles)
|
||||
|
||||
return el.SetAttribute(ctx, "style", str)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error {
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
val, err := el.style.Read(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
styles := val.(*values.Object)
|
||||
|
||||
for _, name := range names {
|
||||
styles.Remove(name)
|
||||
}
|
||||
|
||||
str := common.SerializeStyles(ctx, styles)
|
||||
|
||||
return el.SetAttribute(ctx, "style", str)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) GetAttributes(ctx context.Context) *values.Object {
|
||||
val, err := el.attributes.Read(ctx)
|
||||
|
||||
@ -326,6 +417,18 @@ func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) cor
|
||||
return val
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
|
||||
var err error
|
||||
|
||||
attrs.ForEach(func(value core.Value, key string) bool {
|
||||
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
|
||||
|
||||
return err == nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error {
|
||||
return el.client.DOM.SetAttributeValue(
|
||||
ctx,
|
||||
@ -333,6 +436,21 @@ func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.Stri
|
||||
)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.String) error {
|
||||
for _, name := range names {
|
||||
err := el.client.DOM.RemoveAttribute(
|
||||
ctx,
|
||||
dom.NewRemoveAttributeArgs(el.id.nodeID, name.String()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) GetChildNodes(ctx context.Context) core.Value {
|
||||
val, err := el.loadedChildren.Read(ctx)
|
||||
|
||||
@ -940,8 +1058,14 @@ func (el *HTMLElement) loadInnerText(ctx context.Context) (core.Value, error) {
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) loadAttrs(_ context.Context) (core.Value, error) {
|
||||
return parseAttrs(el.rawAttrs), nil
|
||||
func (el *HTMLElement) loadAttrs(ctx context.Context) (core.Value, error) {
|
||||
repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.nodeID))
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return parseAttrs(repl.Attributes), nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
|
||||
@ -973,6 +1097,20 @@ func (el *HTMLElement) loadChildren(ctx context.Context) (core.Value, error) {
|
||||
return loaded, nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) parseStyle(ctx context.Context) (core.Value, error) {
|
||||
value := el.GetAttribute(ctx, "style")
|
||||
|
||||
if value == values.None {
|
||||
return values.NewObject(), nil
|
||||
}
|
||||
|
||||
if value.Type() != types.String {
|
||||
return values.NewObject(), nil
|
||||
}
|
||||
|
||||
return common.DeserializeStyles(value.(values.String))
|
||||
}
|
||||
|
||||
func (el *HTMLElement) handlePageReload(_ context.Context, _ interface{}) {
|
||||
el.Close()
|
||||
}
|
||||
@ -990,6 +1128,12 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
|
||||
return
|
||||
}
|
||||
|
||||
// they are not event loaded
|
||||
// just ignore the event
|
||||
if !el.attributes.Ready() {
|
||||
return
|
||||
}
|
||||
|
||||
el.attributes.Write(ctx, func(v core.Value, err error) {
|
||||
if err != nil {
|
||||
el.logError(err).Msg("failed to update element")
|
||||
@ -997,6 +1141,10 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
|
||||
return
|
||||
}
|
||||
|
||||
if reply.Name == "style" {
|
||||
el.style.Reset()
|
||||
}
|
||||
|
||||
attrs, ok := v.(*values.Object)
|
||||
|
||||
if !ok {
|
||||
@ -1033,6 +1181,10 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
|
||||
return
|
||||
}
|
||||
|
||||
if reply.Name == "style" {
|
||||
el.style.Reset()
|
||||
}
|
||||
|
||||
attrs, ok := v.(*values.Object)
|
||||
|
||||
if !ok {
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/MontFerret/ferret/pkg/drivers"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/drivers"
|
||||
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
|
||||
"github.com/MontFerret/ferret/pkg/drivers/cdp/events"
|
||||
"github.com/MontFerret/ferret/pkg/drivers/common"
|
||||
|
@ -59,6 +59,18 @@ func GetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
|
||||
}
|
||||
|
||||
return values.GetIn(ctx, attrs, path[1:])
|
||||
case "style":
|
||||
styles, err := el.GetStyles(ctx)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
if len(path) == 1 {
|
||||
return styles, nil
|
||||
}
|
||||
|
||||
return values.GetIn(ctx, styles, path[1:])
|
||||
default:
|
||||
return GetInNode(ctx, el, path)
|
||||
}
|
||||
|
@ -9,18 +9,18 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
LazyFactory func(ctx context.Context) (core.Value, error)
|
||||
LazyValueFactory func(ctx context.Context) (core.Value, error)
|
||||
|
||||
LazyValue struct {
|
||||
sync.Mutex
|
||||
factory LazyFactory
|
||||
factory LazyValueFactory
|
||||
ready bool
|
||||
value core.Value
|
||||
err error
|
||||
}
|
||||
)
|
||||
|
||||
func NewLazyValue(factory LazyFactory) *LazyValue {
|
||||
func NewLazyValue(factory LazyValueFactory) *LazyValue {
|
||||
lz := new(LazyValue)
|
||||
lz.ready = false
|
||||
lz.factory = factory
|
||||
|
@ -54,6 +54,15 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
|
||||
return err
|
||||
}
|
||||
|
||||
curr := el.GetAttributes(ctx)
|
||||
|
||||
// remove all previous attributes
|
||||
err = el.RemoveAttribute(ctx, curr.Keys()...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj := value.(*values.Object)
|
||||
obj.ForEach(func(value core.Value, key string) bool {
|
||||
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
|
||||
@ -61,6 +70,35 @@ func SetInElement(ctx context.Context, el drivers.HTMLElement, path []core.Value
|
||||
return err == nil
|
||||
})
|
||||
|
||||
return err
|
||||
case "style":
|
||||
if len(path) > 1 {
|
||||
attrName := path[1]
|
||||
|
||||
return el.SetStyle(ctx, values.NewString(attrName.String()), value)
|
||||
}
|
||||
|
||||
err := core.ValidateType(value, types.Object)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
styles, err := el.GetStyles(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = el.RemoveStyle(ctx, styles.Keys()...)
|
||||
|
||||
obj := value.(*values.Object)
|
||||
obj.ForEach(func(value core.Value, key string) bool {
|
||||
err = el.SetStyle(ctx, values.NewString(key), value)
|
||||
|
||||
return err == nil
|
||||
})
|
||||
|
||||
return err
|
||||
case "value":
|
||||
if len(path) > 1 {
|
||||
|
103
pkg/drivers/common/styles.go
Normal file
103
pkg/drivers/common/styles.go
Normal file
@ -0,0 +1,103 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/gorilla/css/scanner"
|
||||
)
|
||||
|
||||
func DeserializeStyles(input values.String) (*values.Object, error) {
|
||||
styles := values.NewObject()
|
||||
|
||||
if input == values.EmptyString {
|
||||
return styles, nil
|
||||
}
|
||||
|
||||
s := scanner.New(input.String())
|
||||
|
||||
var name string
|
||||
var value bytes.Buffer
|
||||
var setValue = func() {
|
||||
styles.Set(values.NewString(strings.TrimSpace(name)), values.NewString(strings.TrimSpace(value.String())))
|
||||
name = ""
|
||||
value.Reset()
|
||||
}
|
||||
|
||||
for {
|
||||
token := s.Next()
|
||||
|
||||
if token == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if token.Type == scanner.TokenEOF {
|
||||
break
|
||||
}
|
||||
|
||||
if name == "" && token.Type == scanner.TokenIdent {
|
||||
name = token.Value
|
||||
|
||||
// skip : and white spaces
|
||||
for {
|
||||
token = s.Next()
|
||||
|
||||
if token.Value != ":" && token.Type != scanner.TokenS {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch token.Type {
|
||||
case scanner.TokenChar:
|
||||
// end of style declaration
|
||||
if token.Value == ";" {
|
||||
if name != "" {
|
||||
setValue()
|
||||
}
|
||||
} else {
|
||||
value.WriteString(token.Value)
|
||||
}
|
||||
case scanner.TokenNumber:
|
||||
num, err := strconv.ParseFloat(token.Value, 64)
|
||||
|
||||
if err == nil {
|
||||
styles.Set(values.NewString(name), values.NewFloat(num))
|
||||
// reset prop
|
||||
name = ""
|
||||
value.Reset()
|
||||
}
|
||||
default:
|
||||
value.WriteString(token.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if name != "" && value.Len() > 0 {
|
||||
setValue()
|
||||
}
|
||||
|
||||
return styles, nil
|
||||
}
|
||||
|
||||
func SerializeStyles(_ context.Context, styles *values.Object) values.String {
|
||||
if styles == nil {
|
||||
return values.EmptyString
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
styles.ForEach(func(value core.Value, key string) bool {
|
||||
b.WriteString(key)
|
||||
b.WriteString(": ")
|
||||
b.WriteString(value.String())
|
||||
b.WriteString("; ")
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return values.NewString(b.String())
|
||||
}
|
102
pkg/drivers/common/styles_test.go
Normal file
102
pkg/drivers/common/styles_test.go
Normal file
@ -0,0 +1,102 @@
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/MontFerret/ferret/pkg/drivers/common"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type style struct {
|
||||
raw string
|
||||
name values.String
|
||||
value core.Value
|
||||
}
|
||||
|
||||
func TestDeserializeStyles(t *testing.T) {
|
||||
Convey("DeserializeStyles", t, func() {
|
||||
styles := []style{
|
||||
{
|
||||
raw: "min-height: 1.15",
|
||||
name: "min-height",
|
||||
value: values.NewFloat(1.15),
|
||||
},
|
||||
{
|
||||
raw: "background-color: #4A154B",
|
||||
name: "background-color",
|
||||
value: values.NewString("#4A154B"),
|
||||
},
|
||||
{
|
||||
raw: "font-size:26pt",
|
||||
name: "font-size",
|
||||
value: values.NewString("26pt"),
|
||||
},
|
||||
{
|
||||
raw: "page-break-after:avoid",
|
||||
name: "page-break-after",
|
||||
value: values.NewString("avoid"),
|
||||
},
|
||||
{
|
||||
raw: `font-family: Arial,"Helvetica Neue",Helvetica,sans-serif`,
|
||||
name: "font-family",
|
||||
value: values.NewString(`Arial,"Helvetica Neue",Helvetica,sans-serif`),
|
||||
},
|
||||
{
|
||||
raw: "color: black",
|
||||
name: "color",
|
||||
value: values.NewString("black"),
|
||||
},
|
||||
{
|
||||
raw: "display: inline-block",
|
||||
name: "display",
|
||||
value: values.NewString("inline-block"),
|
||||
},
|
||||
{
|
||||
raw: "min-width: 50",
|
||||
name: "min-width",
|
||||
value: values.NewFloat(50),
|
||||
},
|
||||
}
|
||||
|
||||
Convey("Should parse a single style", func() {
|
||||
for _, s := range styles {
|
||||
out, err := common.DeserializeStyles(values.NewString(s.raw))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldNotBeNil)
|
||||
|
||||
value, exists := out.Get(s.name)
|
||||
|
||||
So(bool(exists), ShouldBeTrue)
|
||||
|
||||
So(value.Compare(s.value) == 0, ShouldBeTrue)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Should parse multiple styles", func() {
|
||||
var buff bytes.Buffer
|
||||
|
||||
for _, s := range styles {
|
||||
buff.WriteString(s.raw)
|
||||
buff.WriteString("; ")
|
||||
}
|
||||
|
||||
out, err := common.DeserializeStyles(values.NewString(buff.String()))
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(out, ShouldNotBeNil)
|
||||
So(int(out.Length()), ShouldEqual, len(styles))
|
||||
|
||||
for _, s := range styles {
|
||||
value, exists := out.Get(s.name)
|
||||
|
||||
So(bool(exists), ShouldBeTrue)
|
||||
|
||||
So(value.Compare(s.value) == 0, ShouldBeTrue)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
type HTMLElement struct {
|
||||
selection *goquery.Selection
|
||||
attrs *values.Object
|
||||
styles *values.Object
|
||||
children *values.Array
|
||||
}
|
||||
|
||||
@ -24,22 +25,22 @@ func NewHTMLElement(node *goquery.Selection) (drivers.HTMLElement, error) {
|
||||
return nil, core.Error(core.ErrMissedArgument, "element selection")
|
||||
}
|
||||
|
||||
return &HTMLElement{node, nil, nil}, nil
|
||||
return &HTMLElement{node, nil, nil, nil}, nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(nd.InnerText(context.Background()).String())
|
||||
func (el *HTMLElement) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(el.InnerText(context.Background()).String())
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Type() core.Type {
|
||||
func (el *HTMLElement) Type() core.Type {
|
||||
return drivers.HTMLElementType
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) String() string {
|
||||
return nd.InnerHTML(context.Background()).String()
|
||||
func (el *HTMLElement) String() string {
|
||||
return el.InnerHTML(context.Background()).String()
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Compare(other core.Value) int64 {
|
||||
func (el *HTMLElement) Compare(other core.Value) int64 {
|
||||
switch other.Type() {
|
||||
case drivers.HTMLElementType:
|
||||
other := other.(drivers.HTMLElement)
|
||||
@ -47,18 +48,18 @@ func (nd *HTMLElement) Compare(other core.Value) int64 {
|
||||
ctx, fn := drivers.WithDefaultTimeout(context.Background())
|
||||
defer fn()
|
||||
|
||||
return nd.InnerHTML(ctx).Compare(other.InnerHTML(ctx))
|
||||
return el.InnerHTML(ctx).Compare(other.InnerHTML(ctx))
|
||||
default:
|
||||
return drivers.Compare(nd.Type(), other.Type())
|
||||
return drivers.Compare(el.Type(), other.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Unwrap() interface{} {
|
||||
return nd.selection
|
||||
func (el *HTMLElement) Unwrap() interface{} {
|
||||
return el.selection
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Hash() uint64 {
|
||||
str, err := nd.selection.Html()
|
||||
func (el *HTMLElement) Hash() uint64 {
|
||||
str, err := el.selection.Html()
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
@ -66,21 +67,21 @@ func (nd *HTMLElement) Hash() uint64 {
|
||||
|
||||
h := fnv.New64a()
|
||||
|
||||
h.Write([]byte(nd.Type().String()))
|
||||
h.Write([]byte(el.Type().String()))
|
||||
h.Write([]byte(":"))
|
||||
h.Write([]byte(str))
|
||||
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Copy() core.Value {
|
||||
c, _ := NewHTMLElement(nd.selection.Clone())
|
||||
func (el *HTMLElement) Copy() core.Value {
|
||||
c, _ := NewHTMLElement(el.selection.Clone())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) NodeType() values.Int {
|
||||
nodes := nd.selection.Nodes
|
||||
func (el *HTMLElement) NodeType() values.Int {
|
||||
nodes := el.selection.Nodes
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return 0
|
||||
@ -89,24 +90,24 @@ func (nd *HTMLElement) NodeType() values.Int {
|
||||
return values.NewInt(common.ToHTMLType(nodes[0].Type))
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Close() error {
|
||||
func (el *HTMLElement) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) NodeName() values.String {
|
||||
return values.NewString(goquery.NodeName(nd.selection))
|
||||
func (el *HTMLElement) NodeName() values.String {
|
||||
return values.NewString(goquery.NodeName(el.selection))
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Length() values.Int {
|
||||
if nd.children == nil {
|
||||
nd.children = nd.parseChildren()
|
||||
func (el *HTMLElement) Length() values.Int {
|
||||
if el.children == nil {
|
||||
el.children = el.parseChildren()
|
||||
}
|
||||
|
||||
return nd.children.Length()
|
||||
return el.children.Length()
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) GetValue(_ context.Context) core.Value {
|
||||
val, ok := nd.selection.Attr("value")
|
||||
func (el *HTMLElement) GetValue(_ context.Context) core.Value {
|
||||
val, ok := el.selection.Attr("value")
|
||||
|
||||
if ok {
|
||||
return values.NewString(val)
|
||||
@ -115,18 +116,18 @@ func (nd *HTMLElement) GetValue(_ context.Context) core.Value {
|
||||
return values.EmptyString
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) SetValue(_ context.Context, value core.Value) error {
|
||||
nd.selection.SetAttr("value", value.String())
|
||||
func (el *HTMLElement) SetValue(_ context.Context, value core.Value) error {
|
||||
el.selection.SetAttr("value", value.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) InnerText(_ context.Context) values.String {
|
||||
return values.NewString(nd.selection.Text())
|
||||
func (el *HTMLElement) InnerText(_ context.Context) values.String {
|
||||
return values.NewString(el.selection.Text())
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) InnerHTML(_ context.Context) values.String {
|
||||
h, err := nd.selection.Html()
|
||||
func (el *HTMLElement) InnerHTML(_ context.Context) values.String {
|
||||
h, err := el.selection.Html()
|
||||
|
||||
if err != nil {
|
||||
return values.EmptyString
|
||||
@ -135,48 +136,140 @@ func (nd *HTMLElement) InnerHTML(_ context.Context) values.String {
|
||||
return values.NewString(h)
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) GetAttributes(_ context.Context) *values.Object {
|
||||
if nd.attrs == nil {
|
||||
nd.attrs = nd.parseAttrs()
|
||||
func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) {
|
||||
if err := el.ensureStyles(ctx); err != nil {
|
||||
return values.NewObject(), err
|
||||
}
|
||||
|
||||
return nd.attrs
|
||||
return el.styles.Copy().(*values.Object), nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
|
||||
v, ok := nd.selection.Attr(name.String())
|
||||
|
||||
if ok {
|
||||
return values.NewString(v)
|
||||
func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) {
|
||||
if err := el.ensureStyles(ctx); err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return values.None
|
||||
return el.styles.MustGet(name), nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
|
||||
nd.selection.SetAttr(string(name), string(value))
|
||||
func (el *HTMLElement) SetStyle(ctx context.Context, name values.String, value core.Value) error {
|
||||
if err := el.ensureStyles(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
el.styles.Set(name, value)
|
||||
|
||||
str := common.SerializeStyles(ctx, el.styles)
|
||||
|
||||
return el.SetAttribute(ctx, "style", str)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetStyles(ctx context.Context, newStyles *values.Object) error {
|
||||
if newStyles == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := el.ensureStyles(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newStyles.ForEach(func(i core.Value, key string) bool {
|
||||
el.styles.Set(values.NewString(key), i)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
str := common.SerializeStyles(ctx, el.styles)
|
||||
|
||||
return el.SetAttribute(ctx, "style", str)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) RemoveStyle(ctx context.Context, name ...values.String) error {
|
||||
if len(name) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := el.ensureStyles(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range name {
|
||||
el.styles.Remove(s)
|
||||
}
|
||||
|
||||
str := common.SerializeStyles(ctx, el.styles)
|
||||
|
||||
return el.SetAttribute(ctx, "style", str)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error {
|
||||
if attrs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
el.ensureAttrs()
|
||||
|
||||
var err error
|
||||
|
||||
attrs.ForEach(func(value core.Value, key string) bool {
|
||||
err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String()))
|
||||
|
||||
return err == nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (el *HTMLElement) GetAttributes(_ context.Context) *values.Object {
|
||||
el.ensureAttrs()
|
||||
|
||||
return el.attrs.Copy().(*values.Object)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) GetAttribute(_ context.Context, name values.String) core.Value {
|
||||
el.ensureAttrs()
|
||||
|
||||
return el.attrs.MustGet(name)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) SetAttribute(_ context.Context, name, value values.String) error {
|
||||
el.ensureAttrs()
|
||||
|
||||
el.attrs.Set(name, value)
|
||||
el.selection.SetAttr(string(name), string(value))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) GetChildNodes(_ context.Context) core.Value {
|
||||
if nd.children == nil {
|
||||
nd.children = nd.parseChildren()
|
||||
func (el *HTMLElement) RemoveAttribute(_ context.Context, name ...values.String) error {
|
||||
el.ensureAttrs()
|
||||
|
||||
for _, attr := range name {
|
||||
el.attrs.Remove(attr)
|
||||
el.selection.RemoveAttr(attr.String())
|
||||
}
|
||||
|
||||
return nd.children
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
|
||||
if nd.children == nil {
|
||||
nd.children = nd.parseChildren()
|
||||
func (el *HTMLElement) GetChildNodes(_ context.Context) core.Value {
|
||||
if el.children == nil {
|
||||
el.children = el.parseChildren()
|
||||
}
|
||||
|
||||
return nd.children.Get(idx)
|
||||
return el.children
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) GetChildNode(_ context.Context, idx values.Int) core.Value {
|
||||
if el.children == nil {
|
||||
el.children = el.parseChildren()
|
||||
}
|
||||
|
||||
return el.children.Get(idx)
|
||||
}
|
||||
|
||||
func (el *HTMLElement) QuerySelector(_ context.Context, selector values.String) core.Value {
|
||||
selection := el.selection.Find(selector.String())
|
||||
|
||||
if selection == nil {
|
||||
return values.None
|
||||
@ -191,8 +284,8 @@ func (nd *HTMLElement) QuerySelector(_ context.Context, selector values.String)
|
||||
return res
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) QuerySelectorAll(_ context.Context, selector values.String) core.Value {
|
||||
selection := el.selection.Find(selector.String())
|
||||
|
||||
if selection == nil {
|
||||
return values.None
|
||||
@ -211,8 +304,8 @@ func (nd *HTMLElement) QuerySelectorAll(_ context.Context, selector values.Strin
|
||||
return arr
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.String) values.String {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.String) values.String {
|
||||
selection := el.selection.Find(selector.String())
|
||||
|
||||
str, err := selection.Html()
|
||||
|
||||
@ -224,8 +317,8 @@ func (nd *HTMLElement) InnerHTMLBySelector(_ context.Context, selector values.St
|
||||
return values.NewString(str)
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values.String) *values.Array {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values.String) *values.Array {
|
||||
selection := el.selection.Find(selector.String())
|
||||
arr := values.NewArray(selection.Length())
|
||||
|
||||
selection.Each(func(_ int, selection *goquery.Selection) {
|
||||
@ -240,14 +333,14 @@ func (nd *HTMLElement) InnerHTMLBySelectorAll(_ context.Context, selector values
|
||||
return arr
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) InnerTextBySelector(_ context.Context, selector values.String) values.String {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) InnerTextBySelector(_ context.Context, selector values.String) values.String {
|
||||
selection := el.selection.Find(selector.String())
|
||||
|
||||
return values.NewString(selection.Text())
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values.String) *values.Array {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values.String) *values.Array {
|
||||
selection := el.selection.Find(selector.String())
|
||||
arr := values.NewArray(selection.Length())
|
||||
|
||||
selection.Each(func(_ int, selection *goquery.Selection) {
|
||||
@ -257,8 +350,8 @@ func (nd *HTMLElement) InnerTextBySelectorAll(_ context.Context, selector values
|
||||
return arr
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
|
||||
selection := nd.selection.Find(selector.String())
|
||||
func (el *HTMLElement) CountBySelector(_ context.Context, selector values.String) values.Int {
|
||||
selection := el.selection.Find(selector.String())
|
||||
|
||||
if selection == nil {
|
||||
return values.ZeroInt
|
||||
@ -267,8 +360,8 @@ func (nd *HTMLElement) CountBySelector(_ context.Context, selector values.String
|
||||
return values.NewInt(selection.Size())
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) values.Boolean {
|
||||
selection := nd.selection.Closest(selector.String())
|
||||
func (el *HTMLElement) ExistsBySelector(_ context.Context, selector values.String) values.Boolean {
|
||||
selection := el.selection.Closest(selector.String())
|
||||
|
||||
if selection == nil {
|
||||
return values.False
|
||||
@ -277,47 +370,83 @@ func (nd *HTMLElement) ExistsBySelector(_ context.Context, selector values.Strin
|
||||
return values.True
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
|
||||
return common.GetInElement(ctx, nd, path)
|
||||
func (el *HTMLElement) GetIn(ctx context.Context, path []core.Value) (core.Value, error) {
|
||||
return common.GetInElement(ctx, el, path)
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
|
||||
return common.SetInElement(ctx, nd, path, value)
|
||||
func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core.Value) error {
|
||||
return common.SetInElement(ctx, el, path, value)
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
|
||||
return common.NewIterator(nd)
|
||||
func (el *HTMLElement) Iterate(_ context.Context) (core.Iterator, error) {
|
||||
return common.NewIterator(el)
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Click(_ context.Context) (values.Boolean, error) {
|
||||
func (el *HTMLElement) Click(_ context.Context) (values.Boolean, error) {
|
||||
return false, core.ErrNotSupported
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error {
|
||||
func (el *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error {
|
||||
return core.ErrNotSupported
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
|
||||
func (el *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
|
||||
return nil, core.ErrNotSupported
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) ScrollIntoView(_ context.Context) error {
|
||||
func (el *HTMLElement) ScrollIntoView(_ context.Context) error {
|
||||
return core.ErrNotSupported
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) Hover(_ context.Context) error {
|
||||
func (el *HTMLElement) Hover(_ context.Context) error {
|
||||
return core.ErrNotSupported
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
|
||||
func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
|
||||
return core.ErrNotSupported
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) parseAttrs() *values.Object {
|
||||
func (el *HTMLElement) ensureStyles(ctx context.Context) error {
|
||||
if el.styles == nil {
|
||||
styles, err := el.parseStyles(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
el.styles = styles
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) parseStyles(ctx context.Context) (*values.Object, error) {
|
||||
str := el.GetAttribute(ctx, "style")
|
||||
|
||||
if str == values.None {
|
||||
return values.NewObject(), nil
|
||||
}
|
||||
|
||||
styles, err := common.DeserializeStyles(values.NewString(str.String()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return styles, nil
|
||||
}
|
||||
|
||||
func (el *HTMLElement) ensureAttrs() {
|
||||
if el.attrs == nil {
|
||||
el.attrs = el.parseAttrs()
|
||||
}
|
||||
}
|
||||
|
||||
func (el *HTMLElement) parseAttrs() *values.Object {
|
||||
obj := values.NewObject()
|
||||
|
||||
for _, name := range common.Attributes {
|
||||
val, ok := nd.selection.Attr(name)
|
||||
val, ok := el.selection.Attr(name)
|
||||
|
||||
if ok {
|
||||
obj.Set(values.NewString(name), values.NewString(val))
|
||||
@ -327,8 +456,8 @@ func (nd *HTMLElement) parseAttrs() *values.Object {
|
||||
return obj
|
||||
}
|
||||
|
||||
func (nd *HTMLElement) parseChildren() *values.Array {
|
||||
children := nd.selection.Children()
|
||||
func (el *HTMLElement) parseChildren() *values.Array {
|
||||
children := el.selection.Children()
|
||||
|
||||
arr := values.NewArray(10)
|
||||
|
||||
|
@ -53,12 +53,26 @@ type (
|
||||
|
||||
SetValue(ctx context.Context, value core.Value) error
|
||||
|
||||
GetStyles(ctx context.Context) (*values.Object, error)
|
||||
|
||||
GetStyle(ctx context.Context, name values.String) (core.Value, error)
|
||||
|
||||
SetStyles(ctx context.Context, values *values.Object) error
|
||||
|
||||
SetStyle(ctx context.Context, name values.String, value core.Value) error
|
||||
|
||||
RemoveStyle(ctx context.Context, name ...values.String) error
|
||||
|
||||
GetAttributes(ctx context.Context) *values.Object
|
||||
|
||||
GetAttribute(ctx context.Context, name values.String) core.Value
|
||||
|
||||
SetAttributes(ctx context.Context, values *values.Object) error
|
||||
|
||||
SetAttribute(ctx context.Context, name, value values.String) error
|
||||
|
||||
RemoveAttribute(ctx context.Context, name ...values.String) error
|
||||
|
||||
InnerHTMLBySelector(ctx context.Context, selector values.String) values.String
|
||||
|
||||
InnerHTMLBySelectorAll(ctx context.Context, selector values.String) *values.Array
|
||||
|
@ -22,7 +22,7 @@ type (
|
||||
KeyedCollection interface {
|
||||
core.Value
|
||||
Measurable
|
||||
Keys() []string
|
||||
Keys() []values.String
|
||||
Get(key values.String) (core.Value, values.Boolean)
|
||||
Set(key values.String, value core.Value)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ type KeyedIterator struct {
|
||||
valVar string
|
||||
keyVar string
|
||||
values KeyedCollection
|
||||
keys []string
|
||||
keys []values.String
|
||||
pos int
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ func (iterator *KeyedIterator) Next(_ context.Context, scope *core.Scope) (*core
|
||||
}
|
||||
|
||||
if len(iterator.keys) > iterator.pos {
|
||||
key := values.NewString(iterator.keys[iterator.pos])
|
||||
key := iterator.keys[iterator.pos]
|
||||
val, _ := iterator.values.Get(key)
|
||||
|
||||
iterator.pos++
|
||||
|
@ -80,10 +80,23 @@ func (t *Object) Compare(other core.Value) int64 {
|
||||
|
||||
var res int64
|
||||
|
||||
sortedT := sort.StringSlice(t.Keys())
|
||||
tKeys := make([]string, 0, len(t.value))
|
||||
|
||||
for k := range t.value {
|
||||
tKeys = append(tKeys, k)
|
||||
}
|
||||
|
||||
sortedT := sort.StringSlice(tKeys)
|
||||
sortedT.Sort()
|
||||
|
||||
sortedOther := sort.StringSlice(other.Keys())
|
||||
otherKeys := make([]string, 0, other.Length())
|
||||
|
||||
other.ForEach(func(value core.Value, k string) bool {
|
||||
otherKeys = append(otherKeys, k)
|
||||
return true
|
||||
})
|
||||
|
||||
sortedOther := sort.StringSlice(otherKeys)
|
||||
sortedOther.Sort()
|
||||
|
||||
var tVal, otherVal core.Value
|
||||
@ -178,11 +191,21 @@ func (t *Object) Length() Int {
|
||||
return Int(len(t.value))
|
||||
}
|
||||
|
||||
func (t *Object) Keys() []string {
|
||||
keys := make([]string, 0, len(t.value))
|
||||
func (t *Object) Keys() []String {
|
||||
keys := make([]String, 0, len(t.value))
|
||||
|
||||
for k := range t.value {
|
||||
keys = append(keys, k)
|
||||
keys = append(keys, NewString(k))
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func (t *Object) Values() []core.Value {
|
||||
keys := make([]core.Value, 0, len(t.value))
|
||||
|
||||
for _, v := range t.value {
|
||||
keys = append(keys, v)
|
||||
}
|
||||
|
||||
return keys
|
||||
|
@ -21,7 +21,6 @@ func NewLib() map[string]core.Function {
|
||||
"REMOVE_NTH": RemoveNth,
|
||||
"REMOVE_VALUE": RemoveValue,
|
||||
"REMOVE_VALUES": RemoveValues,
|
||||
"REVERSE": Reverse,
|
||||
"SHIFT": Shift,
|
||||
"SLICE": Slice,
|
||||
"SORTED": Sorted,
|
||||
|
@ -1,36 +0,0 @@
|
||||
package arrays
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
)
|
||||
|
||||
// Reverse return a new array with its elements reversed.
|
||||
// @param array (Array) - Target array.
|
||||
// @returns (Array) - A new array with its elements reversed.
|
||||
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], types.Array)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr := args[0].(*values.Array)
|
||||
size := int(arr.Length())
|
||||
result := values.NewArray(size)
|
||||
|
||||
for i := size - 1; i >= 0; i-- {
|
||||
result.Push(arr.Get(values.NewInt(i)))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
@ -4,6 +4,7 @@ import "github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
|
||||
func NewLib() map[string]core.Function {
|
||||
return map[string]core.Function{
|
||||
"LENGTH": Length,
|
||||
"LENGTH": Length,
|
||||
"REVERSE": Reverse,
|
||||
}
|
||||
}
|
||||
|
51
pkg/stdlib/collections/reverse.go
Normal file
51
pkg/stdlib/collections/reverse.go
Normal file
@ -0,0 +1,51 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// Reverse returns the reverse of a given string or array value.
|
||||
// @param text (String|Array) - The string or array to reverse.
|
||||
// @returns (String|Array) - Returns a reversed version of a given value.
|
||||
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.EmptyString, err
|
||||
}
|
||||
|
||||
err = core.ValidateType(args[0], types.Array, types.String)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
switch col := args[0].(type) {
|
||||
case values.String:
|
||||
runes := []rune(string(col))
|
||||
size := len(runes)
|
||||
|
||||
// Reverse
|
||||
for i := 0; i < size/2; i++ {
|
||||
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
|
||||
}
|
||||
|
||||
return values.NewString(string(runes)), nil
|
||||
case *values.Array:
|
||||
size := int(col.Length())
|
||||
result := values.NewArray(size)
|
||||
|
||||
for i := size - 1; i >= 0; i-- {
|
||||
result.Push(col.Get(values.NewInt(i)))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
default:
|
||||
return values.None, nil
|
||||
}
|
||||
}
|
@ -1,14 +1,33 @@
|
||||
package arrays_test
|
||||
package collections_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/collections"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
Convey("When args are not passed", t, func() {
|
||||
Convey("It should return an error", func() {
|
||||
var err error
|
||||
_, err = collections.Reverse(context.Background())
|
||||
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should reverse a text with right encoding", t, func() {
|
||||
out, _ := collections.Reverse(
|
||||
context.Background(),
|
||||
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
|
||||
)
|
||||
|
||||
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
|
||||
})
|
||||
|
||||
Convey("Should return a copy of an array with reversed elements", t, func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
@ -19,7 +38,7 @@ func TestReverse(t *testing.T) {
|
||||
values.NewInt(6),
|
||||
)
|
||||
|
||||
out, err := arrays.Reverse(
|
||||
out, err := collections.Reverse(
|
||||
context.Background(),
|
||||
arr,
|
||||
)
|
||||
@ -31,7 +50,7 @@ func TestReverse(t *testing.T) {
|
||||
Convey("Should return an empty array when there no elements in a source one", t, func() {
|
||||
arr := values.NewArray(0)
|
||||
|
||||
out, err := arrays.Reverse(
|
||||
out, err := collections.Reverse(
|
||||
context.Background(),
|
||||
arr,
|
||||
)
|
41
pkg/stdlib/html/attr_get.go
Normal file
41
pkg/stdlib/html/attr_get.go
Normal file
@ -0,0 +1,41 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// AttributeGet 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.
|
||||
func AttributeGet(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
el, err := resolveElement(args[0])
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
names := args[1:]
|
||||
result := values.NewObject()
|
||||
attrs := el.GetAttributes(ctx)
|
||||
|
||||
for _, n := range names {
|
||||
name := values.NewString(n.String())
|
||||
val, exists := attrs.Get(name)
|
||||
|
||||
if exists {
|
||||
result.Set(name, val)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
41
pkg/stdlib/html/attr_remove.go
Normal file
41
pkg/stdlib/html/attr_remove.go
Normal file
@ -0,0 +1,41 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
)
|
||||
|
||||
// AttributeRemove 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) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
el, err := resolveElement(args[0])
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
attrs := args[1:]
|
||||
attrsStr := make([]values.String, 0, len(attrs))
|
||||
|
||||
for _, attr := range attrs {
|
||||
str, ok := attr.(values.String)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.TypeError(attr.Type(), types.String)
|
||||
}
|
||||
|
||||
attrsStr = append(attrsStr, str)
|
||||
}
|
||||
|
||||
return values.None, el.RemoveAttribute(ctx, attrsStr...)
|
||||
}
|
50
pkg/stdlib/html/attr_set.go
Normal file
50
pkg/stdlib/html/attr_set.go
Normal file
@ -0,0 +1,50 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
)
|
||||
|
||||
// AttributeSet 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.
|
||||
func AttributeSet(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
el, err := resolveElement(args[0])
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
switch arg1 := args[1].(type) {
|
||||
case values.String:
|
||||
// ATTR_SET(el, name, value)
|
||||
err = core.ValidateArgs(args, 3, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
arg2, ok := args[2].(values.String)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
|
||||
}
|
||||
|
||||
return values.None, el.SetAttribute(ctx, arg1, arg2)
|
||||
case *values.Object:
|
||||
// ATTR_SET(el, values)
|
||||
return values.None, el.SetAttributes(ctx, arg1)
|
||||
default:
|
||||
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
|
||||
}
|
||||
}
|
@ -14,6 +14,9 @@ const defaultTimeout = 5000
|
||||
|
||||
func NewLib() map[string]core.Function {
|
||||
return map[string]core.Function{
|
||||
"ATTR_GET": AttributeGet,
|
||||
"ATTR_REMOVE": AttributeRemove,
|
||||
"ATTR_SET": AttributeSet,
|
||||
"CLICK": Click,
|
||||
"CLICK_ALL": ClickAll,
|
||||
"DOCUMENT": Document,
|
||||
@ -40,6 +43,9 @@ func NewLib() map[string]core.Function {
|
||||
"SCROLL_ELEMENT": ScrollInto,
|
||||
"SCROLL_TOP": ScrollTop,
|
||||
"SELECT": Select,
|
||||
"STYLE_GET": StyleGet,
|
||||
"STYLE_REMOVE": StyleRemove,
|
||||
"STYLE_SET": StyleSet,
|
||||
"WAIT_ELEMENT": WaitElement,
|
||||
"WAIT_NO_ELEMENT": WaitNoElement,
|
||||
"WAIT_CLASS": WaitClass,
|
||||
|
44
pkg/stdlib/html/style_get.go
Normal file
44
pkg/stdlib/html/style_get.go
Normal file
@ -0,0 +1,44 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// StyleGet gets single or more style attribute value(s) of a given element.
|
||||
// @param el (HTMLElement) - Target element.
|
||||
// @param names (...String) - Style name(s).
|
||||
// @returns Object - Key-value pairs of style values.
|
||||
func StyleGet(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
el, err := resolveElement(args[0])
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
names := args[1:]
|
||||
result := values.NewObject()
|
||||
|
||||
for _, n := range names {
|
||||
name := values.NewString(n.String())
|
||||
val, err := el.GetStyle(ctx, name)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
if val != values.None {
|
||||
result.Set(name, val)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
41
pkg/stdlib/html/style_remove.go
Normal file
41
pkg/stdlib/html/style_remove.go
Normal file
@ -0,0 +1,41 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
)
|
||||
|
||||
// StyleRemove removes single or more style attribute value(s) of a given element.
|
||||
// @param el (HTMLElement) - Target element.
|
||||
// @param names (...String) - Style name(s).
|
||||
func StyleRemove(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
el, err := resolveElement(args[0])
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
attrs := args[1:]
|
||||
attrsStr := make([]values.String, 0, len(attrs))
|
||||
|
||||
for _, attr := range attrs {
|
||||
str, ok := attr.(values.String)
|
||||
|
||||
if !ok {
|
||||
return values.None, core.TypeError(attr.Type(), types.String)
|
||||
}
|
||||
|
||||
attrsStr = append(attrsStr, str)
|
||||
}
|
||||
|
||||
return values.None, el.RemoveStyle(ctx, attrsStr...)
|
||||
}
|
44
pkg/stdlib/html/style_set.go
Normal file
44
pkg/stdlib/html/style_set.go
Normal file
@ -0,0 +1,44 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||
)
|
||||
|
||||
// StyleSet sets or updates a single or more style attribute value of a given element.
|
||||
// @param el (HTMLElement) - Target element.
|
||||
// @param nameOrObj (String | Object) - Style 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 a style value.
|
||||
func StyleSet(ctx context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 2, core.MaxArgs)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
el, err := resolveElement(args[0])
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
switch arg1 := args[1].(type) {
|
||||
case values.String:
|
||||
// STYLE_SET(el, name, value)
|
||||
err = core.ValidateArgs(args, 3, 3)
|
||||
|
||||
if err != nil {
|
||||
return values.None, nil
|
||||
}
|
||||
|
||||
return values.None, el.SetStyle(ctx, arg1, args[2])
|
||||
case *values.Object:
|
||||
// STYLE_SET(el, values)
|
||||
return values.None, el.SetStyles(ctx, arg1)
|
||||
default:
|
||||
return values.None, core.TypeError(arg1.Type(), types.String, types.Object)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package stdlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/arrays"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/collections"
|
||||
@ -17,6 +18,10 @@ func NewLib() map[string]core.Function {
|
||||
|
||||
add := func(l map[string]core.Function) {
|
||||
for name, fn := range l {
|
||||
if _, exists := lib[name]; exists {
|
||||
panic(fmt.Sprintf("%s function already exists", name))
|
||||
}
|
||||
|
||||
lib[name] = fn
|
||||
}
|
||||
}
|
||||
|
@ -185,15 +185,15 @@ func isEqualObjects(obj1 *values.Object, obj2 *values.Object) bool {
|
||||
var val2 core.Value
|
||||
|
||||
for _, key := range obj1.Keys() {
|
||||
val1, _ = obj1.Get(values.NewString(key))
|
||||
val2, _ = obj2.Get(values.NewString(key))
|
||||
val1, _ = obj1.Get(key)
|
||||
val2, _ = obj2.Get(key)
|
||||
if val1.Compare(val2) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, key := range obj2.Keys() {
|
||||
val1, _ = obj1.Get(values.NewString(key))
|
||||
val2, _ = obj2.Get(values.NewString(key))
|
||||
val1, _ = obj1.Get(key)
|
||||
val2, _ = obj2.Get(key)
|
||||
if val2.Compare(val1) != 0 {
|
||||
return false
|
||||
}
|
||||
|
@ -36,7 +36,15 @@ func Keys(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
needSort = bool(args[1].(values.Boolean))
|
||||
}
|
||||
|
||||
keys := sort.StringSlice(obj.Keys())
|
||||
oKeys := make([]string, 0, obj.Length())
|
||||
|
||||
obj.ForEach(func(value core.Value, key string) bool {
|
||||
oKeys = append(oKeys, key)
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
keys := sort.StringSlice(oKeys)
|
||||
keysArray := values.NewArray(len(keys))
|
||||
|
||||
if needSort {
|
||||
|
@ -22,7 +22,6 @@ func NewLib() map[string]core.Function {
|
||||
"REGEXP_SPLIT": RegexSplit,
|
||||
"REGEXP_TEST": RegexTest,
|
||||
"REGEXP_REPLACE": RegexReplace,
|
||||
"REVERSE": Reverse,
|
||||
"RIGHT": Right,
|
||||
"RTRIM": RTrim,
|
||||
"SHA1": Sha1,
|
||||
|
@ -1,30 +0,0 @@
|
||||
package strings
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
// Reverse returns the reverse of the string value.
|
||||
// @param text (String) - The string to revers
|
||||
// @returns (String) - Returns a reversed version of the string.
|
||||
func Reverse(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
err := core.ValidateArgs(args, 1, 1)
|
||||
|
||||
if err != nil {
|
||||
return values.EmptyString, err
|
||||
}
|
||||
|
||||
text := args[0].String()
|
||||
runes := []rune(text)
|
||||
size := len(runes)
|
||||
|
||||
// Reverse
|
||||
for i := 0; i < size/2; i++ {
|
||||
runes[i], runes[size-1-i] = runes[size-1-i], runes[i]
|
||||
}
|
||||
|
||||
return values.NewString(string(runes)), nil
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package strings_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/MontFerret/ferret/pkg/stdlib/strings"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
Convey("When args are not passed", t, func() {
|
||||
Convey("It should return an error", func() {
|
||||
var err error
|
||||
_, err = strings.Reverse(context.Background())
|
||||
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should reverse a text with right encoding", t, func() {
|
||||
out, _ := strings.Reverse(
|
||||
context.Background(),
|
||||
values.NewString("The quick brown 狐 jumped over the lazy 犬"),
|
||||
)
|
||||
|
||||
So(out, ShouldEqual, "犬 yzal eht revo depmuj 狐 nworb kciuq ehT")
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user