mirror of
				https://github.com/MontFerret/ferret.git
				synced 2025-10-30 23:37:40 +02:00 
			
		
		
		
	Feature/#33 wait class function (#63)
* #33 Lib cleanup. Added WAIT_CLASS and WAIT_CLASS_ALL functions * #33 Fixed attr update * #33 HTMLElement.WaitForClass * #33 Updated HTMLDocument.WaitForClass
This commit is contained in:
		
							
								
								
									
										8
									
								
								examples/wait_class.fql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								examples/wait_class.fql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) | ||||
|  | ||||
| LET el = ELEMENT(doc, "#collapseTwo") | ||||
|  | ||||
| CLICK(doc, "#headingTwo > h5 > button") | ||||
| WAIT_CLASS(el, "show") | ||||
|  | ||||
| RETURN TRUE | ||||
							
								
								
									
										6
									
								
								examples/wait_class_doc.fql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/wait_class_doc.fql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) | ||||
|  | ||||
| CLICK(doc, "#headingTwo > h5 > button") | ||||
| WAIT_CLASS(doc, "#collapseTwo", "show") | ||||
|  | ||||
| RETURN TRUE | ||||
| @@ -1774,29 +1774,21 @@ func TestParam(t *testing.T) { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| //func TestHtml(t *testing.T) { | ||||
| //	Convey("Should load a document", t, func() { | ||||
| //		c := compiler.New() | ||||
| // | ||||
| //		out, err := c.MustCompile(` | ||||
| //LET doc = DOCUMENT("https://github.com/", true) | ||||
| //LET main = ELEMENT(doc, '.application-main') | ||||
| //LET mainTxt = main.innerText | ||||
| // | ||||
| //NAVIGATE(doc, "https://github.com/features") | ||||
| // | ||||
| //LET features = ELEMENT(doc, '.application-main') | ||||
| //LET featuresTxt = features.innerText | ||||
| // | ||||
| //LOG("featuresTxt:", featuresTxt) | ||||
| // | ||||
| //RETURN mainTxt == featuresTxt | ||||
| // | ||||
| // | ||||
| //			`).Run(context.Background()) | ||||
| // | ||||
| //		So(err, ShouldBeNil) | ||||
| // | ||||
| //		So(string(out), ShouldEqual, `"int"`) | ||||
| //	}) | ||||
| //} | ||||
| func TestHtml(t *testing.T) { | ||||
| 	Convey("Should load a document", t, func() { | ||||
| 		c := compiler.New() | ||||
|  | ||||
| 		out, err := c.MustCompile(` | ||||
| LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) | ||||
|  | ||||
| CLICK(doc, "#headingTwo > h5 > button") | ||||
| WAIT_CLASS(doc, "#collapseTwo", "bar") | ||||
|  | ||||
| RETURN TRUE | ||||
| 			`).Run(context.Background()) | ||||
|  | ||||
| 		So(err, ShouldBeNil) | ||||
|  | ||||
| 		So(string(out), ShouldEqual, `"int"`) | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -49,3 +49,13 @@ func TypeError(actual Type, expected ...Type) error { | ||||
| func Error(err error, msg string) error { | ||||
| 	return errors.Errorf("%s: %s", err.Error(), msg) | ||||
| } | ||||
|  | ||||
| func Errors(err ...error) error { | ||||
| 	message := "" | ||||
|  | ||||
| 	for _, e := range err { | ||||
| 		message += ": " + e.Error() | ||||
| 	} | ||||
|  | ||||
| 	return errors.New(message) | ||||
| } | ||||
|   | ||||
| @@ -35,5 +35,13 @@ type ( | ||||
| 		HTMLNode | ||||
|  | ||||
| 		URL() core.Value | ||||
|  | ||||
| 		InnerHTMLBySelector(selector String) String | ||||
|  | ||||
| 		InnerHTMLBySelectorAll(selector String) *Array | ||||
|  | ||||
| 		InnerTextBySelector(selector String) String | ||||
|  | ||||
| 		InnerTextBySelectorAll(selector String) *Array | ||||
| 	} | ||||
| ) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
|  * Appends a new item to an array and returns a new array with a given element. | ||||
|  * If ``uniqueOnly`` is set to true, then will add the item only if it's unique. | ||||
|  * @param arr (Array) - Target array. | ||||
|  * @param item (Value) - Target value to add. | ||||
|  * @param item (Read) - Target value to add. | ||||
|  * @returns arr (Array) - New array. | ||||
|  */ | ||||
| func Append(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
| /* | ||||
|  * Returns a first element from a given array. | ||||
|  * @param arr (Array) - Target array. | ||||
|  * @returns element (Value) - First element in a given array. | ||||
|  * @returns element (Read) - First element in a given array. | ||||
|  */ | ||||
| func First(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 1) | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
| /* | ||||
|  * Returns the last element of an array. | ||||
|  * @param array (Array) - The target array. | ||||
|  * @returns (Value) - Last element of an array. | ||||
|  * @returns (Read) - Last element of an array. | ||||
|  */ | ||||
| func Last(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 1) | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import ( | ||||
|  * It is the same as anyArray[position] for positive positions, but does not support negative positions. | ||||
|  * @param array (Array) - An array with elements of arbitrary type. | ||||
|  * @param index (Int) - Position of desired element in array, positions start at 0. | ||||
|  * @returns (Value) - The array element at the given position. | ||||
|  * @returns (Read) - The array element at the given position. | ||||
|  * If position is negative or beyond the upper bound of the array, then NONE will be returned. | ||||
|  */ | ||||
| func Nth(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import ( | ||||
| /* | ||||
|  * Returns a value indicating whether an element is contained in array. Optionally returns its position. | ||||
|  * @param array (Array) - The source array. | ||||
|  * @param value (Value) - The target value. | ||||
|  * @param returnIndex (Boolean, optional) - Value which indicates whether to return item's position. | ||||
|  * @param value (Read) - The target value. | ||||
|  * @param returnIndex (Boolean, optional) - Read which indicates whether to return item's position. | ||||
|  */ | ||||
| func Position(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 3) | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import ( | ||||
| /* | ||||
|  * Create a new array with appended value. | ||||
|  * @param array (Array) - Source array. | ||||
|  * @param value (Value) - Target value. | ||||
|  * @param unique (Boolean, optional) - Value indicating whether to do uniqueness check. | ||||
|  * @param value (Read) - Target value. | ||||
|  * @param unique (Boolean, optional) - Read indicating whether to do uniqueness check. | ||||
|  * @returns (Array) - A new array with appended value. | ||||
|  */ | ||||
| func Push(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
|  * Returns a new array with removed all occurrences of value in a given array. | ||||
|  * Optionally with a limit to the number of removals. | ||||
|  * @param array (Array) - Source array. | ||||
|  * @param value (Value) - Target value. | ||||
|  * @param value (Read) - Target value. | ||||
|  * @param limit (Int, optional) - A limit to the number of removals. | ||||
|  * @returns (Array) - A new array with removed all occurrences of value in a given array. | ||||
|  */ | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
|  * Returns a new sliced array. | ||||
|  * @param array (Array) - Source array. | ||||
|  * @param start (Int) - Start position of extraction. | ||||
|  * @param length (Int, optional) - Value indicating how many elements to extract. | ||||
|  * @param length (Int, optional) - Read indicating how many elements to extract. | ||||
|  * @returns (Array) - Sliced array. | ||||
|  */ | ||||
| func Slice(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
| /* | ||||
|  * Prepends value to a given array. | ||||
|  * @param array (Array) - Target array. | ||||
|  * @param value (Value) - Target value to prepend. | ||||
|  * @param value (Read) - Target value to prepend. | ||||
|  * @param unique (Boolean, optional) - Optional value indicating whether a value must be unique to be prepended. | ||||
|  * Default is false. | ||||
|  * @returns (Array) - New array with prepended value. | ||||
|   | ||||
| @@ -1,188 +0,0 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Dispatches click event on a given element | ||||
|  * @param source (Document | Element) - Event source. | ||||
|  * @param selector (String, optional) - Optional selector. Only used when a document instance is passed. | ||||
|  */ | ||||
| func Click(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	// CLICK(el) | ||||
| 	if len(args) == 1 { | ||||
| 		arg1 := args[0] | ||||
|  | ||||
| 		err := core.ValidateType(arg1, core.HTMLElementType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.False, err | ||||
| 		} | ||||
|  | ||||
| 		el, ok := arg1.(*dynamic.HTMLElement) | ||||
|  | ||||
| 		if !ok { | ||||
| 			return values.False, core.Error(core.ErrInvalidType, "expected dynamic element") | ||||
| 		} | ||||
|  | ||||
| 		return el.Click() | ||||
| 	} | ||||
|  | ||||
| 	// CLICK(doc, selector) | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.ClickBySelector(values.NewString(selector)) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Dispatches click event on all matched element | ||||
|  * @param source (Document) - Document. | ||||
|  * @param selector (String) - Selector. | ||||
|  * @returns (Boolean) - Returns true if matched at least one element. | ||||
|  */ | ||||
| func ClickAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.ClickBySelectorAll(values.NewString(selector)) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Navigates a document to a new resource. | ||||
|  * The operation blocks the execution until the page gets loaded. | ||||
|  * Which means there is no need in WAIT_NAVIGATION function. | ||||
|  * @param doc (Document) - Target document. | ||||
|  * @param url (String) - Target url to navigate. | ||||
|  */ | ||||
| func Navigate(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := args[0].(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.Navigate(args[1].(values.String)) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Sends a value to an underlying input element. | ||||
|  * @param source (Document | Element) - Event target. | ||||
|  * @param valueOrSelector (String) - Selector or a value. | ||||
|  * @param value (String) - Target value. | ||||
|  * @returns (Boolean) - Returns true if an element was found. | ||||
|  */ | ||||
| func Input(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 3) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// TYPE(el, "foobar") | ||||
| 	if len(args) == 2 { | ||||
| 		arg1 := args[0] | ||||
|  | ||||
| 		err := core.ValidateType(arg1, core.HTMLElementType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.False, err | ||||
| 		} | ||||
|  | ||||
| 		el, ok := arg1.(*dynamic.HTMLElement) | ||||
|  | ||||
| 		if !ok { | ||||
| 			return values.False, core.Error(core.ErrInvalidType, "expected dynamic element") | ||||
| 		} | ||||
|  | ||||
| 		err = el.Input(args[1]) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.False, err | ||||
| 		} | ||||
|  | ||||
| 		return values.True, nil | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	arg2 := args[1] | ||||
|  | ||||
| 	err = core.ValidateType(arg2, core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.InputBySelector(arg2.(values.String), args[2]) | ||||
| } | ||||
							
								
								
									
										58
									
								
								pkg/stdlib/html/click.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								pkg/stdlib/html/click.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Dispatches click event on a given element | ||||
|  * @param source (Document | Element) - Event source. | ||||
|  * @param selector (String, optional) - Optional selector. Only used when a document instance is passed. | ||||
|  */ | ||||
| func Click(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	// CLICK(el) | ||||
| 	if len(args) == 1 { | ||||
| 		arg1 := args[0] | ||||
|  | ||||
| 		err := core.ValidateType(arg1, core.HTMLElementType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.False, err | ||||
| 		} | ||||
|  | ||||
| 		el, ok := arg1.(*dynamic.HTMLElement) | ||||
|  | ||||
| 		if !ok { | ||||
| 			return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 		} | ||||
|  | ||||
| 		return el.Click() | ||||
| 	} | ||||
|  | ||||
| 	// CLICK(doc, selector) | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	return doc.ClickBySelector(values.NewString(selector)) | ||||
| } | ||||
							
								
								
									
										39
									
								
								pkg/stdlib/html/click_all.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								pkg/stdlib/html/click_all.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Dispatches click event on all matched element | ||||
|  * @param source (Document) - Document. | ||||
|  * @param selector (String) - Selector. | ||||
|  * @returns (Boolean) - Returns true if matched at least one element. | ||||
|  */ | ||||
| func ClickAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	return doc.ClickBySelectorAll(values.NewString(selector)) | ||||
| } | ||||
| @@ -1,132 +0,0 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Returns inner html of a matched element | ||||
|  * @param doc (Document) - Document | ||||
|  * @param selector (String) - Selector | ||||
|  * @returns str (String) - String value of inner html. | ||||
|  */ | ||||
| func InnerHTML(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.InnerHTMLBySelector(values.NewString(selector)) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Returns inner html of all matched elements. | ||||
|  * @param doc (Document) - Document | ||||
|  * @param selector (String) - Selector | ||||
|  * @returns array (Array) - Array of string values. | ||||
|  */ | ||||
| func InnerHTMLAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.InnerHTMLBySelectorAll(values.NewString(selector)) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Returns inner text of a matched element | ||||
|  * @param doc (Document) - Document | ||||
|  * @param selector (String) - Selector | ||||
|  * @returns str (String) - String value of inner text. | ||||
|  */ | ||||
| func InnerText(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.InnerHTMLBySelector(values.NewString(selector)) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Returns inner text of all matched elements. | ||||
|  * @param doc (Document) - Document | ||||
|  * @param selector (String) - Selector | ||||
|  * @returns array (Array) - Array of string values. | ||||
|  */ | ||||
| func InnerTextAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
| 	selector := args[1].String() | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return doc.InnerHTMLBySelectorAll(values.NewString(selector)) | ||||
| } | ||||
| @@ -5,16 +5,38 @@ import ( | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" | ||||
| ) | ||||
|  | ||||
| func Document(ctx context.Context, inputs ...core.Value) (core.Value, error) { | ||||
| 	url, dynamic, err := documentArgs(inputs) | ||||
| /* | ||||
|  * Loads a document by a given url. | ||||
|  * By default, loads a document by http call - resulted document does not support any interactions. | ||||
|  * If passed "true" as a second argument, headless browser is used for loading the document which support interactions. | ||||
|  * @param url (String) - Target url string. If passed "about:blank" for dynamic document - it will open an empty page. | ||||
|  * @param dynamic (Boolean) - Optional boolean value indicating whether to use dynamic document. | ||||
|  * @returns (HTMLDocument) - Returns loaded HTML document. | ||||
|  */ | ||||
| func Document(ctx context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.StringType) | ||||
|  | ||||
| 	url := args[0].(values.String) | ||||
| 	dynamic := values.False | ||||
|  | ||||
| 	if len(args) == 2 { | ||||
| 		err = core.ValidateType(args[1], core.BooleanType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.None, err | ||||
| 		} | ||||
|  | ||||
| 		dynamic = args[1].(values.Boolean) | ||||
| 	} | ||||
|  | ||||
| 	var drv driver.Driver | ||||
|  | ||||
| 	if !dynamic { | ||||
| @@ -27,56 +49,5 @@ func Document(ctx context.Context, inputs ...core.Value) (core.Value, error) { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	return drv.GetDocument(ctx, url.String()) | ||||
| } | ||||
|  | ||||
| func DocumentParse(ctx context.Context, inputs ...core.Value) (core.Value, error) { | ||||
| 	arg1 := values.EmptyString | ||||
|  | ||||
| 	if len(inputs) == 0 { | ||||
| 		return arg1, core.Error(core.ErrMissedArgument, "document string") | ||||
| 	} | ||||
|  | ||||
| 	a1 := inputs[0] | ||||
|  | ||||
| 	if a1.Type() != core.StringType { | ||||
| 		return arg1, core.Error(core.TypeError(a1.Type(), core.StringType), "arg 1") | ||||
| 	} | ||||
|  | ||||
| 	drv, err := driver.FromContext(ctx, driver.Static) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	return drv.(*static.Driver).ParseDocument(ctx, arg1.String()) | ||||
| } | ||||
|  | ||||
| func documentArgs(inputs []core.Value) (values.String, values.Boolean, error) { | ||||
| 	arg1 := values.EmptyString | ||||
| 	arg2 := values.False | ||||
|  | ||||
| 	if len(inputs) == 0 { | ||||
| 		return arg1, arg2, core.Error(core.ErrMissedArgument, "element and useJs") | ||||
| 	} | ||||
|  | ||||
| 	a1 := inputs[0] | ||||
|  | ||||
| 	if a1.Type() != core.StringType { | ||||
| 		return arg1, arg2, core.Error(core.TypeError(a1.Type(), core.StringType), "arg 1") | ||||
| 	} | ||||
|  | ||||
| 	arg1 = a1.(values.String) | ||||
|  | ||||
| 	if len(inputs) == 2 { | ||||
| 		a2 := inputs[1] | ||||
|  | ||||
| 		if a2.Type() != core.BooleanType { | ||||
| 			return arg1, arg2, core.Error(core.TypeError(a2.Type(), core.BooleanType), "arg 2") | ||||
| 		} | ||||
|  | ||||
| 		arg2 = a2.(values.Boolean) | ||||
| 	} | ||||
|  | ||||
| 	return arg1, arg2, nil | ||||
| 	return drv.GetDocument(ctx, url) | ||||
| } | ||||
|   | ||||
							
								
								
									
										39
									
								
								pkg/stdlib/html/document_parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								pkg/stdlib/html/document_parse.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Parses a given HTML string and returns a HTML document. | ||||
|  * Returned HTML document is always static. | ||||
|  * @param html (String) - Target HTML string. | ||||
|  * @returns (HTMLDocument) - Parsed HTML static document. | ||||
|  */ | ||||
| func DocumentParse(ctx 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], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	drv, err := driver.FromContext(ctx, driver.Static) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	str := args[0].(values.String) | ||||
|  | ||||
| 	return drv.(*static.Driver).ParseDocument(ctx, str) | ||||
| } | ||||
| @@ -1,5 +1,7 @@ | ||||
| package common | ||||
|  | ||||
| import "strings" | ||||
|  | ||||
| var Attributes = []string{ | ||||
| 	"abbr", | ||||
| 	"accept", | ||||
| @@ -147,7 +149,12 @@ func init() { | ||||
| } | ||||
|  | ||||
| func IsAttribute(name string) bool { | ||||
| 	_, yes := attrMap[name] | ||||
| 	_, isDefault := attrMap[name] | ||||
|  | ||||
| 	return yes | ||||
| 	if isDefault { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return strings.HasPrefix(name, "data-") || | ||||
| 		strings.HasPrefix(name, "aria-") | ||||
| } | ||||
|   | ||||
| @@ -27,6 +27,10 @@ func NewLazyValue(factory LazyFactory) *LazyValue { | ||||
| 	return lz | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Indicates whether the value is ready. | ||||
|  * @returns (Boolean) - Boolean value indicating whether the value is ready. | ||||
|  */ | ||||
| func (lv *LazyValue) Ready() bool { | ||||
| 	lv.Lock() | ||||
| 	defer lv.Unlock() | ||||
| @@ -34,11 +38,52 @@ func (lv *LazyValue) Ready() bool { | ||||
| 	return lv.ready | ||||
| } | ||||
|  | ||||
| func (lv *LazyValue) Value() (core.Value, error) { | ||||
| /* | ||||
|  * Returns an underlying value. | ||||
|  * Not thread safe. Should not mutated. | ||||
|  * @returns (Value) - Underlying value if successfully loaded, otherwise error | ||||
|  */ | ||||
| func (lv *LazyValue) Read() (core.Value, error) { | ||||
| 	lv.Lock() | ||||
| 	defer lv.Unlock() | ||||
|  | ||||
| 	if !lv.ready { | ||||
| 		lv.load() | ||||
| 	} | ||||
|  | ||||
| 	return lv.value, lv.err | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Safely mutates an underlying value. | ||||
|  * Loads a value if it's not ready. | ||||
|  * Thread safe. | ||||
|  */ | ||||
| func (lv *LazyValue) Write(writer func(v core.Value, err error)) { | ||||
| 	lv.Lock() | ||||
| 	defer lv.Unlock() | ||||
|  | ||||
| 	if !lv.ready { | ||||
| 		lv.load() | ||||
| 	} | ||||
|  | ||||
| 	writer(lv.value, lv.err) | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Resets the storage. | ||||
|  * Next call of Read will trigger the factory function again. | ||||
|  */ | ||||
| func (lv *LazyValue) Reset() { | ||||
| 	lv.Lock() | ||||
| 	defer lv.Unlock() | ||||
|  | ||||
| 	lv.ready = false | ||||
| 	lv.value = values.None | ||||
| 	lv.err = nil | ||||
| } | ||||
|  | ||||
| func (lv *LazyValue) load() { | ||||
| 	val, err := lv.factory() | ||||
|  | ||||
| 	if err == nil { | ||||
| @@ -48,16 +93,6 @@ func (lv *LazyValue) Value() (core.Value, error) { | ||||
| 		lv.value = values.None | ||||
| 		lv.err = err | ||||
| 	} | ||||
| 	} | ||||
|  | ||||
| 	return lv.value, lv.err | ||||
| } | ||||
|  | ||||
| func (lv *LazyValue) Reset() { | ||||
| 	lv.Lock() | ||||
| 	defer lv.Unlock() | ||||
|  | ||||
| 	lv.ready = false | ||||
| 	lv.value = values.None | ||||
| 	lv.err = nil | ||||
| 	lv.ready = true | ||||
| } | ||||
|   | ||||
| @@ -9,23 +9,23 @@ import ( | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" | ||||
| ) | ||||
|  | ||||
| type DriverName string | ||||
| type Name string | ||||
|  | ||||
| const ( | ||||
| 	Dynamic DriverName = "dynamic" | ||||
| 	Static  DriverName = "static" | ||||
| 	Dynamic Name = "dynamic" | ||||
| 	Static  Name = "static" | ||||
| ) | ||||
|  | ||||
| type Driver interface { | ||||
| 	GetDocument(ctx context.Context, url string) (values.HTMLNode, error) | ||||
| 	GetDocument(ctx context.Context, url values.String) (values.HTMLNode, error) | ||||
| 	Close() error | ||||
| } | ||||
|  | ||||
| func ToContext(ctx context.Context, name DriverName, drv Driver) context.Context { | ||||
| func ToContext(ctx context.Context, name Name, drv Driver) context.Context { | ||||
| 	return context.WithValue(ctx, name, drv) | ||||
| } | ||||
|  | ||||
| func FromContext(ctx context.Context, name DriverName) (Driver, error) { | ||||
| func FromContext(ctx context.Context, name Name) (Driver, error) { | ||||
| 	val := ctx.Value(name) | ||||
|  | ||||
| 	drv, ok := val.(Driver) | ||||
|   | ||||
| @@ -21,7 +21,6 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
|  | ||||
| const BlankPageURL = "about:blank" | ||||
|  | ||||
| type HTMLDocument struct { | ||||
| @@ -378,7 +377,7 @@ func (doc *HTMLDocument) URL() core.Value { | ||||
| 	return doc.url | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) (values.String, error) { | ||||
| func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) values.String { | ||||
| 	res, err := eval.Eval( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| @@ -395,17 +394,23 @@ func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) (values.Str | ||||
| 	) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 		doc.logger.Error(). | ||||
| 			Timestamp(). | ||||
| 			Err(err). | ||||
| 			Str("selector", selector.String()). | ||||
| 			Msg("failed to get inner HTML by selector") | ||||
|  | ||||
| 		return values.EmptyString | ||||
| 	} | ||||
|  | ||||
| 	if res.Type() == core.StringType { | ||||
| 		return res.(values.String), nil | ||||
| 		return res.(values.String) | ||||
| 	} | ||||
|  | ||||
| 	return values.EmptyString, nil | ||||
| 	return values.EmptyString | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) (*values.Array, error) { | ||||
| func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) *values.Array { | ||||
| 	res, err := eval.Eval( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| @@ -427,17 +432,23 @@ func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) (*values | ||||
| 	) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.NewArray(0), err | ||||
| 		doc.logger.Error(). | ||||
| 			Timestamp(). | ||||
| 			Err(err). | ||||
| 			Str("selector", selector.String()). | ||||
| 			Msg("failed to get an array of inner HTML by selector") | ||||
|  | ||||
| 		return values.NewArray(0) | ||||
| 	} | ||||
|  | ||||
| 	if res.Type() == core.ArrayType { | ||||
| 		return res.(*values.Array), nil | ||||
| 		return res.(*values.Array) | ||||
| 	} | ||||
|  | ||||
| 	return values.NewArray(0), nil | ||||
| 	return values.NewArray(0) | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerTextBySelector(selector values.String) (values.String, error) { | ||||
| func (doc *HTMLDocument) InnerTextBySelector(selector values.String) values.String { | ||||
| 	res, err := eval.Eval( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| @@ -454,17 +465,23 @@ func (doc *HTMLDocument) InnerTextBySelector(selector values.String) (values.Str | ||||
| 	) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 		doc.logger.Error(). | ||||
| 			Timestamp(). | ||||
| 			Err(err). | ||||
| 			Str("selector", selector.String()). | ||||
| 			Msg("failed to get inner text by selector") | ||||
|  | ||||
| 		return values.EmptyString | ||||
| 	} | ||||
|  | ||||
| 	if res.Type() == core.StringType { | ||||
| 		return res.(values.String), nil | ||||
| 		return res.(values.String) | ||||
| 	} | ||||
|  | ||||
| 	return values.EmptyString, nil | ||||
| 	return values.EmptyString | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) (*values.Array, error) { | ||||
| func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) *values.Array { | ||||
| 	res, err := eval.Eval( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| @@ -486,14 +503,20 @@ func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) (*values | ||||
| 	) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.NewArray(0), err | ||||
| 		doc.logger.Error(). | ||||
| 			Timestamp(). | ||||
| 			Err(err). | ||||
| 			Str("selector", selector.String()). | ||||
| 			Msg("failed to get an array inner text by selector") | ||||
|  | ||||
| 		return values.NewArray(0) | ||||
| 	} | ||||
|  | ||||
| 	if res.Type() == core.ArrayType { | ||||
| 		return res.(*values.Array), nil | ||||
| 		return res.(*values.Array) | ||||
| 	} | ||||
|  | ||||
| 	return values.NewArray(0), nil | ||||
| 	return values.NewArray(0) | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) ClickBySelector(selector values.String) (values.Boolean, error) { | ||||
| @@ -595,15 +618,16 @@ func (doc *HTMLDocument) InputBySelector(selector values.String, value core.Valu | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error { | ||||
| 	task := events.NewWaitTask( | ||||
| 	task := events.NewEvalWaitTask( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| 			el = document.querySelector(%s); | ||||
| 			var el = document.querySelector(%s); | ||||
|  | ||||
| 			if (el != null) { | ||||
| 				return true; | ||||
| 			} | ||||
|  | ||||
| 			// null means we need to repeat | ||||
| 			return null; | ||||
| 		`, eval.ParamString(selector.String())), | ||||
| 		time.Millisecond*time.Duration(timeout), | ||||
| @@ -615,6 +639,78 @@ func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values. | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) WaitForClass(selector, class values.String, timeout values.Int) error { | ||||
| 	task := events.NewEvalWaitTask( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| 			var el = document.querySelector(%s); | ||||
|  | ||||
| 			if (el == null) { | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			var className = %s; | ||||
| 			var found = el.className.split(' ').find(i => i === className); | ||||
|  | ||||
| 			if (found != null) { | ||||
| 				return true; | ||||
| 			} | ||||
| 			 | ||||
| 			// null means we need to repeat | ||||
| 			return null; | ||||
| 		`, | ||||
| 			eval.ParamString(selector.String()), | ||||
| 			eval.ParamString(class.String()), | ||||
| 		), | ||||
| 		time.Millisecond*time.Duration(timeout), | ||||
| 		events.DefaultPolling, | ||||
| 	) | ||||
|  | ||||
| 	_, err := task.Run() | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) WaitForClassAll(selector, class values.String, timeout values.Int) error { | ||||
| 	task := events.NewEvalWaitTask( | ||||
| 		doc.client, | ||||
| 		fmt.Sprintf(` | ||||
| 			var elements = document.querySelectorAll(%s); | ||||
|  | ||||
| 			if (elements == null || elements.length === 0) { | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			var className = %s; | ||||
| 			var foundCount = 0; | ||||
|  | ||||
| 			elements.forEach((el) => { | ||||
| 				var found = el.className.split(' ').find(i => i === className); | ||||
|  | ||||
| 				if (found != null) { | ||||
| 					foundCount++; | ||||
| 				} | ||||
| 			}); | ||||
|  | ||||
| 			if (foundCount === elements.length) { | ||||
| 				return true; | ||||
| 			} | ||||
| 			 | ||||
| 			// null means we need to repeat | ||||
| 			return null; | ||||
| 		`, | ||||
| 			eval.ParamString(selector.String()), | ||||
| 			eval.ParamString(class.String()), | ||||
| 		), | ||||
| 		time.Millisecond*time.Duration(timeout), | ||||
| 		events.DefaultPolling, | ||||
| 	) | ||||
|  | ||||
| 	_, err := task.Run() | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) WaitForNavigation(timeout values.Int) error { | ||||
| 	timer := time.NewTimer(time.Millisecond * time.Duration(timeout)) | ||||
| 	onEvent := make(chan bool) | ||||
|   | ||||
| @@ -28,7 +28,7 @@ func NewDriver(address string) *Driver { | ||||
| 	return drv | ||||
| } | ||||
|  | ||||
| func (drv *Driver) GetDocument(ctx context.Context, url string) (values.HTMLNode, error) { | ||||
| func (drv *Driver) GetDocument(ctx context.Context, targetURL values.String) (values.HTMLNode, error) { | ||||
| 	err := drv.init(ctx) | ||||
|  | ||||
| 	if err != nil { | ||||
| @@ -38,6 +38,8 @@ func (drv *Driver) GetDocument(ctx context.Context, url string) (values.HTMLNode | ||||
| 	ctx, cancel := context.WithTimeout(ctx, DefaultTimeout) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	url := targetURL.String() | ||||
|  | ||||
| 	if url == "" { | ||||
| 		url = BlankPageURL | ||||
| 	} | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import ( | ||||
| 	"github.com/rs/zerolog" | ||||
| 	"hash/fnv" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -145,7 +146,7 @@ func (el *HTMLElement) Type() core.Type { | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) MarshalJSON() ([]byte, error) { | ||||
| 	val, err := el.innerText.Value() | ||||
| 	val, err := el.innerText.Read() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -188,7 +189,6 @@ func (el *HTMLElement) Unwrap() interface{} { | ||||
| 	return el | ||||
| } | ||||
|  | ||||
|  | ||||
| func (el *HTMLElement) Hash() uint64 { | ||||
| 	el.Lock() | ||||
| 	defer el.Unlock() | ||||
| @@ -243,7 +243,7 @@ func (el *HTMLElement) NodeName() values.String { | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) GetAttributes() core.Value { | ||||
| 	val, err := el.attributes.Value() | ||||
| 	val, err := el.attributes.Read() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None | ||||
| @@ -254,7 +254,7 @@ func (el *HTMLElement) GetAttributes() core.Value { | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) GetAttribute(name values.String) core.Value { | ||||
| 	attrs, err := el.attributes.Value() | ||||
| 	attrs, err := el.attributes.Read() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None | ||||
| @@ -270,7 +270,7 @@ func (el *HTMLElement) GetAttribute(name values.String) core.Value { | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) GetChildNodes() core.Value { | ||||
| 	val, err := el.loadedChildren.Value() | ||||
| 	val, err := el.loadedChildren.Read() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.NewArray(0) | ||||
| @@ -280,7 +280,7 @@ func (el *HTMLElement) GetChildNodes() core.Value { | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) GetChildNode(idx values.Int) core.Value { | ||||
| 	val, err := el.loadedChildren.Value() | ||||
| 	val, err := el.loadedChildren.Read() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None | ||||
| @@ -365,8 +365,38 @@ func (el *HTMLElement) QuerySelectorAll(selector values.String) core.Value { | ||||
| 	return arr | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) WaitForClass(class values.String, timeout values.Int) error { | ||||
| 	task := events.NewWaitTask( | ||||
| 		func() (core.Value, error) { | ||||
| 			current := el.GetAttribute("class") | ||||
|  | ||||
| 			if current.Type() != core.StringType { | ||||
| 				return values.None, nil | ||||
| 			} | ||||
|  | ||||
| 			str := current.(values.String) | ||||
| 			classStr := string(class) | ||||
| 			classes := strings.Split(string(str), " ") | ||||
|  | ||||
| 			for _, c := range classes { | ||||
| 				if c == classStr { | ||||
| 					return values.True, nil | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return values.None, nil | ||||
| 		}, | ||||
| 		time.Millisecond*time.Duration(timeout), | ||||
| 		events.DefaultPolling, | ||||
| 	) | ||||
|  | ||||
| 	_, err := task.Run() | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) InnerText() values.String { | ||||
| 	val, err := el.innerText.Value() | ||||
| 	val, err := el.innerText.Read() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString | ||||
| @@ -469,29 +499,25 @@ func (el *HTMLElement) handleAttrModified(message interface{}) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// they are not event loaded | ||||
| 	// just ignore the event | ||||
| 	if !el.attributes.Ready() { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	val, err := el.attributes.Value() | ||||
|  | ||||
| 	// failed to load | ||||
| 	el.attributes.Write(func(v core.Value, err error) { | ||||
| 		if err != nil { | ||||
| 			el.logger.Error(). | ||||
| 				Timestamp(). | ||||
| 				Err(err). | ||||
| 				Int("id", int(el.id)). | ||||
| 				Msg("failed to update node") | ||||
|  | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	el.Lock() | ||||
| 	defer el.Unlock() | ||||
|  | ||||
| 	attrs, ok := val.(*values.Object) | ||||
| 		attrs, ok := v.(*values.Object) | ||||
|  | ||||
| 		if !ok { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		attrs.Set(values.NewString(reply.Name), values.NewString(reply.Value)) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) handleAttrRemoved(message interface{}) { | ||||
| @@ -513,24 +539,25 @@ func (el *HTMLElement) handleAttrRemoved(message interface{}) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	val, err := el.attributes.Value() | ||||
|  | ||||
| 	// failed to load | ||||
| 	el.attributes.Write(func(v core.Value, err error) { | ||||
| 		if err != nil { | ||||
| 			el.logger.Error(). | ||||
| 				Timestamp(). | ||||
| 				Err(err). | ||||
| 				Int("id", int(el.id)). | ||||
| 				Msg("failed to update node") | ||||
|  | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	el.Lock() | ||||
| 	defer el.Unlock() | ||||
|  | ||||
| 	attrs, ok := val.(*values.Object) | ||||
| 		attrs, ok := v.(*values.Object) | ||||
|  | ||||
| 		if !ok { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	// TODO: actually, we need to sync it too... | ||||
| 		attrs.Remove(values.NewString(reply.Name)) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) handleChildrenCountChanged(message interface{}) { | ||||
| @@ -598,13 +625,8 @@ func (el *HTMLElement) handleChildInserted(message interface{}) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	loaded, err := el.loadedChildren.Value() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	loadedArr := loaded.(*values.Array) | ||||
| 	el.loadedChildren.Write(func(v core.Value, err error) { | ||||
| 		loadedArr := v.(*values.Array) | ||||
| 		loadedEl, err := LoadElement(el.logger, el.client, el.broker, nextID) | ||||
|  | ||||
| 		if err != nil { | ||||
| @@ -632,6 +654,7 @@ func (el *HTMLElement) handleChildInserted(message interface{}) { | ||||
| 		} | ||||
|  | ||||
| 		el.innerHTML = newInnerHTML | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func (el *HTMLElement) handleChildDeleted(message interface{}) { | ||||
| @@ -669,13 +692,18 @@ func (el *HTMLElement) handleChildDeleted(message interface{}) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	loaded, err := el.loadedChildren.Value() | ||||
|  | ||||
| 	el.loadedChildren.Write(func(v core.Value, err error) { | ||||
| 		if err != nil { | ||||
| 			el.logger.Error(). | ||||
| 				Timestamp(). | ||||
| 				Err(err). | ||||
| 				Int("id", int(el.id)). | ||||
| 				Msg("failed to update node") | ||||
|  | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	loadedArr := loaded.(*values.Array) | ||||
| 		loadedArr := v.(*values.Array) | ||||
| 		loadedArr.RemoveAt(values.NewInt(targetIDx)) | ||||
|  | ||||
| 		newInnerHTML, err := loadInnerHTML(el.client, el.id) | ||||
| @@ -691,4 +719,5 @@ func (el *HTMLElement) handleChildDeleted(message interface{}) { | ||||
| 		} | ||||
|  | ||||
| 		el.innerHTML = newInnerHTML | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -8,24 +8,24 @@ import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type WaitTask struct { | ||||
| 	client    *cdp.Client | ||||
| 	predicate string | ||||
| type ( | ||||
| 	Function func() (core.Value, error) | ||||
| 	WaitTask struct { | ||||
| 		fun     Function | ||||
| 		timeout time.Duration | ||||
| 		polling time.Duration | ||||
| } | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| const DefaultPolling = time.Millisecond * time.Duration(200) | ||||
|  | ||||
| func NewWaitTask( | ||||
| 	client *cdp.Client, | ||||
| 	predicate string, | ||||
| 	fun Function, | ||||
| 	timeout time.Duration, | ||||
| 	polling time.Duration, | ||||
| ) *WaitTask { | ||||
| 	return &WaitTask{ | ||||
| 		client, | ||||
| 		predicate, | ||||
| 		fun, | ||||
| 		timeout, | ||||
| 		polling, | ||||
| 	} | ||||
| @@ -39,14 +39,9 @@ func (task *WaitTask) Run() (core.Value, error) { | ||||
| 		case <-timer.C: | ||||
| 			return values.None, core.ErrTimeout | ||||
| 		default: | ||||
| 			out, err := eval.Eval( | ||||
| 				task.client, | ||||
| 				task.predicate, | ||||
| 				true, | ||||
| 				false, | ||||
| 			) | ||||
| 			out, err := task.fun() | ||||
|  | ||||
| 			// JS expression failed | ||||
| 			// expression failed | ||||
| 			// terminating | ||||
| 			if err != nil { | ||||
| 				timer.Stop() | ||||
| @@ -54,7 +49,7 @@ func (task *WaitTask) Run() (core.Value, error) { | ||||
| 				return values.None, err | ||||
| 			} | ||||
|  | ||||
| 			// JS output is not empty | ||||
| 			// output is not empty | ||||
| 			// terminating | ||||
| 			if out != values.None { | ||||
| 				timer.Stop() | ||||
| @@ -67,3 +62,23 @@ func (task *WaitTask) Run() (core.Value, error) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func NewEvalWaitTask( | ||||
| 	client *cdp.Client, | ||||
| 	predicate string, | ||||
| 	timeout time.Duration, | ||||
| 	polling time.Duration, | ||||
| ) *WaitTask { | ||||
| 	return NewWaitTask( | ||||
| 		func() (core.Value, error) { | ||||
| 			return eval.Eval( | ||||
| 				client, | ||||
| 				predicate, | ||||
| 				true, | ||||
| 				false, | ||||
| 			) | ||||
| 		}, | ||||
| 		timeout, | ||||
| 		polling, | ||||
| 	) | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import ( | ||||
| 	"github.com/mafredri/cdp/protocol/page" | ||||
| 	"github.com/rs/zerolog" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func pointerInt(input int) *int { | ||||
| @@ -34,6 +35,7 @@ func parseAttrs(attrs []string) *values.Object { | ||||
| 	res := values.NewObject() | ||||
|  | ||||
| 	for _, el := range attrs { | ||||
| 		el = strings.TrimSpace(el) | ||||
| 		str := values.NewString(el) | ||||
|  | ||||
| 		if common.IsAttribute(el) { | ||||
| @@ -43,7 +45,11 @@ func parseAttrs(attrs []string) *values.Object { | ||||
| 			current, ok := res.Get(attr) | ||||
|  | ||||
| 			if ok { | ||||
| 				if current.String() != "" { | ||||
| 					res.Set(attr, current.(values.String).Concat(values.SpaceString).Concat(str)) | ||||
| 				} else { | ||||
| 					res.Set(attr, str) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -32,16 +32,16 @@ func NewHTMLDocument( | ||||
| 	return &HTMLDocument{el, values.NewString(url)}, nil | ||||
| } | ||||
|  | ||||
| func (el *HTMLDocument) Type() core.Type { | ||||
| func (doc *HTMLDocument) Type() core.Type { | ||||
| 	return core.HTMLDocumentType | ||||
| } | ||||
|  | ||||
| func (el *HTMLDocument) Compare(other core.Value) int { | ||||
| func (doc *HTMLDocument) Compare(other core.Value) int { | ||||
| 	switch other.Type() { | ||||
| 	case core.HTMLDocumentType: | ||||
| 		otherDoc := other.(values.HTMLDocument) | ||||
|  | ||||
| 		return el.url.Compare(otherDoc.URL()) | ||||
| 		return doc.url.Compare(otherDoc.URL()) | ||||
| 	default: | ||||
| 		if other.Type() > core.HTMLDocumentType { | ||||
| 			return -1 | ||||
| @@ -51,6 +51,52 @@ func (el *HTMLDocument) Compare(other core.Value) int { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (el *HTMLDocument) URL() core.Value { | ||||
| 	return el.url | ||||
| func (doc *HTMLDocument) URL() core.Value { | ||||
| 	return doc.url | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) values.String { | ||||
| 	selection := doc.selection.Find(selector.String()) | ||||
|  | ||||
| 	str, err := selection.Html() | ||||
|  | ||||
| 	// TODO: log error | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString | ||||
| 	} | ||||
|  | ||||
| 	return values.NewString(str) | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) *values.Array { | ||||
| 	selection := doc.selection.Find(selector.String()) | ||||
| 	arr := values.NewArray(selection.Length()) | ||||
|  | ||||
| 	selection.Each(func(_ int, selection *goquery.Selection) { | ||||
| 		str, err := selection.Html() | ||||
|  | ||||
| 		// TODO: log error | ||||
| 		if err == nil { | ||||
| 			arr.Push(values.NewString(str)) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	return arr | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerTextBySelector(selector values.String) values.String { | ||||
| 	selection := doc.selection.Find(selector.String()) | ||||
|  | ||||
| 	return values.NewString(selection.Text()) | ||||
| } | ||||
|  | ||||
| func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) *values.Array { | ||||
| 	selection := doc.selection.Find(selector.String()) | ||||
| 	arr := values.NewArray(selection.Length()) | ||||
|  | ||||
| 	selection.Each(func(_ int, selection *goquery.Selection) { | ||||
| 		arr.Push(values.NewString(selection.Text())) | ||||
| 	}) | ||||
|  | ||||
| 	return arr | ||||
| } | ||||
|   | ||||
| @@ -53,7 +53,6 @@ func (el *HTMLElement) Unwrap() interface{} { | ||||
| 	return el.selection | ||||
| } | ||||
|  | ||||
|  | ||||
| func (el *HTMLElement) Hash() uint64 { | ||||
| 	str, err := el.selection.Html() | ||||
|  | ||||
|   | ||||
| @@ -297,7 +297,7 @@ func TestElement(t *testing.T) { | ||||
| 		So(el.Length(), ShouldEqual, 4) | ||||
| 	}) | ||||
|  | ||||
| 	Convey(".Value", t, func() { | ||||
| 	Convey(".Read", t, func() { | ||||
| 		buff := bytes.NewBuffer([]byte(` | ||||
| 			<html> | ||||
| 				<head></head> | ||||
|   | ||||
| @@ -28,7 +28,8 @@ func NewDriver(setters ...Option) *Driver { | ||||
| 	return &Driver{client} | ||||
| } | ||||
|  | ||||
| func (d *Driver) GetDocument(_ context.Context, url string) (values.HTMLNode, error) { | ||||
| func (d *Driver) GetDocument(_ context.Context, targetURL values.String) (values.HTMLNode, error) { | ||||
| 	url := targetURL.String() | ||||
| 	req, err := httpx.NewRequest(httpx.MethodGet, url, nil) | ||||
|  | ||||
| 	if err != nil { | ||||
| @@ -58,7 +59,7 @@ func (d *Driver) GetDocument(_ context.Context, url string) (values.HTMLNode, er | ||||
| 	return NewHTMLDocument(url, doc) | ||||
| } | ||||
|  | ||||
| func (d *Driver) ParseDocument(_ context.Context, str string) (values.HTMLNode, error) { | ||||
| func (d *Driver) ParseDocument(_ context.Context, str values.String) (values.HTMLNode, error) { | ||||
| 	buf := bytes.NewBuffer([]byte(str)) | ||||
|  | ||||
| 	doc, err := goquery.NewDocumentFromReader(buf) | ||||
|   | ||||
| @@ -6,8 +6,15 @@ import ( | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| ) | ||||
|  | ||||
| func Element(_ context.Context, inputs ...core.Value) (core.Value, error) { | ||||
| 	el, selector, err := elementArgs(inputs) | ||||
| /* | ||||
|  * 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. | ||||
|  * @returns (HTMLElement | None) - Returns an HTMLElement if found, otherwise NONE. | ||||
|  */ | ||||
| func Element(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	el, selector, err := queryArgs(args) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| @@ -16,36 +23,24 @@ func Element(_ context.Context, inputs ...core.Value) (core.Value, error) { | ||||
| 	return el.QuerySelector(selector), nil | ||||
| } | ||||
|  | ||||
| func Elements(_ context.Context, inputs ...core.Value) (core.Value, error) { | ||||
| 	el, selector, err := elementArgs(inputs) | ||||
| func queryArgs(args []core.Value) (values.HTMLNode, values.String, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 		return nil, values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	return el.QuerySelectorAll(selector), nil | ||||
| } | ||||
|  | ||||
| func elementArgs(inputs []core.Value) (values.HTMLNode, values.String, error) { | ||||
| 	if len(inputs) == 0 { | ||||
| 		return nil, values.EmptyString, core.Error(core.ErrMissedArgument, "element and arg2") | ||||
| 	} | ||||
|  | ||||
| 	if len(inputs) == 1 { | ||||
| 		return nil, values.EmptyString, core.Error(core.ErrMissedArgument, "arg2") | ||||
| 	} | ||||
|  | ||||
| 	arg1 := inputs[0] | ||||
| 	arg2 := inputs[1] | ||||
|  | ||||
| 	if arg1.Type() != core.HTMLDocumentType && | ||||
| 		arg1.Type() != core.HTMLElementType { | ||||
| 		return nil, values.EmptyString, core.TypeError(arg1.Type(), core.HTMLDocumentType, core.HTMLElementType) | ||||
| 	} | ||||
|  | ||||
| 	if arg2.Type() != core.StringType { | ||||
| 		return nil, values.EmptyString, core.TypeError(arg2.Type(), core.StringType) | ||||
| 	} | ||||
|  | ||||
| 	return arg1.(values.HTMLNode), arg2.(values.String), nil | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	return args[0].(values.HTMLNode), args[1].(values.String), nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										24
									
								
								pkg/stdlib/html/elements.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pkg/stdlib/html/elements.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Finds HTML elements by a given CSS selector. | ||||
|  * Returns an empty array if element not found. | ||||
|  * @param docOrEl (HTMLDocument|HTMLElement) - Parent document or element. | ||||
|  * @param selector (String) - CSS selector. | ||||
|  * @returns (Array) - Returns an array of found HTML element. | ||||
|  */ | ||||
| func Elements(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	el, selector, err := queryArgs(args) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	return el.QuerySelectorAll(selector), nil | ||||
| } | ||||
| @@ -1,74 +0,0 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| func WaitElement(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 3) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	arg := args[0] | ||||
| 	selector := args[1].String() | ||||
| 	timeout := values.NewInt(5000) | ||||
|  | ||||
| 	if len(args) > 2 { | ||||
| 		if args[2].Type() == core.IntType { | ||||
| 			timeout = args[2].(values.Int) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(arg, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.WaitForSelector(values.NewString(selector), timeout) | ||||
| } | ||||
|  | ||||
| func WaitNavigation(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := args[0].(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.None, core.Error(core.ErrInvalidType, "expected dynamic document") | ||||
| 	} | ||||
|  | ||||
| 	timeout := values.NewInt(5000) | ||||
|  | ||||
| 	if len(args) > 1 { | ||||
| 		err = core.ValidateType(args[1], core.IntType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.None, err | ||||
| 		} | ||||
|  | ||||
| 		timeout = args[1].(values.Int) | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.WaitForNavigation(timeout) | ||||
| } | ||||
							
								
								
									
										38
									
								
								pkg/stdlib/html/inner_html.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								pkg/stdlib/html/inner_html.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Returns inner HTML string of a matched element | ||||
|  * @param doc (Document) - Parent document. | ||||
|  * @param selector (String) - String of CSS selector. | ||||
|  * @returns (String) - Inner HTML string if an element found, otherwise NONE. | ||||
|  */ | ||||
| func InnerHTML(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc := args[0].(values.HTMLDocument) | ||||
| 	selector := args[1].(values.String) | ||||
|  | ||||
| 	return doc.InnerHTMLBySelector(selector), nil | ||||
| } | ||||
							
								
								
									
										38
									
								
								pkg/stdlib/html/inner_html_all.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								pkg/stdlib/html/inner_html_all.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Returns an array of inner HTML strings of matched elements. | ||||
|  * @param doc (Document) - Parent document. | ||||
|  * @param selector (String) - String of CSS selector. | ||||
|  * @returns (String) - An array of inner HTML strings if any element found, otherwise empty array. | ||||
|  */ | ||||
| func InnerHTMLAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc := args[0].(values.HTMLDocument) | ||||
| 	selector := args[1].(values.String) | ||||
|  | ||||
| 	return doc.InnerHTMLBySelectorAll(selector), nil | ||||
| } | ||||
							
								
								
									
										38
									
								
								pkg/stdlib/html/inner_text.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								pkg/stdlib/html/inner_text.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Returns inner text of a matched element | ||||
|  * @param doc (Document) - Parent document. | ||||
|  * @param selector (String) - String of CSS selector. | ||||
|  * @returns (String) - Inner text if an element found, otherwise NONE. | ||||
|  */ | ||||
| func InnerText(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.EmptyString, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc := args[0].(values.HTMLDocument) | ||||
| 	selector := args[1].(values.String) | ||||
|  | ||||
| 	return doc.InnerTextBySelector(selector), nil | ||||
| } | ||||
							
								
								
									
										38
									
								
								pkg/stdlib/html/inner_text_all.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								pkg/stdlib/html/inner_text_all.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Returns an array of inner text of matched elements. | ||||
|  * @param doc (Document) - Parent document. | ||||
|  * @param selector (String) - String of CSS selector. | ||||
|  * @returns (String) - An array of inner text if any element found, otherwise empty array. | ||||
|  */ | ||||
| func InnerTextAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc := args[0].(values.HTMLDocument) | ||||
| 	selector := args[1].(values.String) | ||||
|  | ||||
| 	return doc.InnerTextBySelectorAll(selector), nil | ||||
| } | ||||
							
								
								
									
										72
									
								
								pkg/stdlib/html/input.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								pkg/stdlib/html/input.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Sends a value to an underlying input element. | ||||
|  * @param source (Document | Element) - Event target. | ||||
|  * @param valueOrSelector (String) - Selector or a value. | ||||
|  * @param value (String) - Target value. | ||||
|  * @returns (Boolean) - Returns true if an element was found. | ||||
|  */ | ||||
| func Input(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 3) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// TYPE(el, "foobar") | ||||
| 	if len(args) == 2 { | ||||
| 		arg1 := args[0] | ||||
|  | ||||
| 		err := core.ValidateType(arg1, core.HTMLElementType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.False, err | ||||
| 		} | ||||
|  | ||||
| 		el, ok := arg1.(*dynamic.HTMLElement) | ||||
|  | ||||
| 		if !ok { | ||||
| 			return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 		} | ||||
|  | ||||
| 		err = el.Input(args[1]) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.False, err | ||||
| 		} | ||||
|  | ||||
| 		return values.True, nil | ||||
| 	} | ||||
|  | ||||
| 	arg1 := args[0] | ||||
|  | ||||
| 	err = core.ValidateType(arg1, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	arg2 := args[1] | ||||
|  | ||||
| 	err = core.ValidateType(arg2, core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.False, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg1.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	return doc.InputBySelector(arg2.(values.String), args[2]) | ||||
| } | ||||
| @@ -1,6 +1,15 @@ | ||||
| package html | ||||
|  | ||||
| import "github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| import ( | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| const defaultTimeout = 5000 | ||||
|  | ||||
| var ( | ||||
| 	ErrNotDynamic = errors.New("expected dynamic document or element") | ||||
| ) | ||||
|  | ||||
| func NewLib() map[string]core.Function { | ||||
| 	return map[string]core.Function{ | ||||
| @@ -10,6 +19,8 @@ func NewLib() map[string]core.Function { | ||||
| 		"ELEMENTS":        Elements, | ||||
| 		"WAIT_ELEMENT":    WaitElement, | ||||
| 		"WAIT_NAVIGATION": WaitNavigation, | ||||
| 		"WAIT_CLASS":      WaitClass, | ||||
| 		"WAIT_CLASS_ALL":  WaitClassAll, | ||||
| 		"CLICK":           Click, | ||||
| 		"CLICK_ALL":       ClickAll, | ||||
| 		"NAVIGATE":        Navigate, | ||||
|   | ||||
							
								
								
									
										43
									
								
								pkg/stdlib/html/navigate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								pkg/stdlib/html/navigate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Navigates a document to a new resource. | ||||
|  * The operation blocks the execution until the page gets loaded. | ||||
|  * Which means there is no need in WAIT_NAVIGATION function. | ||||
|  * @param doc (Document) - Target document. | ||||
|  * @param url (String) - Target url to navigate. | ||||
|  */ | ||||
| func Navigate(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := args[0].(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.Navigate(args[1].(values.String)) | ||||
| } | ||||
							
								
								
									
										87
									
								
								pkg/stdlib/html/wait_class.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								pkg/stdlib/html/wait_class.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Waits for a class to appear on a given element. | ||||
|  * Stops the execution until the navigation ends or operation times out. | ||||
|  * @param docOrEl (HTMLDocument|HTMLElement) - Target document or element. | ||||
|  * @param selectorOrClass (String) - If document is passed, this param must represent an element selector. | ||||
|  * Otherwise target class. | ||||
|  * @param classOrTimeout (String|Int, optional) - If document is passed, this param must represent target class name. | ||||
|  * Otherwise timeout. | ||||
|  * @param timeout (Int, optional) - If document is passed, this param must represent timeout. | ||||
|  * Otherwise not passed. | ||||
|  */ | ||||
| func WaitClass(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 4) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// document or element | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// selector or class | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	timeout := values.NewInt(defaultTimeout) | ||||
|  | ||||
| 	// lets figure out what is passed as 1st argument | ||||
| 	switch args[0].(type) { | ||||
| 	case *dynamic.HTMLDocument: | ||||
| 		// class | ||||
| 		err = core.ValidateType(args[2], core.StringType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.None, err | ||||
| 		} | ||||
|  | ||||
| 		doc := args[0].(*dynamic.HTMLDocument) | ||||
| 		selector := args[1].(values.String) | ||||
| 		class := args[2].(values.String) | ||||
|  | ||||
| 		if len(args) == 4 { | ||||
| 			err = core.ValidateType(args[3], core.IntType) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				return values.None, err | ||||
| 			} | ||||
|  | ||||
| 			timeout = args[3].(values.Int) | ||||
| 		} | ||||
|  | ||||
| 		return values.None, doc.WaitForClass(selector, class, timeout) | ||||
| 	case *dynamic.HTMLElement: | ||||
| 		el := args[0].(*dynamic.HTMLElement) | ||||
| 		class := args[1].(values.String) | ||||
|  | ||||
| 		if len(args) == 3 { | ||||
| 			err = core.ValidateType(args[2], core.IntType) | ||||
|  | ||||
| 			if err != nil { | ||||
| 				return values.None, err | ||||
| 			} | ||||
|  | ||||
| 			timeout = args[3].(values.Int) | ||||
| 		} | ||||
|  | ||||
| 		return values.None, el.WaitForClass(class, timeout) | ||||
| 	default: | ||||
| 		return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										67
									
								
								pkg/stdlib/html/wait_class_all.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								pkg/stdlib/html/wait_class_all.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Waits for a class to appear on all matched elements. | ||||
|  * Stops the execution until the navigation ends or operation times out. | ||||
|  * @param doc (HTMLDocument) - Parent document. | ||||
|  * @param selector (String) - String of CSS selector. | ||||
|  * @param class (String) - String of target CSS class. | ||||
|  * @param timeout (Int, optional) - Optional timeout. | ||||
|  */ | ||||
| func WaitClassAll(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 3, 4) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// document only | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// selector | ||||
| 	err = core.ValidateType(args[1], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	// class | ||||
| 	err = core.ValidateType(args[2], core.StringType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := args[0].(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	selector := args[1].(values.String) | ||||
| 	class := args[2].(values.String) | ||||
| 	timeout := values.NewInt(defaultTimeout) | ||||
|  | ||||
| 	if len(args) == 4 { | ||||
| 		err = core.ValidateType(args[3], core.IntType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.None, err | ||||
| 		} | ||||
|  | ||||
| 		timeout = args[3].(values.Int) | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.WaitForClassAll(selector, class, timeout) | ||||
| } | ||||
							
								
								
									
										51
									
								
								pkg/stdlib/html/wait_element.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								pkg/stdlib/html/wait_element.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Waits for element to appear in the DOM. | ||||
|  * Stops the execution until it finds an element or operation times out. | ||||
|  * @param doc (HTMLDocument) - Dynamic HTMLDocument. | ||||
|  * @param selector (String) - Target element's selector. | ||||
|  * @param timeout (Int, optional) - Optional timeout. Default 5000 ms. | ||||
|  */ | ||||
| func WaitElement(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 2, 3) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	arg := args[0] | ||||
| 	selector := args[1].String() | ||||
| 	timeout := values.NewInt(defaultTimeout) | ||||
|  | ||||
| 	if len(args) > 2 { | ||||
| 		err = core.ValidateType(args[2], core.IntType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.None, err | ||||
| 		} | ||||
|  | ||||
| 		timeout = args[2].(values.Int) | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(arg, core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := arg.(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.WaitForSelector(values.NewString(selector), timeout) | ||||
| } | ||||
							
								
								
									
										48
									
								
								pkg/stdlib/html/wait_navigation.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								pkg/stdlib/html/wait_navigation.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| package html | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/core" | ||||
| 	"github.com/MontFerret/ferret/pkg/runtime/values" | ||||
| 	"github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Waits for document to navigate to a new url. | ||||
|  * Stops the execution until the navigation ends or operation times out. | ||||
|  * @param doc (HTMLDocument) - Dynamic HTMLDocument. | ||||
|  * @param timeout (Int, optional) - Optional timeout. Default 5000 ms. | ||||
|  */ | ||||
| func WaitNavigation(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 2) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	err = core.ValidateType(args[0], core.HTMLDocumentType) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return values.None, err | ||||
| 	} | ||||
|  | ||||
| 	doc, ok := args[0].(*dynamic.HTMLDocument) | ||||
|  | ||||
| 	if !ok { | ||||
| 		return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) | ||||
| 	} | ||||
|  | ||||
| 	timeout := values.NewInt(defaultTimeout) | ||||
|  | ||||
| 	if len(args) > 1 { | ||||
| 		err = core.ValidateType(args[1], core.IntType) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return values.None, err | ||||
| 		} | ||||
|  | ||||
| 		timeout = args[1].(values.Int) | ||||
| 	} | ||||
|  | ||||
| 	return values.None, doc.WaitForNavigation(timeout) | ||||
| } | ||||
| @@ -7,7 +7,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| /* | ||||
|  * Concatenates one or more instances of Value, or an Array. | ||||
|  * Concatenates one or more instances of Read, or an Array. | ||||
|  * @params src (String...|Array) - The source string / array. | ||||
|  * @returns String | ||||
|  */ | ||||
| @@ -42,7 +42,7 @@ func Concat(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Concatenates one or more instances of Value, or an Array with a given separator. | ||||
|  * Concatenates one or more instances of Read, or an Array with a given separator. | ||||
|  * @params separator (string) - The separator string. | ||||
|  * @params src (string...|array) - The source string / array. | ||||
|  * @returns string | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
| /* | ||||
|  * Returns a FQL value described by the JSON-encoded input string. | ||||
|  * @params text (String) - The string to parse as JSON. | ||||
|  * @returns FQL value (Value) | ||||
|  * @returns FQL value (Read) | ||||
|  */ | ||||
| func JSONParse(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
| 	err := core.ValidateArgs(args, 1, 1) | ||||
| @@ -32,7 +32,7 @@ func JSONParse(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
|  | ||||
| /* | ||||
|  * Returns a JSON string representation of the input value. | ||||
|  * @params value (Value) - The input value to serialize. | ||||
|  * @params value (Read) - The input value to serialize. | ||||
|  * @returns json (String) | ||||
|  */ | ||||
| func JSONStringify(_ context.Context, args ...core.Value) (core.Value, error) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user